[{"data":1,"prerenderedAt":123210},["ShallowReactive",2],{"posts-page-\u002Fposts":3,"posts-schema-\u002Fposts":28,"posts-list-en":66943},{"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\u002Fposts.md","Posts",{"type":7,"value":8,"toc":12},"minimark",[9],[10,11],"posts-list",{},{"title":13,"searchDepth":14,"depth":14,"links":15},"",2,[],"e005ab26-d9f2-4bfc-98ff-e866fd765418","My blog posts about my life, my projects, and my thoughts.","md",{},true,"\u002Fposts","---\ncontentId: e005ab26-d9f2-4bfc-98ff-e866fd765418\ntitle: Posts\ndescription: My blog posts about my life, my projects, and my thoughts.\n---\n\n::posts-list\n::\n",null,{"title":5,"description":17},{"loc":21},"posts","cMbWYe-6O6yx6kpJ584qvi5n-lDTVi8s6W4HscEKpCo",[29,1191,1616,1851,3003,5628,5741,6791,7472,8176,8766,9423,9651,11295,13780,14125,16374,16868,17223,17546,17744,17848,18182,18595,19375,20412,20813,21338,24169,29206,30715,31204,33478,33883,34081,34303,34536,36550,36941,38099,38348,38517,38888,40803,45990,60414,60861,61064,63612,63903,64129],{"id":30,"title":31,"body":32,"contentId":1181,"date":1182,"description":1183,"extension":18,"meta":1184,"navigation":20,"path":1185,"rawbody":1186,"robots":23,"schemaOrg":23,"seo":1187,"sitemap":1188,"stem":1189,"__hash__":1190},"posts\u002Fposts\u002Ffrom-browsing-to-asking-the-web-learns-to-take-action.md","From Browsing to Asking: The Web Learns to Take Action",{"type":7,"value":33,"toc":1174},[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,492,586,589,938,952,955,964,1131,1142,1146,1149,1152,1155,1158,1161,1170],[35,36,37,38,43],"p",{},"A couple of months ago, I wrote an article titled ",[39,40,42],"a",{"href":41},"\u002Fposts\u002Fai-agents-will-deeply-transform-our-experience-with-the-web","AI Agents Will Deeply Transform Our Experience With the Web",". I shared my vision of how AI agents will radically change our experience with the web. I concluded by saying that a new paradigm would emerge: \"Website as a Search Bar.\" I never really explained what I meant because it was more of a feeling, based on my reading of the ecosystem, than something concrete.",[35,45,46],{},"Since then, a lot has changed, and that idea is now a reality. I never expected the future to arrive so quickly. Things have been moving insanely fast lately.",[35,48,49],{},"In that future, which is already here, we will ask agents to take action, and I already love it.",[51,52,54],"h2",{"id":53},"website-as-a-search-bar","Website as a Search Bar",[35,56,57],{},"Today, we navigate the web. We click links, scroll through pages, fill out forms, and read content. The same used to be true of my work as a software engineer. Since I started using AI agents, that is no longer the case for me. Now, I ask them to take action more often than I perform those actions myself. That new behavior is coming to the web.",[35,59,60],{},"The idea behind \"Website as a Search Bar\" is that a website's main page will simply be a search bar: an input where we can ask for anything we want to do on the website. Behind the scenes, the website will have an agent that acts for us. When I first thought about it, I imagined an MCP server and a server-side agent. The agent would call the MCP server to perform actions, and the UI would intelligently adapt or refresh based on the server's response. That experience is similar to what Notion AI offers today, and it works very well.",[35,62,63],{},"However, a new approach has recently begun to emerge.",[35,65,66],{},"The Chrome team is developing two new APIs that could completely reshape how a website becomes a search bar:",[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",", which lets you build and expose structured tools for AI agents;",[71,81,82,87],{},[39,83,86],{"href":84,"rel":85},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fai\u002Fprompt-api",[77],"The Prompt API",", which lets you send natural language requests to an LLM in Chrome.",[35,89,90],{},"With both, we can now have client-side agents that perform actions on the web on our behalf. This is mind-blowing.",[51,92,94],{"id":93},"the-future-is-already-here","The future is already here",[35,96,97],{},"Yes, that sounds crazy, I know, but the future is already here.",[35,99,100,101,106,107,112],{},"Both WebMCP and the Prompt API are already available experimentally in Chrome. This is no longer just a discussion, an idea, or a specification: experimental implementations are available and working. You can now create a website that exposes WebMCP tools, and you can ",[39,102,105],{"href":103,"rel":104},"https:\u002F\u002Fdeveloper.chrome.com\u002Fblog\u002Fprompt-api-with-vercel-ai",[77],"chat with an in-browser AI",". ",[39,108,111],{"href":109,"rel":110},"https:\u002F\u002Fblog.cloudflare.com\u002Fwebmcp\u002F#:~:text=You%20do%20not,Copy%20link",[77],"Cloudflare is also experimenting with WebMCP"," through its BrowserRun product, and ChatGPT Desktop supports WebMCP to make browsing the web easier for agents.",[35,114,115],{},"In-browser AI refers to LLMs such as Chrome's Gemini Nano or Google's Gemma models that can run privately and entirely in the browser on your computer. I'm not talking about Codex or Claude connecting to your browser and performing actions by reading the DOM or taking screenshots. I'm talking about an agent whose model can run offline and use dedicated APIs to interact seamlessly with websites.",[51,117,119],{"id":118},"ask-for-actions","Ask for actions",[35,121,122,123,128],{},"To make the idea more concrete, I built a small demo showing how it works. At first, my goal was simply to explore and use WebMCP. It turned into something bigger than I expected because, apart from ",[39,124,127],{"href":125,"rel":126},"https:\u002F\u002Fchromewebstore.google.com\u002Fdetail\u002Fwebmcp-model-context-tool\u002Fgbpdfapgefenggkahomfgkhfehlcenpd?pli=1",[77],"a test browser extension",", I couldn't find any other tool for testing it.",[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",{},"Demo of the in-browser AI assistant performing actions on the website.",[35,147,148],{},"What did you just see?",[35,150,151,152,157],{},"We are using ",[39,153,156],{"href":154,"rel":155},"https:\u002F\u002Fcode.soubiran.dev",[77],"code",", my tool for turning raw code into beautiful, shareable images. You paste a code snippet, select the language and background color, and capture it as an image. Then, you can share it with anyone on social media.",[35,159,160],{},"Normally, the user performs all the actions I've described manually.",[35,162,163],{},"The agent edits the code using the prompt provided, changes the settings, and finally takes a screenshot of the result. In the end, that is nothing more than what a human can do, but a local agent performs all those actions. That is the real change, and that is the future.",[35,165,166],{},"I know that this demo is useless. However, it gives you a concrete idea of this new paradigm. Everything, from the interface to the experience, still has to be invented and designed.",[51,168,170],{"id":169},"two-apis-to-make-it-happen","Two APIs to make it happen",[35,172,173],{},"If this is the future, we should take a closer look at the two APIs that make it possible. I have good news for you: on the surface, they are dead simple. However, the new doors they open make using them more complex than they initially seem.",[35,175,176,179],{},[39,177,78],{"href":75,"rel":178},[77]," comes in two forms:",[68,181,182,185],{},[71,183,184],{},"An imperative API for defining tools using JavaScript;",[71,186,187],{},"A declarative API for defining tools using HTML annotations.",[35,189,190],{},"The first is similar to the way an MCP server defines tools.",[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],{},"The ",[156,421,422],{},"document"," gets a new property called ",[156,425,218],{},", which is the entry point for creating and registering tools on the current page. Agents using your site need to know which tools are available, so ",[156,428,218],{}," exposes a ",[156,431,432],{},"getTools()"," method that returns the list of tools available on the 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],{},"This is similar to the ",[156,471,472],{},"listTools()"," method exposed by ",[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()"," in the AI SDK. It can be a little confusing because, in the browser, we act as both the server by registering tools and the client by accessing them. That is because your website is not necessarily the consumer of those tools.",[35,483,484,485,488,489,491],{},"You can also execute a tool using the ",[156,486,487],{},"executeTool()"," method of the ",[156,490,218],{}," object.",[192,493,495],{"className":194,"code":494,"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,496,497,525,530,563,579],{"__ignoreMap":13},[200,498,499,501,503,506,508,511,513,515,517,519,521,523],{"class":202,"line":203},[200,500,443],{"class":379},[200,502,350],{"class":214},[200,504,505],{"class":210},"tool",[200,507,360],{"class":214},[200,509,510],{"class":214}," =",[200,512,452],{"class":206},[200,514,211],{"class":210},[200,516,215],{"class":214},[200,518,218],{"class":210},[200,520,215],{"class":214},[200,522,463],{"class":223},[200,524,466],{"class":210},[200,526,527],{"class":202,"line":14},[200,528,529],{"emptyLinePlaceholder":20},"\n",[200,531,532,534,536,538,540,542,545,548,551,554,556,559,561],{"class":202,"line":255},[200,533,207],{"class":206},[200,535,211],{"class":210},[200,537,215],{"class":214},[200,539,218],{"class":210},[200,541,215],{"class":214},[200,543,544],{"class":223},"executeTool",[200,546,547],{"class":210},"(tool",[200,549,550],{"class":214},",",[200,552,553],{"class":210}," JSON",[200,555,215],{"class":214},[200,557,558],{"class":223},"stringify",[200,560,227],{"class":210},[200,562,230],{"class":214},[200,564,565,568,570,572,575,577],{"class":202,"line":272},[200,566,567],{"class":235},"  input",[200,569,239],{"class":214},[200,571,242],{"class":214},[200,573,574],{"class":245},"...",[200,576,249],{"class":214},[200,578,252],{"class":214},[200,580,581,583],{"class":202,"line":283},[200,582,413],{"class":214},[200,584,585],{"class":210},"))\n",[35,587,588],{},"The second is the declarative API, which lets you define tools using HTML annotations. It is primarily designed for forms:",[192,590,594],{"className":591,"code":592,"language":593,"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,595,596,618,632,649,653,686,708,712,740,760,764,784,800,831,859,888,898,903,929],{"__ignoreMap":13},[200,597,598,601,604,607,609,612,615],{"class":202,"line":203},[200,599,600],{"class":214},"\u003C",[200,602,603],{"class":235},"form",[200,605,606],{"class":379}," toolname",[200,608,449],{"class":214},[200,610,611],{"class":214},"\"",[200,613,614],{"class":245},"supportRequestTool",[200,616,617],{"class":214},"\"\n",[200,619,620,623,625,627,630],{"class":202,"line":14},[200,621,622],{"class":379},"  tooldescription",[200,624,449],{"class":214},[200,626,611],{"class":214},[200,628,629],{"class":245},"Submit a request for support.",[200,631,617],{"class":214},[200,633,634,637,639,641,644,646],{"class":202,"line":255},[200,635,636],{"class":379},"  action",[200,638,449],{"class":214},[200,640,611],{"class":214},[200,642,643],{"class":245},"\u002Fsubmit",[200,645,611],{"class":214},[200,647,648],{"class":214},">\n",[200,650,651],{"class":202,"line":272},[200,652,529],{"emptyLinePlaceholder":20},[200,654,655,658,661,664,666,668,671,673,676,679,682,684],{"class":202,"line":283},[200,656,657],{"class":214},"  \u003C",[200,659,660],{"class":235},"label",[200,662,663],{"class":379}," for",[200,665,449],{"class":214},[200,667,611],{"class":214},[200,669,670],{"class":245},"firstName",[200,672,611],{"class":214},[200,674,675],{"class":214},">",[200,677,678],{"class":210},"First Name",[200,680,681],{"class":214},"\u003C\u002F",[200,683,660],{"class":235},[200,685,648],{"class":214},[200,687,688,690,692,694,696,699,702,704,706],{"class":202,"line":300},[200,689,657],{"class":214},[200,691,355],{"class":235},[200,693,321],{"class":379},[200,695,449],{"class":214},[200,697,698],{"class":245},"text",[200,700,701],{"class":379}," name",[200,703,449],{"class":214},[200,705,670],{"class":245},[200,707,648],{"class":214},[200,709,710],{"class":202,"line":310},[200,711,529],{"emptyLinePlaceholder":20},[200,713,714,716,718,720,722,724,727,729,731,734,736,738],{"class":202,"line":336},[200,715,657],{"class":214},[200,717,660],{"class":235},[200,719,663],{"class":379},[200,721,449],{"class":214},[200,723,611],{"class":214},[200,725,726],{"class":245},"lastName",[200,728,611],{"class":214},[200,730,675],{"class":214},[200,732,733],{"class":210},"Last Name",[200,735,681],{"class":214},[200,737,660],{"class":235},[200,739,648],{"class":214},[200,741,742,744,746,748,750,752,754,756,758],{"class":202,"line":342},[200,743,657],{"class":214},[200,745,355],{"class":235},[200,747,321],{"class":379},[200,749,449],{"class":214},[200,751,698],{"class":245},[200,753,701],{"class":379},[200,755,449],{"class":214},[200,757,726],{"class":245},[200,759,648],{"class":214},[200,761,762],{"class":202,"line":365},[200,763,529],{"emptyLinePlaceholder":20},[200,765,766,768,771,773,775,777,779,781],{"class":202,"line":371},[200,767,657],{"class":214},[200,769,770],{"class":235},"select",[200,772,701],{"class":379},[200,774,449],{"class":214},[200,776,611],{"class":214},[200,778,770],{"class":245},[200,780,611],{"class":214},[200,782,783],{"class":379}," required\n",[200,785,786,789,791,793,796,798],{"class":202,"line":398},[200,787,788],{"class":379},"    toolparamdescription",[200,790,449],{"class":214},[200,792,611],{"class":214},[200,794,795],{"class":245},"Determines what team this request is routed to.",[200,797,611],{"class":214},[200,799,648],{"class":214},[200,801,802,805,808,811,813,815,818,820,822,825,827,829],{"class":202,"line":405},[200,803,804],{"class":214},"    \u003C",[200,806,807],{"class":235},"option",[200,809,810],{"class":379}," value",[200,812,449],{"class":214},[200,814,611],{"class":214},[200,816,817],{"class":245},"Customer happiness team",[200,819,611],{"class":214},[200,821,675],{"class":214},[200,823,824],{"class":210},"Return my purchase.",[200,826,681],{"class":214},[200,828,807],{"class":235},[200,830,648],{"class":214},[200,832,833,835,837,839,841,843,846,848,850,853,855,857],{"class":202,"line":410},[200,834,804],{"class":214},[200,836,807],{"class":235},[200,838,810],{"class":379},[200,840,449],{"class":214},[200,842,611],{"class":214},[200,844,845],{"class":245},"Distribution team",[200,847,611],{"class":214},[200,849,675],{"class":214},[200,851,852],{"class":210},"Check where my package is.",[200,854,681],{"class":214},[200,856,807],{"class":235},[200,858,648],{"class":214},[200,860,862,864,866,868,870,872,875,877,879,882,884,886],{"class":202,"line":861},15,[200,863,804],{"class":214},[200,865,807],{"class":235},[200,867,810],{"class":379},[200,869,449],{"class":214},[200,871,611],{"class":214},[200,873,874],{"class":245},"Website support team",[200,876,611],{"class":214},[200,878,675],{"class":214},[200,880,881],{"class":210},"Get help on the website.",[200,883,681],{"class":214},[200,885,807],{"class":235},[200,887,648],{"class":214},[200,889,891,894,896],{"class":202,"line":890},16,[200,892,893],{"class":214},"  \u003C\u002F",[200,895,770],{"class":235},[200,897,648],{"class":214},[200,899,901],{"class":202,"line":900},17,[200,902,529],{"emptyLinePlaceholder":20},[200,904,906,908,911,913,915,918,920,923,925,927],{"class":202,"line":905},18,[200,907,657],{"class":214},[200,909,910],{"class":235},"button",[200,912,321],{"class":379},[200,914,449],{"class":214},[200,916,917],{"class":245},"submit",[200,919,675],{"class":214},[200,921,922],{"class":210},"Submit",[200,924,681],{"class":214},[200,926,910],{"class":235},[200,928,648],{"class":214},[200,930,932,934,936],{"class":202,"line":931},19,[200,933,681],{"class":214},[200,935,603],{"class":235},[200,937,648],{"class":214},[939,940,941],"blockquote",{},[35,942,943,946,947,215],{},[200,944,945],{},"!NOTE","\nExample from the ",[39,948,951],{"href":949,"rel":950},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fai\u002Fwebmcp\u002Fdeclarative-api",[77],"WebMCP Declarative API documentation",[35,953,954],{},"Then comes the Prompt API. It is more complicated because it requires more availability and lifecycle management. For example, you need to detect whether the in-browser LLM is available. If it isn't, you need to know why. Maybe the device is incompatible, or the LLM hasn't been downloaded yet. If it can be downloaded, you have to tell the browser to download it, which can take a while. You therefore need to inform the user that something is happening in the background. On top of that, the API exposes low-level primitives that you probably don't want to deal with directly.",[35,956,957,958,963],{},"That is more work than calling an API. The good news is that a community provider called ",[39,959,962],{"href":960,"rel":961},"https:\u002F\u002Fai-sdk.dev\u002Fproviders\u002Fcommunity-providers\u002Fbrowser-ai",[77],"Browser AI"," makes the Prompt API easier to use with the AI SDK.",[192,965,967],{"className":194,"code":966,"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,968,969,993,1013,1017,1030,1034,1053,1060,1076,1082,1086,1109,1126],{"__ignoreMap":13},[200,970,971,974,976,979,982,985,987,990],{"class":202,"line":203},[200,972,973],{"class":206},"import",[200,975,318],{"class":214},[200,977,978],{"class":210}," browserAI",[200,980,981],{"class":214}," }",[200,983,984],{"class":206}," from",[200,986,242],{"class":214},[200,988,989],{"class":245},"@browser-ai\u002Fcore",[200,991,992],{"class":214},"'\n",[200,994,995,997,999,1002,1004,1006,1008,1011],{"class":202,"line":14},[200,996,973],{"class":206},[200,998,318],{"class":214},[200,1000,1001],{"class":210}," streamText",[200,1003,981],{"class":214},[200,1005,984],{"class":206},[200,1007,242],{"class":214},[200,1009,1010],{"class":245},"ai",[200,1012,992],{"class":214},[200,1014,1015],{"class":202,"line":255},[200,1016,529],{"emptyLinePlaceholder":20},[200,1018,1019,1021,1024,1026,1028],{"class":202,"line":272},[200,1020,443],{"class":379},[200,1022,1023],{"class":210}," model ",[200,1025,449],{"class":214},[200,1027,978],{"class":223},[200,1029,466],{"class":210},[200,1031,1032],{"class":202,"line":283},[200,1033,529],{"emptyLinePlaceholder":20},[200,1035,1036,1038,1040,1043,1045,1047,1049,1051],{"class":202,"line":300},[200,1037,443],{"class":379},[200,1039,318],{"class":214},[200,1041,1042],{"class":210}," textStream ",[200,1044,413],{"class":214},[200,1046,510],{"class":214},[200,1048,1001],{"class":223},[200,1050,227],{"class":210},[200,1052,230],{"class":214},[200,1054,1055,1058],{"class":202,"line":310},[200,1056,1057],{"class":210},"  model",[200,1059,252],{"class":214},[200,1061,1062,1065,1067,1069,1072,1074],{"class":202,"line":336},[200,1063,1064],{"class":235},"  prompt",[200,1066,239],{"class":214},[200,1068,242],{"class":214},[200,1070,1071],{"class":245},"Describe the future of the web in five sentences.",[200,1073,249],{"class":214},[200,1075,252],{"class":214},[200,1077,1078,1080],{"class":202,"line":342},[200,1079,413],{"class":214},[200,1081,416],{"class":210},[200,1083,1084],{"class":202,"line":365},[200,1085,529],{"emptyLinePlaceholder":20},[200,1087,1088,1091,1093,1096,1098,1101,1104,1107],{"class":202,"line":371},[200,1089,1090],{"class":206},"for",[200,1092,452],{"class":206},[200,1094,1095],{"class":210}," (",[200,1097,443],{"class":379},[200,1099,1100],{"class":210}," textPart ",[200,1102,1103],{"class":214},"of",[200,1105,1106],{"class":210}," textStream) ",[200,1108,230],{"class":214},[200,1110,1111,1114,1116,1119,1121,1124],{"class":202,"line":398},[200,1112,1113],{"class":210},"  console",[200,1115,215],{"class":214},[200,1117,1118],{"class":223},"log",[200,1120,227],{"class":235},[200,1122,1123],{"class":210},"textPart",[200,1125,416],{"class":235},[200,1127,1128],{"class":202,"line":405},[200,1129,1130],{"class":214},"}\n",[939,1132,1133],{},[35,1134,1135,1137,1138,215],{},[200,1136,945],{},"\nI highly recommend reading the article ",[39,1139,1141],{"href":103,"rel":1140},[77],"Use the built-in Prompt API with the Vercel AI SDK",[51,1143,1145],{"id":1144},"thats-only-the-beginning","That's only the beginning",[35,1147,1148],{},"For now, even though you can already test both APIs, they remain experimental and have limited browser support. This means that most people won't be able to use them. They still need to be standardized and adopted by other browser vendors, such as Mozilla and Apple.",[35,1150,1151],{},"Also, in-browser models are limited, especially compared with state-of-the-art models. They can answer questions and call tools, but you have to keep both the instructions and tool parameters simple, really simple.",[35,1153,1154],{},"They will inevitably become smarter and smarter. Recently, Qwen released Qwen3.8-27B, a model that can run on some consumer devices. Google is also making significant progress with its Gemma series of models, which are designed to run on-device. Devices are becoming more powerful and better able to run these models. Apple's M-series SoCs include a Neural Engine, or NPU, and Google has done the same with its Tensor SoCs. The latest version of Siri also performs AI work on-device, and it seems really powerful. Now, it is only a matter of time.",[35,1156,1157],{},"Finally, when all of this is standardized and mature, having everything ready to use will give you a competitive advantage.",[1159,1160],"hr",{},[35,1162,1163,1164,1169],{},"While I was writing this article, OpenAI announced a ",[39,1165,1168],{"href":1166,"rel":1167},"https:\u002F\u002Fopenai.com\u002Fwebmcp-challenge\u002F",[77],"WebMCP challenge"," to explore what is possible. It looks like I'm heading in the right direction.",[1171,1172,1173],"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":1175},[1176,1177,1178,1179,1180],{"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":1144,"depth":14,"text":1145},"b3613556-823c-4765-820b-142659321461","2026-08-28","WebMCP and Chrome's Prompt API are bringing client-side AI agents to the web, letting users ask websites to take action on their behalf.",{},"\u002Fposts\u002Ffrom-browsing-to-asking-the-web-learns-to-take-action","---\ncontentId: b3613556-823c-4765-820b-142659321461\ntitle: \"From Browsing to Asking: The Web Learns to Take Action\"\ndescription: WebMCP and Chrome's Prompt API are bringing client-side AI agents to the web, letting users ask websites to take action on their behalf.\ndate: 2026-08-28\n---\n\nA couple of months ago, I wrote an article titled [AI Agents Will Deeply Transform Our Experience With the Web](\u002Fposts\u002Fai-agents-will-deeply-transform-our-experience-with-the-web). I shared my vision of how AI agents will radically change our experience with the web. I concluded by saying that a new paradigm would emerge: \"Website as a Search Bar.\" I never really explained what I meant because it was more of a feeling, based on my reading of the ecosystem, than something concrete.\n\nSince then, a lot has changed, and that idea is now a reality. I never expected the future to arrive so quickly. Things have been moving insanely fast lately.\n\nIn that future, which is already here, we will ask agents to take action, and I already love it.\n\n## Website as a Search Bar\n\nToday, we navigate the web. We click links, scroll through pages, fill out forms, and read content. The same used to be true of my work as a software engineer. Since I started using AI agents, that is no longer the case for me. Now, I ask them to take action more often than I perform those actions myself. That new behavior is coming to the web.\n\nThe idea behind \"Website as a Search Bar\" is that a website's main page will simply be a search bar: an input where we can ask for anything we want to do on the website. Behind the scenes, the website will have an agent that acts for us. When I first thought about it, I imagined an MCP server and a server-side agent. The agent would call the MCP server to perform actions, and the UI would intelligently adapt or refresh based on the server's response. That experience is similar to what Notion AI offers today, and it works very well.\n\nHowever, a new approach has recently begun to emerge.\n\nThe Chrome team is developing two new APIs that could completely reshape how a website becomes a search bar:\n\n1. [WebMCP](https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fai\u002Fwebmcp), which lets you build and expose structured tools for AI agents;\n2. [The Prompt API](https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fai\u002Fprompt-api), which lets you send natural language requests to an LLM in Chrome.\n\nWith both, we can now have client-side agents that perform actions on the web on our behalf. This is mind-blowing.\n\n## The future is already here\n\nYes, that sounds crazy, I know, but the future is already here.\n\nBoth WebMCP and the Prompt API are already available experimentally in Chrome. This is no longer just a discussion, an idea, or a specification: experimental implementations are available and working. You can now create a website that exposes WebMCP tools, and you can [chat with an in-browser AI](https:\u002F\u002Fdeveloper.chrome.com\u002Fblog\u002Fprompt-api-with-vercel-ai). [Cloudflare is also experimenting with WebMCP](https:\u002F\u002Fblog.cloudflare.com\u002Fwebmcp\u002F#:~:text=You%20do%20not,Copy%20link) through its BrowserRun product, and ChatGPT Desktop supports WebMCP to make browsing the web easier for agents.\n\nIn-browser AI refers to LLMs such as Chrome's Gemini Nano or Google's Gemma models that can run privately and entirely in the browser on your computer. I'm not talking about Codex or Claude connecting to your browser and performing actions by reading the DOM or taking screenshots. I'm talking about an agent whose model can run offline and use dedicated APIs to interact seamlessly with websites.\n\n## Ask for actions\n\nTo make the idea more concrete, I built a small demo showing how it works. At first, my goal was simply to explore and use WebMCP. It turned into something bigger than I expected because, apart from [a test browser extension](https:\u002F\u002Fchromewebstore.google.com\u002Fdetail\u002Fwebmcp-model-context-tool\u002Fgbpdfapgefenggkahomfgkhfehlcenpd?pli=1), I couldn't find any other tool for testing it.\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>Demo of the in-browser AI assistant performing actions on the website.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nWhat did you just see?\n\nWe are using [code](https:\u002F\u002Fcode.soubiran.dev), my tool for turning raw code into beautiful, shareable images. You paste a code snippet, select the language and background color, and capture it as an image. Then, you can share it with anyone on social media.\n\nNormally, the user performs all the actions I've described manually.\n\nThe agent edits the code using the prompt provided, changes the settings, and finally takes a screenshot of the result. In the end, that is nothing more than what a human can do, but a local agent performs all those actions. That is the real change, and that is the future.\n\nI know that this demo is useless. However, it gives you a concrete idea of this new paradigm. Everything, from the interface to the experience, still has to be invented and designed.\n\n## Two APIs to make it happen\n\nIf this is the future, we should take a closer look at the two APIs that make it possible. I have good news for you: on the surface, they are dead simple. However, the new doors they open make using them more complex than they initially seem.\n\n[WebMCP](https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fai\u002Fwebmcp) comes in two forms:\n\n1. An imperative API for defining tools using JavaScript;\n2. A declarative API for defining tools using HTML annotations.\n\nThe first is similar to the way an MCP server defines tools.\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\nThe `document` gets a new property called `modelContext`, which is the entry point for creating and registering tools on the current page. Agents using your site need to know which tools are available, so `modelContext` exposes a `getTools()` method that returns the list of tools available on the page.\n\n```typescript\nconst tools = await document.modelContext.getTools()\n```\n\nThis is similar to the `listTools()` method exposed by [`createMCPClient()`](https:\u002F\u002Fai-sdk.dev\u002Fdocs\u002Freference\u002Fai-sdk-core\u002Fcreate-mcp-client) in the AI SDK. It can be a little confusing because, in the browser, we act as both the server by registering tools and the client by accessing them. That is because your website is not necessarily the consumer of those tools.\n\nYou can also execute a tool using the `executeTool()` method of the `modelContext` object.\n\n```typescript\nconst [tool] = await document.modelContext.getTools()\n\nawait document.modelContext.executeTool(tool, JSON.stringify({\n  input: '...',\n}))\n```\n\nThe second is the declarative API, which lets you define tools using HTML annotations. It is primarily designed for forms:\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> Example from the [WebMCP Declarative API documentation](https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fai\u002Fwebmcp\u002Fdeclarative-api).\n\nThen comes the Prompt API. It is more complicated because it requires more availability and lifecycle management. For example, you need to detect whether the in-browser LLM is available. If it isn't, you need to know why. Maybe the device is incompatible, or the LLM hasn't been downloaded yet. If it can be downloaded, you have to tell the browser to download it, which can take a while. You therefore need to inform the user that something is happening in the background. On top of that, the API exposes low-level primitives that you probably don't want to deal with directly.\n\nThat is more work than calling an API. The good news is that a community provider called [Browser AI](https:\u002F\u002Fai-sdk.dev\u002Fproviders\u002Fcommunity-providers\u002Fbrowser-ai) makes the Prompt API easier to use with the 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> I highly recommend reading the article [Use the built-in Prompt API with the Vercel AI SDK](https:\u002F\u002Fdeveloper.chrome.com\u002Fblog\u002Fprompt-api-with-vercel-ai).\n\n## That's only the beginning\n\nFor now, even though you can already test both APIs, they remain experimental and have limited browser support. This means that most people won't be able to use them. They still need to be standardized and adopted by other browser vendors, such as Mozilla and Apple.\n\nAlso, in-browser models are limited, especially compared with state-of-the-art models. They can answer questions and call tools, but you have to keep both the instructions and tool parameters simple, really simple.\n\nThey will inevitably become smarter and smarter. Recently, Qwen released Qwen3.8-27B, a model that can run on some consumer devices. Google is also making significant progress with its Gemma series of models, which are designed to run on-device. Devices are becoming more powerful and better able to run these models. Apple's M-series SoCs include a Neural Engine, or NPU, and Google has done the same with its Tensor SoCs. The latest version of Siri also performs AI work on-device, and it seems really powerful. Now, it is only a matter of time.\n\nFinally, when all of this is standardized and mature, having everything ready to use will give you a competitive advantage.\n\n---\n\nWhile I was writing this article, OpenAI announced a [WebMCP challenge](https:\u002F\u002Fopenai.com\u002Fwebmcp-challenge\u002F) to explore what is possible. It looks like I'm heading in the right direction.\n",{"title":31,"description":1183},{"loc":1185},"posts\u002Ffrom-browsing-to-asking-the-web-learns-to-take-action","NbBqeCP5zVJdevnMFtQX7Dha_0D696XVqulhOJn5BFw",{"id":1192,"title":1193,"body":1194,"contentId":1606,"date":1607,"description":1608,"extension":18,"meta":1609,"navigation":20,"path":1610,"rawbody":1611,"robots":23,"schemaOrg":23,"seo":1612,"sitemap":1613,"stem":1614,"__hash__":1615},"posts\u002Fposts\u002Fagent-skills-will-replace-side-projects-in-portfolios.md","Agent Skills Will Replace Side Projects in Portfolios",{"type":7,"value":1195,"toc":1600},[1196,1199,1202,1205,1208,1211,1214,1220,1224,1227,1230,1241,1244,1253,1256,1263,1266,1273,1276,1279,1283,1286,1289,1292,1295,1308,1320,1323,1331,1334,1338,1341,1350,1353,1365,1368,1426,1429,1439,1442,1445,1454,1457,1547,1553,1557,1572,1575,1591,1597],[35,1197,1198],{},"I've often heard people, from my teachers to other developers, say that side projects are a good way to stand out in a stack of resumes, sell yourself during an interview, and prove your competence without having to say it.",[35,1200,1201],{},"I still believe that. In fact, I would go further and say that side projects are one of the most convincing ways to demonstrate what you can do. But the landscape is changing. Since the rise of AI and LLMs, platforms like Lovable and v0 have made it possible for many more people to turn an idea into a working project.",[35,1203,1204],{},"Of course, not everyone will build. But more people will, increasing competitive pressure and making differentiation harder.",[35,1206,1207],{},"That does not make projects worthless. It makes them less scarce. A project will still show what you built, but it may no longer be enough to show how you think, make decisions, and take responsibility for the result.",[35,1209,1210],{},"We are developers, not merely builders. Building software requires engineering, and it is far more difficult than it seems. For example, my dad is experimenting with vibe coding, but without my help choosing the tools, understanding the architecture, and selecting the platform, it would be nearly impossible for him to create projects that live up to his ambitions.",[35,1212,1213],{},"I love seeing my dad vibe-coding on Cloudflare with Antigravity, and I never expected to see that. But this fun story also carries a lesson: we have to raise the bar and broaden our mindset. What we used to call a side project can now be created with a few prompts, and a startup idea can now become a side project. Our ambitions have to be bigger, but so does the evidence we provide of our engineering ability. That is where differentiation will come from.",[35,1215,1216],{},[1217,1218,1219],"em",{},"Just do things honestly. You'll still be way ahead of most people, more than you think.",[51,1221,1223],{"id":1222},"youre-not-alone-anymore","You’re Not Alone Anymore",[35,1225,1226],{},"Until recently, building software meant relying on your own hands and time. You had to dedicate days and nights to learning the tools, choosing the architecture, and writing the code. Now, you can be a few prompts away from a working project.",[35,1228,1229],{},"With agents, you can spend more time on the things that matter: understanding the problem, thinking about the architecture and useful features, writing content, and checking the result. The agent can handle more of the production work.",[35,1231,1232,1233],{},"You're not alone anymore. You have a partner, a teammate, to whom you can delegate part of the work. ",[1217,1234,1235,1236],{},"Please, delegate wisely. ",[39,1237,1240],{"href":1238,"rel":1239},"https:\u002F\u002Fromainlanz.com\u002Farticles\u002Fdeleguer-le-clavier-pas-la-reflexion",[77],"Delegate the keyboard, not the thinking.",[35,1242,1243],{},"For a while, we heard that AI was a bubble, that it was hype that would soon be over. Individual products may come and go, but the underlying capabilities are not going away. With the release of Qwen3.8 27B, we can now run powerful LLMs on consumer-grade hardware. Sure, it is slow, the hardware is expensive, and it is not at the level of frontier models. But ChatGPT was released in November 2022, and look where we are now.",[35,1245,1246,1247,1252],{},"LLMs and agents are here to stay. We, as a market, have to adapt, evolve, and broaden our mindset. As I explain in ",[39,1248,1251],{"href":1249,"rel":1250},"https:\u002F\u002Fsoubiran.dev\u002Ftalks\u002F2026-04-17\u002Fmixit",[77],"my MiXiT talk",", the developer's job is changing rather than disappearing. Companies will have to adapt their hiring processes, and candidates will have to adapt their portfolios.",[35,1254,1255],{},"Until recently, a technical test often looked like this: \"Here is the problem. Explain how you would solve it, then write the code.\" With agents, writing code as the sole evaluation criterion becomes less relevant. A stronger assessment could evaluate how a candidate takes an agent from an idea to a working product, or from an existing product to a new feature, in both greenfield and brownfield contexts. The candidate would still need to explain the decisions, review the output, and remain responsible for the result.",[35,1257,1258,1259,1262],{},"This has huge implications for how we prepare as candidates. We need to understand what an ",[156,1260,1261],{},"AGENTS.md"," file is and what an agent skill is, but also how to write and evaluate them. We need to recognize, just by reading them, what is good, what is bad, and what could fail. We need to improve our prompt and context engineering.",[35,1264,1265],{},"This does not mean that the fundamentals are becoming irrelevant. Quite the opposite: the less code we write ourselves, the more we need the knowledge and judgment required to evaluate what an agent produces. Architecture, databases, networking, security, testing, and maintainability still matter. So do critical thinking, curiosity, and the ability to act.",[35,1267,1268,1269,1272],{},"When I use the word ",[1217,1270,1271],{},"skills"," in this article, I am talking about two related but distinct things. First, there are human skills: engineering fundamentals, judgment, critical thinking, prompt engineering, context engineering, and the ability to guide and review agents. Second, there are reusable agent skills: documented instructions, resources, and workflows that we give to agents so they can perform a task consistently.",[35,1274,1275],{},"The second kind is the new portfolio artifact I want to focus on. Reusable agent skills do not replace human skills, and they do not replace projects. They sit alongside them. Projects show what you built while agent skills can show how you work and what you have learned to make reusable.",[35,1277,1278],{},"We could go even further by publishing ready-to-use assets that help others start faster.",[51,1280,1282],{"id":1281},"show-skills-alongside-projects","Show Skills Alongside Projects",[35,1284,1285],{},"If you don't have a domain yet, buy one. For real.",[35,1287,1288],{},"A website is the best storefront. For years, we have used websites to showcase our professional experience, side projects, and open-source contributions. Now, we can also use them to publish the skills we have created for our agents.",[35,1290,1291],{},"These skills are not a replacement for projects. They are a new kind of artifact that can sit alongside them, and a new way to differentiate yourself from everyone else.",[35,1293,1294],{},"Agent skills are meant to be reused across projects, easily installed, and easily discovered. So we need a way to expose them on our websites.",[35,1296,1297,1298,1303,1304,1307],{},"That's why Cloudflare published the RFC ",[39,1299,1302],{"href":1300,"rel":1301},"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fagent-skills-discovery-rfc",[77],"Agent Skills Discovery via Well-Known URIs",". The idea is to standardize how skills are exposed on a website, so anyone can discover them and use them in their own projects. A central index will be available at ",[156,1305,1306],{},"https:\u002F\u002F\u003Cyour-site>\u002F.well-known\u002Fagent-skills\u002Findex.json"," and will list all the skills available on your website. By following each entry, people will be able to access the skill and its corresponding files.",[35,1309,1310,1311,1315,1316,1319],{},"Today, tools like ",[39,1312,1271],{"href":1313,"rel":1314},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fskills",[77]," allow you to add skills from a website to your project. For example, to add my skills to your project, you just have to run ",[156,1317,1318],{},"npx skills add https:\u002F\u002Fsoubiran.dev",". In the future, when participating in a technical test, you may import skills to help your agent work as expected, or discuss those skills with the interviewer. This could become one more way to evaluate a candidate.",[35,1321,1322],{},"When you hire a plumber, you don't provide them with a wrench, a screwdriver, and a drill. They come with their own tools. Developers will increasingly bring their own reusable tools and ways of working too. A portfolio will show not only what they have built, but also how they approach building it.",[35,1324,1325,1326,215],{},"That's my take on the future of hiring and candidate portfolios. I may be wrong about the details, but things are going to change, and ",[39,1327,1330],{"href":1328,"rel":1329},"https:\u002F\u002Fwww.epicweb.dev\u002Ftalks\u002Fepic-bets-practical-tips-for-betting-on-yourself",[77],"we will have to adapt",[35,1332,1333],{},"If reusable skills are going to become portfolio artifacts, they need to be easy to publish, discover, and reuse. That brings me to the practical part of this article.",[51,1335,1337],{"id":1336},"make-your-skills-available-to-the-world","Make Your Skills Available to the World",[35,1339,1340],{},"The RFC is promising, but does it mean that we have to manually create and maintain a well-known agent skills file? Honestly, that would be cumbersome. Nobody wants that.",[35,1342,1343,1344,1346,1347,215],{},"So I built a Nuxt module that automatically bundles the skills stored in your project's root-level ",[156,1345,1271],{}," directory into your site, along with the well-known agent skills file at ",[156,1348,1349],{},".well-known\u002Fagent-skills\u002Findex.json",[35,1351,1352],{},"There is no configuration to write. Just install the module and it will do the job for you.",[192,1354,1359],{"className":1355,"code":1356,"filename":1357,"language":1358,"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,1360,1361],{"__ignoreMap":13},[200,1362,1363],{"class":202,"line":203},[200,1364,1356],{},[35,1366,1367],{},"Then, add the module to your Nuxt config:",[192,1369,1374],{"className":1370,"code":1371,"filename":1372,"language":1373,"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,1375,1376,1391,1401,1413,1420],{"__ignoreMap":13},[200,1377,1378,1381,1384,1387,1389],{"class":202,"line":203},[200,1379,1380],{"class":206},"export",[200,1382,1383],{"class":206}," default",[200,1385,1386],{"class":223}," defineNuxtConfig",[200,1388,227],{"class":210},[200,1390,230],{"class":214},[200,1392,1393,1396,1398],{"class":202,"line":14},[200,1394,1395],{"class":235},"  modules",[200,1397,239],{"class":214},[200,1399,1400],{"class":210}," [\n",[200,1402,1403,1406,1409,1411],{"class":202,"line":255},[200,1404,1405],{"class":214},"    '",[200,1407,1408],{"class":245},"nuxt-skills",[200,1410,249],{"class":214},[200,1412,252],{"class":214},[200,1414,1415,1418],{"class":202,"line":272},[200,1416,1417],{"class":210},"  ]",[200,1419,252],{"class":214},[200,1421,1422,1424],{"class":202,"line":283},[200,1423,413],{"class":214},[200,1425,416],{"class":210},[35,1427,1428],{},"That's it. The Nuxt module takes care of the rest.",[35,1430,1431,1432,1435,1436,1438],{},"Your visitors can then use ",[156,1433,1434],{},"npx skills add https:\u002F\u002F\u003Cyour-site>"," to add your skills to their projects. You could be one of these visitors. To add my skills to a project, I use ",[156,1437,1318],{},". Easy to remember, simple to use, and it just works.",[35,1440,1441],{},"Maybe you don't use Nuxt. No problem: I also built a Vite plugin.",[35,1443,1444],{},"First, install the plugin:",[192,1446,1448],{"className":1355,"code":1447,"filename":1357,"language":1358,"meta":13,"style":13},"pnpm install -D unplugin-skills\n",[156,1449,1450],{"__ignoreMap":13},[200,1451,1452],{"class":202,"line":203},[200,1453,1447],{},[35,1455,1456],{},"Then, add the plugin to your Vite config:",[192,1458,1461],{"className":1370,"code":1459,"filename":1460,"language":1373,"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,1462,1463,1480,1500,1504,1516,1525,1535,1541],{"__ignoreMap":13},[200,1464,1465,1467,1470,1473,1475,1478],{"class":202,"line":203},[200,1466,973],{"class":206},[200,1468,1469],{"class":210}," skills ",[200,1471,1472],{"class":206},"from",[200,1474,242],{"class":214},[200,1476,1477],{"class":245},"unplugin-skills\u002Fvite",[200,1479,992],{"class":214},[200,1481,1482,1484,1486,1489,1491,1493,1495,1498],{"class":202,"line":14},[200,1483,973],{"class":206},[200,1485,318],{"class":214},[200,1487,1488],{"class":210}," defineConfig",[200,1490,981],{"class":214},[200,1492,984],{"class":206},[200,1494,242],{"class":214},[200,1496,1497],{"class":245},"vite",[200,1499,992],{"class":214},[200,1501,1502],{"class":202,"line":255},[200,1503,529],{"emptyLinePlaceholder":20},[200,1505,1506,1508,1510,1512,1514],{"class":202,"line":272},[200,1507,1380],{"class":206},[200,1509,1383],{"class":206},[200,1511,1488],{"class":223},[200,1513,227],{"class":210},[200,1515,230],{"class":214},[200,1517,1518,1521,1523],{"class":202,"line":283},[200,1519,1520],{"class":235},"  plugins",[200,1522,239],{"class":214},[200,1524,1400],{"class":210},[200,1526,1527,1530,1533],{"class":202,"line":300},[200,1528,1529],{"class":223},"    skills",[200,1531,1532],{"class":210},"()",[200,1534,252],{"class":214},[200,1536,1537,1539],{"class":202,"line":310},[200,1538,1417],{"class":210},[200,1540,252],{"class":214},[200,1542,1543,1545],{"class":202,"line":336},[200,1544,413],{"class":214},[200,1546,416],{"class":210},[35,1548,1549,1550,1552],{},"Without any additional configuration, the plugin automatically bundles skills available at the root of the project into your site with the well-known agent skills file (",[156,1551,1349],{},").",[51,1554,1556],{"id":1555},"building-on-the-docus-teams-work","Building on the Docus Team's Work",[35,1558,1559,1560,1565,1566,1571],{},"This work would not have been possible without the incredibly talented ",[39,1561,1564],{"href":1562,"rel":1563},"https:\u002F\u002Fdocus.dev\u002F",[77],"Docus"," team. On 31 March 2026, Hugo opened a pull request to ",[39,1567,1570],{"href":1568,"rel":1569},"https:\u002F\u002Fgithub.com\u002Fnuxt-content\u002Fdocus\u002Fpull\u002F1297",[77],"add support for the well-known discovery of agent skills",". They added it as a module to the Docus layer.",[35,1573,1574],{},"This pull request inspired my work. At first, I needed a Vite plugin to bundle my skills into my site. Then, I realized that creating a Nuxt module anyone could use would not be too difficult if I reused the Vite plugin's core logic. So I did it, and now you can use it too.",[1576,1577,1578,1585],"ul",{},[71,1579,1580,1581],{},"Vite plugin: ",[1582,1583],"git-hub-link",{"repo":1584},"barbapapazes\u002Funplugin-skills",[71,1586,1587,1588],{},"Nuxt module: ",[1582,1589],{"repo":1590},"barbapapazes\u002Fnuxt-skills",[35,1592,1593,1594],{},"That's the power of open source. ",[1217,1595,1596],{},"Thank you, Hugo, for inspiring me.",[1171,1598,1599],{},"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}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}",{"title":13,"searchDepth":14,"depth":14,"links":1601},[1602,1603,1604,1605],{"id":1222,"depth":14,"text":1223},{"id":1281,"depth":14,"text":1282},{"id":1336,"depth":14,"text":1337},{"id":1555,"depth":14,"text":1556},"c0aa9f37-204d-46df-b986-84e222dcbff1","2026-08-25","As AI makes software easier to produce, portfolios will showcase engineering skills and reusable agent skills alongside projects.",{},"\u002Fposts\u002Fagent-skills-will-replace-side-projects-in-portfolios","---\ncontentId: c0aa9f37-204d-46df-b986-84e222dcbff1\ntitle: Agent Skills Will Replace Side Projects in Portfolios\ndescription: As AI makes software easier to produce, portfolios will showcase engineering skills and reusable agent skills alongside projects.\ndate: 2026-08-25\n---\n\nI've often heard people, from my teachers to other developers, say that side projects are a good way to stand out in a stack of resumes, sell yourself during an interview, and prove your competence without having to say it.\n\nI still believe that. In fact, I would go further and say that side projects are one of the most convincing ways to demonstrate what you can do. But the landscape is changing. Since the rise of AI and LLMs, platforms like Lovable and v0 have made it possible for many more people to turn an idea into a working project.\n\nOf course, not everyone will build. But more people will, increasing competitive pressure and making differentiation harder.\n\nThat does not make projects worthless. It makes them less scarce. A project will still show what you built, but it may no longer be enough to show how you think, make decisions, and take responsibility for the result.\n\nWe are developers, not merely builders. Building software requires engineering, and it is far more difficult than it seems. For example, my dad is experimenting with vibe coding, but without my help choosing the tools, understanding the architecture, and selecting the platform, it would be nearly impossible for him to create projects that live up to his ambitions.\n\nI love seeing my dad vibe-coding on Cloudflare with Antigravity, and I never expected to see that. But this fun story also carries a lesson: we have to raise the bar and broaden our mindset. What we used to call a side project can now be created with a few prompts, and a startup idea can now become a side project. Our ambitions have to be bigger, but so does the evidence we provide of our engineering ability. That is where differentiation will come from.\n\n_Just do things honestly. You'll still be way ahead of most people, more than you think._\n\n## You’re Not Alone Anymore\n\nUntil recently, building software meant relying on your own hands and time. You had to dedicate days and nights to learning the tools, choosing the architecture, and writing the code. Now, you can be a few prompts away from a working project.\n\nWith agents, you can spend more time on the things that matter: understanding the problem, thinking about the architecture and useful features, writing content, and checking the result. The agent can handle more of the production work.\n\nYou're not alone anymore. You have a partner, a teammate, to whom you can delegate part of the work. _Please, delegate wisely. [Delegate the keyboard, not the thinking.](https:\u002F\u002Fromainlanz.com\u002Farticles\u002Fdeleguer-le-clavier-pas-la-reflexion)_\n\nFor a while, we heard that AI was a bubble, that it was hype that would soon be over. Individual products may come and go, but the underlying capabilities are not going away. With the release of Qwen3.8 27B, we can now run powerful LLMs on consumer-grade hardware. Sure, it is slow, the hardware is expensive, and it is not at the level of frontier models. But ChatGPT was released in November 2022, and look where we are now.\n\nLLMs and agents are here to stay. We, as a market, have to adapt, evolve, and broaden our mindset. As I explain in [my MiXiT talk](https:\u002F\u002Fsoubiran.dev\u002Ftalks\u002F2026-04-17\u002Fmixit), the developer's job is changing rather than disappearing. Companies will have to adapt their hiring processes, and candidates will have to adapt their portfolios.\n\nUntil recently, a technical test often looked like this: \"Here is the problem. Explain how you would solve it, then write the code.\" With agents, writing code as the sole evaluation criterion becomes less relevant. A stronger assessment could evaluate how a candidate takes an agent from an idea to a working product, or from an existing product to a new feature, in both greenfield and brownfield contexts. The candidate would still need to explain the decisions, review the output, and remain responsible for the result.\n\nThis has huge implications for how we prepare as candidates. We need to understand what an `AGENTS.md` file is and what an agent skill is, but also how to write and evaluate them. We need to recognize, just by reading them, what is good, what is bad, and what could fail. We need to improve our prompt and context engineering.\n\nThis does not mean that the fundamentals are becoming irrelevant. Quite the opposite: the less code we write ourselves, the more we need the knowledge and judgment required to evaluate what an agent produces. Architecture, databases, networking, security, testing, and maintainability still matter. So do critical thinking, curiosity, and the ability to act.\n\nWhen I use the word _skills_ in this article, I am talking about two related but distinct things. First, there are human skills: engineering fundamentals, judgment, critical thinking, prompt engineering, context engineering, and the ability to guide and review agents. Second, there are reusable agent skills: documented instructions, resources, and workflows that we give to agents so they can perform a task consistently.\n\nThe second kind is the new portfolio artifact I want to focus on. Reusable agent skills do not replace human skills, and they do not replace projects. They sit alongside them. Projects show what you built while agent skills can show how you work and what you have learned to make reusable.\n\nWe could go even further by publishing ready-to-use assets that help others start faster.\n\n## Show Skills Alongside Projects\n\nIf you don't have a domain yet, buy one. For real.\n\nA website is the best storefront. For years, we have used websites to showcase our professional experience, side projects, and open-source contributions. Now, we can also use them to publish the skills we have created for our agents.\n\nThese skills are not a replacement for projects. They are a new kind of artifact that can sit alongside them, and a new way to differentiate yourself from everyone else.\n\nAgent skills are meant to be reused across projects, easily installed, and easily discovered. So we need a way to expose them on our websites.\n\nThat's why Cloudflare published the RFC [Agent Skills Discovery via Well-Known URIs](https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fagent-skills-discovery-rfc). The idea is to standardize how skills are exposed on a website, so anyone can discover them and use them in their own projects. A central index will be available at `https:\u002F\u002F\u003Cyour-site>\u002F.well-known\u002Fagent-skills\u002Findex.json` and will list all the skills available on your website. By following each entry, people will be able to access the skill and its corresponding files.\n\nToday, tools like [skills](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fskills) allow you to add skills from a website to your project. For example, to add my skills to your project, you just have to run `npx skills add https:\u002F\u002Fsoubiran.dev`. In the future, when participating in a technical test, you may import skills to help your agent work as expected, or discuss those skills with the interviewer. This could become one more way to evaluate a candidate.\n\nWhen you hire a plumber, you don't provide them with a wrench, a screwdriver, and a drill. They come with their own tools. Developers will increasingly bring their own reusable tools and ways of working too. A portfolio will show not only what they have built, but also how they approach building it.\n\nThat's my take on the future of hiring and candidate portfolios. I may be wrong about the details, but things are going to change, and [we will have to adapt](https:\u002F\u002Fwww.epicweb.dev\u002Ftalks\u002Fepic-bets-practical-tips-for-betting-on-yourself).\n\nIf reusable skills are going to become portfolio artifacts, they need to be easy to publish, discover, and reuse. That brings me to the practical part of this article.\n\n## Make Your Skills Available to the World\n\nThe RFC is promising, but does it mean that we have to manually create and maintain a well-known agent skills file? Honestly, that would be cumbersome. Nobody wants that.\n\nSo I built a Nuxt module that automatically bundles the skills stored in your project's root-level `skills` directory into your site, along with the well-known agent skills file at `.well-known\u002Fagent-skills\u002Findex.json`.\n\nThere is no configuration to write. Just install the module and it will do the job for you.\n\n```sh [terminal]\npnpm install -D nuxt-skills\n```\n\nThen, add the module to your Nuxt config:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  modules: [\n    'nuxt-skills',\n  ],\n})\n```\n\nThat's it. The Nuxt module takes care of the rest.\n\nYour visitors can then use `npx skills add https:\u002F\u002F\u003Cyour-site>` to add your skills to their projects. You could be one of these visitors. To add my skills to a project, I use `npx skills add https:\u002F\u002Fsoubiran.dev`. Easy to remember, simple to use, and it just works.\n\nMaybe you don't use Nuxt. No problem: I also built a Vite plugin.\n\nFirst, install the plugin:\n\n```sh [terminal]\npnpm install -D unplugin-skills\n```\n\nThen, add the plugin to your Vite config:\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\nWithout any additional configuration, the plugin automatically bundles skills available at the root of the project into your site with the well-known agent skills file (`.well-known\u002Fagent-skills\u002Findex.json`).\n\n## Building on the Docus Team's Work\n\nThis work would not have been possible without the incredibly talented [Docus](https:\u002F\u002Fdocus.dev\u002F) team. On 31 March 2026, Hugo opened a pull request to [add support for the well-known discovery of agent skills](https:\u002F\u002Fgithub.com\u002Fnuxt-content\u002Fdocus\u002Fpull\u002F1297). They added it as a module to the Docus layer.\n\nThis pull request inspired my work. At first, I needed a Vite plugin to bundle my skills into my site. Then, I realized that creating a Nuxt module anyone could use would not be too difficult if I reused the Vite plugin's core logic. So I did it, and now you can use it too.\n\n- Vite plugin: \u003CGitHubLink repo=\"barbapapazes\u002Funplugin-skills\" \u002F>\n- Nuxt module: \u003CGitHubLink repo=\"barbapapazes\u002Fnuxt-skills\" \u002F>\n\nThat's the power of open source. _Thank you, Hugo, for inspiring me._\n",{"title":1193,"description":1608},{"loc":1610},"posts\u002Fagent-skills-will-replace-side-projects-in-portfolios","FXjTqe19fhrwV-J218t-_sIoZzX5LMMckR54SCWJhfU",{"id":1617,"title":1618,"body":1619,"contentId":1835,"date":1607,"description":1836,"extension":18,"meta":1837,"navigation":20,"path":1838,"rawbody":1839,"robots":23,"schemaOrg":23,"seo":1840,"sitemap":1841,"stem":1849,"__hash__":1850},"posts\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder.md","AI Is Changing the World: Hackathons Are for Every Builder",{"type":7,"value":1620,"toc":1830},[1621,1624,1627,1630,1645,1648,1652,1655,1658,1661,1664,1678,1681,1684,1687,1694,1698,1701,1704,1707,1716,1719,1722,1725,1728,1741,1747,1756,1762,1768,1777,1783,1789,1793,1796,1799,1802,1805,1808,1811,1819,1822,1825,1827],[35,1622,1623],{},"In today's article, I want to try something new.",[35,1625,1626],{},"I want to write for everyone. Absolutely everyone. Whether you're a seasoned developer or someone who knows only the basics of using a computer, I'll do my best to make this article accessible to you.",[35,1628,1629],{},"You might be wondering why I'm making this change, especially in an article about hackathons. These events are usually associated with developers and programmers. But lately, that has started to change.",[35,1631,1632,1633,1638,1639,1644],{},"Since AI coding tools became widely available, writing code has become much more accessible. Services like ",[39,1634,1637],{"href":1635,"rel":1636},"https:\u002F\u002Fbolt.new\u002F",[77],"Bolt"," and ",[39,1640,1643],{"href":1641,"rel":1642},"https:\u002F\u002Flovable.dev\u002F",[77],"Lovable"," can help people turn a simple idea into a working prototype within minutes. Even my dad, who is not a software engineer, has started building personal software for his own needs.",[35,1646,1647],{},"For hackathons, this lower barrier to entry changes who can take part and what they can build. And it could be the tip of the iceberg.",[51,1649,1651],{"id":1650},"what-is-a-hackathon","What is a hackathon?",[35,1653,1654],{},"Let's start with the basics.",[35,1656,1657],{},"A hackathon is a short event where people turn ideas into working prototypes. Participants work alone or in teams, usually around a theme chosen by the organizers. The theme is often broad enough to leave room for creativity. The event can last from a few hours to several days, and each team presents what it has built at the end. Hackathons adapt to each participant, bringing both the competitions, networking opportunities, learning experiences, collaboration, or simply the chance to meet new people.",[35,1659,1660],{},"The definition is broad because organizers shape the format to their goals. A company running an internal hackathon might ask employees to create new features or step away from their routines to explore a technology like AI. For a public hackathon, it might invite people to experiment with its products or imagine new uses for them. A hackathon can support marketing, recruitment, product research, or simply bring a community together.",[35,1662,1663],{},"People, on their side, participate for many reasons:",[1576,1665,1666,1669,1672,1675],{},[71,1667,1668],{},"to learn new skills;",[71,1670,1671],{},"to meet people with similar interests;",[71,1673,1674],{},"to break away from their routine and have fun;",[71,1676,1677],{},"to compete and perhaps win a prize.",[35,1679,1680],{},"A hackathon gives you a reason to create, learn, and meet new people. Everyone can find their own reason to participate in one.",[35,1682,1683],{},"During a hackathon, time feels distorted. Each hour is so dense with learning, building, and chatting that it can feel like a whole week. Hackathons can be exhausting and frustrating, but I have always learned something from them.",[35,1685,1686],{},"I highly encourage you to try one, even if you don't have any technical skills. You might be surprised by what you can achieve.",[35,1688,1689],{},[1690,1691],"img",{"alt":1692,"src":1693},"Participants of the Mistral Worldwide Hackathon in Paris, forming the Mistral logo","\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fparticipants-of-the-mistral-worldwide-hackathon.webp",[51,1695,1697],{"id":1696},"eight-years-of-hackathons","Eight years of hackathons",[35,1699,1700],{},"I've been participating in hackathons since 2018.",[35,1702,1703],{},"I started in 2018 with the Nuit de l'Info. It brings together thousands of students at sites across France to develop an application overnight. Held every year on the night between the first Thursday and Friday of December, it attracted more than 4,000 students in 2018.",[35,1705,1706],{},"At the time, I was a student, and I knew nothing about web development. One of the associations on my campus organized a course before the hackathon. They taught us the basics of HTML and CSS, along with just enough Git to work as a team. That was it.",[35,1708,1709,1710,1715],{},"The theme was ",[39,1711,1714],{"href":1712,"rel":1713},"https:\u002F\u002Fwww.nuitdelinfo.com\u002Fnuitinfo\u002F_media\u002Fsujets:sujet_n2i_2018.pdf",[77],"\"Digital support for an explorer in extreme conditions\"",". My team built a dashboard for a mission to explore Mars. It displayed an interactive 3D model of the Martian base in the browser. When users clicked a building, the view zoomed in and displayed its status. I felt like a child, amazed by everything I saw. I will never forget that feeling the first time I saw it working.",[35,1717,1718],{},"Honestly, I didn't do much during the hackathon. However, it was a revelation for me. At that moment, I realized that I wanted to create on the web. I fell in love with the idea of having a blank canvas and the ability to create anything I wanted on it. I was hooked.",[35,1720,1721],{},"Then, I took part in the 2019, 2020, and 2021 editions, with a different team each time. At first, I learned from the people around me. Later, I was the one helping teammates get started.",[35,1723,1724],{},"All of this happened before tools like ChatGPT could generate and explain code on demand.",[35,1726,1727],{},"In 2026, I took part in two Mistral hackathons.",[35,1729,1730,1731,1736,1737,215],{},"The first was ",[39,1732,1735],{"href":1733,"rel":1734},"https:\u002F\u002Fworldwide-hackathon.mistral.ai\u002F",[77],"the Mistral Worldwide Hackathon"," in February. It took place in seven cities around the world: Paris, London, New York, San Francisco, Singapore, Sydney, and Tokyo. Each location hosted 100 participants for two days, with everyone competing on the same theme. My teammate and I won the Paris event. If you want to learn more about it, I wrote a blog post: ",[39,1738,1740],{"href":1739},".\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon","From Idea to Victory: the 2026 Mistral AI Paris Hackathon",[35,1742,1743],{},[1690,1744],{"alt":1745,"src":1746},"The winning team of the Mistral AI Hackathon in Paris","\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fthe-winning-team-of-the-mistral-ai-hackathon-in-paris.webp",[35,1748,1749,1750,1755],{},"The second was ",[39,1751,1754],{"href":1752,"rel":1753},"https:\u002F\u002Fluma.com\u002Fmistral-summer-vibe-hackathon-paris",[77],"the Mistral Vibe Hackathon"," in July. Mistral organized it around Vibe, the name of both its chat and its AI coding assistant. The hackathon had two tracks. Participants could either build something with the chat or contribute new capabilities to the public source code behind its command-line application.",[35,1757,1758],{},[1690,1759],{"alt":1760,"src":1761},"The main room of the Mistral Vibe Hackathon","\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fthe-main-room-of-the-mistral-vibe-hackathon.webp",[35,1763,1764],{},[1690,1765],{"alt":1766,"src":1767},"Marion working on our project during the Mistral Vibe Hackathon","\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fmarion-working-on-our-project.webp",[35,1769,1770,1771,1776],{},"I took part in the first track with my teammate ",[39,1772,1775],{"href":1773,"rel":1774},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F",[77],"Marion",". We wanted to help readers evaluate potentially misleading political posts on social media. Publishing a false claim takes seconds, but checking and correcting it can take much longer. Our tool examined a post's sources, traced the history of its claims, checked factual consistency, and analyzed its language. It was fun to discover Mistral Vibe and work with Marion. We learned a lot in only eight hours.",[35,1778,1779],{},[1690,1780],{"alt":1781,"src":1782},"Marion and Estéban before pitching our project in front of the jury","\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fmarion-and-esteban-before-pitching-our-project.webp",[35,1784,1785],{},[1690,1786],{"alt":1787,"src":1788},"Winners of the Mistral Vibe Hackathon","\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fwinners-of-the-mistral-vibe-hackathon.webp",[51,1790,1792],{"id":1791},"hackathons-are-now-for-everyone","Hackathons are now for everyone",[35,1794,1795],{},"I shared this history because the hackathons I attended in 2026 felt very different from those I knew in 2018. My first team had 10 people, mainly web enthusiasts. After working together overnight, we delivered our project without preparing a pitch or slides for a jury. At the Mistral events, teams had one to four people. In as little as eight hours, they delivered a working product, a pitch, slides, and a live demo.",[35,1797,1798],{},"That contrast is striking. The events I knew were mostly made for developers. The ones I attended in 2026 welcomed a wider range of people who wanted to build something. For many participants, the first question is becoming less about technical expertise and more about whether they want to try.",[35,1800,1801],{},"You can be a designer, a product manager, a student, or someone writing a thesis, whether or not you work in tech. What matters is the desire to turn an idea into something real and learn along the way. Some hackathons remain highly technical, but others are reaching a broader audience. They may last anywhere from a few hours to two days, yet participants can still build impressive projects.",[35,1803,1804],{},"AI plays a growing role in making this possible. Participants can ask an AI coding assistant to create a first version, explain unfamiliar code, or help fix a problem. During hackathons, you go even further, you vibe code.",[35,1806,1807],{},"That is a huge shift. You no longer need to be a developer to start building software, and hackathons make that change easy to see.",[35,1809,1810],{},"Easier access to code does not make software development expertise irrelevant. Building a prototype is one thing. Creating software that remains reliable, secure, and useful over time is another. Still, the developer's job is changing quickly. We have to keep learning and adapting, which can be exciting and unsettling at the same time.",[35,1812,1813,1814,1818],{},"I recently gave ",[39,1815,1817],{"href":1816},"\u002Ftalks","a talk about agency",", which I define as the power to act. Agency helps you start before you have every answer, learn what a project requires, and adjust when your first approach fails. That matters when the tools and skills around us keep changing.",[35,1820,1821],{},"Hackathons are a great way to develop this skill. They give you a deadline, people to learn from, and permission to experiment, fail, and try again.",[35,1823,1824],{},"You arrive with an idea. A few hours later, you leave having built something, even if it is imperfect. To me, that is agency in practice.",[1159,1826],{},[35,1828,1829],{},"Thank you, Marion, for taking part in the Mistral Vibe Hackathon!",{"title":13,"searchDepth":14,"depth":14,"links":1831},[1832,1833,1834],{"id":1650,"depth":14,"text":1651},{"id":1696,"depth":14,"text":1697},{"id":1791,"depth":14,"text":1792},"95a478c5-2f16-4af0-8f1b-c17695bf53e9","How AI is making hackathons accessible beyond developers and helping more people turn their ideas into something real.",{},"\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder","---\ncontentId: 95a478c5-2f16-4af0-8f1b-c17695bf53e9\ntitle: \"AI Is Changing the World: Hackathons Are for Every Builder\"\ndescription: How AI is making hackathons accessible beyond developers and helping more people turn their ideas into something real.\ndate: 2026-08-25\n---\n\nIn today's article, I want to try something new.\n\nI want to write for everyone. Absolutely everyone. Whether you're a seasoned developer or someone who knows only the basics of using a computer, I'll do my best to make this article accessible to you.\n\nYou might be wondering why I'm making this change, especially in an article about hackathons. These events are usually associated with developers and programmers. But lately, that has started to change.\n\nSince AI coding tools became widely available, writing code has become much more accessible. Services like [Bolt](https:\u002F\u002Fbolt.new\u002F) and [Lovable](https:\u002F\u002Flovable.dev\u002F) can help people turn a simple idea into a working prototype within minutes. Even my dad, who is not a software engineer, has started building personal software for his own needs.\n\nFor hackathons, this lower barrier to entry changes who can take part and what they can build. And it could be the tip of the iceberg.\n\n## What is a hackathon?\n\nLet's start with the basics.\n\nA hackathon is a short event where people turn ideas into working prototypes. Participants work alone or in teams, usually around a theme chosen by the organizers. The theme is often broad enough to leave room for creativity. The event can last from a few hours to several days, and each team presents what it has built at the end. Hackathons adapt to each participant, bringing both the competitions, networking opportunities, learning experiences, collaboration, or simply the chance to meet new people.\n\nThe definition is broad because organizers shape the format to their goals. A company running an internal hackathon might ask employees to create new features or step away from their routines to explore a technology like AI. For a public hackathon, it might invite people to experiment with its products or imagine new uses for them. A hackathon can support marketing, recruitment, product research, or simply bring a community together.\n\nPeople, on their side, participate for many reasons:\n\n- to learn new skills;\n- to meet people with similar interests;\n- to break away from their routine and have fun;\n- to compete and perhaps win a prize.\n\nA hackathon gives you a reason to create, learn, and meet new people. Everyone can find their own reason to participate in one.\n\nDuring a hackathon, time feels distorted. Each hour is so dense with learning, building, and chatting that it can feel like a whole week. Hackathons can be exhausting and frustrating, but I have always learned something from them.\n\nI highly encourage you to try one, even if you don't have any technical skills. You might be surprised by what you can achieve.\n\n![Participants of the Mistral Worldwide Hackathon in Paris, forming the Mistral logo](\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fparticipants-of-the-mistral-worldwide-hackathon.webp)\n\n## Eight years of hackathons\n\nI've been participating in hackathons since 2018.\n\nI started in 2018 with the Nuit de l'Info. It brings together thousands of students at sites across France to develop an application overnight. Held every year on the night between the first Thursday and Friday of December, it attracted more than 4,000 students in 2018.\n\nAt the time, I was a student, and I knew nothing about web development. One of the associations on my campus organized a course before the hackathon. They taught us the basics of HTML and CSS, along with just enough Git to work as a team. That was it.\n\nThe theme was [\"Digital support for an explorer in extreme conditions\"](https:\u002F\u002Fwww.nuitdelinfo.com\u002Fnuitinfo\u002F_media\u002Fsujets:sujet_n2i_2018.pdf). My team built a dashboard for a mission to explore Mars. It displayed an interactive 3D model of the Martian base in the browser. When users clicked a building, the view zoomed in and displayed its status. I felt like a child, amazed by everything I saw. I will never forget that feeling the first time I saw it working.\n\nHonestly, I didn't do much during the hackathon. However, it was a revelation for me. At that moment, I realized that I wanted to create on the web. I fell in love with the idea of having a blank canvas and the ability to create anything I wanted on it. I was hooked.\n\nThen, I took part in the 2019, 2020, and 2021 editions, with a different team each time. At first, I learned from the people around me. Later, I was the one helping teammates get started.\n\nAll of this happened before tools like ChatGPT could generate and explain code on demand.\n\nIn 2026, I took part in two Mistral hackathons.\n\nThe first was [the Mistral Worldwide Hackathon](https:\u002F\u002Fworldwide-hackathon.mistral.ai\u002F) in February. It took place in seven cities around the world: Paris, London, New York, San Francisco, Singapore, Sydney, and Tokyo. Each location hosted 100 participants for two days, with everyone competing on the same theme. My teammate and I won the Paris event. If you want to learn more about it, I wrote a blog post: [From Idea to Victory: the 2026 Mistral AI Paris Hackathon](.\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon.md).\n\n![The winning team of the Mistral AI Hackathon in Paris](\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fthe-winning-team-of-the-mistral-ai-hackathon-in-paris.webp)\n\nThe second was [the Mistral Vibe Hackathon](https:\u002F\u002Fluma.com\u002Fmistral-summer-vibe-hackathon-paris) in July. Mistral organized it around Vibe, the name of both its chat and its AI coding assistant. The hackathon had two tracks. Participants could either build something with the chat or contribute new capabilities to the public source code behind its command-line application.\n\n![The main room of the 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 working on our project during the Mistral Vibe Hackathon](\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fmarion-working-on-our-project.webp)\n\nI took part in the first track with my teammate [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F). We wanted to help readers evaluate potentially misleading political posts on social media. Publishing a false claim takes seconds, but checking and correcting it can take much longer. Our tool examined a post's sources, traced the history of its claims, checked factual consistency, and analyzed its language. It was fun to discover Mistral Vibe and work with Marion. We learned a lot in only eight hours.\n\n![Marion and Estéban before pitching our project in front of the jury](\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fmarion-and-esteban-before-pitching-our-project.webp)\n\n![Winners of the Mistral Vibe Hackathon](\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fwinners-of-the-mistral-vibe-hackathon.webp)\n\n## Hackathons are now for everyone\n\nI shared this history because the hackathons I attended in 2026 felt very different from those I knew in 2018. My first team had 10 people, mainly web enthusiasts. After working together overnight, we delivered our project without preparing a pitch or slides for a jury. At the Mistral events, teams had one to four people. In as little as eight hours, they delivered a working product, a pitch, slides, and a live demo.\n\nThat contrast is striking. The events I knew were mostly made for developers. The ones I attended in 2026 welcomed a wider range of people who wanted to build something. For many participants, the first question is becoming less about technical expertise and more about whether they want to try.\n\nYou can be a designer, a product manager, a student, or someone writing a thesis, whether or not you work in tech. What matters is the desire to turn an idea into something real and learn along the way. Some hackathons remain highly technical, but others are reaching a broader audience. They may last anywhere from a few hours to two days, yet participants can still build impressive projects.\n\nAI plays a growing role in making this possible. Participants can ask an AI coding assistant to create a first version, explain unfamiliar code, or help fix a problem. During hackathons, you go even further, you vibe code.\n\nThat is a huge shift. You no longer need to be a developer to start building software, and hackathons make that change easy to see.\n\nEasier access to code does not make software development expertise irrelevant. Building a prototype is one thing. Creating software that remains reliable, secure, and useful over time is another. Still, the developer's job is changing quickly. We have to keep learning and adapting, which can be exciting and unsettling at the same time.\n\nI recently gave [a talk about agency](\u002Ftalks), which I define as the power to act. Agency helps you start before you have every answer, learn what a project requires, and adjust when your first approach fails. That matters when the tools and skills around us keep changing.\n\nHackathons are a great way to develop this skill. They give you a deadline, people to learn from, and permission to experiment, fail, and try again.\n\nYou arrive with an idea. A few hours later, you leave having built something, even if it is imperfect. To me, that is agency in practice.\n\n---\n\nThank you, Marion, for taking part in the Mistral Vibe Hackathon!\n",{"title":1618,"description":1836},{"loc":1838,"images":1842},[1843,1844,1845,1846,1847,1848],{"loc":1693},{"loc":1746},{"loc":1761},{"loc":1767},{"loc":1782},{"loc":1788},"posts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder","7tf9i1o9hpzpzeEJUojpCRmLx5HbC86a2n9LyimNlR4",{"id":1852,"title":1853,"body":1854,"contentId":2993,"date":2994,"description":2995,"extension":18,"meta":2996,"navigation":20,"path":2997,"rawbody":2998,"robots":23,"schemaOrg":23,"seo":2999,"sitemap":3000,"stem":3001,"__hash__":3002},"posts\u002Fposts\u002Fbringing-cloudflare-ai-search-to-docus-and-vitepress.md","Bringing Cloudflare AI Search to Docus and VitePress",{"type":7,"value":1855,"toc":2982},[1856,1868,1872,1887,1890,1896,1900,1903,1906,1909,1912,1917,1920,1927,2109,2119,2122,2132,2146,2149,2167,2172,2279,2282,2294,2298,2301,2312,2319,2322,2330,2333,2347,2352,2437,2440,2443,2447,2456,2463,2466,2469,2473,2476,2479,2491,2498,2501,2515,2521,2654,2661,2663,2675,2680,2772,2782,2785,2797,2801,2810,2813,2823,2839,2848,2851,2873,2888,2891,2894,2979],[35,1857,1858,1859,1864,1865],{},"In July 2025, ",[39,1860,1863],{"href":1861,"rel":1862},"https:\u002F\u002Fvercel.com\u002Fblog\u002Fnuxtlabs-joins-vercel",[77],"NuxtLabs joined Vercel",". As a huge fan of the framework and the team, I was really happy to see them join a company like Vercel. Open-source work is extremely demanding, and the team was spending a lot of energy building a sustainable financial foundation. Joining Vercel gives them more room to focus on the framework and its ecosystem. They are doing a great job. ",[1217,1866,1867],{},"Thank you, guys!",[51,1869,1871],{"id":1870},"bringing-cloudflare-into-the-nuxt-world","Bringing Cloudflare Into the Nuxt World",[35,1873,1874,1875,1880,1881,1886],{},"Since NuxtLabs joined Vercel, Vercel's services have naturally become more visible in the surrounding ecosystem. That preference does not extend to Nuxt itself, which remains platform-agnostic thanks in large part to ",[39,1876,1879],{"href":1877,"rel":1878},"https:\u002F\u002Fgithub.com\u002Fdanielroe",[77],"Daniel's"," work on the framework. The same is true of the underlying UnJS primitives, thanks to ",[39,1882,1885],{"href":1883,"rel":1884},"https:\u002F\u002Fgithub.com\u002Fpi0",[77],"Pooya's"," work.",[35,1888,1889],{},"I, however, am a Cloudflare user. I have been using many of its developer services for years, and sometimes the documentation only covers Vercel.",[35,1891,1892,1893],{},"I want to change that. I want to bring Cloudflare further into the Nuxt ecosystem to make it more of a first-class citizen. Fortunately, the Nuxt team is open to contributions and really welcoming of changes that make their work compatible with more than Vercel. ",[1217,1894,1895],{},"Honestly, it's totally understandable that they optimize for Vercel first. I'm just that guy who has decided to use Cloudflare.",[51,1897,1899],{"id":1898},"docus-the-nuxt-documentation-framework","Docus, the Nuxt Documentation Framework",[35,1901,1902],{},"The journey starts with Docus.",[35,1904,1905],{},"Docus is a documentation framework built on top of Nuxt Content. It allows you to create a documentation website in seconds with many features out of the box. It includes search, an assistant, an MCP server, agent skill distribution, and many other features that make it easy to create a documentation website. Oh, and it's beautiful.",[35,1907,1908],{},"For a long time, I used VitePress because it was much simpler. But since the rise of AI, Docus has become much better suited to needs such as assistants, agent access, and skill distribution. So, I'm now using Docus wherever I need a documentation website.",[35,1910,1911],{},"But if you take a look at the documentation, you'll see that the built-in assistant is Vercel-only and there's no integration with Cloudflare AI Search to replace the built-in full-text search.",[1913,1914,1916],"h3",{"id":1915},"the-nuxt-module-for-docus","The Nuxt Module for Docus",[35,1918,1919],{},"Docus is based on Nuxt. This means that we can create a Nuxt module to extend or override it.",[35,1921,1922,1923,1926],{},"In fact, Docus' search is powered by a component named ",[156,1924,1925],{},"AppSearch",". We can change its behavior by simply swapping it with our own component. In a Nuxt module, it's as simple as registering a component with a higher priority than the built-in one so that our implementation takes precedence.",[192,1928,1930],{"className":1370,"code":1929,"language":1373,"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,1931,1932,1962,1966,1986,1995,2023,2027,2036,2049,2064,2092,2099,2103],{"__ignoreMap":13},[200,1933,1934,1936,1938,1941,1943,1946,1948,1951,1953,1955,1957,1960],{"class":202,"line":203},[200,1935,973],{"class":206},[200,1937,318],{"class":214},[200,1939,1940],{"class":210}," addComponent",[200,1942,550],{"class":214},[200,1944,1945],{"class":210}," createResolver",[200,1947,550],{"class":214},[200,1949,1950],{"class":210}," defineNuxtModule",[200,1952,981],{"class":214},[200,1954,984],{"class":206},[200,1956,242],{"class":214},[200,1958,1959],{"class":245},"nuxt\u002Fkit",[200,1961,992],{"class":214},[200,1963,1964],{"class":202,"line":14},[200,1965,529],{"emptyLinePlaceholder":20},[200,1967,1968,1970,1972,1974,1976,1980,1982,1984],{"class":202,"line":255},[200,1969,1380],{"class":206},[200,1971,1383],{"class":206},[200,1973,1950],{"class":223},[200,1975,600],{"class":214},[200,1977,1979],{"class":1978},"sBMFI","AiSearchOptions",[200,1981,675],{"class":214},[200,1983,227],{"class":210},[200,1985,230],{"class":214},[200,1987,1988,1991,1993],{"class":202,"line":272},[200,1989,1990],{"class":235},"  setup",[200,1992,1532],{"class":214},[200,1994,280],{"class":214},[200,1996,1997,2000,2003,2005,2007,2009,2011,2013,2016,2018,2021],{"class":202,"line":283},[200,1998,1999],{"class":379},"    const",[200,2001,2002],{"class":210}," resolver",[200,2004,510],{"class":214},[200,2006,1945],{"class":223},[200,2008,227],{"class":235},[200,2010,973],{"class":206},[200,2012,215],{"class":214},[200,2014,2015],{"class":210},"meta",[200,2017,215],{"class":214},[200,2019,2020],{"class":210},"url",[200,2022,416],{"class":235},[200,2024,2025],{"class":202,"line":300},[200,2026,529],{"emptyLinePlaceholder":20},[200,2028,2029,2032,2034],{"class":202,"line":310},[200,2030,2031],{"class":223},"    addComponent",[200,2033,227],{"class":235},[200,2035,230],{"class":214},[200,2037,2038,2041,2043,2047],{"class":202,"line":336},[200,2039,2040],{"class":235},"      priority",[200,2042,239],{"class":214},[200,2044,2046],{"class":2045},"sbssI"," 100",[200,2048,252],{"class":214},[200,2050,2051,2054,2056,2058,2060,2062],{"class":202,"line":342},[200,2052,2053],{"class":235},"      name",[200,2055,239],{"class":214},[200,2057,242],{"class":214},[200,2059,1925],{"class":245},[200,2061,249],{"class":214},[200,2063,252],{"class":214},[200,2065,2066,2069,2071,2073,2075,2078,2080,2082,2085,2087,2090],{"class":202,"line":365},[200,2067,2068],{"class":235},"      filePath",[200,2070,239],{"class":214},[200,2072,2002],{"class":210},[200,2074,215],{"class":214},[200,2076,2077],{"class":223},"resolve",[200,2079,227],{"class":235},[200,2081,249],{"class":214},[200,2083,2084],{"class":245},".\u002Fruntime\u002Fcomponents\u002FAppSearch.vue",[200,2086,249],{"class":214},[200,2088,2089],{"class":235},")",[200,2091,252],{"class":214},[200,2093,2094,2097],{"class":202,"line":371},[200,2095,2096],{"class":214},"    }",[200,2098,416],{"class":235},[200,2100,2101],{"class":202,"line":398},[200,2102,368],{"class":214},[200,2104,2105,2107],{"class":202,"line":405},[200,2106,413],{"class":214},[200,2108,416],{"class":210},[35,2110,2111,2112,2114,2115,2118],{},"Then we have full control over the ",[156,2113,1925],{}," component. We can reuse the ",[156,2116,2117],{},"ContentSearch"," component from Nuxt UI and feed it results from the Cloudflare AI Search public endpoint.",[35,2120,2121],{},"That works like a charm!",[130,2123,132,2124,132,2129],{},[134,2125,136,2126,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,2127],{"src":2128,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fbringing-cloudflare-ai-search-to-docus-and-vitepress\u002Fdocus.mp4",[143,2130,2131],{},"Cloudflare AI Search integration in Docus",[35,2133,2134,2135,2140,2141,215],{},"I'm really happy with the result, and I'll definitely use it in my next projects. You can check the demo on ",[39,2136,2139],{"href":2137,"rel":2138},"https:\u002F\u002Fdocus-cloudflare-ai-search.barbapapazes.dev",[77],"docus-cloudflare-ai-search.barbapapazes.dev"," or the ",[39,2142,2145],{"href":2143,"rel":2144},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fdocus-cloudflare-ai-search",[77],"GitHub repository",[35,2147,2148],{},"To use the module, you need to install it:",[192,2150,2154],{"className":2151,"code":2152,"language":2153,"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,2155,2156],{"__ignoreMap":13},[200,2157,2158,2161,2164],{"class":202,"line":203},[200,2159,2160],{"class":1978},"pnpm",[200,2162,2163],{"class":245}," add",[200,2165,2166],{"class":245}," docus-cloudflare-ai-search\n",[35,2168,2169,2170,239],{},"Then, you need to add the module to your ",[156,2171,1372],{},[192,2173,2175],{"className":1370,"code":2174,"language":1373,"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,2176,2177,2196,2200,2212,2231,2240,2249,2265,2269,2273],{"__ignoreMap":13},[200,2178,2179,2181,2183,2185,2187,2189,2191,2194],{"class":202,"line":203},[200,2180,973],{"class":206},[200,2182,318],{"class":214},[200,2184,1386],{"class":210},[200,2186,981],{"class":214},[200,2188,984],{"class":206},[200,2190,242],{"class":214},[200,2192,2193],{"class":245},"nuxt",[200,2195,992],{"class":214},[200,2197,2198],{"class":202,"line":14},[200,2199,529],{"emptyLinePlaceholder":20},[200,2201,2202,2204,2206,2208,2210],{"class":202,"line":255},[200,2203,1380],{"class":206},[200,2205,1383],{"class":206},[200,2207,1386],{"class":223},[200,2209,227],{"class":210},[200,2211,230],{"class":214},[200,2213,2214,2216,2218,2220,2222,2225,2227,2229],{"class":202,"line":272},[200,2215,1395],{"class":235},[200,2217,239],{"class":214},[200,2219,350],{"class":210},[200,2221,249],{"class":214},[200,2223,2224],{"class":245},"docus-cloudflare-ai-search",[200,2226,249],{"class":214},[200,2228,360],{"class":210},[200,2230,252],{"class":214},[200,2232,2233,2236,2238],{"class":202,"line":283},[200,2234,2235],{"class":235},"  docus",[200,2237,239],{"class":214},[200,2239,280],{"class":214},[200,2241,2242,2245,2247],{"class":202,"line":300},[200,2243,2244],{"class":235},"    aiSearch",[200,2246,239],{"class":214},[200,2248,280],{"class":214},[200,2250,2251,2254,2256,2258,2261,2263],{"class":202,"line":310},[200,2252,2253],{"class":235},"      endpoint",[200,2255,239],{"class":214},[200,2257,242],{"class":214},[200,2259,2260],{"class":245},"https:\u002F\u002F\u003Cyour-domain>",[200,2262,249],{"class":214},[200,2264,252],{"class":214},[200,2266,2267],{"class":202,"line":336},[200,2268,339],{"class":214},[200,2270,2271],{"class":202,"line":342},[200,2272,368],{"class":214},[200,2274,2275,2277],{"class":202,"line":365},[200,2276,413],{"class":214},[200,2278,416],{"class":210},[35,2280,2281],{},"While working on this module, I realized that the built-in assistant was only available through Vercel. Too bad, because having Cloudflare AI Search but no way to run the assistant through Cloudflare was far from what I'd call Cloudflare-native.",[35,2283,2284,2285,2290,2291],{},"The assistant uses an AI gateway to route requests to its model. Docus supported Vercel AI Gateway, but it did not expose the configuration needed to use Cloudflare AI Gateway instead. So, I opened a PR to add the provider, gateway, and model configuration required for Cloudflare: ",[39,2286,2289],{"href":2287,"rel":2288},"https:\u002F\u002Fgithub.com\u002Fnuxt-content\u002Fdocus\u002Fpull\u002F1421",[77],"feat(assistant): can use cloudflare for assistant",". The PR keeps Vercel as the default and makes Cloudflare an optional provider configured through environment variables. ",[1217,2292,2293],{},"As with everything in open source, you can just open a PR and make it happen.",[51,2295,2297],{"id":2296},"search-needs-something-to-search","Search Needs Something to Search",[35,2299,2300],{},"The integration works, which is a nice first step. But for now, our search index is empty. Cloudflare AI Search provides three ways to populate it:",[68,2302,2303,2306,2309],{},[71,2304,2305],{},"Using built-in storage, populated through the dashboard or Items API",[71,2307,2308],{},"Connecting an R2 bucket",[71,2310,2311],{},"Crawling a public website on a domain you own",[35,2313,2314,2315,2318],{},"Manual uploads through the dashboard do not fit because the content must be synchronized on every production deployment. An R2 bucket is viable, but uploading the content is not as simple as using rclone because each file also needs custom metadata. That would require additional tooling. Website crawling can extract custom metadata from HTML ",[156,2316,2317],{},"\u003Cmeta>"," tags, but adding those tags to every generated page would also require a custom integration.",[35,2320,2321],{},"The best fit is therefore to populate the built-in storage through the Items API, which supports attaching custom metadata while uploading each file. This still needs a custom integration, but with the right one, we can really smooth out the developer experience.",[35,2323,2324,2325,239],{},"For Nuxt, you can use the package ",[39,2326,2329],{"href":2327,"rel":2328},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync",[77],"cloudflare-ai-search-sync",[35,2331,2332],{},"Install the package:",[192,2334,2336],{"className":2151,"code":2335,"language":2153,"meta":13,"style":13},"pnpm add cloudflare-ai-search-sync\n",[156,2337,2338],{"__ignoreMap":13},[200,2339,2340,2342,2344],{"class":202,"line":203},[200,2341,2160],{"class":1978},[200,2343,2163],{"class":245},[200,2345,2346],{"class":245}," cloudflare-ai-search-sync\n",[35,2348,2349,2350,239],{},"Then, add the module to your ",[156,2351,1372],{},[192,2353,2355],{"className":1370,"code":2354,"language":1373,"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,2356,2357,2369,2377,2388,2399,2405,2414,2427,2431],{"__ignoreMap":13},[200,2358,2359,2361,2363,2365,2367],{"class":202,"line":203},[200,2360,1380],{"class":206},[200,2362,1383],{"class":206},[200,2364,1386],{"class":223},[200,2366,227],{"class":210},[200,2368,230],{"class":214},[200,2370,2371,2373,2375],{"class":202,"line":14},[200,2372,1395],{"class":235},[200,2374,239],{"class":214},[200,2376,1400],{"class":210},[200,2378,2379,2381,2384,2386],{"class":202,"line":255},[200,2380,1405],{"class":214},[200,2382,2383],{"class":245},"@nuxt\u002Fcontent",[200,2385,249],{"class":214},[200,2387,252],{"class":214},[200,2389,2390,2392,2395,2397],{"class":202,"line":272},[200,2391,1405],{"class":214},[200,2393,2394],{"class":245},"cloudflare-ai-search-sync\u002Fnuxt",[200,2396,249],{"class":214},[200,2398,252],{"class":214},[200,2400,2401,2403],{"class":202,"line":283},[200,2402,1417],{"class":210},[200,2404,252],{"class":214},[200,2406,2407,2410,2412],{"class":202,"line":300},[200,2408,2409],{"class":235},"  cloudflareAISearchSync",[200,2411,239],{"class":214},[200,2413,280],{"class":214},[200,2415,2416,2419,2421,2425],{"class":202,"line":310},[200,2417,2418],{"class":235},"    enabled",[200,2420,239],{"class":214},[200,2422,2424],{"class":2423},"sfNiH"," true",[200,2426,252],{"class":214},[200,2428,2429],{"class":202,"line":336},[200,2430,368],{"class":214},[200,2432,2433,2435],{"class":202,"line":342},[200,2434,413],{"class":214},[200,2436,416],{"class":210},[35,2438,2439],{},"Now, each time you build your Nuxt project, Markdown files processed by Nuxt Content will be uploaded to Cloudflare AI Search with the right metadata. Feels like magic!",[35,2441,2442],{},"It's not mandatory to use this integration as long as you provide the correct metadata for your content.",[51,2444,2446],{"id":2445},"going-above-and-beyond","Going Above and Beyond",[35,2448,2449,2450,2455],{},"Bringing a new feature to the Nuxt ecosystem was really easy thanks to the Nuxt module system. It provides extension points for a wide range of use cases. Just take a look at the ",[39,2451,2454],{"href":2452,"rel":2453},"https:\u002F\u002Fnuxt.com\u002Fmodules",[77],"Nuxt modules"," page to see the possibilities.",[35,2457,2458,2459,2462],{},"To try an idea locally, modules are the perfect fit. Create a Nuxt project, add a ",[156,2460,2461],{},"modules"," folder, and start hacking on your idea. If you want to publish the module, the official Nuxt module starter provides the full project structure. Give your AI assistant the Nuxt modules documentation, and you can get a prototype running surprisingly quickly. There's never been a better time to try it.",[35,2464,2465],{},"But not everything in the Vue ecosystem is Nuxt. We have VitePress and tons of Vite plugins to create our own systems. Speaking of Vite plugins, they are a good way to reach a wider audience. A Vite plugin can often serve frameworks such as Astro, Svelte, React, and even Nuxt, although each integration may still require framework-specific work. However, Vite plugins are much harder to create. So, starting at a high level with a Nuxt module, testing whether the idea really works, and then moving down the stack to create a Vite plugin is a good way to proceed.",[35,2467,2468],{},"And that's exactly what I did with the Cloudflare AI Search integration.",[1913,2470,2472],{"id":2471},"vitepress-was-the-first-obvious-choice","VitePress Was the First Obvious Choice",[35,2474,2475],{},"Once the proof of concept was validated for Docus, I started thinking about how to make it available for VitePress. I started as a VitePress user, my portfolio still uses it, and the framework has a large documentation community.",[35,2477,2478],{},"Also, VitePress already has first-party support for local and Algolia search, as well as multiple third-party plugins for other search engines. So, bringing Cloudflare AI Search to VitePress was a natural choice, and I could leverage the work already done for the Docus integration.",[35,2480,2481,2482,2487,2488],{},"Using the ",[39,2483,2486],{"href":2484,"rel":2485},"https:\u002F\u002Fsearch.ai.cloudflare.com\u002F",[77],"Cloudflare AI Search Snippet",", I was able to quickly integrate the search engine into VitePress. With the POC validated, I created a dedicated VitePress plugin: ",[1582,2489,215],{"repo":2490},"barbapapazes\u002Fvitepress-plugin-cloudflare-ai-search",[35,2492,2493,2494,2497],{},"Under the hood, the plugin is pretty simple. It swaps the built-in ",[156,2495,2496],{},"VPNavBarSearch"," component with a custom one that integrates the Cloudflare AI Search snippet and receives its configuration through a virtual module.",[35,2499,2500],{},"To get started, you need to install the plugin:",[192,2502,2504],{"className":2151,"code":2503,"language":2153,"meta":13,"style":13},"pnpm add vitepress-plugin-cloudflare-ai-search\n",[156,2505,2506],{"__ignoreMap":13},[200,2507,2508,2510,2512],{"class":202,"line":203},[200,2509,2160],{"class":1978},[200,2511,2163],{"class":245},[200,2513,2514],{"class":245}," vitepress-plugin-cloudflare-ai-search\n",[35,2516,2517,2518,239],{},"Then, you need to add the plugin to your ",[156,2519,2520],{},"config.ts",[192,2522,2524],{"className":1370,"code":2523,"language":1373,"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,2525,2526,2531,2550,2570,2574,2586,2595,2604,2613,2628,2637,2644,2648],{"__ignoreMap":13},[200,2527,2528],{"class":202,"line":203},[200,2529,2530],{"class":401},"\u002F\u002F .vitepress\u002Fconfig.ts\n",[200,2532,2533,2535,2537,2539,2541,2543,2545,2548],{"class":202,"line":14},[200,2534,973],{"class":206},[200,2536,318],{"class":214},[200,2538,1488],{"class":210},[200,2540,981],{"class":214},[200,2542,984],{"class":206},[200,2544,242],{"class":214},[200,2546,2547],{"class":245},"vitepress",[200,2549,992],{"class":214},[200,2551,2552,2554,2556,2559,2561,2563,2565,2568],{"class":202,"line":255},[200,2553,973],{"class":206},[200,2555,318],{"class":214},[200,2557,2558],{"class":210}," cloudflareAISearch",[200,2560,981],{"class":214},[200,2562,984],{"class":206},[200,2564,242],{"class":214},[200,2566,2567],{"class":245},"vitepress-plugin-cloudflare-ai-search",[200,2569,992],{"class":214},[200,2571,2572],{"class":202,"line":272},[200,2573,529],{"emptyLinePlaceholder":20},[200,2575,2576,2578,2580,2582,2584],{"class":202,"line":283},[200,2577,1380],{"class":206},[200,2579,1383],{"class":206},[200,2581,1488],{"class":223},[200,2583,227],{"class":210},[200,2585,230],{"class":214},[200,2587,2588,2591,2593],{"class":202,"line":300},[200,2589,2590],{"class":235},"  vite",[200,2592,239],{"class":214},[200,2594,280],{"class":214},[200,2596,2597,2600,2602],{"class":202,"line":310},[200,2598,2599],{"class":235},"    plugins",[200,2601,239],{"class":214},[200,2603,1400],{"class":210},[200,2605,2606,2609,2611],{"class":202,"line":336},[200,2607,2608],{"class":223},"      cloudflareAISearch",[200,2610,227],{"class":210},[200,2612,230],{"class":214},[200,2614,2615,2618,2620,2622,2624,2626],{"class":202,"line":342},[200,2616,2617],{"class":235},"        endpoint",[200,2619,239],{"class":214},[200,2621,242],{"class":214},[200,2623,2260],{"class":245},[200,2625,249],{"class":214},[200,2627,252],{"class":214},[200,2629,2630,2633,2635],{"class":202,"line":365},[200,2631,2632],{"class":214},"      }",[200,2634,2089],{"class":210},[200,2636,252],{"class":214},[200,2638,2639,2642],{"class":202,"line":371},[200,2640,2641],{"class":210},"    ]",[200,2643,252],{"class":214},[200,2645,2646],{"class":202,"line":398},[200,2647,368],{"class":214},[200,2649,2650,2652],{"class":202,"line":405},[200,2651,413],{"class":214},[200,2653,416],{"class":210},[35,2655,2656,2657,2660],{},"You may also want to upload your content to the AI Search engine to make it searchable. To do that, you can also use the package ",[39,2658,2329],{"href":2327,"rel":2659},[77]," to handle this automatically.",[35,2662,2332],{},[192,2664,2665],{"className":2151,"code":2335,"language":2153,"meta":13,"style":13},[156,2666,2667],{"__ignoreMap":13},[200,2668,2669,2671,2673],{"class":202,"line":203},[200,2670,2160],{"class":1978},[200,2672,2163],{"class":245},[200,2674,2346],{"class":245},[35,2676,2677,2678,239],{},"Then, add the plugin to your ",[156,2679,2520],{},[192,2681,2683],{"className":1370,"code":2682,"language":1373,"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,2684,2685,2705,2723,2727,2739,2766],{"__ignoreMap":13},[200,2686,2687,2689,2691,2694,2696,2698,2700,2703],{"class":202,"line":203},[200,2688,973],{"class":206},[200,2690,318],{"class":214},[200,2692,2693],{"class":210}," cloudflareAISearchSync",[200,2695,981],{"class":214},[200,2697,984],{"class":206},[200,2699,242],{"class":214},[200,2701,2702],{"class":245},"cloudflare-ai-search-sync\u002Fvitepress",[200,2704,992],{"class":214},[200,2706,2707,2709,2711,2713,2715,2717,2719,2721],{"class":202,"line":14},[200,2708,973],{"class":206},[200,2710,318],{"class":214},[200,2712,1488],{"class":210},[200,2714,981],{"class":214},[200,2716,984],{"class":206},[200,2718,242],{"class":214},[200,2720,2547],{"class":245},[200,2722,992],{"class":214},[200,2724,2725],{"class":202,"line":255},[200,2726,529],{"emptyLinePlaceholder":20},[200,2728,2729,2731,2733,2735,2737],{"class":202,"line":272},[200,2730,1380],{"class":206},[200,2732,1383],{"class":206},[200,2734,1488],{"class":223},[200,2736,227],{"class":210},[200,2738,230],{"class":214},[200,2740,2741,2744,2746,2748,2750,2753,2756,2758,2760,2762,2764],{"class":202,"line":283},[200,2742,2743],{"class":235},"  buildEnd",[200,2745,239],{"class":214},[200,2747,2693],{"class":223},[200,2749,227],{"class":210},[200,2751,2752],{"class":214},"{",[200,2754,2755],{"class":235}," enabled",[200,2757,239],{"class":214},[200,2759,2424],{"class":2423},[200,2761,981],{"class":214},[200,2763,2089],{"class":210},[200,2765,252],{"class":214},[200,2767,2768,2770],{"class":202,"line":300},[200,2769,413],{"class":214},[200,2771,416],{"class":210},[130,2773,132,2774,132,2779],{},[134,2775,136,2776,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,2777],{"src":2778,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fbringing-cloudflare-ai-search-to-docus-and-vitepress\u002Fvitepress.mp4",[143,2780,2781],{},"Cloudflare AI Search integration in VitePress",[35,2783,2784],{},"Now, you can build your VitePress project and the content will be synced to AI Search. Feels like magic!",[35,2786,2787,2788,2140,2793,215],{},"If you prefer to simply give it a try, you can check the demo on ",[39,2789,2792],{"href":2790,"rel":2791},"https:\u002F\u002Fvitepress-plugin-cloudflare-ai-search.barbapapazes.dev",[77],"vitepress-plugin-cloudflare-ai-search.barbapapazes.dev",[39,2794,2145],{"href":2795,"rel":2796},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fvitepress-plugin-cloudflare-ai-search",[77],[51,2798,2800],{"id":2799},"not-everything-worked-as-expected","Not Everything Worked as Expected",[35,2802,2803,2804,2809],{},"You may think: \"Oh great, that works well!\" But that was not what happened initially, and I learned it the hard way. At the time, ",[39,2805,2808],{"href":2806,"rel":2807},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fai-search\u002Fconfiguration\u002Fretrieval\u002Fpublic-endpoint\u002Fembed-search-snippets\u002F",[77],"Cloudflare's documentation"," explained how to embed the UI snippet, but it did not clearly connect that process to indexing content with the metadata expected by the component. Without indexed content in the expected shape, the UI snippet does not work.",[35,2811,2812],{},"As mentioned earlier, there are three ways to make content available to the search engine:",[68,2814,2815,2818,2820],{},[71,2816,2817],{},"Using built-in storage through the dashboard or Items API",[71,2819,2308],{},[71,2821,2822],{},"Crawling a public website",[35,2824,2825,2826,1638,2831,106,2836],{},"None of them worked out of the box with the snippet. Even worse, after tweaking them and trying to change the item keys or metadata, nothing worked. So, I did what I do best: I went to the source code for the ",[39,2827,2830],{"href":2828,"rel":2829},"https:\u002F\u002Fdocs.emdashcms.com\u002Fdeployment\u002Fcloudflare\u002F#cloudflare-ai-search",[77],"EmDash integration",[39,2832,2835],{"href":2833,"rel":2834},"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet",[77],"UI snippet",[1217,2837,2838],{},"Luckily for me, it's open source.",[35,2840,2841,2842,2847],{},"I discovered that EmDash does not query the public endpoint directly. Instead, its integration exposes ",[39,2843,2846],{"href":2844,"rel":2845},"https:\u002F\u002Fgithub.com\u002Femdash-cms\u002Femdash\u002Fblob\u002Fe7978d3da8884a1d829e2f64fc7cc7a534ca1f40\u002Fpackages\u002Fcloudflare\u002Fsrc\u002Fplugins\u002Fai-search.ts#L767-L780",[77],"a dedicated endpoint that rewrites the metadata"," in the AI Search response to match the shape expected by the snippet. Sad news. I also inspected the network responses from Cloudflare's blog search to understand the expected shape, and that confirmed my discovery.",[35,2849,2850],{},"I'm not going to let this get me down. First, let's find a way to make it work. Then, let's open some PRs to improve the situation. And finally, let's ship a demo to showcase how cool the product is when it works.",[35,2852,2853,2854,2857,2858,2861,2862,2865,2866,2869,2870,215],{},"I dug into the source code of the snippet, especially the modal, and discovered that it used the item key as the URL when a user clicked a search result. The problem is that an AI Search key is not necessarily a public URL. It's a path to the item in storage. So, if you upload a file named ",[156,2855,2856],{},"my-file.md"," in the ",[156,2859,2860],{},"docs"," directory, the key will be ",[156,2863,2864],{},"docs\u002Fmy-file.md",". But the snippet uses the key as a link, so clicking the result opens ",[156,2867,2868],{},"https:\u002F\u002F\u003Cyour-domain>\u002Fdocs\u002Fmy-file.md",", which is not the right URL. I wanted it to open ",[156,2871,2872],{},"https:\u002F\u002F\u003Cyour-domain>\u002Fdocs\u002Fmy-file",[35,2874,2875,2876,2879,2880,2883,2884,2887],{},"But why not just push ",[156,2877,2878],{},"\u002Fdocs\u002Fmy-file"," as the key? Because it is not a valid AI Search item key. Item keys cannot start with ",[156,2881,2882],{},"\u002F"," and must include a file extension. Also, the snippet can differentiate pages and sections to improve the search experience. That's a good idea until you realize that linking to a section requires a ",[156,2885,2886],{},"#"," fragment in the public URL, which cannot be represented in the item key.",[35,2889,2890],{},"I can't rewrite the key on the fly because the promise of the snippet is to use it with the public endpoint and make it work out of the box, without configuration or a server.",[35,2892,2893],{},"So, instead of using the key, I decided to use a custom metadata field to store the URL. AI Search allows us to define additional metadata fields and attach their values to uploaded items. However, this also requires a PR to the snippet to make it work:",[2895,2896,2899,2902],"pull-request",{"title":2897,"href":2898},"feat: use metadata url","https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet\u002Fpull\u002F44",[35,2900,2901],{},"So, I opened the PR. In the meantime, I also opened another one to update the outdated README:",[2895,2903,2906,2920,2928,2931,2934,2946,2956,2959,2962,2966,2973,2976],{"title":2904,"href":2905},"docs: update readme","https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet\u002Fpull\u002F45",[35,2907,2908,2909,1638,2914,2919],{},"In the ",[39,2910,2913],{"href":2911,"rel":2912},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync\u002Fblob\u002F08055be4ca257cd618064f9e9aa03d96b220ce6f\u002Fsrc\u002Fvitepress\u002Futils.ts#L13",[77],"VitePress",[39,2915,2918],{"href":2916,"rel":2917},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync\u002Fblob\u002F08055be4ca257cd618064f9e9aa03d96b220ce6f\u002Fsrc\u002Fnuxt\u002Futils.ts#L26",[77],"Nuxt"," adapters, I derive each page's public URL from its source path and store that URL in the item's custom metadata.",[35,2921,2922,2923,2927],{},"I also had to create the ",[39,2924,2926],{"href":2327,"rel":2925},[77],"adapters that synchronize content"," to AI Search. We needed custom metadata and wanted the simplest developer experience, where you install the integration and you're good to go. R2 required additional upload tooling, and crawling meant adding metadata to every generated website page. The best option was to use the Items API to upload Markdown files and their metadata directly to built-in storage.",[35,2929,2930],{},"Finally, something was working! Huge!",[35,2932,2933],{},"But that only worked in my local workspace. Sad!",[35,2935,2936,2937,2941,2942,2945],{},"While ",[39,2938,2940],{"href":2898,"rel":2939},[77],"PR #44"," remains open, the upstream snippet still uses the item key as the URL. My local workspace used the patched dependency, but the published plugin dynamically loaded the upstream package instead. To make it work today, I had to bundle the patched snippet into the plugin. It was a little hacky because the module is loaded dynamically in a Vue component outside the usual bundling pipeline, so adding it to ",[156,2943,2944],{},"alwaysBundle"," in the tsdown configuration was not enough. I had to be more creative.",[35,2947,2948,2949,2952,2953,215],{},"In the end, it works. See the VitePress demo on ",[39,2950,2792],{"href":2790,"rel":2951},[77]," and the Nuxt module demo on ",[39,2954,2139],{"href":2137,"rel":2955},[77],[35,2957,2958],{},"Cloudflare's developer experience is getting better and better, but newer products can still require digging into source code and trying the system multiple times when the documentation and integrations evolve at different speeds.",[35,2960,2961],{},"In the end, it works, and I'm sure they will quickly address the issues! Their PMs are really open to feedback from the community.",[51,2963,2965],{"id":2964},"what-to-keep-in-mind","What to Keep in Mind",[35,2967,2968,2969,215],{},"First, open source is amazing. Build open-source projects and contribute to open source. It's one of the best ways to learn and grow as a developer. But don't forget that ",[39,2970,2972],{"href":2971},"\u002Fposts\u002Fwant-to-contribute-to-open-source-youre-doing-it-wrong","it's just a side effect of doing things",[35,2974,2975],{},"Second, start narrow and go wider. I began with a Nuxt module because it was the easiest way to validate the idea. After sharing a short video on X and collecting feedback, I extracted the synchronization process into a dedicated package and expanded the integration to VitePress. Interestingly, the VitePress version was ready first, and the Nuxt module followed later.",[35,2977,2978],{},"Third, be persistent and patient. Sometimes, things don't work as expected and are harder than they seem. However, that does not mean that it's impossible. By persisting and pushing a little bit more, you can make it work, and the reward is always worth it.",[1171,2980,2981],{},"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":2983},[2984,2985,2988,2989,2992],{"id":1870,"depth":14,"text":1871},{"id":1898,"depth":14,"text":1899,"children":2986},[2987],{"id":1915,"depth":255,"text":1916},{"id":2296,"depth":14,"text":2297},{"id":2445,"depth":14,"text":2446,"children":2990},[2991],{"id":2471,"depth":255,"text":2472},{"id":2799,"depth":14,"text":2800},"ee96a1f1-e521-44e4-a2dd-4b7bcdc703fe","2026-08-24","How I brought Cloudflare AI Search to Docus and VitePress, added content synchronization, and contributed AI Gateway support upstream.",{},"\u002Fposts\u002Fbringing-cloudflare-ai-search-to-docus-and-vitepress","---\ncontentId: ee96a1f1-e521-44e4-a2dd-4b7bcdc703fe\ntitle: Bringing Cloudflare AI Search to Docus and VitePress\ndescription: How I brought Cloudflare AI Search to Docus and VitePress, added content synchronization, and contributed AI Gateway support upstream.\ndate: 2026-08-24\n---\n\nIn July 2025, [NuxtLabs joined Vercel](https:\u002F\u002Fvercel.com\u002Fblog\u002Fnuxtlabs-joins-vercel). As a huge fan of the framework and the team, I was really happy to see them join a company like Vercel. Open-source work is extremely demanding, and the team was spending a lot of energy building a sustainable financial foundation. Joining Vercel gives them more room to focus on the framework and its ecosystem. They are doing a great job. _Thank you, guys!_\n\n## Bringing Cloudflare Into the Nuxt World\n\nSince NuxtLabs joined Vercel, Vercel's services have naturally become more visible in the surrounding ecosystem. That preference does not extend to Nuxt itself, which remains platform-agnostic thanks in large part to [Daniel's](https:\u002F\u002Fgithub.com\u002Fdanielroe) work on the framework. The same is true of the underlying UnJS primitives, thanks to [Pooya's](https:\u002F\u002Fgithub.com\u002Fpi0) work.\n\nI, however, am a Cloudflare user. I have been using many of its developer services for years, and sometimes the documentation only covers Vercel.\n\nI want to change that. I want to bring Cloudflare further into the Nuxt ecosystem to make it more of a first-class citizen. Fortunately, the Nuxt team is open to contributions and really welcoming of changes that make their work compatible with more than Vercel. _Honestly, it's totally understandable that they optimize for Vercel first. I'm just that guy who has decided to use Cloudflare._\n\n## Docus, the Nuxt Documentation Framework\n\nThe journey starts with Docus.\n\nDocus is a documentation framework built on top of Nuxt Content. It allows you to create a documentation website in seconds with many features out of the box. It includes search, an assistant, an MCP server, agent skill distribution, and many other features that make it easy to create a documentation website. Oh, and it's beautiful.\n\nFor a long time, I used VitePress because it was much simpler. But since the rise of AI, Docus has become much better suited to needs such as assistants, agent access, and skill distribution. So, I'm now using Docus wherever I need a documentation website.\n\nBut if you take a look at the documentation, you'll see that the built-in assistant is Vercel-only and there's no integration with Cloudflare AI Search to replace the built-in full-text search.\n\n### The Nuxt Module for Docus\n\nDocus is based on Nuxt. This means that we can create a Nuxt module to extend or override it.\n\nIn fact, Docus' search is powered by a component named `AppSearch`. We can change its behavior by simply swapping it with our own component. In a Nuxt module, it's as simple as registering a component with a higher priority than the built-in one so that our implementation takes precedence.\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\nThen we have full control over the `AppSearch` component. We can reuse the `ContentSearch` component from Nuxt UI and feed it results from the Cloudflare AI Search public endpoint.\n\nThat works like a charm!\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>Cloudflare AI Search integration in Docus\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nI'm really happy with the result, and I'll definitely use it in my next projects. You can check the demo on [docus-cloudflare-ai-search.barbapapazes.dev](https:\u002F\u002Fdocus-cloudflare-ai-search.barbapapazes.dev) or the [GitHub repository](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fdocus-cloudflare-ai-search).\n\nTo use the module, you need to install it:\n\n```bash\npnpm add docus-cloudflare-ai-search\n```\n\nThen, you need to add the module to your `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\nWhile working on this module, I realized that the built-in assistant was only available through Vercel. Too bad, because having Cloudflare AI Search but no way to run the assistant through Cloudflare was far from what I'd call Cloudflare-native.\n\nThe assistant uses an AI gateway to route requests to its model. Docus supported Vercel AI Gateway, but it did not expose the configuration needed to use Cloudflare AI Gateway instead. So, I opened a PR to add the provider, gateway, and model configuration required for Cloudflare: [feat(assistant): can use cloudflare for assistant](https:\u002F\u002Fgithub.com\u002Fnuxt-content\u002Fdocus\u002Fpull\u002F1421). The PR keeps Vercel as the default and makes Cloudflare an optional provider configured through environment variables. _As with everything in open source, you can just open a PR and make it happen._\n\n## Search Needs Something to Search\n\nThe integration works, which is a nice first step. But for now, our search index is empty. Cloudflare AI Search provides three ways to populate it:\n\n1. Using built-in storage, populated through the dashboard or Items API\n2. Connecting an R2 bucket\n3. Crawling a public website on a domain you own\n\nManual uploads through the dashboard do not fit because the content must be synchronized on every production deployment. An R2 bucket is viable, but uploading the content is not as simple as using rclone because each file also needs custom metadata. That would require additional tooling. Website crawling can extract custom metadata from HTML `\u003Cmeta>` tags, but adding those tags to every generated page would also require a custom integration.\n\nThe best fit is therefore to populate the built-in storage through the Items API, which supports attaching custom metadata while uploading each file. This still needs a custom integration, but with the right one, we can really smooth out the developer experience.\n\nFor Nuxt, you can use the package [cloudflare-ai-search-sync](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync):\n\nInstall the package:\n\n```bash\npnpm add cloudflare-ai-search-sync\n```\n\nThen, add the module to your `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\nNow, each time you build your Nuxt project, Markdown files processed by Nuxt Content will be uploaded to Cloudflare AI Search with the right metadata. Feels like magic!\n\nIt's not mandatory to use this integration as long as you provide the correct metadata for your content.\n\n## Going Above and Beyond\n\nBringing a new feature to the Nuxt ecosystem was really easy thanks to the Nuxt module system. It provides extension points for a wide range of use cases. Just take a look at the [Nuxt modules](https:\u002F\u002Fnuxt.com\u002Fmodules) page to see the possibilities.\n\nTo try an idea locally, modules are the perfect fit. Create a Nuxt project, add a `modules` folder, and start hacking on your idea. If you want to publish the module, the official Nuxt module starter provides the full project structure. Give your AI assistant the Nuxt modules documentation, and you can get a prototype running surprisingly quickly. There's never been a better time to try it.\n\nBut not everything in the Vue ecosystem is Nuxt. We have VitePress and tons of Vite plugins to create our own systems. Speaking of Vite plugins, they are a good way to reach a wider audience. A Vite plugin can often serve frameworks such as Astro, Svelte, React, and even Nuxt, although each integration may still require framework-specific work. However, Vite plugins are much harder to create. So, starting at a high level with a Nuxt module, testing whether the idea really works, and then moving down the stack to create a Vite plugin is a good way to proceed.\n\nAnd that's exactly what I did with the Cloudflare AI Search integration.\n\n### VitePress Was the First Obvious Choice\n\nOnce the proof of concept was validated for Docus, I started thinking about how to make it available for VitePress. I started as a VitePress user, my portfolio still uses it, and the framework has a large documentation community.\n\nAlso, VitePress already has first-party support for local and Algolia search, as well as multiple third-party plugins for other search engines. So, bringing Cloudflare AI Search to VitePress was a natural choice, and I could leverage the work already done for the Docus integration.\n\nUsing the [Cloudflare AI Search Snippet](https:\u002F\u002Fsearch.ai.cloudflare.com\u002F), I was able to quickly integrate the search engine into VitePress. With the POC validated, I created a dedicated VitePress plugin: \u003CGitHubLink repo=\"barbapapazes\u002Fvitepress-plugin-cloudflare-ai-search\" \u002F>.\n\nUnder the hood, the plugin is pretty simple. It swaps the built-in `VPNavBarSearch` component with a custom one that integrates the Cloudflare AI Search snippet and receives its configuration through a virtual module.\n\nTo get started, you need to install the plugin:\n\n```bash\npnpm add vitepress-plugin-cloudflare-ai-search\n```\n\nThen, you need to add the plugin to your `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\nYou may also want to upload your content to the AI Search engine to make it searchable. To do that, you can also use the package [cloudflare-ai-search-sync](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync) to handle this automatically.\n\nInstall the package:\n\n```bash\npnpm add cloudflare-ai-search-sync\n```\n\nThen, add the plugin to your `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>Cloudflare AI Search integration in VitePress\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nNow, you can build your VitePress project and the content will be synced to AI Search. Feels like magic!\n\nIf you prefer to simply give it a try, you can check the demo on [vitepress-plugin-cloudflare-ai-search.barbapapazes.dev](https:\u002F\u002Fvitepress-plugin-cloudflare-ai-search.barbapapazes.dev) or the [GitHub repository](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fvitepress-plugin-cloudflare-ai-search).\n\n## Not Everything Worked as Expected\n\nYou may think: \"Oh great, that works well!\" But that was not what happened initially, and I learned it the hard way. At the time, [Cloudflare's documentation](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fai-search\u002Fconfiguration\u002Fretrieval\u002Fpublic-endpoint\u002Fembed-search-snippets\u002F) explained how to embed the UI snippet, but it did not clearly connect that process to indexing content with the metadata expected by the component. Without indexed content in the expected shape, the UI snippet does not work.\n\nAs mentioned earlier, there are three ways to make content available to the search engine:\n\n1. Using built-in storage through the dashboard or Items API\n2. Connecting an R2 bucket\n3. Crawling a public website\n\nNone of them worked out of the box with the snippet. Even worse, after tweaking them and trying to change the item keys or metadata, nothing worked. So, I did what I do best: I went to the source code for the [EmDash integration](https:\u002F\u002Fdocs.emdashcms.com\u002Fdeployment\u002Fcloudflare\u002F#cloudflare-ai-search) and [UI snippet](https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet). _Luckily for me, it's open source._\n\nI discovered that EmDash does not query the public endpoint directly. Instead, its integration exposes [a dedicated endpoint that rewrites the metadata](https:\u002F\u002Fgithub.com\u002Femdash-cms\u002Femdash\u002Fblob\u002Fe7978d3da8884a1d829e2f64fc7cc7a534ca1f40\u002Fpackages\u002Fcloudflare\u002Fsrc\u002Fplugins\u002Fai-search.ts#L767-L780) in the AI Search response to match the shape expected by the snippet. Sad news. I also inspected the network responses from Cloudflare's blog search to understand the expected shape, and that confirmed my discovery.\n\nI'm not going to let this get me down. First, let's find a way to make it work. Then, let's open some PRs to improve the situation. And finally, let's ship a demo to showcase how cool the product is when it works.\n\nI dug into the source code of the snippet, especially the modal, and discovered that it used the item key as the URL when a user clicked a search result. The problem is that an AI Search key is not necessarily a public URL. It's a path to the item in storage. So, if you upload a file named `my-file.md` in the `docs` directory, the key will be `docs\u002Fmy-file.md`. But the snippet uses the key as a link, so clicking the result opens `https:\u002F\u002F\u003Cyour-domain>\u002Fdocs\u002Fmy-file.md`, which is not the right URL. I wanted it to open `https:\u002F\u002F\u003Cyour-domain>\u002Fdocs\u002Fmy-file`.\n\nBut why not just push `\u002Fdocs\u002Fmy-file` as the key? Because it is not a valid AI Search item key. Item keys cannot start with `\u002F` and must include a file extension. Also, the snippet can differentiate pages and sections to improve the search experience. That's a good idea until you realize that linking to a section requires a `#` fragment in the public URL, which cannot be represented in the item key.\n\nI can't rewrite the key on the fly because the promise of the snippet is to use it with the public endpoint and make it work out of the box, without configuration or a server.\n\nSo, instead of using the key, I decided to use a custom metadata field to store the URL. AI Search allows us to define additional metadata fields and attach their values to uploaded items. However, this also requires a PR to the snippet to make it work:\n\n\u003CPullRequest title=\"feat: use metadata url\" href=\"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet\u002Fpull\u002F44\" \u002F>\n\nSo, I opened the PR. In the meantime, I also opened another one to update the outdated README:\n\n\u003CPullRequest title=\"docs: update readme\" href=\"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet\u002Fpull\u002F45\" \u002F>\n\nIn the [VitePress](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync\u002Fblob\u002F08055be4ca257cd618064f9e9aa03d96b220ce6f\u002Fsrc\u002Fvitepress\u002Futils.ts#L13) and [Nuxt](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync\u002Fblob\u002F08055be4ca257cd618064f9e9aa03d96b220ce6f\u002Fsrc\u002Fnuxt\u002Futils.ts#L26) adapters, I derive each page's public URL from its source path and store that URL in the item's custom metadata.\n\nI also had to create the [adapters that synchronize content](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync) to AI Search. We needed custom metadata and wanted the simplest developer experience, where you install the integration and you're good to go. R2 required additional upload tooling, and crawling meant adding metadata to every generated website page. The best option was to use the Items API to upload Markdown files and their metadata directly to built-in storage.\n\nFinally, something was working! Huge!\n\nBut that only worked in my local workspace. Sad!\n\nWhile [PR #44](https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet\u002Fpull\u002F44) remains open, the upstream snippet still uses the item key as the URL. My local workspace used the patched dependency, but the published plugin dynamically loaded the upstream package instead. To make it work today, I had to bundle the patched snippet into the plugin. It was a little hacky because the module is loaded dynamically in a Vue component outside the usual bundling pipeline, so adding it to `alwaysBundle` in the tsdown configuration was not enough. I had to be more creative.\n\nIn the end, it works. See the VitePress demo on [vitepress-plugin-cloudflare-ai-search.barbapapazes.dev](https:\u002F\u002Fvitepress-plugin-cloudflare-ai-search.barbapapazes.dev) and the Nuxt module demo on [docus-cloudflare-ai-search.barbapapazes.dev](https:\u002F\u002Fdocus-cloudflare-ai-search.barbapapazes.dev).\n\nCloudflare's developer experience is getting better and better, but newer products can still require digging into source code and trying the system multiple times when the documentation and integrations evolve at different speeds.\n\nIn the end, it works, and I'm sure they will quickly address the issues! Their PMs are really open to feedback from the community.\n\n## What to Keep in Mind\n\nFirst, open source is amazing. Build open-source projects and contribute to open source. It's one of the best ways to learn and grow as a developer. But don't forget that [it's just a side effect of doing things](\u002Fposts\u002Fwant-to-contribute-to-open-source-youre-doing-it-wrong).\n\nSecond, start narrow and go wider. I began with a Nuxt module because it was the easiest way to validate the idea. After sharing a short video on X and collecting feedback, I extracted the synchronization process into a dedicated package and expanded the integration to VitePress. Interestingly, the VitePress version was ready first, and the Nuxt module followed later.\n\nThird, be persistent and patient. Sometimes, things don't work as expected and are harder than they seem. However, that does not mean that it's impossible. By persisting and pushing a little bit more, you can make it work, and the reward is always worth it.\n",{"title":1853,"description":2995},{"loc":2997},"posts\u002Fbringing-cloudflare-ai-search-to-docus-and-vitepress","UVWYSoFGJ47Spp83HnGArS-X9uH_BUN4oG3Uz0dmmmw",{"id":3004,"title":3005,"body":3006,"contentId":5619,"date":2994,"description":5620,"extension":18,"meta":5621,"navigation":20,"path":5622,"rawbody":5623,"robots":23,"schemaOrg":23,"seo":5624,"sitemap":5625,"stem":5626,"__hash__":5627},"posts\u002Fposts\u002Fthe-three-mcp-tools-you-need-for-a-content-oriented-website.md","The Three MCP Tools You Need for a Content-Oriented Website",{"type":7,"value":3007,"toc":5613},[3008,3011,3025,3033,3036,3040,3043,3046,3063,3066,3070,3085,3090,3099,3329,3342,3345,3355,3360,3371,3648,3658,3661,3670,3675,3678,3681,3687,3699,3702,4023,4034,4037,4209,4219,4228,4231,4234,4251,4254,4258,4265,4268,4690,5610],[35,3009,3010],{},"Since October 2025, I haven't been able to shake a few questions:",[1576,3012,3013,3016,3019,3022],{},[71,3014,3015],{},"Summarize the article about Code Mode.",[71,3017,3018],{},"What to keep in mind in the series about AI agents?",[71,3020,3021],{},"Is there content about Vite?",[71,3023,3024],{},"What's the latest article about Devoxx France?",[35,3026,3027,3028],{},"In October, I published the article ",[3029,3030,3032],"link-to-article",{"id":3031},"e258d6e6-2f2f-400d-9b6f-02801a7284db",". I explained that I had built an MCP server and an agent to chat with my website's content. I had successfully created a proof of concept (POC). It was the first time I had created such a system, and I was really impressed. However, it was never production-ready.",[35,3034,3035],{},"Those questions helped me iterate on the MCP until it was efficient and usable. Today, I want to share what I learned about building an MCP for a content-oriented website.",[51,3037,3039],{"id":3038},"challenges-for-agents","Challenges for Agents",[35,3041,3042],{},"For a human, these questions are relatively trivial. It can take time to browse the website and read the content, but that's the only difficulty. For an agent, it's a different story.",[35,3044,3045],{},"An agent must:",[68,3047,3048,3051,3054,3057,3060],{},[71,3049,3050],{},"answer as quickly as possible to avoid making the user wait too long;",[71,3052,3053],{},"answer as accurately as possible to avoid frustrating the user by not finding what they are looking for;",[71,3055,3056],{},"continue to answer effectively as the website's content grows;",[71,3058,3059],{},"use as few tokens as possible to avoid making users pay too much for an answer;",[71,3061,3062],{},"ground answers in the website’s actual content and identify the source, so users can verify them.",[35,3064,3065],{},"That's a lot of constraints to meet when designing an MCP server.",[51,3067,3069],{"id":3068},"the-current-approach","The Current Approach",[35,3071,3072,3073,3076,3077,3080,3081,3084],{},"The final version of my MCP has three tools: ",[156,3074,3075],{},"get_page",", ",[156,3078,3079],{},"search_content",", and ",[156,3082,3083],{},"list_pages",". Each answers a different kind of question. Let's tackle them one by one.",[939,3086,3087],{},[35,3088,3089],{},"\"Summarize the article about Code Mode.\"",[35,3091,3092,3093,3095,3096,3098],{},"To answer this question, the agent must be able to read the article's content. Many MCP servers I explored while creating mine exposed a ",[156,3094,3075],{}," tool for exactly that purpose. My ",[156,3097,3075],{}," tool takes a page ID and internally retrieves the corresponding URL to fetch the page content.",[192,3100,3102],{"className":194,"code":3101,"language":196,"meta":13,"style":13},"server.registerTool(\n  'get_page',\n  {\n    description: '...',\n    inputSchema: {\n      id: z.string().trim().min(1).describe('Exact, globally unique content ID returned by search_content or 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,3103,3104,3116,3127,3132,3147,3156,3207,3211,3215,3231,3247,3251,3288,3292,3321,3325],{"__ignoreMap":13},[200,3105,3106,3109,3111,3113],{"class":202,"line":203},[200,3107,3108],{"class":210},"server",[200,3110,215],{"class":214},[200,3112,224],{"class":223},[200,3114,3115],{"class":210},"(\n",[200,3117,3118,3121,3123,3125],{"class":202,"line":14},[200,3119,3120],{"class":214},"  '",[200,3122,3075],{"class":245},[200,3124,249],{"class":214},[200,3126,252],{"class":214},[200,3128,3129],{"class":202,"line":255},[200,3130,3131],{"class":214},"  {\n",[200,3133,3134,3137,3139,3141,3143,3145],{"class":202,"line":272},[200,3135,3136],{"class":235},"    description",[200,3138,239],{"class":214},[200,3140,242],{"class":214},[200,3142,574],{"class":245},[200,3144,249],{"class":214},[200,3146,252],{"class":214},[200,3148,3149,3152,3154],{"class":202,"line":283},[200,3150,3151],{"class":235},"    inputSchema",[200,3153,239],{"class":214},[200,3155,280],{"class":214},[200,3157,3158,3161,3163,3166,3168,3170,3172,3174,3177,3179,3181,3184,3186,3189,3191,3193,3196,3198,3200,3203,3205],{"class":202,"line":300},[200,3159,3160],{"class":235},"      id",[200,3162,239],{"class":214},[200,3164,3165],{"class":210}," z",[200,3167,215],{"class":214},[200,3169,328],{"class":223},[200,3171,1532],{"class":210},[200,3173,215],{"class":214},[200,3175,3176],{"class":223},"trim",[200,3178,1532],{"class":210},[200,3180,215],{"class":214},[200,3182,3183],{"class":223},"min",[200,3185,227],{"class":210},[200,3187,3188],{"class":2045},"1",[200,3190,2089],{"class":210},[200,3192,215],{"class":214},[200,3194,3195],{"class":223},"describe",[200,3197,227],{"class":210},[200,3199,249],{"class":214},[200,3201,3202],{"class":245},"Exact, globally unique content ID returned by search_content or list_pages.",[200,3204,249],{"class":214},[200,3206,416],{"class":210},[200,3208,3209],{"class":202,"line":310},[200,3210,339],{"class":214},[200,3212,3213],{"class":202,"line":336},[200,3214,368],{"class":214},[200,3216,3217,3220,3222,3225,3227,3229],{"class":202,"line":342},[200,3218,3219],{"class":379},"  async",[200,3221,383],{"class":214},[200,3223,3224],{"class":386}," id",[200,3226,390],{"class":214},[200,3228,393],{"class":379},[200,3230,280],{"class":214},[200,3232,3233,3235,3238,3240,3242,3245],{"class":202,"line":365},[200,3234,1999],{"class":379},[200,3236,3237],{"class":210}," pages",[200,3239,510],{"class":214},[200,3241,452],{"class":206},[200,3243,3244],{"class":223}," loadPages",[200,3246,466],{"class":235},[200,3248,3249],{"class":202,"line":371},[200,3250,529],{"emptyLinePlaceholder":20},[200,3252,3253,3255,3258,3260,3262,3264,3267,3269,3272,3274,3276,3278,3281,3284,3286],{"class":202,"line":398},[200,3254,1999],{"class":379},[200,3256,3257],{"class":210}," page",[200,3259,510],{"class":214},[200,3261,3237],{"class":210},[200,3263,215],{"class":214},[200,3265,3266],{"class":223},"find",[200,3268,227],{"class":235},[200,3270,3271],{"class":386},"page",[200,3273,393],{"class":379},[200,3275,3257],{"class":210},[200,3277,215],{"class":214},[200,3279,3280],{"class":210},"id",[200,3282,3283],{"class":214}," ===",[200,3285,3224],{"class":210},[200,3287,416],{"class":235},[200,3289,3290],{"class":202,"line":405},[200,3291,529],{"emptyLinePlaceholder":20},[200,3293,3294,3297,3300,3302,3305,3307,3309,3311,3313,3316,3319],{"class":202,"line":410},[200,3295,3296],{"class":206},"    return",[200,3298,3299],{"class":223}," fetch",[200,3301,227],{"class":235},[200,3303,3304],{"class":214},"`${",[200,3306,3271],{"class":210},[200,3308,215],{"class":214},[200,3310,2020],{"class":210},[200,3312,413],{"class":214},[200,3314,3315],{"class":245},".md",[200,3317,3318],{"class":214},"`",[200,3320,416],{"class":235},[200,3322,3323],{"class":202,"line":861},[200,3324,368],{"class":214},[200,3326,3327],{"class":202,"line":890},[200,3328,416],{"class":210},[939,3330,3331],{},[35,3332,3333,3335,3336,3338,3339],{},[200,3334,945],{},"\nThis is not the real implementation of the ",[156,3337,3075],{}," tool. It is an oversimplified version to illustrate the idea. For the full implementation, check ",[1582,3340,215],{"repo":3341},"barbapapazes\u002Fmcp.soubiran.dev",[35,3343,3344],{},"That covers reading a known page. The remaining question is: how does the agent know its ID?",[130,3346,132,3347,132,3352],{},[134,3348,136,3349,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,3350],{"src":3351,"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,3353,3354],{},"Summarizing the article about Code Mode",[939,3356,3357],{},[35,3358,3359],{},"\"Is there content about Vite?\"",[35,3361,3362,3363,3365,3366,215],{},"To solve this question, the agent must be able to search across all content, including titles, descriptions, and body text. So, we need a ",[156,3364,3079],{}," tool. That tool takes a query as a parameter and internally uses semantic and keyword search to retrieve matching content. This is done using ",[39,3367,3370],{"href":3368,"rel":3369},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fai-search\u002F",[77],"Cloudflare AI Search",[192,3372,3374],{"className":194,"code":3373,"language":196,"meta":13,"style":13},"server.registerTool(\n  'search_content',\n  {\n    description: '...',\n    inputSchema: {\n      query: z.string().trim().min(1).describe('Query to search for content.')\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,3375,3376,3386,3396,3400,3414,3422,3468,3472,3476,3491,3512,3516,3538,3553,3569,3585,3601,3616,3632,3638,3643],{"__ignoreMap":13},[200,3377,3378,3380,3382,3384],{"class":202,"line":203},[200,3379,3108],{"class":210},[200,3381,215],{"class":214},[200,3383,224],{"class":223},[200,3385,3115],{"class":210},[200,3387,3388,3390,3392,3394],{"class":202,"line":14},[200,3389,3120],{"class":214},[200,3391,3079],{"class":245},[200,3393,249],{"class":214},[200,3395,252],{"class":214},[200,3397,3398],{"class":202,"line":255},[200,3399,3131],{"class":214},[200,3401,3402,3404,3406,3408,3410,3412],{"class":202,"line":272},[200,3403,3136],{"class":235},[200,3405,239],{"class":214},[200,3407,242],{"class":214},[200,3409,574],{"class":245},[200,3411,249],{"class":214},[200,3413,252],{"class":214},[200,3415,3416,3418,3420],{"class":202,"line":283},[200,3417,3151],{"class":235},[200,3419,239],{"class":214},[200,3421,280],{"class":214},[200,3423,3424,3427,3429,3431,3433,3435,3437,3439,3441,3443,3445,3447,3449,3451,3453,3455,3457,3459,3461,3464,3466],{"class":202,"line":300},[200,3425,3426],{"class":235},"      query",[200,3428,239],{"class":214},[200,3430,3165],{"class":210},[200,3432,215],{"class":214},[200,3434,328],{"class":223},[200,3436,1532],{"class":210},[200,3438,215],{"class":214},[200,3440,3176],{"class":223},[200,3442,1532],{"class":210},[200,3444,215],{"class":214},[200,3446,3183],{"class":223},[200,3448,227],{"class":210},[200,3450,3188],{"class":2045},[200,3452,2089],{"class":210},[200,3454,215],{"class":214},[200,3456,3195],{"class":223},[200,3458,227],{"class":210},[200,3460,249],{"class":214},[200,3462,3463],{"class":245},"Query to search for content.",[200,3465,249],{"class":214},[200,3467,416],{"class":210},[200,3469,3470],{"class":202,"line":310},[200,3471,339],{"class":214},[200,3473,3474],{"class":202,"line":336},[200,3475,368],{"class":214},[200,3477,3478,3480,3482,3485,3487,3489],{"class":202,"line":342},[200,3479,3219],{"class":379},[200,3481,383],{"class":214},[200,3483,3484],{"class":386}," query",[200,3486,390],{"class":214},[200,3488,393],{"class":379},[200,3490,280],{"class":214},[200,3492,3493,3495,3498,3500,3502,3505,3507,3510],{"class":202,"line":365},[200,3494,1999],{"class":379},[200,3496,3497],{"class":210}," results",[200,3499,510],{"class":214},[200,3501,452],{"class":206},[200,3503,3504],{"class":223}," searchContent",[200,3506,227],{"class":235},[200,3508,3509],{"class":210},"query",[200,3511,416],{"class":235},[200,3513,3514],{"class":202,"line":371},[200,3515,529],{"emptyLinePlaceholder":20},[200,3517,3518,3520,3522,3524,3527,3529,3532,3534,3536],{"class":202,"line":398},[200,3519,3296],{"class":206},[200,3521,3497],{"class":210},[200,3523,215],{"class":214},[200,3525,3526],{"class":223},"map",[200,3528,227],{"class":235},[200,3530,3531],{"class":386},"result",[200,3533,393],{"class":379},[200,3535,1095],{"class":235},[200,3537,230],{"class":214},[200,3539,3540,3542,3544,3547,3549,3551],{"class":202,"line":405},[200,3541,3160],{"class":235},[200,3543,239],{"class":214},[200,3545,3546],{"class":210}," result",[200,3548,215],{"class":214},[200,3550,3280],{"class":210},[200,3552,252],{"class":214},[200,3554,3555,3558,3560,3562,3564,3567],{"class":202,"line":410},[200,3556,3557],{"class":235},"      title",[200,3559,239],{"class":214},[200,3561,3546],{"class":210},[200,3563,215],{"class":214},[200,3565,3566],{"class":210},"title",[200,3568,252],{"class":214},[200,3570,3571,3574,3576,3578,3580,3583],{"class":202,"line":861},[200,3572,3573],{"class":235},"      description",[200,3575,239],{"class":214},[200,3577,3546],{"class":210},[200,3579,215],{"class":214},[200,3581,3582],{"class":210},"description",[200,3584,252],{"class":214},[200,3586,3587,3590,3592,3594,3596,3599],{"class":202,"line":890},[200,3588,3589],{"class":235},"      content",[200,3591,239],{"class":214},[200,3593,3546],{"class":210},[200,3595,215],{"class":214},[200,3597,3598],{"class":210},"chunk",[200,3600,252],{"class":214},[200,3602,3603,3606,3608,3610,3612,3614],{"class":202,"line":900},[200,3604,3605],{"class":235},"      url",[200,3607,239],{"class":214},[200,3609,3546],{"class":210},[200,3611,215],{"class":214},[200,3613,2020],{"class":210},[200,3615,252],{"class":214},[200,3617,3618,3621,3623,3625,3627,3630],{"class":202,"line":905},[200,3619,3620],{"class":235},"      date",[200,3622,239],{"class":214},[200,3624,3546],{"class":210},[200,3626,215],{"class":214},[200,3628,3629],{"class":210},"date",[200,3631,252],{"class":214},[200,3633,3634,3636],{"class":202,"line":931},[200,3635,2096],{"class":214},[200,3637,585],{"class":235},[200,3639,3641],{"class":202,"line":3640},20,[200,3642,368],{"class":214},[200,3644,3646],{"class":202,"line":3645},21,[200,3647,416],{"class":210},[939,3649,3650],{},[35,3651,3652,3335,3654,3338,3656],{},[200,3653,945],{},[156,3655,3079],{},[1582,3657,215],{"repo":3341},[35,3659,3660],{},"With a well-chosen query, this tool could also help answer the first question. Thanks to keyword search, the agent could search for \"Code Mode\" and retrieve the corresponding page ID. However, this is not reliable enough on its own: a query can be ambiguous or fail to rank the intended page first.",[130,3662,132,3663,132,3668],{},[134,3664,136,3665,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,3666],{"src":3667,"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,3669,3021],{},[939,3671,3672],{},[35,3673,3674],{},"\"What's the latest article about Devoxx France?\"",[35,3676,3677],{},"This one is more complicated. It requires comparing article dates. To identify the latest matching article efficiently, the agent needs access to the content metadata and a way to analyze it with code. Without that capability, it would have to retrieve the full list and perform the comparison itself.",[35,3679,3680],{},"That may work, but it is not efficient. The full list of my website's content is 203,535 characters long, or about 60,000 tokens. Sure, it would fit within the context window of most models today, but it would consume time and tokens for no benefit. It would also pollute the context, making it harder for the agent to find relevant information.",[35,3682,3683,3684,3686],{},"Despite this, I decided to create the ",[156,3685,3083],{}," tool anyway. However, it does not work as you might expect.",[35,3688,3689,3690,3692,3693,3698],{},"The tool takes a ",[156,3691,156],{}," input. It lets the agent write JavaScript against typed data to filter, sort, and map exactly the information it needs. ",[39,3694,3697],{"href":3695,"rel":3696},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fdynamic-workers\u002F",[77],"Cloudflare Dynamic Workers"," execute the code in a lightweight, secure, and isolated environment.",[35,3700,3701],{},"For example, the agent could write the following code to retrieve the latest article about Devoxx France:",[192,3703,3705],{"className":194,"code":3704,"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,3706,3707,3719,3735,3739,3751,3783,3796,3825,3835,3850,3855,3877,3918,3922,3941,3982,3988,3994,4000,4006,4012,4018],{"__ignoreMap":13},[200,3708,3709,3712,3715,3717],{"class":202,"line":203},[200,3710,3711],{"class":379},"async",[200,3713,3714],{"class":214}," ()",[200,3716,393],{"class":379},[200,3718,280],{"class":214},[200,3720,3721,3724,3726,3728,3730,3733],{"class":202,"line":14},[200,3722,3723],{"class":379},"  const",[200,3725,3484],{"class":210},[200,3727,510],{"class":214},[200,3729,242],{"class":214},[200,3731,3732],{"class":245},"devoxx france",[200,3734,992],{"class":214},[200,3736,3737],{"class":202,"line":255},[200,3738,529],{"emptyLinePlaceholder":20},[200,3740,3741,3744,3746,3748],{"class":202,"line":272},[200,3742,3743],{"class":206},"  return",[200,3745,3237],{"class":210},[200,3747,215],{"class":214},[200,3749,3750],{"class":210},"data\n",[200,3752,3753,3756,3759,3761,3763,3765,3767,3769,3772,3774,3776,3779,3781],{"class":202,"line":283},[200,3754,3755],{"class":214},"    .",[200,3757,3758],{"class":223},"filter",[200,3760,227],{"class":235},[200,3762,3271],{"class":386},[200,3764,393],{"class":379},[200,3766,3257],{"class":210},[200,3768,215],{"class":214},[200,3770,3771],{"class":210},"type",[200,3773,3283],{"class":214},[200,3775,242],{"class":214},[200,3777,3778],{"class":245},"post",[200,3780,249],{"class":214},[200,3782,416],{"class":235},[200,3784,3785,3787,3789,3791,3793],{"class":202,"line":300},[200,3786,3755],{"class":214},[200,3788,3758],{"class":223},[200,3790,227],{"class":235},[200,3792,3271],{"class":386},[200,3794,3795],{"class":379}," =>\n",[200,3797,3798,3801,3803,3805,3807,3809,3812,3814,3816,3819,3822],{"class":202,"line":310},[200,3799,3800],{"class":214},"      `${",[200,3802,3271],{"class":210},[200,3804,215],{"class":214},[200,3806,3566],{"class":210},[200,3808,413],{"class":214},[200,3810,3811],{"class":214}," ${",[200,3813,3271],{"class":210},[200,3815,215],{"class":214},[200,3817,3818],{"class":210},"description ",[200,3820,3821],{"class":214},"??",[200,3823,3824],{"class":214}," ''}`\n",[200,3826,3827,3830,3833],{"class":202,"line":336},[200,3828,3829],{"class":214},"        .",[200,3831,3832],{"class":223},"toLowerCase",[200,3834,466],{"class":235},[200,3836,3837,3839,3842,3844,3846,3848],{"class":202,"line":342},[200,3838,3829],{"class":214},[200,3840,3841],{"class":223},"includes",[200,3843,227],{"class":235},[200,3845,3509],{"class":210},[200,3847,2089],{"class":235},[200,3849,252],{"class":214},[200,3851,3852],{"class":202,"line":365},[200,3853,3854],{"class":235},"    )\n",[200,3856,3857,3859,3862,3864,3866,3868,3870,3873,3875],{"class":202,"line":371},[200,3858,3755],{"class":214},[200,3860,3861],{"class":223},"sort",[200,3863,227],{"class":235},[200,3865,227],{"class":214},[200,3867,39],{"class":386},[200,3869,550],{"class":214},[200,3871,3872],{"class":386}," b",[200,3874,2089],{"class":214},[200,3876,3795],{"class":379},[200,3878,3879,3882,3885,3887,3889,3892,3895,3897,3899,3902,3904,3906,3908,3910,3912,3914,3916],{"class":202,"line":398},[200,3880,3881],{"class":235},"      (",[200,3883,3884],{"class":210},"b",[200,3886,215],{"class":214},[200,3888,3629],{"class":210},[200,3890,3891],{"class":214}," ??",[200,3893,3894],{"class":214}," ''",[200,3896,2089],{"class":235},[200,3898,215],{"class":214},[200,3900,3901],{"class":223},"localeCompare",[200,3903,227],{"class":235},[200,3905,39],{"class":210},[200,3907,215],{"class":214},[200,3909,3629],{"class":210},[200,3911,3891],{"class":214},[200,3913,3894],{"class":214},[200,3915,2089],{"class":235},[200,3917,252],{"class":214},[200,3919,3920],{"class":202,"line":405},[200,3921,3854],{"class":235},[200,3923,3924,3926,3929,3931,3934,3936,3939],{"class":202,"line":410},[200,3925,3755],{"class":214},[200,3927,3928],{"class":223},"slice",[200,3930,227],{"class":235},[200,3932,3933],{"class":2045},"0",[200,3935,550],{"class":214},[200,3937,3938],{"class":2045}," 1",[200,3940,416],{"class":235},[200,3942,3943,3945,3947,3949,3952,3954,3956,3959,3961,3964,3966,3969,3971,3974,3976,3978,3980],{"class":202,"line":861},[200,3944,3755],{"class":214},[200,3946,3526],{"class":223},[200,3948,227],{"class":235},[200,3950,3951],{"class":214},"({",[200,3953,3224],{"class":386},[200,3955,550],{"class":214},[200,3957,3958],{"class":386}," title",[200,3960,550],{"class":214},[200,3962,3963],{"class":386}," description",[200,3965,550],{"class":214},[200,3967,3968],{"class":386}," date",[200,3970,550],{"class":214},[200,3972,3973],{"class":386}," url",[200,3975,390],{"class":214},[200,3977,393],{"class":379},[200,3979,1095],{"class":235},[200,3981,230],{"class":214},[200,3983,3984,3986],{"class":202,"line":890},[200,3985,3160],{"class":210},[200,3987,252],{"class":214},[200,3989,3990,3992],{"class":202,"line":900},[200,3991,3557],{"class":210},[200,3993,252],{"class":214},[200,3995,3996,3998],{"class":202,"line":905},[200,3997,3573],{"class":210},[200,3999,252],{"class":214},[200,4001,4002,4004],{"class":202,"line":931},[200,4003,3620],{"class":210},[200,4005,252],{"class":214},[200,4007,4008,4010],{"class":202,"line":3640},[200,4009,3605],{"class":210},[200,4011,252],{"class":214},[200,4013,4014,4016],{"class":202,"line":3645},[200,4015,2096],{"class":214},[200,4017,585],{"class":235},[200,4019,4021],{"class":202,"line":4020},22,[200,4022,1130],{"class":214},[939,4024,4025],{},[35,4026,4027,4029,4030,215],{},[200,4028,945],{},"\nTo understand what code mode is, read ",[39,4031,4033],{"href":4032},".\u002Fcode-mode-two-tools-and-an-mcp-can-save-your-llm-context","Code Mode, Two Tools, and an MCP Can Save Your LLM Context",[35,4035,4036],{},"Under the hood, the tool looks like this. Keep in mind that one of the interesting parts of a code mode tool is its description.",[192,4038,4040],{"className":194,"code":4039,"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('An async JavaScript arrow function with read-only pages, talks, and infra globals.'),\n    },\n  },\n  async ({ code }) => {\n    const result = await executeCode(code)\n\n    return result\n  },\n)\n",[156,4041,4042,4052,4062,4066,4080,4088,4148,4152,4156,4171,4190,4194,4201,4205],{"__ignoreMap":13},[200,4043,4044,4046,4048,4050],{"class":202,"line":203},[200,4045,3108],{"class":210},[200,4047,215],{"class":214},[200,4049,224],{"class":223},[200,4051,3115],{"class":210},[200,4053,4054,4056,4058,4060],{"class":202,"line":14},[200,4055,3120],{"class":214},[200,4057,3083],{"class":245},[200,4059,249],{"class":214},[200,4061,252],{"class":214},[200,4063,4064],{"class":202,"line":255},[200,4065,3131],{"class":214},[200,4067,4068,4070,4072,4074,4076,4078],{"class":202,"line":272},[200,4069,3136],{"class":235},[200,4071,239],{"class":214},[200,4073,242],{"class":214},[200,4075,574],{"class":245},[200,4077,249],{"class":214},[200,4079,252],{"class":214},[200,4081,4082,4084,4086],{"class":202,"line":283},[200,4083,3151],{"class":235},[200,4085,239],{"class":214},[200,4087,280],{"class":214},[200,4089,4090,4093,4095,4097,4099,4101,4103,4105,4107,4109,4111,4113,4115,4117,4119,4121,4124,4126,4129,4131,4133,4135,4137,4139,4142,4144,4146],{"class":202,"line":300},[200,4091,4092],{"class":235},"      code",[200,4094,239],{"class":214},[200,4096,3165],{"class":210},[200,4098,215],{"class":214},[200,4100,328],{"class":223},[200,4102,1532],{"class":210},[200,4104,215],{"class":214},[200,4106,3176],{"class":223},[200,4108,1532],{"class":210},[200,4110,215],{"class":214},[200,4112,3183],{"class":223},[200,4114,227],{"class":210},[200,4116,3188],{"class":2045},[200,4118,2089],{"class":210},[200,4120,215],{"class":214},[200,4122,4123],{"class":223},"max",[200,4125,227],{"class":210},[200,4127,4128],{"class":2045},"20_000",[200,4130,2089],{"class":210},[200,4132,215],{"class":214},[200,4134,3195],{"class":223},[200,4136,227],{"class":210},[200,4138,249],{"class":214},[200,4140,4141],{"class":245},"An async JavaScript arrow function with read-only pages, talks, and infra globals.",[200,4143,249],{"class":214},[200,4145,2089],{"class":210},[200,4147,252],{"class":214},[200,4149,4150],{"class":202,"line":310},[200,4151,339],{"class":214},[200,4153,4154],{"class":202,"line":336},[200,4155,368],{"class":214},[200,4157,4158,4160,4162,4165,4167,4169],{"class":202,"line":342},[200,4159,3219],{"class":379},[200,4161,383],{"class":214},[200,4163,4164],{"class":386}," code",[200,4166,390],{"class":214},[200,4168,393],{"class":379},[200,4170,280],{"class":214},[200,4172,4173,4175,4177,4179,4181,4184,4186,4188],{"class":202,"line":365},[200,4174,1999],{"class":379},[200,4176,3546],{"class":210},[200,4178,510],{"class":214},[200,4180,452],{"class":206},[200,4182,4183],{"class":223}," executeCode",[200,4185,227],{"class":235},[200,4187,156],{"class":210},[200,4189,416],{"class":235},[200,4191,4192],{"class":202,"line":371},[200,4193,529],{"emptyLinePlaceholder":20},[200,4195,4196,4198],{"class":202,"line":398},[200,4197,3296],{"class":206},[200,4199,4200],{"class":210}," result\n",[200,4202,4203],{"class":202,"line":405},[200,4204,368],{"class":214},[200,4206,4207],{"class":202,"line":410},[200,4208,416],{"class":210},[939,4210,4211],{},[35,4212,4213,3335,4215,3338,4217],{},[200,4214,945],{},[156,4216,3083],{},[1582,4218,215],{"repo":3341},[130,4220,132,4221,132,4226],{},[134,4222,136,4223,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,4224],{"src":4225,"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,4227,3024],{},[35,4229,4230],{},"Does this design meet the constraints we set at the beginning? Yes, it does.",[35,4232,4233],{},"I learned a lot while designing this MCP.",[1576,4235,4236,4239,4242,4245,4248],{},[71,4237,4238],{},"Reduce the toolset as much as possible. Use parameters to add flexibility to a tool instead of creating a new one. I really like GitHub's approach to MCP here;",[71,4240,4241],{},"Use your tools manually to see if they can answer your questions. If not, iterate on them until they can;",[71,4243,4244],{},"Keep tools broad enough in scope but specialized enough to avoid stepping on each other's toes;",[71,4246,4247],{},"Reduce the amount of information you provide to the agent. The less it has to read, the better it will perform;",[71,4249,4250],{},"Sometimes, the agent should orchestrate the tools itself.",[35,4252,4253],{},"I know that it is a lot of technology, AI Search and Dynamic Workers, just to build an MCP server, but it makes a real difference to answer quality. An MCP that cannot answer users' questions has limited value. If you want to build an MCP for your content-oriented website, I hope this article helps you avoid the mistakes I made and build a better one.",[51,4255,4257],{"id":4256},"how-i-arrived-at-three-tools","How I Arrived at Three Tools",[35,4259,4260,4261],{},"I created the first version of the MCP in October 2025. It was the second MCP I had created; I had used the first to explore the concept in ",[3029,4262,4264],{"id":4263},"4ff51ee2-926d-4924-86f5-97cd9e378fa7",". I had no idea how to architect it, so I created everything I could think of.",[35,4266,4267],{},"In the end, I created 10 tools just for the content of my main website:",[1576,4269,4270,4286,4300,4313,4327,4341,4355,4497,4621,4677],{},[71,4271,4272,4275],{},[156,4273,4274],{},"list_languages",[192,4276,4280],{"className":4277,"code":4278,"language":4279,"meta":13,"style":13},"language-txt shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","Returns a machine-readable JSON array of all supported languages for Estéban\\'s website. Each object includes a \"code\" (ISO 639-1) and \"name\" (English name). Example response: [{\"code\":\"en\",\"name\":\"English\"},{\"code\":\"fr\",\"name\":\"French\"}].\n","txt",[156,4281,4282],{"__ignoreMap":13},[200,4283,4284],{"class":202,"line":203},[200,4285,4278],{},[71,4287,4288,4291],{},[156,4289,4290],{},"list_parts",[192,4292,4294],{"className":4277,"code":4293,"language":4279,"meta":13,"style":13},"Returns a machine-readable JSON array of all available parts (sections) of Estéban\\'s website. Each object includes an \"id\" (string), \"name\" (string), and \"description\" (string). Example response: [{\"id\":\"pages\",\"name\":\"Pages\",\"description\":\"All website pages available.\"},{\"id\":\"blog\",\"name\":\"Blog\",\"description\":\"All blog posts available.\"}].\n",[156,4295,4296],{"__ignoreMap":13},[200,4297,4298],{"class":202,"line":203},[200,4299,4293],{},[71,4301,4302,4304],{},[156,4303,3083],{},[192,4305,4307],{"className":4277,"code":4306,"language":4279,"meta":13,"style":13},"Returns a list of all available pages on Estéban\\'s website for a specified language. Each page includes its title, description, URL, and date. Use the \"language\" parameter to select the language (e.g., \"en\" for English, \"fr\" for French). The response is a JSON array of objects: [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n",[156,4308,4309],{"__ignoreMap":13},[200,4310,4311],{"class":202,"line":203},[200,4312,4306],{},[71,4314,4315,4318],{},[156,4316,4317],{},"list_posts",[192,4319,4321],{"className":4277,"code":4320,"language":4279,"meta":13,"style":13},"Returns a list of all available blog posts on Estéban\\'s website for a specified language. Each post includes its title, description, URL and date. Use the \"language\" parameter to select the language (e.g., \"en\" for English, \"fr\" for French). The response is a JSON array of objects: [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n",[156,4322,4323],{"__ignoreMap":13},[200,4324,4325],{"class":202,"line":203},[200,4326,4320],{},[71,4328,4329,4332],{},[156,4330,4331],{},"list_series",[192,4333,4335],{"className":4277,"code":4334,"language":4279,"meta":13,"style":13},"Returns a list of all available series on Estéban\\'s website for a specified language. Each series includes its title, description, URL, and date. Use the \"language\" parameter to select the language (e.g., \"en\" for English, \"fr\" for French). The response is a JSON array of objects: [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n",[156,4336,4337],{"__ignoreMap":13},[200,4338,4339],{"class":202,"line":203},[200,4340,4334],{},[71,4342,4343,4346],{},[156,4344,4345],{},"list_series_articles",[192,4347,4349],{"className":4277,"code":4348,"language":4279,"meta":13,"style":13},"Returns a list of all articles within a specified series on Estéban\\'s website for a given language. Each article includes its title, description, URL, and date. Use the \"language\" parameter to select the language (e.g., \"en\" for English, \"fr\" for French) and the \"series\" parameter to specify the series URI. The response is a JSON array of objects: [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n",[156,4350,4351],{"__ignoreMap":13},[200,4352,4353],{"class":202,"line":203},[200,4354,4348],{},[71,4356,4357,4360],{},[156,4358,4359],{},"list_projects",[192,4361,4363],{"className":4277,"code":4362,"language":4279,"meta":13,"style":13},"Returns a machine-readable JSON array of all project categories for Estéban, each with a \"title\" (category name) and a \"projects\" array.\nEach project includes:\n- \"name\" (string, e.g. \"barbapapazes\u002Fcode.soubiran.dev\"),\n- \"description\" (string),\n- \"stars\" (number),\n- \"updatedAt\" (ISO 8601 string),\n- \"topics\" (array of strings),\n- \"url\" (string),\n- \"license\" (string, optional).\n\nExample response:\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,4364,4365,4370,4375,4380,4385,4390,4395,4400,4405,4410,4414,4419,4424,4428,4433,4438,4443,4448,4453,4458,4463,4468,4473,4479,4485,4491],{"__ignoreMap":13},[200,4366,4367],{"class":202,"line":203},[200,4368,4369],{},"Returns a machine-readable JSON array of all project categories for Estéban, each with a \"title\" (category name) and a \"projects\" array.\n",[200,4371,4372],{"class":202,"line":14},[200,4373,4374],{},"Each project includes:\n",[200,4376,4377],{"class":202,"line":255},[200,4378,4379],{},"- \"name\" (string, e.g. \"barbapapazes\u002Fcode.soubiran.dev\"),\n",[200,4381,4382],{"class":202,"line":272},[200,4383,4384],{},"- \"description\" (string),\n",[200,4386,4387],{"class":202,"line":283},[200,4388,4389],{},"- \"stars\" (number),\n",[200,4391,4392],{"class":202,"line":300},[200,4393,4394],{},"- \"updatedAt\" (ISO 8601 string),\n",[200,4396,4397],{"class":202,"line":310},[200,4398,4399],{},"- \"topics\" (array of strings),\n",[200,4401,4402],{"class":202,"line":336},[200,4403,4404],{},"- \"url\" (string),\n",[200,4406,4407],{"class":202,"line":342},[200,4408,4409],{},"- \"license\" (string, optional).\n",[200,4411,4412],{"class":202,"line":365},[200,4413,529],{"emptyLinePlaceholder":20},[200,4415,4416],{"class":202,"line":371},[200,4417,4418],{},"Example response:\n",[200,4420,4421],{"class":202,"line":398},[200,4422,4423],{},"[\n",[200,4425,4426],{"class":202,"line":405},[200,4427,3131],{},[200,4429,4430],{"class":202,"line":410},[200,4431,4432],{},"    \"title\": \"Ecosystem\",\n",[200,4434,4435],{"class":202,"line":861},[200,4436,4437],{},"    \"projects\": [\n",[200,4439,4440],{"class":202,"line":890},[200,4441,4442],{},"      {\n",[200,4444,4445],{"class":202,"line":900},[200,4446,4447],{},"        \"name\": \"barbapapazes\u002Fcode.soubiran.dev\",\n",[200,4449,4450],{"class":202,"line":905},[200,4451,4452],{},"        \"description\": \"Create beautiful images from code.\",\n",[200,4454,4455],{"class":202,"line":931},[200,4456,4457],{},"        \"stars\": 3,\n",[200,4459,4460],{"class":202,"line":3640},[200,4461,4462],{},"        \"updatedAt\": \"2025-03-16T21:16:15Z\",\n",[200,4464,4465],{"class":202,"line":3645},[200,4466,4467],{},"        \"topics\": [\"code\", \"vue\"],\n",[200,4469,4470],{"class":202,"line":4020},[200,4471,4472],{},"        \"url\": \"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcode.soubiran.dev\"\n",[200,4474,4476],{"class":202,"line":4475},23,[200,4477,4478],{},"      }\n",[200,4480,4482],{"class":202,"line":4481},24,[200,4483,4484],{},"    ]\n",[200,4486,4488],{"class":202,"line":4487},25,[200,4489,4490],{},"  }\n",[200,4492,4494],{"class":202,"line":4493},26,[200,4495,4496],{},"]\n",[71,4498,4499,4502],{},[156,4500,4501],{},"list_talks",[192,4503,4505],{"className":4277,"code":4504,"language":4279,"meta":13,"style":13},"Returns a machine-readable JSON array of all talks given by Estéban Soubiran.\nEach talk includes:\n- \"name\" (title of the talk, string)\n- \"event\" (event name, string)\n- \"date\" (ISO 8601 date, string)\n- \"url\" (main talk URL, string)\n- \"pdf_url\" (slides PDF URL, string, optional)\n- \"thumbnail_url\" (thumbnail image URL, string, optional)\n- \"github_url\" (GitHub repo URL, string, optional)\n- \"recording_url\" (video recording URL, string, optional)\n\nExample response:\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,4506,4507,4512,4517,4522,4527,4532,4537,4542,4547,4552,4557,4561,4565,4569,4573,4578,4583,4588,4593,4598,4603,4608,4613,4617],{"__ignoreMap":13},[200,4508,4509],{"class":202,"line":203},[200,4510,4511],{},"Returns a machine-readable JSON array of all talks given by Estéban Soubiran.\n",[200,4513,4514],{"class":202,"line":14},[200,4515,4516],{},"Each talk includes:\n",[200,4518,4519],{"class":202,"line":255},[200,4520,4521],{},"- \"name\" (title of the talk, string)\n",[200,4523,4524],{"class":202,"line":272},[200,4525,4526],{},"- \"event\" (event name, string)\n",[200,4528,4529],{"class":202,"line":283},[200,4530,4531],{},"- \"date\" (ISO 8601 date, string)\n",[200,4533,4534],{"class":202,"line":300},[200,4535,4536],{},"- \"url\" (main talk URL, string)\n",[200,4538,4539],{"class":202,"line":310},[200,4540,4541],{},"- \"pdf_url\" (slides PDF URL, string, optional)\n",[200,4543,4544],{"class":202,"line":336},[200,4545,4546],{},"- \"thumbnail_url\" (thumbnail image URL, string, optional)\n",[200,4548,4549],{"class":202,"line":342},[200,4550,4551],{},"- \"github_url\" (GitHub repo URL, string, optional)\n",[200,4553,4554],{"class":202,"line":365},[200,4555,4556],{},"- \"recording_url\" (video recording URL, string, optional)\n",[200,4558,4559],{"class":202,"line":371},[200,4560,529],{"emptyLinePlaceholder":20},[200,4562,4563],{"class":202,"line":398},[200,4564,4418],{},[200,4566,4567],{"class":202,"line":405},[200,4568,4423],{},[200,4570,4571],{"class":202,"line":410},[200,4572,3131],{},[200,4574,4575],{"class":202,"line":861},[200,4576,4577],{},"    \"name\": \"Unpoly pour reprendre le contrôle !\",\n",[200,4579,4580],{"class":202,"line":890},[200,4581,4582],{},"    \"event\": \"Devoxx France\",\n",[200,4584,4585],{"class":202,"line":900},[200,4586,4587],{},"    \"date\": \"2023-04-12\",\n",[200,4589,4590],{"class":202,"line":905},[200,4591,4592],{},"    \"url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\",\n",[200,4594,4595],{"class":202,"line":931},[200,4596,4597],{},"    \"pdf_url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\u002Fpdf\",\n",[200,4599,4600],{"class":202,"line":3640},[200,4601,4602],{},"    \"thumbnail_url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\u002Fthumbnail.png\",\n",[200,4604,4605],{"class":202,"line":3645},[200,4606,4607],{},"    \"github_url\": \"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Ftalks\u002Ftree\u002Fmain\u002F2023-04-12\",\n",[200,4609,4610],{"class":202,"line":4020},[200,4611,4612],{},"    \"recording_url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\u002Frecording\"\n",[200,4614,4615],{"class":202,"line":4475},[200,4616,4490],{},[200,4618,4619],{"class":202,"line":4481},[200,4620,4496],{},[71,4622,4623,4626],{},[156,4624,4625],{},"list_socials",[192,4627,4629],{"className":4277,"code":4628,"language":4279,"meta":13,"style":13},"Returns a machine-readable JSON array of all social media profiles for Estéban Soubiran.\nEach profile includes:\n- \"name\" (platform name, string, e.g. \"Twitter\")\n- \"url\" (profile URL, string)\n\nExample response:\n[\n  { \"name\": \"Twitter\", \"url\": \"https:\u002F\u002Ftwitter.com\u002Festebansoubiran\" },\n  { \"name\": \"GitHub\", \"url\": \"https:\u002F\u002Fgithub.com\u002FBarbapapazes\" }\n]\n",[156,4630,4631,4636,4641,4646,4651,4655,4659,4663,4668,4673],{"__ignoreMap":13},[200,4632,4633],{"class":202,"line":203},[200,4634,4635],{},"Returns a machine-readable JSON array of all social media profiles for Estéban Soubiran.\n",[200,4637,4638],{"class":202,"line":14},[200,4639,4640],{},"Each profile includes:\n",[200,4642,4643],{"class":202,"line":255},[200,4644,4645],{},"- \"name\" (platform name, string, e.g. \"Twitter\")\n",[200,4647,4648],{"class":202,"line":272},[200,4649,4650],{},"- \"url\" (profile URL, string)\n",[200,4652,4653],{"class":202,"line":283},[200,4654,529],{"emptyLinePlaceholder":20},[200,4656,4657],{"class":202,"line":300},[200,4658,4418],{},[200,4660,4661],{"class":202,"line":310},[200,4662,4423],{},[200,4664,4665],{"class":202,"line":336},[200,4666,4667],{},"  { \"name\": \"Twitter\", \"url\": \"https:\u002F\u002Ftwitter.com\u002Festebansoubiran\" },\n",[200,4669,4670],{"class":202,"line":342},[200,4671,4672],{},"  { \"name\": \"GitHub\", \"url\": \"https:\u002F\u002Fgithub.com\u002FBarbapapazes\" }\n",[200,4674,4675],{"class":202,"line":365},[200,4676,4496],{},[71,4678,4679,4681],{},[156,4680,3075],{},[192,4682,4684],{"className":4277,"code":4683,"language":4279,"meta":13,"style":13},"Fetches a specific page from Estéban\\'s website. The response is the Markdown content of the page.\n",[156,4685,4686],{"__ignoreMap":13},[200,4687,4688],{"class":202,"line":203},[200,4689,4683],{},[1582,4691,4693,4696,4703,4717,4971,4974,5134,5141,5147,5462,5465,5473,5476,5479,5487,5490,5502,5520,5526,5529,5532,5535,5538,5551,5565,5569,5572,5589,5592,5595,5598,5601,5603],{"repo":4692},"barbapapazes\u002Fmcp.soubiran.dev\u002Fcommit\u002F8c544b5adde3450d18b7b6d13b5927030054be7f",[35,4694,4695],{},"Most of these tools follow the same pattern.",[35,4697,4698,4702],{},[39,4699,4701],{"href":4700},".\u002Fvitepress-for-a-personal-website-is-that-possible","My website is VitePress-based"," and generates each page statically. I took advantage of this to generate many JSON files containing the information I need at build time. Each list tool then makes a simple HTTP request to fetch its corresponding JSON file.",[35,4704,4705,4706,4708,4709,4712,4713,4716],{},"For example, the ",[156,4707,3083],{}," tool fetches a JSON file named ",[156,4710,4711],{},"pages.en.json","—or ",[156,4714,4715],{},"pages.fr.json",", depending on the language requested through the parameter—and returns its content to the agent:",[192,4718,4720],{"className":194,"code":4719,"language":196,"meta":13,"style":13},"this.server.tool(\n  'list_pages',\n  '...',\n  {\n    language: z.string().min(2).max(2).describe('Language code for the content pages (e.g., \"en\", \"fr\")'),\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,4721,4722,4735,4745,4755,4759,4812,4816,4831,4868,4885,4891,4897,4905,4910,4925,4946,4951,4958,4963,4967],{"__ignoreMap":13},[200,4723,4724,4727,4729,4731,4733],{"class":202,"line":203},[200,4725,4726],{"class":214},"this.",[200,4728,3108],{"class":210},[200,4730,215],{"class":214},[200,4732,505],{"class":223},[200,4734,3115],{"class":210},[200,4736,4737,4739,4741,4743],{"class":202,"line":14},[200,4738,3120],{"class":214},[200,4740,3083],{"class":245},[200,4742,249],{"class":214},[200,4744,252],{"class":214},[200,4746,4747,4749,4751,4753],{"class":202,"line":255},[200,4748,3120],{"class":214},[200,4750,574],{"class":245},[200,4752,249],{"class":214},[200,4754,252],{"class":214},[200,4756,4757],{"class":202,"line":272},[200,4758,3131],{"class":214},[200,4760,4761,4764,4766,4768,4770,4772,4774,4776,4778,4780,4783,4785,4787,4789,4791,4793,4795,4797,4799,4801,4803,4806,4808,4810],{"class":202,"line":283},[200,4762,4763],{"class":235},"    language",[200,4765,239],{"class":214},[200,4767,3165],{"class":210},[200,4769,215],{"class":214},[200,4771,328],{"class":223},[200,4773,1532],{"class":210},[200,4775,215],{"class":214},[200,4777,3183],{"class":223},[200,4779,227],{"class":210},[200,4781,4782],{"class":2045},"2",[200,4784,2089],{"class":210},[200,4786,215],{"class":214},[200,4788,4123],{"class":223},[200,4790,227],{"class":210},[200,4792,4782],{"class":2045},[200,4794,2089],{"class":210},[200,4796,215],{"class":214},[200,4798,3195],{"class":223},[200,4800,227],{"class":210},[200,4802,249],{"class":214},[200,4804,4805],{"class":245},"Language code for the content pages (e.g., \"en\", \"fr\")",[200,4807,249],{"class":214},[200,4809,2089],{"class":210},[200,4811,252],{"class":214},[200,4813,4814],{"class":202,"line":300},[200,4815,368],{"class":214},[200,4817,4818,4820,4822,4825,4827,4829],{"class":202,"line":310},[200,4819,3219],{"class":379},[200,4821,383],{"class":214},[200,4823,4824],{"class":386}," language",[200,4826,390],{"class":214},[200,4828,393],{"class":379},[200,4830,280],{"class":214},[200,4832,4833,4835,4837,4839,4841,4844,4846,4848,4851,4854,4857,4859,4862,4864,4866],{"class":202,"line":336},[200,4834,1999],{"class":379},[200,4836,3546],{"class":210},[200,4838,510],{"class":214},[200,4840,452],{"class":206},[200,4842,4843],{"class":223}," ofetch",[200,4845,227],{"class":235},[200,4847,3318],{"class":214},[200,4849,4850],{"class":245},"pages.",[200,4852,4853],{"class":214},"${",[200,4855,4856],{"class":210},"language",[200,4858,413],{"class":214},[200,4860,4861],{"class":245},".json",[200,4863,3318],{"class":214},[200,4865,550],{"class":214},[200,4867,280],{"class":214},[200,4869,4870,4873,4875,4878,4880,4883],{"class":202,"line":342},[200,4871,4872],{"class":235},"      baseURL",[200,4874,239],{"class":214},[200,4876,4877],{"class":210}," env",[200,4879,215],{"class":214},[200,4881,4882],{"class":210},"BASE_API_URL",[200,4884,252],{"class":214},[200,4886,4887,4889],{"class":202,"line":365},[200,4888,2096],{"class":214},[200,4890,416],{"class":235},[200,4892,4893,4895],{"class":202,"line":371},[200,4894,3296],{"class":206},[200,4896,280],{"class":214},[200,4898,4899,4901,4903],{"class":202,"line":398},[200,4900,3589],{"class":235},[200,4902,239],{"class":214},[200,4904,1400],{"class":235},[200,4906,4907],{"class":202,"line":405},[200,4908,4909],{"class":214},"        {\n",[200,4911,4912,4915,4917,4919,4921,4923],{"class":202,"line":410},[200,4913,4914],{"class":235},"          type",[200,4916,239],{"class":214},[200,4918,242],{"class":214},[200,4920,698],{"class":245},[200,4922,249],{"class":214},[200,4924,252],{"class":214},[200,4926,4927,4930,4932,4934,4936,4938,4940,4942,4944],{"class":202,"line":861},[200,4928,4929],{"class":235},"          text",[200,4931,239],{"class":214},[200,4933,553],{"class":210},[200,4935,215],{"class":214},[200,4937,558],{"class":223},[200,4939,227],{"class":235},[200,4941,3531],{"class":210},[200,4943,2089],{"class":235},[200,4945,252],{"class":214},[200,4947,4948],{"class":202,"line":890},[200,4949,4950],{"class":214},"        },\n",[200,4952,4953,4956],{"class":202,"line":900},[200,4954,4955],{"class":235},"      ]",[200,4957,252],{"class":214},[200,4959,4960],{"class":202,"line":905},[200,4961,4962],{"class":214},"    }\n",[200,4964,4965],{"class":202,"line":931},[200,4966,368],{"class":214},[200,4968,4969],{"class":202,"line":3640},[200,4970,416],{"class":210},[35,4972,4973],{},"The JSON file itself was generated at build time with a Vite plugin that encapsulated the following logic:",[192,4975,4977],{"className":194,"code":4976,"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,4978,4979,5006,5032,5047,5061,5076,5090,5094,5100,5115,5126,5130],{"__ignoreMap":13},[200,4980,4981,4983,4985,4988,4991,4994,4997,4999,5002,5004],{"class":202,"line":203},[200,4982,1380],{"class":206},[200,4984,380],{"class":379},[200,4986,4987],{"class":379}," function",[200,4989,4990],{"class":223}," listEnPages",[200,4992,4993],{"class":214},"():",[200,4995,4996],{"class":1978}," Promise",[200,4998,600],{"class":214},[200,5000,5001],{"class":1978},"McpGenerator",[200,5003,675],{"class":214},[200,5005,280],{"class":214},[200,5007,5008,5010,5012,5014,5016,5019,5021,5023,5026,5028,5030],{"class":202,"line":14},[200,5009,3723],{"class":379},[200,5011,3237],{"class":210},[200,5013,510],{"class":214},[200,5015,452],{"class":206},[200,5017,5018],{"class":223}," createContentLoader",[200,5020,227],{"class":235},[200,5022,249],{"class":214},[200,5024,5025],{"class":245},"**\u002F*.md",[200,5027,249],{"class":214},[200,5029,550],{"class":214},[200,5031,280],{"class":214},[200,5033,5034,5037,5039,5042,5044],{"class":202,"line":255},[200,5035,5036],{"class":223},"    transform",[200,5038,239],{"class":214},[200,5040,5041],{"class":386}," data",[200,5043,393],{"class":379},[200,5045,5046],{"class":210}," data\n",[200,5048,5049,5052,5054,5056,5059],{"class":202,"line":272},[200,5050,5051],{"class":214},"      .",[200,5053,3758],{"class":223},[200,5055,227],{"class":235},[200,5057,5058],{"class":210},"isContentEn",[200,5060,416],{"class":235},[200,5062,5063,5065,5067,5069,5072,5074],{"class":202,"line":283},[200,5064,5051],{"class":214},[200,5066,3526],{"class":223},[200,5068,227],{"class":235},[200,5070,5071],{"class":210},"contentMapper",[200,5073,2089],{"class":235},[200,5075,252],{"class":214},[200,5077,5078,5081,5083,5085,5088],{"class":202,"line":300},[200,5079,5080],{"class":214},"  }",[200,5082,2089],{"class":235},[200,5084,215],{"class":214},[200,5086,5087],{"class":223},"load",[200,5089,466],{"class":235},[200,5091,5092],{"class":202,"line":310},[200,5093,529],{"emptyLinePlaceholder":20},[200,5095,5096,5098],{"class":202,"line":336},[200,5097,3743],{"class":206},[200,5099,280],{"class":214},[200,5101,5102,5105,5107,5109,5111,5113],{"class":202,"line":342},[200,5103,5104],{"class":235},"    filename",[200,5106,239],{"class":214},[200,5108,242],{"class":214},[200,5110,4711],{"class":245},[200,5112,249],{"class":214},[200,5114,252],{"class":214},[200,5116,5117,5120,5122,5124],{"class":202,"line":365},[200,5118,5119],{"class":235},"    content",[200,5121,239],{"class":214},[200,5123,3237],{"class":210},[200,5125,252],{"class":214},[200,5127,5128],{"class":202,"line":371},[200,5129,4490],{"class":214},[200,5131,5132],{"class":202,"line":398},[200,5133,1130],{"class":214},[35,5135,5136,5137,5140],{},"I list all the Markdown files, filter them to keep only the ones I need, and map them to the corresponding JSON structure. The result is a new JSON file in the ",[156,5138,5139],{},"dist"," folder that is served like any other static asset.",[35,5142,5143,5144,5146],{},"Only the ",[156,5145,3075],{}," tool is different. It fetches the raw Markdown content of a page.",[192,5148,5150],{"className":194,"code":5149,"language":196,"meta":13,"style":13},"this.server.tool(\n  'get_page',\n  '...',\n  {\n    url: z.string().min(1).describe('URL to the page to retrieve (e.g., \"\u002Fabout\", \"\u002Fcontact\")'),\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,5151,5152,5164,5174,5184,5188,5230,5234,5248,5270,5283,5287,5291,5312,5325,5329,5333,5366,5380,5386,5392,5400,5404,5418,5438,5442,5448,5452,5457],{"__ignoreMap":13},[200,5153,5154,5156,5158,5160,5162],{"class":202,"line":203},[200,5155,4726],{"class":214},[200,5157,3108],{"class":210},[200,5159,215],{"class":214},[200,5161,505],{"class":223},[200,5163,3115],{"class":210},[200,5165,5166,5168,5170,5172],{"class":202,"line":14},[200,5167,3120],{"class":214},[200,5169,3075],{"class":245},[200,5171,249],{"class":214},[200,5173,252],{"class":214},[200,5175,5176,5178,5180,5182],{"class":202,"line":255},[200,5177,3120],{"class":214},[200,5179,574],{"class":245},[200,5181,249],{"class":214},[200,5183,252],{"class":214},[200,5185,5186],{"class":202,"line":272},[200,5187,3131],{"class":214},[200,5189,5190,5193,5195,5197,5199,5201,5203,5205,5207,5209,5211,5213,5215,5217,5219,5221,5224,5226,5228],{"class":202,"line":283},[200,5191,5192],{"class":235},"    url",[200,5194,239],{"class":214},[200,5196,3165],{"class":210},[200,5198,215],{"class":214},[200,5200,328],{"class":223},[200,5202,1532],{"class":210},[200,5204,215],{"class":214},[200,5206,3183],{"class":223},[200,5208,227],{"class":210},[200,5210,3188],{"class":2045},[200,5212,2089],{"class":210},[200,5214,215],{"class":214},[200,5216,3195],{"class":223},[200,5218,227],{"class":210},[200,5220,249],{"class":214},[200,5222,5223],{"class":245},"URL to the page to retrieve (e.g., \"\u002Fabout\", \"\u002Fcontact\")",[200,5225,249],{"class":214},[200,5227,2089],{"class":210},[200,5229,252],{"class":214},[200,5231,5232],{"class":202,"line":300},[200,5233,368],{"class":214},[200,5235,5236,5238,5240,5242,5244,5246],{"class":202,"line":310},[200,5237,3219],{"class":379},[200,5239,383],{"class":214},[200,5241,3973],{"class":386},[200,5243,390],{"class":214},[200,5245,393],{"class":379},[200,5247,280],{"class":214},[200,5249,5250,5253,5255,5257,5259,5261,5263,5265,5268],{"class":202,"line":336},[200,5251,5252],{"class":206},"    if",[200,5254,1095],{"class":235},[200,5256,2020],{"class":210},[200,5258,3283],{"class":214},[200,5260,242],{"class":214},[200,5262,2882],{"class":245},[200,5264,249],{"class":214},[200,5266,5267],{"class":235},") ",[200,5269,230],{"class":214},[200,5271,5272,5274,5276,5278,5281],{"class":202,"line":342},[200,5273,3605],{"class":210},[200,5275,510],{"class":214},[200,5277,242],{"class":214},[200,5279,5280],{"class":245},"\u002Findex",[200,5282,992],{"class":214},[200,5284,5285],{"class":202,"line":365},[200,5286,4962],{"class":214},[200,5288,5289],{"class":202,"line":371},[200,5290,529],{"emptyLinePlaceholder":20},[200,5292,5293,5295,5297,5299,5301,5303,5306,5308,5310],{"class":202,"line":398},[200,5294,5252],{"class":206},[200,5296,1095],{"class":235},[200,5298,2020],{"class":210},[200,5300,3283],{"class":214},[200,5302,242],{"class":214},[200,5304,5305],{"class":245},"\u002Ffr\u002F",[200,5307,249],{"class":214},[200,5309,5267],{"class":235},[200,5311,230],{"class":214},[200,5313,5314,5316,5318,5320,5323],{"class":202,"line":405},[200,5315,3605],{"class":210},[200,5317,510],{"class":214},[200,5319,242],{"class":214},[200,5321,5322],{"class":245},"\u002Ffr\u002Findex",[200,5324,992],{"class":214},[200,5326,5327],{"class":202,"line":410},[200,5328,4962],{"class":214},[200,5330,5331],{"class":202,"line":861},[200,5332,529],{"emptyLinePlaceholder":20},[200,5334,5335,5337,5339,5341,5343,5345,5347,5349,5352,5354,5356,5358,5360,5362,5364],{"class":202,"line":890},[200,5336,1999],{"class":379},[200,5338,3546],{"class":210},[200,5340,510],{"class":214},[200,5342,452],{"class":206},[200,5344,4843],{"class":223},[200,5346,227],{"class":235},[200,5348,3318],{"class":214},[200,5350,5351],{"class":245},"pages",[200,5353,4853],{"class":214},[200,5355,2020],{"class":210},[200,5357,413],{"class":214},[200,5359,3315],{"class":245},[200,5361,3318],{"class":214},[200,5363,550],{"class":214},[200,5365,280],{"class":214},[200,5367,5368,5370,5372,5374,5376,5378],{"class":202,"line":900},[200,5369,4872],{"class":235},[200,5371,239],{"class":214},[200,5373,4877],{"class":210},[200,5375,215],{"class":214},[200,5377,4882],{"class":210},[200,5379,252],{"class":214},[200,5381,5382,5384],{"class":202,"line":905},[200,5383,2096],{"class":214},[200,5385,416],{"class":235},[200,5387,5388,5390],{"class":202,"line":931},[200,5389,3296],{"class":206},[200,5391,280],{"class":214},[200,5393,5394,5396,5398],{"class":202,"line":3640},[200,5395,3589],{"class":235},[200,5397,239],{"class":214},[200,5399,1400],{"class":235},[200,5401,5402],{"class":202,"line":3645},[200,5403,4909],{"class":214},[200,5405,5406,5408,5410,5412,5414,5416],{"class":202,"line":4020},[200,5407,4914],{"class":235},[200,5409,239],{"class":214},[200,5411,242],{"class":214},[200,5413,698],{"class":245},[200,5415,249],{"class":214},[200,5417,252],{"class":214},[200,5419,5420,5422,5424,5426,5428,5430,5432,5434,5436],{"class":202,"line":4475},[200,5421,4929],{"class":235},[200,5423,239],{"class":214},[200,5425,553],{"class":210},[200,5427,215],{"class":214},[200,5429,558],{"class":223},[200,5431,227],{"class":235},[200,5433,3531],{"class":210},[200,5435,2089],{"class":235},[200,5437,252],{"class":214},[200,5439,5440],{"class":202,"line":4481},[200,5441,4950],{"class":214},[200,5443,5444,5446],{"class":202,"line":4487},[200,5445,4955],{"class":235},[200,5447,252],{"class":214},[200,5449,5450],{"class":202,"line":4493},[200,5451,4962],{"class":214},[200,5453,5455],{"class":202,"line":5454},27,[200,5456,368],{"class":214},[200,5458,5460],{"class":202,"line":5459},28,[200,5461,416],{"class":210},[35,5463,5464],{},"As you can guess, that's way too many tools. There are two direct consequences:",[68,5466,5467,5470],{},[71,5468,5469],{},"too much description that pollutes the LLM context.",[71,5471,5472],{},"too many tools to manage effectively.",[35,5474,5475],{},"In everyday use, this broad, overlapping toolset made reliable tool selection and orchestration harder for the agent.",[35,5477,5478],{},"I learned two lessons from this initial experience:",[68,5480,5481,5484],{},[71,5482,5483],{},"reduce the toolset to a minimum, so adding parameters to a tool is better than creating a new one;",[71,5485,5486],{},"make tool orchestration understandable from the tools' names and descriptions alone.",[35,5488,5489],{},"In July 2026, I continued to iterate on the MCP. I reduced the number of tools to only two:",[68,5491,5492,5497],{},[71,5493,5494,5496],{},[156,5495,3075],{},";",[71,5498,5499,215],{},[156,5500,5501],{},"search_pages",[35,5503,419,5504,5506,5507,5509,5510,5512,5513,5516,5517,5519],{},[156,5505,3075],{}," tool was similar to the previous ",[156,5508,3075],{}," tool: it fetched the raw Markdown content of a page. However, the ",[156,5511,5501],{}," tool was completely different. It used ",[39,5514,3370],{"href":3368,"rel":5515},[77]," to let the agent search my website's content using natural language. The result included both the filename and the matching content chunk. The agent could then call ",[156,5518,3075],{}," to fetch the full page content and answer the question. At that time, AI Search could only populate its search index from R2, so I used a GitHub Action and rclone to populate a bucket with my website's content. This automatically rebuilt the search index whenever I pushed new content to my website.",[35,5521,5522,5523,5525],{},"Removing the ",[156,5524,4274],{}," tool raised a lot of concerns. This tool indicated to the agent that the website is multilingual. However, should we really provide this information that way? Should we add a parameter to each tool to let the agent select the language? Or should we always provide both languages in the tool output?",[35,5527,5528],{},"At that moment, I decided to provide only English content to the agent. LLMs can translate that content into other languages.",[35,5530,5531],{},"I also removed projects and talks because I didn't know how to make them available to search. At that time, my goal was to completely remove the idea of a tool that simply returns a list of items.",[35,5533,5534],{},"However, after a couple of days working with it, I realized that this configuration could never answer a question like \"What's the latest article about Devoxx France?\" and that was a real problem for me.",[35,5536,5537],{},"I needed a way to analyze content metadata to answer that question, and a list tool was the most straightforward option. At the same time, I did not want to add one because of all the problems I explained earlier. So, I was blocked.",[35,5539,5540,5541,5546,5547,5550],{},"Then, I remembered the OpenAPI specification problem Cloudflare faced when it ",[39,5542,5545],{"href":5543,"rel":5544},"https:\u002F\u002Fblog.cloudflare.com\u002Fcode-mode\u002F",[77],"introduced its code mode MCP",". An OpenAPI specification is a list of endpoints, much like my content metadata is a list of pages. So, what about applying the same solution? I could provide the agent with a list of articles and let it use code to find what it needed, returning only the relevant information. That is how the ",[156,5548,5549],{},"list_content"," tool was born.",[35,5552,5553,5554,5557,5558,1638,5560,5557,5562,5564],{},"The final iteration renamed ",[156,5555,5556],{},"get_content"," to ",[156,5559,3075],{},[156,5561,5549],{},[156,5563,3083],{},". The result is the three-tool design described at the beginning of this article.",[51,5566,5568],{"id":5567},"whats-next","What's Next",[35,5570,5571],{},"Today, the MCP has only three tools:",[1576,5573,5574,5579,5584],{},[71,5575,5576,5578],{},[156,5577,3075],{},", read the content of a page;",[71,5580,5581,5583],{},[156,5582,3079],{},", discover content using natural language;",[71,5585,5586,5588],{},[156,5587,3083],{},", programmatically analyze the website's available content.",[35,5590,5591],{},"Each tackles a different problem and, together, they allow the agent to answer any question about my website's content.",[35,5593,5594],{},"It's truly fascinating to see how questions that seem simple on paper can become a real challenge. Most MCP servers for content-oriented websites, such as documentation sites, combine a list tool and a get tool, or a search tool and a get tool, despite the different behaviors of list and search tools.",[35,5596,5597],{},"Now, it is time to use my MCP in real-world scenarios. I will create a chat connected to it, which will let me tweak the system prompt and allow anyone to ask questions about my website's content.",[35,5599,5600],{},"I will then create evals to see how small changes to the system prompt or tool descriptions affect answer quality.",[1159,5602],{},[35,5604,5605,5606,5609],{},"Ultimately, for developer documentation, I have found that a well-maintained ",[156,5607,5608],{},"llms.txt"," file can be a simpler and more effective starting point than an MCP.",[1171,5611,5612],{},"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":5614},[5615,5616,5617,5618],{"id":3038,"depth":14,"text":3039},{"id":3068,"depth":14,"text":3069},{"id":4256,"depth":14,"text":4257},{"id":5567,"depth":14,"text":5568},"23e3c4f4-0808-4bb4-9230-3b9cc082769a","Learn how three focused MCP tools—search, page retrieval, and code-powered listing—make content websites accurate, efficient, and scalable.",{},"\u002Fposts\u002Fthe-three-mcp-tools-you-need-for-a-content-oriented-website","---\ncontentId: 23e3c4f4-0808-4bb4-9230-3b9cc082769a\ntitle: The Three MCP Tools You Need for a Content-Oriented Website\ndescription: Learn how three focused MCP tools—search, page retrieval, and code-powered listing—make content websites accurate, efficient, and scalable.\ndate: 2026-08-24\n---\n\nSince October 2025, I haven't been able to shake a few questions:\n\n- Summarize the article about Code Mode.\n- What to keep in mind in the series about AI agents?\n- Is there content about Vite?\n- What's the latest article about Devoxx France?\n\nIn October, I published the article \u003CLinkToArticle id=\"e258d6e6-2f2f-400d-9b6f-02801a7284db\" \u002F>. I explained that I had built an MCP server and an agent to chat with my website's content. I had successfully created a proof of concept (POC). It was the first time I had created such a system, and I was really impressed. However, it was never production-ready.\n\nThose questions helped me iterate on the MCP until it was efficient and usable. Today, I want to share what I learned about building an MCP for a content-oriented website.\n\n## Challenges for Agents\n\nFor a human, these questions are relatively trivial. It can take time to browse the website and read the content, but that's the only difficulty. For an agent, it's a different story.\n\nAn agent must:\n\n1. answer as quickly as possible to avoid making the user wait too long;\n2. answer as accurately as possible to avoid frustrating the user by not finding what they are looking for;\n3. continue to answer effectively as the website's content grows;\n4. use as few tokens as possible to avoid making users pay too much for an answer;\n5. ground answers in the website’s actual content and identify the source, so users can verify them.\n\nThat's a lot of constraints to meet when designing an MCP server.\n\n## The Current Approach\n\nThe final version of my MCP has three tools: `get_page`, `search_content`, and `list_pages`. Each answers a different kind of question. Let's tackle them one by one.\n\n> \"Summarize the article about Code Mode.\"\n\nTo answer this question, the agent must be able to read the article's content. Many MCP servers I explored while creating mine exposed a `get_page` tool for exactly that purpose. My `get_page` tool takes a page ID and internally retrieves the corresponding URL to fetch the page content.\n\n```typescript\nserver.registerTool(\n  'get_page',\n  {\n    description: '...',\n    inputSchema: {\n      id: z.string().trim().min(1).describe('Exact, globally unique content ID returned by search_content or 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> This is not the real implementation of the `get_page` tool. It is an oversimplified version to illustrate the idea. For the full implementation, check \u003CGitHubLink repo=\"barbapapazes\u002Fmcp.soubiran.dev\" \u002F>.\n\nThat covers reading a known page. The remaining question is: how does the agent know its ID?\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>Summarizing the article about Code Mode\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n> \"Is there content about Vite?\"\n\nTo solve this question, the agent must be able to search across all content, including titles, descriptions, and body text. So, we need a `search_content` tool. That tool takes a query as a parameter and internally uses semantic and keyword search to retrieve matching content. This is done using [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('Query to search for content.')\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> This is not the real implementation of the `search_content` tool. It is an oversimplified version to illustrate the idea. For the full implementation, check \u003CGitHubLink repo=\"barbapapazes\u002Fmcp.soubiran.dev\" \u002F>.\n\nWith a well-chosen query, this tool could also help answer the first question. Thanks to keyword search, the agent could search for \"Code Mode\" and retrieve the corresponding page ID. However, this is not reliable enough on its own: a query can be ambiguous or fail to rank the intended page first.\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>Is there content about Vite?\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n> \"What's the latest article about Devoxx France?\"\n\nThis one is more complicated. It requires comparing article dates. To identify the latest matching article efficiently, the agent needs access to the content metadata and a way to analyze it with code. Without that capability, it would have to retrieve the full list and perform the comparison itself.\n\nThat may work, but it is not efficient. The full list of my website's content is 203,535 characters long, or about 60,000 tokens. Sure, it would fit within the context window of most models today, but it would consume time and tokens for no benefit. It would also pollute the context, making it harder for the agent to find relevant information.\n\nDespite this, I decided to create the `list_pages` tool anyway. However, it does not work as you might expect.\n\nThe tool takes a `code` input. It lets the agent write JavaScript against typed data to filter, sort, and map exactly the information it needs. [Cloudflare Dynamic Workers](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fdynamic-workers\u002F) execute the code in a lightweight, secure, and isolated environment.\n\nFor example, the agent could write the following code to retrieve the latest article about 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> To understand what code mode is, read [Code Mode, Two Tools, and an MCP Can Save Your LLM Context](.\u002Fcode-mode-two-tools-and-an-mcp-can-save-your-llm-context.md).\n\nUnder the hood, the tool looks like this. Keep in mind that one of the interesting parts of a code mode tool is its description.\n\n```typescript\nserver.registerTool(\n  'list_pages',\n  {\n    description: '...',\n    inputSchema: {\n      code: z.string().trim().min(1).max(20_000).describe('An async JavaScript arrow function with read-only pages, talks, and infra globals.'),\n    },\n  },\n  async ({ code }) => {\n    const result = await executeCode(code)\n\n    return result\n  },\n)\n```\n\n> [!NOTE]\n> This is not the real implementation of the `list_pages` tool. It is an oversimplified version to illustrate the idea. For the full implementation, check \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>What's the latest article about Devoxx France?\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nDoes this design meet the constraints we set at the beginning? Yes, it does.\n\nI learned a lot while designing this MCP.\n\n- Reduce the toolset as much as possible. Use parameters to add flexibility to a tool instead of creating a new one. I really like GitHub's approach to MCP here;\n- Use your tools manually to see if they can answer your questions. If not, iterate on them until they can;\n- Keep tools broad enough in scope but specialized enough to avoid stepping on each other's toes;\n- Reduce the amount of information you provide to the agent. The less it has to read, the better it will perform;\n- Sometimes, the agent should orchestrate the tools itself.\n\nI know that it is a lot of technology, AI Search and Dynamic Workers, just to build an MCP server, but it makes a real difference to answer quality. An MCP that cannot answer users' questions has limited value. If you want to build an MCP for your content-oriented website, I hope this article helps you avoid the mistakes I made and build a better one.\n\n## How I Arrived at Three Tools\n\nI created the first version of the MCP in October 2025. It was the second MCP I had created; I had used the first to explore the concept in \u003CLinkToArticle id=\"4ff51ee2-926d-4924-86f5-97cd9e378fa7\" \u002F>. I had no idea how to architect it, so I created everything I could think of.\n\nIn the end, I created 10 tools just for the content of my main website:\n\n- `list_languages`\n\n  ```txt\n  Returns a machine-readable JSON array of all supported languages for Estéban\\'s website. Each object includes a \"code\" (ISO 639-1) and \"name\" (English name). Example response: [{\"code\":\"en\",\"name\":\"English\"},{\"code\":\"fr\",\"name\":\"French\"}].\n  ```\n\n- `list_parts`\n\n  ```txt\n  Returns a machine-readable JSON array of all available parts (sections) of Estéban\\'s website. Each object includes an \"id\" (string), \"name\" (string), and \"description\" (string). Example response: [{\"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  Returns a list of all available pages on Estéban\\'s website for a specified language. Each page includes its title, description, URL, and date. Use the \"language\" parameter to select the language (e.g., \"en\" for English, \"fr\" for French). The response is a JSON array of objects: [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n  ```\n\n- `list_posts`\n\n  ```txt\n  Returns a list of all available blog posts on Estéban\\'s website for a specified language. Each post includes its title, description, URL and date. Use the \"language\" parameter to select the language (e.g., \"en\" for English, \"fr\" for French). The response is a JSON array of objects: [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n  ```\n\n- `list_series`\n\n  ```txt\n  Returns a list of all available series on Estéban\\'s website for a specified language. Each series includes its title, description, URL, and date. Use the \"language\" parameter to select the language (e.g., \"en\" for English, \"fr\" for French). The response is a JSON array of objects: [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n  ```\n\n- `list_series_articles`\n\n  ```txt\n  Returns a list of all articles within a specified series on Estéban\\'s website for a given language. Each article includes its title, description, URL, and date. Use the \"language\" parameter to select the language (e.g., \"en\" for English, \"fr\" for French) and the \"series\" parameter to specify the series URI. The response is a JSON array of objects: [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n  ```\n\n- `list_projects`\n\n  ```txt\n  Returns a machine-readable JSON array of all project categories for Estéban, each with a \"title\" (category name) and a \"projects\" array.\n  Each project includes:\n  - \"name\" (string, e.g. \"barbapapazes\u002Fcode.soubiran.dev\"),\n  - \"description\" (string),\n  - \"stars\" (number),\n  - \"updatedAt\" (ISO 8601 string),\n  - \"topics\" (array of strings),\n  - \"url\" (string),\n  - \"license\" (string, optional).\n\n  Example response:\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  Returns a machine-readable JSON array of all talks given by Estéban Soubiran.\n  Each talk includes:\n  - \"name\" (title of the talk, string)\n  - \"event\" (event name, string)\n  - \"date\" (ISO 8601 date, string)\n  - \"url\" (main talk URL, string)\n  - \"pdf_url\" (slides PDF URL, string, optional)\n  - \"thumbnail_url\" (thumbnail image URL, string, optional)\n  - \"github_url\" (GitHub repo URL, string, optional)\n  - \"recording_url\" (video recording URL, string, optional)\n\n  Example response:\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  Returns a machine-readable JSON array of all social media profiles for Estéban Soubiran.\n  Each profile includes:\n  - \"name\" (platform name, string, e.g. \"Twitter\")\n  - \"url\" (profile URL, string)\n\n  Example response:\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  Fetches a specific page from Estéban\\'s website. The response is the Markdown content of the page.\n  ```\n\n\u003CGitHubLink repo=\"barbapapazes\u002Fmcp.soubiran.dev\u002Fcommit\u002F8c544b5adde3450d18b7b6d13b5927030054be7f\" \u002F>\n\nMost of these tools follow the same pattern.\n\n[My website is VitePress-based](.\u002Fvitepress-for-a-personal-website-is-that-possible.md) and generates each page statically. I took advantage of this to generate many JSON files containing the information I need at build time. Each list tool then makes a simple HTTP request to fetch its corresponding JSON file.\n\nFor example, the `list_pages` tool fetches a JSON file named `pages.en.json`—or `pages.fr.json`, depending on the language requested through the parameter—and returns its content to the agent:\n\n```typescript\nthis.server.tool(\n  'list_pages',\n  '...',\n  {\n    language: z.string().min(2).max(2).describe('Language code for the content pages (e.g., \"en\", \"fr\")'),\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\nThe JSON file itself was generated at build time with a Vite plugin that encapsulated the following logic:\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\nI list all the Markdown files, filter them to keep only the ones I need, and map them to the corresponding JSON structure. The result is a new JSON file in the `dist` folder that is served like any other static asset.\n\nOnly the `get_page` tool is different. It fetches the raw Markdown content of a page.\n\n```typescript\nthis.server.tool(\n  'get_page',\n  '...',\n  {\n    url: z.string().min(1).describe('URL to the page to retrieve (e.g., \"\u002Fabout\", \"\u002Fcontact\")'),\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\nAs you can guess, that's way too many tools. There are two direct consequences:\n\n1. too much description that pollutes the LLM context.\n2. too many tools to manage effectively.\n\nIn everyday use, this broad, overlapping toolset made reliable tool selection and orchestration harder for the agent.\n\nI learned two lessons from this initial experience:\n\n1. reduce the toolset to a minimum, so adding parameters to a tool is better than creating a new one;\n2. make tool orchestration understandable from the tools' names and descriptions alone.\n\nIn July 2026, I continued to iterate on the MCP. I reduced the number of tools to only two:\n\n1. `get_page`;\n2. `search_pages`.\n\nThe `get_page` tool was similar to the previous `get_page` tool: it fetched the raw Markdown content of a page. However, the `search_pages` tool was completely different. It used [Cloudflare AI Search](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fai-search\u002F) to let the agent search my website's content using natural language. The result included both the filename and the matching content chunk. The agent could then call `get_page` to fetch the full page content and answer the question. At that time, AI Search could only populate its search index from R2, so I used a GitHub Action and rclone to populate a bucket with my website's content. This automatically rebuilt the search index whenever I pushed new content to my website.\n\nRemoving the `list_languages` tool raised a lot of concerns. This tool indicated to the agent that the website is multilingual. However, should we really provide this information that way? Should we add a parameter to each tool to let the agent select the language? Or should we always provide both languages in the tool output?\n\nAt that moment, I decided to provide only English content to the agent. LLMs can translate that content into other languages.\n\nI also removed projects and talks because I didn't know how to make them available to search. At that time, my goal was to completely remove the idea of a tool that simply returns a list of items.\n\nHowever, after a couple of days working with it, I realized that this configuration could never answer a question like \"What's the latest article about Devoxx France?\" and that was a real problem for me.\n\nI needed a way to analyze content metadata to answer that question, and a list tool was the most straightforward option. At the same time, I did not want to add one because of all the problems I explained earlier. So, I was blocked.\n\nThen, I remembered the OpenAPI specification problem Cloudflare faced when it [introduced its code mode MCP](https:\u002F\u002Fblog.cloudflare.com\u002Fcode-mode\u002F). An OpenAPI specification is a list of endpoints, much like my content metadata is a list of pages. So, what about applying the same solution? I could provide the agent with a list of articles and let it use code to find what it needed, returning only the relevant information. That is how the `list_content` tool was born.\n\nThe final iteration renamed `get_content` to `get_page` and `list_content` to `list_pages`. The result is the three-tool design described at the beginning of this article.\n\n## What's Next\n\nToday, the MCP has only three tools:\n\n- `get_page`, read the content of a page;\n- `search_content`, discover content using natural language;\n- `list_pages`, programmatically analyze the website's available content.\n\nEach tackles a different problem and, together, they allow the agent to answer any question about my website's content.\n\nIt's truly fascinating to see how questions that seem simple on paper can become a real challenge. Most MCP servers for content-oriented websites, such as documentation sites, combine a list tool and a get tool, or a search tool and a get tool, despite the different behaviors of list and search tools.\n\nNow, it is time to use my MCP in real-world scenarios. I will create a chat connected to it, which will let me tweak the system prompt and allow anyone to ask questions about my website's content.\n\nI will then create evals to see how small changes to the system prompt or tool descriptions affect answer quality.\n\n---\n\nUltimately, for developer documentation, I have found that a well-maintained `llms.txt` file can be a simpler and more effective starting point than an MCP.\n",{"title":3005,"description":5620},{"loc":5622},"posts\u002Fthe-three-mcp-tools-you-need-for-a-content-oriented-website","TDPHOtAWHfsYF0FVTMmdpqgz8PrBfuQhYS-TSjJjqXg",{"id":5629,"title":5630,"body":5631,"contentId":5731,"date":5732,"description":5733,"extension":18,"meta":5734,"navigation":20,"path":5735,"rawbody":5736,"robots":23,"schemaOrg":23,"seo":5737,"sitemap":5738,"stem":5739,"__hash__":5740},"posts\u002Fposts\u002Fwelcome-to-you-can-just-do-things-week-we-must-ship.md","Welcome to You Can Just Do Things Week: We Must Ship",{"type":7,"value":5632,"toc":5727},[5633,5636,5639,5643,5652,5660,5663,5666,5669,5672,5675,5679,5682,5691,5700,5714,5716,5719,5721,5724],[35,5634,5635],{},"Starting something has never been easier.",[35,5637,5638],{},"However, finishing something and shipping it is a different story. I stopped counting all the ideas, projects, experiments, and writings that I started but never finished. Sometimes, the only thing missing was clicking on the \"Publish\" button. Other times, a new idea popped into my head, shifted my focus completely, or I never found the energy for the last 20% of the work.",[51,5640,5642],{"id":5641},"you-can-just-do-things-week","You Can Just Do Things Week",[35,5644,5645,5646,5651],{},"From 24 to 28 August, every day, I've decided to ship something. Expect Nuxt modules, Vite plugins, AI Search integrations, something built with ",[39,5647,5650],{"href":5648,"rel":5649},"https:\u002F\u002Fflueframework.com\u002F",[77],"Flue",", a learning series, agent skills, and more that I've been working on over the last few months.",[35,5653,5654,5655],{},"I've recently given a talk about agency, the idea that you don't need permissions to do something, to create, and to take control of your life. Last year, I also wrote about it in ",[5656,5657,5659],"link-article",{"id":5658},"47cda943-2e43-45d7-9257-0f98eaf651e6"," and, since then, I try to put it into practice every single day. With this week, it's like a consecration of that mindset.",[35,5661,5662],{},"You can just do things is at the heart of agency. It is now something etched in my mind. Honestly, it's like a lifestyle.",[35,5664,5665],{},"I have a north star that helps me choose what to work on, but I am also attracted by opportunities. Some ideas are meaningful only during a specific period, and if I don't take advantage of them, I might miss the opportunity. The balance is hard to find: staying in motion should not mean leaving every project behind but our time is limited. This public commitment helps me stay focused, organized, and disciplined while there are projects that I just want to do for fun.",[35,5667,5668],{},"Don't get me wrong, I don't want to gatekeep everything I build until I have enough content to make a full release week. I ship everything I can, when I find time, and when I feel it's worth it. This time, it's different: I've started too many things that I need to finish.",[35,5670,5671],{},"I'm so excited about this week. I have so many things I want to share with you!",[35,5673,5674],{},"Follow along here from 24 to 28 August. I'll ship something every day, and I hope you'll be there!",[51,5676,5678],{"id":5677},"those-who-inspire-me","Those who inspire me",[35,5680,5681],{},"That idea of an innovation week isn't from me. I happily borrowed it from Cloudflare.",[35,5683,5684,5685,5690],{},"Every year, and multiple times a year, Cloudflare organizes weeks to ship. In early August, they organized an Agents Week, and ",[39,5686,5689],{"href":5687,"rel":5688},"https:\u002F\u002Fblog.cloudflare.com\u002Fagents-week-review-august-2026\u002F",[77],"they really shipped",". They also have a birthday week that I've carefully followed every year for the past couple of years, even if sometimes, it's hard to follow. I love the idea of dedicating a week to ship because it forces to clearly communicate what is being shipped, to write about it.",[35,5692,5693,5694,5699],{},"\"We must ship\" is the mantra of Taylor Otwell, the creator of Laravel. He said it during ",[39,5695,5698],{"href":5696,"rel":5697},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NTMDzGLs8pw",[77],"his 2025 Laracon EU keynote"," with the idea that done is better than perfect. It resonates with me a lot and I'm trying to apply it in my daily life, similar to the agency mindset.",[35,5701,5702,5703,5708,5709,5713],{},"Finally, \"You can just do things\" is a motto that Aaron Francis promotes a lot on Twitter and that he talked about during the ",[39,5704,5707],{"href":5705,"rel":5706},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=dgr-WAUgELw",[77],"Laracon US 2025 introduction keynote",", or even during Epic Web in a talk named ",[39,5710,5712],{"href":1328,"rel":5711},[77],"Epic Bets: Practical Tips For Betting on Yourself",". I just love that mindset and the way it encourages people to just do things.",[1159,5715],{},[35,5717,5718],{},"I'm alone at the helm of my ship, so this week will be smaller than Cloudflare's, but no less personal. I'm proud of what I've built so far, and I hope you'll enjoy it.",[1159,5720],{},[35,5722,5723],{},"That could be a one-off event.",[35,5725,5726],{},"Or maybe I'll revive it in 16 months.",{"title":13,"searchDepth":14,"depth":14,"links":5728},[5729,5730],{"id":5641,"depth":14,"text":5642},{"id":5677,"depth":14,"text":5678},"f339abd4-daf8-4e1b-8388-ea0f912f50c9","2026-08-19","From 24 to 28 August, I'll ship one thing every day: articles, experiments, tools, and projects I've been meaning to share.",{},"\u002Fposts\u002Fwelcome-to-you-can-just-do-things-week-we-must-ship","---\ncontentId: f339abd4-daf8-4e1b-8388-ea0f912f50c9\ntitle: \"Welcome to You Can Just Do Things Week: We Must Ship\"\ndescription: \"From 24 to 28 August, I'll ship one thing every day: articles, experiments, tools, and projects I've been meaning to share.\"\ndate: 2026-08-19\n---\n\nStarting something has never been easier.\n\nHowever, finishing something and shipping it is a different story. I stopped counting all the ideas, projects, experiments, and writings that I started but never finished. Sometimes, the only thing missing was clicking on the \"Publish\" button. Other times, a new idea popped into my head, shifted my focus completely, or I never found the energy for the last 20% of the work.\n\n## You Can Just Do Things Week\n\nFrom 24 to 28 August, every day, I've decided to ship something. Expect Nuxt modules, Vite plugins, AI Search integrations, something built with [Flue](https:\u002F\u002Fflueframework.com\u002F), a learning series, agent skills, and more that I've been working on over the last few months.\n\nI've recently given a talk about agency, the idea that you don't need permissions to do something, to create, and to take control of your life. Last year, I also wrote about it in \u003CLinkArticle id=\"47cda943-2e43-45d7-9257-0f98eaf651e6\" \u002F> and, since then, I try to put it into practice every single day. With this week, it's like a consecration of that mindset.\n\nYou can just do things is at the heart of agency. It is now something etched in my mind. Honestly, it's like a lifestyle.\n\nI have a north star that helps me choose what to work on, but I am also attracted by opportunities. Some ideas are meaningful only during a specific period, and if I don't take advantage of them, I might miss the opportunity. The balance is hard to find: staying in motion should not mean leaving every project behind but our time is limited. This public commitment helps me stay focused, organized, and disciplined while there are projects that I just want to do for fun.\n\nDon't get me wrong, I don't want to gatekeep everything I build until I have enough content to make a full release week. I ship everything I can, when I find time, and when I feel it's worth it. This time, it's different: I've started too many things that I need to finish.\n\nI'm so excited about this week. I have so many things I want to share with you!\n\nFollow along here from 24 to 28 August. I'll ship something every day, and I hope you'll be there!\n\n## Those who inspire me\n\nThat idea of an innovation week isn't from me. I happily borrowed it from Cloudflare.\n\nEvery year, and multiple times a year, Cloudflare organizes weeks to ship. In early August, they organized an Agents Week, and [they really shipped](https:\u002F\u002Fblog.cloudflare.com\u002Fagents-week-review-august-2026\u002F). They also have a birthday week that I've carefully followed every year for the past couple of years, even if sometimes, it's hard to follow. I love the idea of dedicating a week to ship because it forces to clearly communicate what is being shipped, to write about it.\n\n\"We must ship\" is the mantra of Taylor Otwell, the creator of Laravel. He said it during [his 2025 Laracon EU keynote](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NTMDzGLs8pw) with the idea that done is better than perfect. It resonates with me a lot and I'm trying to apply it in my daily life, similar to the agency mindset.\n\nFinally, \"You can just do things\" is a motto that Aaron Francis promotes a lot on Twitter and that he talked about during the [Laracon US 2025 introduction keynote](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=dgr-WAUgELw), or even during Epic Web in a talk named [Epic Bets: Practical Tips For Betting on Yourself](https:\u002F\u002Fwww.epicweb.dev\u002Ftalks\u002Fepic-bets-practical-tips-for-betting-on-yourself). I just love that mindset and the way it encourages people to just do things.\n\n---\n\nI'm alone at the helm of my ship, so this week will be smaller than Cloudflare's, but no less personal. I'm proud of what I've built so far, and I hope you'll enjoy it.\n\n---\n\nThat could be a one-off event.\n\nOr maybe I'll revive it in 16 months.\n",{"title":5630,"description":5733},{"loc":5735},"posts\u002Fwelcome-to-you-can-just-do-things-week-we-must-ship","tH153n2IwLLCB1MNIyBrUXUrjXISi4cy1WR69VmzklM",{"id":5742,"title":4033,"body":5743,"contentId":6781,"date":6782,"description":6783,"extension":18,"meta":6784,"navigation":20,"path":6785,"rawbody":6786,"robots":23,"schemaOrg":23,"seo":6787,"sitemap":6788,"stem":6789,"__hash__":6790},"posts\u002Fposts\u002Fcode-mode-two-tools-and-an-mcp-can-save-your-llm-context.md",{"type":7,"value":5744,"toc":6778},[5745,5748,5751,5754,5757,5761,5764,5772,5789,5792,6775],[35,5746,5747],{},"One of the tools I'm building is a custom MCP server, based on the Cloudflare Code Mode idea, that lets me interact with local services I'm developing through their APIs as a local client, without having to do it myself.",[35,5749,5750],{},"It works so much better than I expected. I was both impressed and excited by the possibilities this approach offered. But I encountered a wall: absolutely no one I talked to about this shared my enthusiasm or had a vision of how powerful this new approach could be.",[35,5752,5753],{},"So, I did what I do best: I wrote about it.",[35,5755,5756],{},"This article is that write-up.",[51,5758,5760],{"id":5759},"context-is-everything","Context is everything",[35,5762,5763],{},"One of the things we have to care about most when using LLMs is the context. The context is that window of text that the LLM sees and uses to generate its answer, filled by the provider, the user, and the model itself.",[35,5765,419,5766,5771],{},[39,5767,5770],{"href":5768,"rel":5769},"https:\u002F\u002Fmodel-spec.openai.com\u002F2025-02-12.html",[77],"OpenAI model spec"," defines five roles that can be used to fill the context:",[1576,5773,5774,5777,5780,5783,5786],{},[71,5775,5776],{},"system: messages added by OpenAI",[71,5778,5779],{},"developer: from the application developer (possibly also OpenAI)",[71,5781,5782],{},"user: input from end users, or a catch-all for data we want to provide to the model",[71,5784,5785],{},"assistant: sampled from the language model",[71,5787,5788],{},"tool: generated by some program, such as code execution or an API call",[35,5790,5791],{},"All these roles and their messages are stacked together to form what we call the context. Think of it as a long chat history, where the LLM can see everything that has been said and done.",[5793,5794,5795,5798],"llm-context",{},[35,5796,5797],{},"The context is limited by the model's maximum context length, which is usually measured in tokens. For example, GPT-5.5 has a maximum context length of up to 1M tokens. But there's a catch: the price increases after 272k tokens, and the model's performance starts to degrade after 50% of the maximum context length (which isn't really true for OpenAI models if you stay within the 272k-token limit). And if the context exceeds the maximum context length, older messages may be truncated or removed, causing the loss of valuable information that the model could have used to generate better answers.",[5799,5800,5801,5804],"llm-context-length",{},[35,5802,5803],{},"Of course, removing older messages is not the only way to make room. An application can compact the context by asking a model to summarize those messages, then replacing them with a single compacted message.",[5805,5806,5807,5810,5813,5816,5819,5822,5826,5829,5832,5840,5848,5851,5854,5857,5860,5868,5871],"llm-context-compaction",{},[35,5808,5809],{},"This preserves the decisions and constraints that matter while freeing up space. This summary will omit or distort details, so guiding the LLM when summarizing is essential. You can tell the LLM what will be the next actions to help the model keep the relevant information.",[35,5811,5812],{},"So you have to be careful with the context, taking care of it as if it were the apple of one's eye.",[35,5814,5815],{},"It means that you can't just fill the context with a large amount of text and expect the model to both find the relevant information and generate a good answer. You have to provide only relevant context.",[35,5817,5818],{},"That's one of the reasons RAG, Retrieval-Augmented Generation, is so important.",[35,5820,5821],{},"But RAG can't solve everything. It performs well at adding external and specific knowledge to the context on demand, but it isn't about executing anything. That's an issue.",[51,5823,5825],{"id":5824},"llms-know-code","LLMs know code",[35,5827,5828],{},"I am a software engineer. I know how to write code, and if you're reading this, you probably know how to write code too.",[35,5830,5831],{},"Since late 2025, you can't have missed that LLMs are now able to write code, and write it well. It's gotten to the point that more than 95% of the code I deliver to production or use is generated by LLMs using an agent.",[35,5833,5834,5835,5839],{},"We can take advantage of this to solve the context problem. That may sound counterintuitive. But let's have a look at the ",[39,5836,5838],{"href":5543,"rel":5837},[77],"Cloudflare Code Mode"," article.",[939,5841,5842],{},[35,5843,5844,5845,5847],{},"MCP server designers are encouraged to present greatly simplified APIs as compared to the more traditional API they might expose to developers. ",[200,5846,574],{}," LLMs asked to write code against the full, complex APIs normally exposed to developers don't seem to have too much trouble with it. Why, then, do MCP interfaces have to \"dumb it down\"?",[35,5849,5850],{},"What do they mean by that?",[35,5852,5853],{},"LLMs can call multiple tools in a row, and sometimes in parallel. But when one call depends on the result of another, the LLM has to receive that result in a later turn. Imagine the following scenario: you have a tool that can read a file from a drive, and another tool that can create a new file in the drive. Then, you want to create a new file containing all the titles from a file in the drive.",[35,5855,5856],{},"What will the LLM do?",[35,5858,5859],{},"To solve this, the LLM will make two calls over two turns:",[68,5861,5862,5865],{},[71,5863,5864],{},"Call the first tool to read the file. This action will return the content of the file, which will be added to the context.",[71,5866,5867],{},"Call the second tool to create the new file containing all the titles. The tool call, containing the titles extracted from the first tool's output, will be added to the context.",[35,5869,5870],{},"Visually, it looks like this:",[5872,5873,5874,5877,5880,5883,5886,5889,6012,6015,6205,6212],"llm-context-two-tools",{},[35,5875,5876],{},"But this is far from optimal.",[35,5878,5879],{},"The context is bloated with content from the file that the LLM does not really need to see. Extracting titles can be done without the LLM knowing the content of the file. This also means that for a confidential file, the LLM, and by extension the provider, will have access to the content of the file, which is not ideal. The non-deterministic nature of LLMs also means that title extraction may not be perfect. That could lead to real business trouble. And finally, this doesn't scale. If the document is 1M tokens long, the LLM will have to read it all, and the context will be bloated with a lot of unnecessary information.",[35,5881,5882],{},"Even though this example may sound simple and unrealistic, it is not. If we want our AI to perform complex tasks, we have to find a better way to do it. That's where Code Mode comes in.",[35,5884,5885],{},"With Code Mode, you ask the LLM to write code that will perform the task for you. To inform the LLM about the API it can use, you provide type interfaces in the tool description. The LLM will then generate code that will call the tools in the right order and with the right parameters to perform the task.",[35,5887,5888],{},"In our example, the tool description will look like this:",[192,5890,5893],{"className":5891,"code":5892,"language":18,"meta":13,"style":13},"language-md shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","Execute JavaScript code against the Drive API.\n\nAvailable in your 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\nYour code must be an async arrow function.\n\nExample:\n\n\u002F\u002F Create a table of contents from all files in the drive\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,5894,5895,5900,5904,5909,5914,5919,5933,5947,5951,5955,5960,5964,5969,5973,5978,5982,5987,5992,5997,6002,6007],{"__ignoreMap":13},[200,5896,5897],{"class":202,"line":203},[200,5898,5899],{"class":210},"Execute JavaScript code against the Drive API.\n",[200,5901,5902],{"class":202,"line":14},[200,5903,529],{"emptyLinePlaceholder":20},[200,5905,5906],{"class":202,"line":255},[200,5907,5908],{"class":210},"Available in your code:\n",[200,5910,5911],{"class":202,"line":272},[200,5912,5913],{"class":210},"interface Drive {\n",[200,5915,5916],{"class":202,"line":283},[200,5917,5918],{"class":210},"  listFiles: () => Promise\u003C{ title: string, filePath: string }[]>;\n",[200,5920,5921,5924,5926,5928,5930],{"class":202,"line":300},[200,5922,5923],{"class":210},"  readFile: (filePath: string) => Promise",[200,5925,600],{"class":214},[200,5927,328],{"class":235},[200,5929,675],{"class":214},[200,5931,5932],{"class":210},";\n",[200,5934,5935,5938,5940,5943,5945],{"class":202,"line":310},[200,5936,5937],{"class":210},"  createFile: (filePath: string, content: string) => Promise",[200,5939,600],{"class":214},[200,5941,5942],{"class":235},"void",[200,5944,675],{"class":214},[200,5946,5932],{"class":210},[200,5948,5949],{"class":202,"line":336},[200,5950,1130],{"class":210},[200,5952,5953],{"class":202,"line":342},[200,5954,529],{"emptyLinePlaceholder":20},[200,5956,5957],{"class":202,"line":365},[200,5958,5959],{"class":210},"declare const drive: Drive;\n",[200,5961,5962],{"class":202,"line":371},[200,5963,529],{"emptyLinePlaceholder":20},[200,5965,5966],{"class":202,"line":398},[200,5967,5968],{"class":210},"Your code must be an async arrow function.\n",[200,5970,5971],{"class":202,"line":405},[200,5972,529],{"emptyLinePlaceholder":20},[200,5974,5975],{"class":202,"line":410},[200,5976,5977],{"class":210},"Example:\n",[200,5979,5980],{"class":202,"line":861},[200,5981,529],{"emptyLinePlaceholder":20},[200,5983,5984],{"class":202,"line":890},[200,5985,5986],{"class":210},"\u002F\u002F Create a table of contents from all files in the drive\n",[200,5988,5989],{"class":202,"line":900},[200,5990,5991],{"class":210},"async () => {\n",[200,5993,5994],{"class":202,"line":905},[200,5995,5996],{"class":210},"  const files = await drive.listFiles();\n",[200,5998,5999],{"class":202,"line":931},[200,6000,6001],{"class":210},"  const titles = files.map(file => file.title);\n",[200,6003,6004],{"class":202,"line":3640},[200,6005,6006],{"class":210},"  await drive.createFile('table-of-contents.txt', titles.join('\\n'));\n",[200,6008,6009],{"class":202,"line":3645},[200,6010,6011],{"class":210},"};\n",[35,6013,6014],{},"Then, the LLM will generate the following code to perform the task:",[192,6016,6018],{"className":1370,"code":6017,"language":1373,"meta":13,"style":13},"\u002F\u002F Create a new file containing all the titles of a file in the drive\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,6019,6020,6025,6035,6064,6161,6201],{"__ignoreMap":13},[200,6021,6022],{"class":202,"line":203},[200,6023,6024],{"class":401},"\u002F\u002F Create a new file containing all the titles of a file in the drive\n",[200,6026,6027,6029,6031,6033],{"class":202,"line":14},[200,6028,3711],{"class":379},[200,6030,3714],{"class":214},[200,6032,393],{"class":379},[200,6034,280],{"class":214},[200,6036,6037,6039,6042,6044,6046,6049,6051,6054,6056,6058,6060,6062],{"class":202,"line":255},[200,6038,3723],{"class":379},[200,6040,6041],{"class":210}," content",[200,6043,510],{"class":214},[200,6045,452],{"class":206},[200,6047,6048],{"class":210}," drive",[200,6050,215],{"class":214},[200,6052,6053],{"class":223},"readFile",[200,6055,227],{"class":235},[200,6057,249],{"class":214},[200,6059,2856],{"class":245},[200,6061,249],{"class":214},[200,6063,416],{"class":235},[200,6065,6066,6068,6071,6073,6075,6077,6080,6082,6084,6087,6089,6091,6093,6095,6097,6099,6101,6104,6106,6109,6111,6113,6115,6117,6120,6122,6124,6126,6128,6130,6132,6134,6137,6139,6141,6144,6146,6149,6152,6155,6157,6159],{"class":202,"line":272},[200,6067,3723],{"class":379},[200,6069,6070],{"class":210}," titles",[200,6072,510],{"class":214},[200,6074,6041],{"class":210},[200,6076,215],{"class":214},[200,6078,6079],{"class":223},"split",[200,6081,227],{"class":235},[200,6083,249],{"class":214},[200,6085,6086],{"class":210},"\\n",[200,6088,249],{"class":214},[200,6090,2089],{"class":235},[200,6092,215],{"class":214},[200,6094,3758],{"class":223},[200,6096,227],{"class":235},[200,6098,202],{"class":386},[200,6100,393],{"class":379},[200,6102,6103],{"class":210}," line",[200,6105,215],{"class":214},[200,6107,6108],{"class":223},"startsWith",[200,6110,227],{"class":235},[200,6112,249],{"class":214},[200,6114,2886],{"class":245},[200,6116,249],{"class":214},[200,6118,6119],{"class":235},"))",[200,6121,215],{"class":214},[200,6123,3526],{"class":223},[200,6125,227],{"class":235},[200,6127,202],{"class":386},[200,6129,393],{"class":379},[200,6131,6103],{"class":210},[200,6133,215],{"class":214},[200,6135,6136],{"class":223},"replace",[200,6138,227],{"class":235},[200,6140,2882],{"class":214},[200,6142,6143],{"class":206},"^",[200,6145,2886],{"class":245},[200,6147,6148],{"class":214},"+",[200,6150,6151],{"class":245},"\\s",[200,6153,6154],{"class":214},"*\u002F",[200,6156,550],{"class":214},[200,6158,3894],{"class":214},[200,6160,585],{"class":235},[200,6162,6163,6166,6168,6170,6173,6175,6177,6180,6182,6184,6186,6188,6191,6193,6195,6197,6199],{"class":202,"line":283},[200,6164,6165],{"class":206},"  await",[200,6167,6048],{"class":210},[200,6169,215],{"class":214},[200,6171,6172],{"class":223},"createFile",[200,6174,227],{"class":235},[200,6176,249],{"class":214},[200,6178,6179],{"class":245},"titles.txt",[200,6181,249],{"class":214},[200,6183,550],{"class":214},[200,6185,6070],{"class":210},[200,6187,215],{"class":214},[200,6189,6190],{"class":223},"join",[200,6192,227],{"class":235},[200,6194,249],{"class":214},[200,6196,6086],{"class":210},[200,6198,249],{"class":214},[200,6200,585],{"class":235},[200,6202,6203],{"class":202,"line":300},[200,6204,1130],{"class":214},[35,6206,6207,6208,6211],{},"The tool returns ",[156,6209,6210],{},"{ success: true }",". So, the context of the LLM is now only the tool call and the result of the tool call, which is way better than having the whole content of the file in the context. Provided the execution environment cannot leak it, the LLM does not need access to the content of the file.",[6213,6214,6215,6218,6222,6225,6234,6237,6243,6247,6250,6253,6257,6260,6263,6324,6327,6330,6333,6336,6340,6343,6346,6349,6358,6363,6366,6370,6373,6381,6388,6391,6396,6635,6638,6643,6728,6731,6744,6747,6750,6754,6757,6767,6769,6772],"llm-context-code-mode",{},[35,6216,6217],{},"The LLM can still generate the wrong code. But the data processing is now explicit, repeatable, and testable. Once the code is validated, title extraction is no longer left to the LLM. Huge.",[51,6219,6221],{"id":6220},"two-ways-to-split-a-text","Two ways to split a text",[35,6223,6224],{},"Imagine the following situation.",[35,6226,6227,6228,6233],{},"You're scraping a webpage to extract the content of an article to feed a news aggregator. To do so, you're using ",[39,6229,6232],{"href":6230,"rel":6231},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fbrowser-run\u002F",[77],"Cloudflare Browser Run",", which can fetch a webpage and return its content as Markdown.",[35,6235,6236],{},"But here's the catch. The complete webpage is returned, which includes the article content, but also the header, the footer, the sidebar, and all the other stuff that isn't relevant to the context. You only want to extract the article content.",[35,6238,6239,6240],{},"From now on, you have two ways to do it. ",[1217,6241,6242],{},"Three ways, actually, if we consider that we can ignore the problem and just feed the whole content to the LLM.",[1913,6244,6246],{"id":6245},"rewriting-the-content","Rewriting the content",[35,6248,6249],{},"The first and simplest way would be to ask the LLM to rewrite the content and keep only the relevant part. The LLM will then output the article content. Simple. And highly inefficient.",[35,6251,6252],{},"You may have seen that output tokens are way more expensive than input tokens. For GPT-5.5, input tokens cost $5 per 1M tokens, while output tokens cost $30 per 1M tokens. So, if you have a 100k-token webpage and the article content is only 10k tokens, you will pay $0.50 for the input tokens and $0.30 for the output tokens. That's a total of $0.80 to extract 10k tokens of content, with no certainty that the LLM will perfectly rewrite the content.",[1913,6254,6256],{"id":6255},"splitting-the-content","Splitting the content",[35,6258,6259],{},"The second way is a bit more complex, but way more efficient. The idea is to give the whole webpage to the LLM and ask it to generate a function that will extract the article content from it. That way, the LLM will only output the function, which is a few tokens, and then you can execute the function to extract the article content.",[35,6261,6262],{},"To give you an idea, the function could look like this:",[192,6264,6266],{"className":1370,"code":6265,"language":1373,"meta":13,"style":13},"() => content.split('#')[1].split('next steps.')[0].trim() \u002F\u002F The article content is between the first H1 and the \"next steps.\" text. That's the power of an LLM: it adapts to the content of the webpage and generates a function that extracts the article content without having to hardcode it.\n",[156,6267,6268],{"__ignoreMap":13},[200,6269,6270,6272,6274,6276,6278,6280,6282,6284,6286,6288,6291,6293,6295,6297,6299,6301,6303,6306,6308,6310,6312,6314,6316,6318,6321],{"class":202,"line":203},[200,6271,1532],{"class":214},[200,6273,393],{"class":379},[200,6275,6041],{"class":210},[200,6277,215],{"class":214},[200,6279,6079],{"class":223},[200,6281,227],{"class":210},[200,6283,249],{"class":214},[200,6285,2886],{"class":245},[200,6287,249],{"class":214},[200,6289,6290],{"class":210},")[",[200,6292,3188],{"class":2045},[200,6294,360],{"class":210},[200,6296,215],{"class":214},[200,6298,6079],{"class":223},[200,6300,227],{"class":210},[200,6302,249],{"class":214},[200,6304,6305],{"class":245},"next steps.",[200,6307,249],{"class":214},[200,6309,6290],{"class":210},[200,6311,3933],{"class":2045},[200,6313,360],{"class":210},[200,6315,215],{"class":214},[200,6317,3176],{"class":223},[200,6319,6320],{"class":210},"() ",[200,6322,6323],{"class":401},"\u002F\u002F The article content is between the first H1 and the \"next steps.\" text. That's the power of an LLM: it adapts to the content of the webpage and generates a function that extracts the article content without having to hardcode it.\n",[35,6325,6326],{},"This function is only 18 tokens long. Using GPT-5.5 for both approaches, you will pay $0.50 for the input tokens and $0.00054 for the output tokens. That's a total of $0.50054 to extract 10k tokens of content, with a small function that you can inspect and test.",[35,6328,6329],{},"Code Mode also opens the door to using less expensive models to write the function. For example, GPT-5.4-nano is priced at $0.20 and $1.25 per 1M input and output tokens, respectively. With this model, you will pay $0.02 for the input tokens and $0.0000225 for the output tokens. That's a total of $0.0200225 to extract 10k tokens of content, with a small function that you can inspect and test.",[35,6331,6332],{},"$0.80 for a model rewrite vs $0.50054 for a small function using the same model, or $0.0200225 using a less expensive model.",[35,6334,6335],{},"The choice is yours, but mine is clear. Code Mode is the way to go.",[51,6337,6339],{"id":6338},"the-openapi-spec-issue","The OpenAPI spec issue",[35,6341,6342],{},"Imagine an OpenAPI spec containing 1.7M characters, which is roughly 425k tokens.",[35,6344,6345],{},"That's way too much to feed to the LLM, and this approach can't scale. If the OpenAPI spec exceeds the maximum context length, the LLM will not be able to see the whole spec, and even if it does, the context will be bloated with unnecessary information.",[35,6347,6348],{},"But with only the OpenAPI spec, and without any knowledge of the business logic, the LLM was able to complete quite a complex task in a couple of minutes using less than 100k tokens of context. That's insane.",[35,6350,6351,6352,6357],{},"That's a true story, and it was solved using the custom MCP server I mentioned earlier. But that's also ",[39,6353,6356],{"href":6354,"rel":6355},"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fmcp",[77],"Cloudflare's story with its MCP server",". The first line of the README makes the context problem explicit:",[939,6359,6360],{},[35,6361,6362],{},"A token-efficient MCP server for the entire Cloudflare API. 2500 endpoints in 1k tokens, powered by Code Mode.",[35,6364,6365],{},"Their OpenAPI spec is around 2M tokens. At that point, that's not huge. That's unusable.",[1913,6367,6369],{"id":6368},"code-mode-to-the-rescue","Code Mode to the rescue",[35,6371,6372],{},"For our OpenAPI spec problem, our Code Mode solution will consist of two tools:",[68,6374,6375,6378],{},[71,6376,6377],{},"Search, which allows the LLM to search the OpenAPI spec for relevant endpoints and parameters.",[71,6379,6380],{},"Execute, which allows the LLM to execute the relevant endpoints with the right parameters.",[35,6382,6383,6384,6387],{},"Each tool will take ",[156,6385,6386],{},"{ code: string }"," as input.",[35,6389,6390],{},"A nominal workflow will look like this:",[68,6392,6393],{},[71,6394,6395],{},"The LLM will search the OpenAPI spec for relevant endpoints and parameters using the Search tool. The tool will return a list of relevant endpoints and parameters, which will be added to the context. The generated code will look like this:",[192,6397,6399],{"className":1370,"code":6398,"language":1373,"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 In a real-world scenario, the LLM will replace \u003Ctag> with the relevant tag to search for in the OpenAPI spec.\n        results.push({ method: method.toUpperCase(), path, summary: op.summary })\n      }\n    }\n  }\n  return results\n}\n",[156,6400,6401,6411,6422,6469,6507,6561,6612,6616,6620,6624,6631],{"__ignoreMap":13},[200,6402,6403,6405,6407,6409],{"class":202,"line":203},[200,6404,3711],{"class":379},[200,6406,3714],{"class":214},[200,6408,393],{"class":379},[200,6410,280],{"class":214},[200,6412,6413,6415,6417,6419],{"class":202,"line":14},[200,6414,3723],{"class":379},[200,6416,3497],{"class":210},[200,6418,510],{"class":214},[200,6420,6421],{"class":235}," []\n",[200,6423,6424,6427,6429,6431,6433,6436,6438,6441,6443,6446,6449,6451,6454,6456,6459,6461,6464,6467],{"class":202,"line":255},[200,6425,6426],{"class":206},"  for",[200,6428,1095],{"class":235},[200,6430,443],{"class":379},[200,6432,350],{"class":214},[200,6434,6435],{"class":210},"path",[200,6437,550],{"class":214},[200,6439,6440],{"class":210}," methods",[200,6442,360],{"class":214},[200,6444,6445],{"class":214}," of",[200,6447,6448],{"class":210}," Object",[200,6450,215],{"class":214},[200,6452,6453],{"class":223},"entries",[200,6455,227],{"class":235},[200,6457,6458],{"class":210},"spec",[200,6460,215],{"class":214},[200,6462,6463],{"class":210},"paths",[200,6465,6466],{"class":235},")) ",[200,6468,230],{"class":214},[200,6470,6471,6474,6476,6478,6480,6483,6485,6488,6490,6492,6494,6496,6498,6500,6503,6505],{"class":202,"line":272},[200,6472,6473],{"class":206},"    for",[200,6475,1095],{"class":235},[200,6477,443],{"class":379},[200,6479,350],{"class":214},[200,6481,6482],{"class":210},"method",[200,6484,550],{"class":214},[200,6486,6487],{"class":210}," op",[200,6489,360],{"class":214},[200,6491,6445],{"class":214},[200,6493,6448],{"class":210},[200,6495,215],{"class":214},[200,6497,6453],{"class":223},[200,6499,227],{"class":235},[200,6501,6502],{"class":210},"methods",[200,6504,6466],{"class":235},[200,6506,230],{"class":214},[200,6508,6509,6512,6514,6517,6519,6522,6525,6528,6530,6533,6535,6538,6540,6542,6544,6547,6549,6552,6554,6556,6558],{"class":202,"line":283},[200,6510,6511],{"class":206},"      if",[200,6513,1095],{"class":235},[200,6515,6516],{"class":210},"op",[200,6518,215],{"class":214},[200,6520,6521],{"class":210},"tags",[200,6523,6524],{"class":214},"?.",[200,6526,6527],{"class":223},"some",[200,6529,227],{"class":235},[200,6531,6532],{"class":386},"tag",[200,6534,393],{"class":379},[200,6536,6537],{"class":210}," tag",[200,6539,215],{"class":214},[200,6541,3832],{"class":223},[200,6543,6320],{"class":235},[200,6545,6546],{"class":214},"===",[200,6548,242],{"class":214},[200,6550,6551],{"class":245},"\u003Ctag>",[200,6553,249],{"class":214},[200,6555,6466],{"class":235},[200,6557,2752],{"class":214},[200,6559,6560],{"class":401}," \u002F\u002F In a real-world scenario, the LLM will replace \u003Ctag> with the relevant tag to search for in the OpenAPI spec.\n",[200,6562,6563,6566,6568,6571,6573,6575,6578,6580,6582,6584,6587,6589,6591,6594,6596,6599,6601,6603,6605,6608,6610],{"class":202,"line":300},[200,6564,6565],{"class":210},"        results",[200,6567,215],{"class":214},[200,6569,6570],{"class":223},"push",[200,6572,227],{"class":235},[200,6574,2752],{"class":214},[200,6576,6577],{"class":235}," method",[200,6579,239],{"class":214},[200,6581,6577],{"class":210},[200,6583,215],{"class":214},[200,6585,6586],{"class":223},"toUpperCase",[200,6588,1532],{"class":235},[200,6590,550],{"class":214},[200,6592,6593],{"class":210}," path",[200,6595,550],{"class":214},[200,6597,6598],{"class":235}," summary",[200,6600,239],{"class":214},[200,6602,6487],{"class":210},[200,6604,215],{"class":214},[200,6606,6607],{"class":210},"summary",[200,6609,981],{"class":214},[200,6611,416],{"class":235},[200,6613,6614],{"class":202,"line":310},[200,6615,4478],{"class":214},[200,6617,6618],{"class":202,"line":336},[200,6619,4962],{"class":214},[200,6621,6622],{"class":202,"line":342},[200,6623,4490],{"class":214},[200,6625,6626,6628],{"class":202,"line":365},[200,6627,3743],{"class":206},[200,6629,6630],{"class":210}," results\n",[200,6632,6633],{"class":202,"line":371},[200,6634,1130],{"class":214},[35,6636,6637],{},"It's just a function that will be run against the OpenAPI spec to find the relevant endpoints and parameters.",[68,6639,6640],{"start":14},[71,6641,6642],{},"The LLM will generate code to execute the relevant endpoints with the right parameters using the Execute tool. The tool will return the result of the execution, which will be added to the context. The generated code will look like this:",[192,6644,6646],{"className":1370,"code":6645,"language":1373,"meta":13,"style":13},"async () => api.request({\n  method: 'GET',\n  path: '\u002Fapi\u002Fv1\u002Fresources', \u002F\u002F In a real-world scenario, the LLM will replace this with the relevant endpoint to execute.\n  query: { max: 10 },\n})\n",[156,6647,6648,6668,6684,6703,6722],{"__ignoreMap":13},[200,6649,6650,6652,6654,6656,6659,6661,6664,6666],{"class":202,"line":203},[200,6651,3711],{"class":379},[200,6653,3714],{"class":214},[200,6655,393],{"class":379},[200,6657,6658],{"class":210}," api",[200,6660,215],{"class":214},[200,6662,6663],{"class":223},"request",[200,6665,227],{"class":210},[200,6667,230],{"class":214},[200,6669,6670,6673,6675,6677,6680,6682],{"class":202,"line":14},[200,6671,6672],{"class":235},"  method",[200,6674,239],{"class":214},[200,6676,242],{"class":214},[200,6678,6679],{"class":245},"GET",[200,6681,249],{"class":214},[200,6683,252],{"class":214},[200,6685,6686,6689,6691,6693,6696,6698,6700],{"class":202,"line":255},[200,6687,6688],{"class":235},"  path",[200,6690,239],{"class":214},[200,6692,242],{"class":214},[200,6694,6695],{"class":245},"\u002Fapi\u002Fv1\u002Fresources",[200,6697,249],{"class":214},[200,6699,550],{"class":214},[200,6701,6702],{"class":401}," \u002F\u002F In a real-world scenario, the LLM will replace this with the relevant endpoint to execute.\n",[200,6704,6705,6708,6710,6712,6715,6717,6720],{"class":202,"line":272},[200,6706,6707],{"class":235},"  query",[200,6709,239],{"class":214},[200,6711,318],{"class":214},[200,6713,6714],{"class":235}," max",[200,6716,239],{"class":214},[200,6718,6719],{"class":2045}," 10",[200,6721,333],{"class":214},[200,6723,6724,6726],{"class":202,"line":283},[200,6725,413],{"class":214},[200,6727,416],{"class":210},[35,6729,6730],{},"Once again, it's just a function that will be run within the MCP server to execute the relevant endpoint with the right parameters. That's it.",[35,6732,6733,6734,6737,6738,6743],{},"Under the hood, the MCP server will execute the LLM-generated code and return the result to the LLM. Of course, you have to run the code within a secure sandbox to avoid security issues. Do not do what I do here: the ",[156,6735,6736],{},"Function"," constructor is not a secure sandbox, and executing LLM-generated code with it can lead to major security issues, including arbitrary code execution and data exfiltration. I currently use it only to simplify development, but when I start sharing the MCP server, I'll use ",[39,6739,6742],{"href":6740,"rel":6741},"https:\u002F\u002Fnpmjs.org\u002Fpackage\u002Fisolate-vm",[77],"isolate-vm"," to run the code in a secure sandbox. A prompt injection could ask the LLM to exfiltrate data from the MCP server, so you have to be careful about that. I know that Cloudflare dynamic workers allow you to block outbound network requests.",[35,6745,6746],{},"With these two tools and all the information provided by the OpenAPI spec, such as the path, the description, the parameter name, and any other relevant human-readable information, the LLM can infer how to interact with your API, and sometimes infer parts of your business logic, without the pain of writing a custom tool for each endpoint. But an OpenAPI spec does not always describe business logic completely, and its descriptions can be incomplete or ambiguous. Combine this with an access to the business documentation, and your LLM is the perfect agent to interact with your API.",[35,6748,6749],{},"That's the power of Code Mode.",[51,6751,6753],{"id":6752},"cloudflares-mcp-showcase","Cloudflare's MCP showcase",[35,6755,6756],{},"Until now, everything was theoretical. So, let's have a look at how Cloudflare's MCP server behaves in practice:",[130,6758,132,6759,132,6764],{},[134,6760,136,6761,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,6762],{"src":6763,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fcode-mode-two-tools-and-an-mcp-can-save-your-llm-context\u002Fmcp-showcase.mp4",[143,6765,6766],{},"Cloudflare's MCP server in action.",[1159,6768],{},[35,6770,6771],{},"Now, you may be wondering: \"That was a great read, but how can I build my own MCP server to solve my context problem?\"",[35,6773,6774],{},"Be patient. I'll have something for you in the coming weeks!",[1171,6776,6777],{},"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":6779},[6780],{"id":5759,"depth":14,"text":5760},"32b4bde8-8a27-42d0-bfe2-dd6ceb741716","2026-07-08","Code Mode lets AI agents run complex MCP workflows without flooding their context. Learn how two tools can search and use huge OpenAPI specifications.",{},"\u002Fposts\u002Fcode-mode-two-tools-and-an-mcp-can-save-your-llm-context","---\ncontentId: 32b4bde8-8a27-42d0-bfe2-dd6ceb741716\ntitle: Code Mode, Two Tools, and an MCP Can Save Your LLM Context\ndescription: Code Mode lets AI agents run complex MCP workflows without flooding their context. Learn how two tools can search and use huge OpenAPI specifications.\ndate: 2026-07-08\n---\n\nOne of the tools I'm building is a custom MCP server, based on the Cloudflare Code Mode idea, that lets me interact with local services I'm developing through their APIs as a local client, without having to do it myself.\n\nIt works so much better than I expected. I was both impressed and excited by the possibilities this approach offered. But I encountered a wall: absolutely no one I talked to about this shared my enthusiasm or had a vision of how powerful this new approach could be.\n\nSo, I did what I do best: I wrote about it.\n\nThis article is that write-up.\n\n## Context is everything\n\nOne of the things we have to care about most when using LLMs is the context. The context is that window of text that the LLM sees and uses to generate its answer, filled by the provider, the user, and the model itself.\n\nThe [OpenAI model spec](https:\u002F\u002Fmodel-spec.openai.com\u002F2025-02-12.html) defines five roles that can be used to fill the context:\n\n- system: messages added by OpenAI\n- developer: from the application developer (possibly also OpenAI)\n- user: input from end users, or a catch-all for data we want to provide to the model\n- assistant: sampled from the language model\n- tool: generated by some program, such as code execution or an API call\n\nAll these roles and their messages are stacked together to form what we call the context. Think of it as a long chat history, where the LLM can see everything that has been said and done.\n\n\u003CLlmContext \u002F>\n\nThe context is limited by the model's maximum context length, which is usually measured in tokens. For example, GPT-5.5 has a maximum context length of up to 1M tokens. But there's a catch: the price increases after 272k tokens, and the model's performance starts to degrade after 50% of the maximum context length (which isn't really true for OpenAI models if you stay within the 272k-token limit). And if the context exceeds the maximum context length, older messages may be truncated or removed, causing the loss of valuable information that the model could have used to generate better answers.\n\n\u003CLlmContextLength \u002F>\n\nOf course, removing older messages is not the only way to make room. An application can compact the context by asking a model to summarize those messages, then replacing them with a single compacted message.\n\n\u003CLlmContextCompaction \u002F>\n\nThis preserves the decisions and constraints that matter while freeing up space. This summary will omit or distort details, so guiding the LLM when summarizing is essential. You can tell the LLM what will be the next actions to help the model keep the relevant information.\n\nSo you have to be careful with the context, taking care of it as if it were the apple of one's eye.\n\nIt means that you can't just fill the context with a large amount of text and expect the model to both find the relevant information and generate a good answer. You have to provide only relevant context.\n\nThat's one of the reasons RAG, Retrieval-Augmented Generation, is so important.\n\nBut RAG can't solve everything. It performs well at adding external and specific knowledge to the context on demand, but it isn't about executing anything. That's an issue.\n\n## LLMs know code\n\nI am a software engineer. I know how to write code, and if you're reading this, you probably know how to write code too.\n\nSince late 2025, you can't have missed that LLMs are now able to write code, and write it well. It's gotten to the point that more than 95% of the code I deliver to production or use is generated by LLMs using an agent.\n\nWe can take advantage of this to solve the context problem. That may sound counterintuitive. But let's have a look at the [Cloudflare Code Mode](https:\u002F\u002Fblog.cloudflare.com\u002Fcode-mode\u002F) article.\n\n> MCP server designers are encouraged to present greatly simplified APIs as compared to the more traditional API they might expose to developers. [...] LLMs asked to write code against the full, complex APIs normally exposed to developers don't seem to have too much trouble with it. Why, then, do MCP interfaces have to \"dumb it down\"?\n\nWhat do they mean by that?\n\nLLMs can call multiple tools in a row, and sometimes in parallel. But when one call depends on the result of another, the LLM has to receive that result in a later turn. Imagine the following scenario: you have a tool that can read a file from a drive, and another tool that can create a new file in the drive. Then, you want to create a new file containing all the titles from a file in the drive.\n\nWhat will the LLM do?\n\nTo solve this, the LLM will make two calls over two turns:\n\n1. Call the first tool to read the file. This action will return the content of the file, which will be added to the context.\n2. Call the second tool to create the new file containing all the titles. The tool call, containing the titles extracted from the first tool's output, will be added to the context.\n\nVisually, it looks like this:\n\n\u003CLlmContextTwoTools \u002F>\n\nBut this is far from optimal.\n\nThe context is bloated with content from the file that the LLM does not really need to see. Extracting titles can be done without the LLM knowing the content of the file. This also means that for a confidential file, the LLM, and by extension the provider, will have access to the content of the file, which is not ideal. The non-deterministic nature of LLMs also means that title extraction may not be perfect. That could lead to real business trouble. And finally, this doesn't scale. If the document is 1M tokens long, the LLM will have to read it all, and the context will be bloated with a lot of unnecessary information.\n\nEven though this example may sound simple and unrealistic, it is not. If we want our AI to perform complex tasks, we have to find a better way to do it. That's where Code Mode comes in.\n\nWith Code Mode, you ask the LLM to write code that will perform the task for you. To inform the LLM about the API it can use, you provide type interfaces in the tool description. The LLM will then generate code that will call the tools in the right order and with the right parameters to perform the task.\n\nIn our example, the tool description will look like this:\n\n```md\nExecute JavaScript code against the Drive API.\n\nAvailable in your 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\nYour code must be an async arrow function.\n\nExample:\n\n\u002F\u002F Create a table of contents from all files in the drive\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\nThen, the LLM will generate the following code to perform the task:\n\n```ts\n\u002F\u002F Create a new file containing all the titles of a file in the drive\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\nThe tool returns `{ success: true }`. So, the context of the LLM is now only the tool call and the result of the tool call, which is way better than having the whole content of the file in the context. Provided the execution environment cannot leak it, the LLM does not need access to the content of the file.\n\n\u003CLlmContextCodeMode \u002F>\n\nThe LLM can still generate the wrong code. But the data processing is now explicit, repeatable, and testable. Once the code is validated, title extraction is no longer left to the LLM. Huge.\n\n## Two ways to split a text\n\nImagine the following situation.\n\nYou're scraping a webpage to extract the content of an article to feed a news aggregator. To do so, you're using [Cloudflare Browser Run](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fbrowser-run\u002F), which can fetch a webpage and return its content as Markdown.\n\nBut here's the catch. The complete webpage is returned, which includes the article content, but also the header, the footer, the sidebar, and all the other stuff that isn't relevant to the context. You only want to extract the article content.\n\nFrom now on, you have two ways to do it. _Three ways, actually, if we consider that we can ignore the problem and just feed the whole content to the LLM._\n\n### Rewriting the content\n\nThe first and simplest way would be to ask the LLM to rewrite the content and keep only the relevant part. The LLM will then output the article content. Simple. And highly inefficient.\n\nYou may have seen that output tokens are way more expensive than input tokens. For GPT-5.5, input tokens cost $5 per 1M tokens, while output tokens cost $30 per 1M tokens. So, if you have a 100k-token webpage and the article content is only 10k tokens, you will pay $0.50 for the input tokens and $0.30 for the output tokens. That's a total of $0.80 to extract 10k tokens of content, with no certainty that the LLM will perfectly rewrite the content.\n\n### Splitting the content\n\nThe second way is a bit more complex, but way more efficient. The idea is to give the whole webpage to the LLM and ask it to generate a function that will extract the article content from it. That way, the LLM will only output the function, which is a few tokens, and then you can execute the function to extract the article content.\n\nTo give you an idea, the function could look like this:\n\n```ts\n() => content.split('#')[1].split('next steps.')[0].trim() \u002F\u002F The article content is between the first H1 and the \"next steps.\" text. That's the power of an LLM: it adapts to the content of the webpage and generates a function that extracts the article content without having to hardcode it.\n```\n\nThis function is only 18 tokens long. Using GPT-5.5 for both approaches, you will pay $0.50 for the input tokens and $0.00054 for the output tokens. That's a total of $0.50054 to extract 10k tokens of content, with a small function that you can inspect and test.\n\nCode Mode also opens the door to using less expensive models to write the function. For example, GPT-5.4-nano is priced at $0.20 and $1.25 per 1M input and output tokens, respectively. With this model, you will pay $0.02 for the input tokens and $0.0000225 for the output tokens. That's a total of $0.0200225 to extract 10k tokens of content, with a small function that you can inspect and test.\n\n$0.80 for a model rewrite vs $0.50054 for a small function using the same model, or $0.0200225 using a less expensive model.\n\nThe choice is yours, but mine is clear. Code Mode is the way to go.\n\n## The OpenAPI spec issue\n\nImagine an OpenAPI spec containing 1.7M characters, which is roughly 425k tokens.\n\nThat's way too much to feed to the LLM, and this approach can't scale. If the OpenAPI spec exceeds the maximum context length, the LLM will not be able to see the whole spec, and even if it does, the context will be bloated with unnecessary information.\n\nBut with only the OpenAPI spec, and without any knowledge of the business logic, the LLM was able to complete quite a complex task in a couple of minutes using less than 100k tokens of context. That's insane.\n\nThat's a true story, and it was solved using the custom MCP server I mentioned earlier. But that's also [Cloudflare's story with its MCP server](https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fmcp). The first line of the README makes the context problem explicit:\n\n> A token-efficient MCP server for the entire Cloudflare API. 2500 endpoints in 1k tokens, powered by Code Mode.\n\nTheir OpenAPI spec is around 2M tokens. At that point, that's not huge. That's unusable.\n\n### Code Mode to the rescue\n\nFor our OpenAPI spec problem, our Code Mode solution will consist of two tools:\n\n1. Search, which allows the LLM to search the OpenAPI spec for relevant endpoints and parameters.\n2. Execute, which allows the LLM to execute the relevant endpoints with the right parameters.\n\nEach tool will take `{ code: string }` as input.\n\nA nominal workflow will look like this:\n\n1. The LLM will search the OpenAPI spec for relevant endpoints and parameters using the Search tool. The tool will return a list of relevant endpoints and parameters, which will be added to the context. The generated code will look like this:\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 In a real-world scenario, the LLM will replace \u003Ctag> with the relevant tag to search for in the OpenAPI spec.\n          results.push({ method: method.toUpperCase(), path, summary: op.summary })\n        }\n      }\n    }\n    return results\n  }\n  ```\n\n  It's just a function that will be run against the OpenAPI spec to find the relevant endpoints and parameters.\n\n2. The LLM will generate code to execute the relevant endpoints with the right parameters using the Execute tool. The tool will return the result of the execution, which will be added to the context. The generated code will look like this:\n\n  ```ts\n  async () => api.request({\n    method: 'GET',\n    path: '\u002Fapi\u002Fv1\u002Fresources', \u002F\u002F In a real-world scenario, the LLM will replace this with the relevant endpoint to execute.\n    query: { max: 10 },\n  })\n  ```\n\n  Once again, it's just a function that will be run within the MCP server to execute the relevant endpoint with the right parameters. That's it.\n\nUnder the hood, the MCP server will execute the LLM-generated code and return the result to the LLM. Of course, you have to run the code within a secure sandbox to avoid security issues. Do not do what I do here: the `Function` constructor is not a secure sandbox, and executing LLM-generated code with it can lead to major security issues, including arbitrary code execution and data exfiltration. I currently use it only to simplify development, but when I start sharing the MCP server, I'll use [isolate-vm](https:\u002F\u002Fnpmjs.org\u002Fpackage\u002Fisolate-vm) to run the code in a secure sandbox. A prompt injection could ask the LLM to exfiltrate data from the MCP server, so you have to be careful about that. I know that Cloudflare dynamic workers allow you to block outbound network requests.\n\nWith these two tools and all the information provided by the OpenAPI spec, such as the path, the description, the parameter name, and any other relevant human-readable information, the LLM can infer how to interact with your API, and sometimes infer parts of your business logic, without the pain of writing a custom tool for each endpoint. But an OpenAPI spec does not always describe business logic completely, and its descriptions can be incomplete or ambiguous. Combine this with an access to the business documentation, and your LLM is the perfect agent to interact with your API.\n\nThat's the power of Code Mode.\n\n## Cloudflare's MCP showcase\n\nUntil now, everything was theoretical. So, let's have a look at how Cloudflare's MCP server behaves in practice:\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>Cloudflare's MCP server in action.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n---\n\nNow, you may be wondering: \"That was a great read, but how can I build my own MCP server to solve my context problem?\"\n\nBe patient. I'll have something for you in the coming weeks!\n",{"title":4033,"description":6783},{"loc":6785},"posts\u002Fcode-mode-two-tools-and-an-mcp-can-save-your-llm-context","7yTh6nbCSP87IEshRzwoTluCtgm_bXn69AgyHU2On84",{"id":6792,"title":6793,"body":6794,"contentId":7451,"date":7452,"description":7453,"extension":18,"meta":7454,"navigation":20,"path":7455,"rawbody":7456,"robots":23,"schemaOrg":23,"seo":7457,"sitemap":7458,"stem":7470,"__hash__":7471},"posts\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks.md","Devoxx France 2026: Prioritizing People Over Talks",{"type":7,"value":6795,"toc":7444},[6796,6799,6802,6808,6811,6814,6819,6823,6826,6834,6837,6840,7124,7129,7135,7138],[35,6797,6798],{},"I know, I'm a little late to the party on this one. But since I now do this for most conferences I attend, I wanted to share some of my notes and thoughts about Devoxx France 2026.",[35,6800,6801],{},"Devoxx France is a very different conference because of its size. With 4,500 attendees, 3 days, 14 tracks, and more than 250 talks, it goes far beyond any other technical conference in France. Some speakers only speak at Devoxx, making it the only opportunity to see them.",[35,6803,6804],{},[1690,6805],{"alt":6806,"src":6807},"The main stage of Devoxx France, during the opening","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fmain-stage.webp",[35,6809,6810],{},"At the same time, and that's my personal opinion, the size of the conference makes it very difficult to fully appreciate. Once a talk is over, you're running, literally, to the next one, hoping to find a seat. You're always in a rush. You always have FOMO because of the number of tracks, and you can't just take the time to slow down, talk with people, and enjoy the conference. You can quickly fall into the trap of wanting to see as many talks as possible.",[35,6812,6813],{},"This year, I really took the time to chat with people. There were some talks I didn't attend to free up some time. For example, I wanted to go to the Dev with AI meetup on Tuesday night, but I was chatting with some friends, didn't see the time pass, and missed it. That's the law of the conference: you have to make choices.",[35,6815,6816],{},[1217,6817,6818],{},"Please, don't ask me about the food at Devoxx. Just know that I always have some biscuits in my bag.",[51,6820,6822],{"id":6821},"my-schedule","My schedule",[35,6824,6825],{},"This year, for the first time, I looked at the schedule before the conference. I wanted to get an overview of the general atmosphere of the event. These days, everything is about AI. I open X, I see AI. I talk with friends, we talk about AI. I go to work, we push AI even further. Even with my date, my family, or with people I don't know during a carpool, I talk about AI. This topic is everywhere. Devoxx is no exception.",[35,6827,6828,6829,215],{},"On top of that, I've spent the last four months writing a talk about the future of developers with ",[39,6830,6833],{"href":6831,"rel":6832},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux\u002F",[77],"Julie",[35,6835,6836],{},"AI saturates so much of our lives, and I wanted a little break from it. So I decided to focus on human-centric talks.",[35,6838,6839],{},"Here is my schedule for the conference:",[68,6841,6842,6944,7044],{},[71,6843,6844,6845],{},"Wednesday, April 15, 2026\n",[1576,6846,6847,6859,6869,6879,6889,6899,6909,6924,6934],{},[71,6848,6849,6853,6854],{},[6850,6851,6852],"strong",{},"L'IA au service du bien commun : Agir avec raison dans l'incertain"," by ",[39,6855,6858],{"href":6856,"rel":6857},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurence-devillers-4041899\u002F",[77],"Laurence Devillers",[71,6860,6861,6853,6864],{},[6850,6862,6863],{},"Les échecs à l'ère de l'intelligence artificielle",[39,6865,6868],{"href":6866,"rel":6867},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurent-fressinet-752236188\u002F",[77],"Laurent Fressinet",[71,6870,6871,6853,6874],{},[6850,6872,6873],{},"Neurodiversité & Tech : vos “profils atypiques” sont votre plus grande force",[39,6875,6878],{"href":6876,"rel":6877},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Famelierigano\u002F",[77],"Amélie Rigano",[71,6880,6881,6853,6884],{},[6850,6882,6883],{},"The evolution of Software engineering",[39,6885,6888],{"href":6886,"rel":6887},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ftheophane-gregoir\u002F",[77],"Theophane Gregoir",[71,6890,6891,6853,6894],{},[6850,6892,6893],{},"La transactionalité expliquée",[39,6895,6898],{"href":6896,"rel":6897},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbenjamin-cavy-aa67a2a6\u002F",[77],"Benjamin Cavy",[71,6900,6901,6853,6904],{},[6850,6902,6903],{},"Et si on débuguait notre rapport au temps ?",[39,6905,6908],{"href":6906,"rel":6907},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falbane-fagot-veyron\u002F",[77],"Albane Fagot-Veyron",[71,6910,6911,6853,6914,1638,6919],{},[6850,6912,6913],{},"De zéro à des milliards de traces, le tracing distribué chez Winamax",[39,6915,6918],{"href":6916,"rel":6917},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fanthonymaffert\u002F",[77],"Anthony Maffert",[39,6920,6923],{"href":6921,"rel":6922},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolas-fidel\u002F",[77],"Nicolas Fidel",[71,6925,6926,6853,6929],{},[6850,6927,6928],{},"Kafka 4, fantastique ?",[39,6930,6933],{"href":6931,"rel":6932},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdamien-lucas\u002F",[77],"Damien Lucas",[71,6935,6936,6853,6939],{},[6850,6937,6938],{},"CLI-Driven Developer Experience : L'ingrédient secret d'une équipe alignée",[39,6940,6943],{"href":6941,"rel":6942},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-briault-441539137\u002F",[77],"Julien Briault",[71,6945,6946,6947],{},"Thursday, April 16, 2026\n",[1576,6948,6949,6959,6969,6979,6989,6999,7009,7024,7034],{},[71,6950,6951,6853,6954],{},[6850,6952,6953],{},"Le développeur face à l'IA : du prototypage rapide à la fin des intermédiaires ?",[39,6955,6958],{"href":6956,"rel":6957},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolasgrenie\u002F",[77],"Nicolas Grenié",[71,6960,6961,6853,6964],{},[6850,6962,6963],{},"L'IA sur le terrain : l'humain au cœur de la valeur",[39,6965,6968],{"href":6966,"rel":6967},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarjory-canonne-7aab19153\u002F",[77],"Marjory Canonne",[71,6970,6971,6853,6974],{},[6850,6972,6973],{},"L'ampoule qui en savait trop : voyage au cœur de la sécurité des objets connecté",[39,6975,6978],{"href":6976,"rel":6977},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpaulpinault\u002F",[77],"Paul Pinault",[71,6980,6981,6853,6984],{},[6850,6982,6983],{},"Top 10 Event-Driven Architecture Pitfalls",[39,6985,6988],{"href":6986,"rel":6987},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictor-rentea-trainer\u002F",[77],"Victor Rentea",[71,6990,6991,6853,6994],{},[6850,6992,6993],{},"Transformer son API Doc en levier de conversion : best practices",[39,6995,6998],{"href":6996,"rel":6997},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fanne-flore-bernard\u002F",[77],"Anne-Flore Bernard",[71,7000,7001,6853,7004],{},[6850,7002,7003],{},"Clean Code Matters",[39,7005,7008],{"href":7006,"rel":7007},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarc-lecanu\u002F",[77],"Marc LECANU",[71,7010,7011,6853,7014,1638,7019],{},[6850,7012,7013],{},"Les gardiens du Prompt: menaces et recettes de sécurité pour une production zen.",[39,7015,7018],{"href":7016,"rel":7017},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaumay\u002F",[77],"Philippe Laumay",[39,7020,7023],{"href":7021,"rel":7022},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floic-gudet\u002F",[77],"Loïc Gudet",[71,7025,7026,6853,7029],{},[6850,7027,7028],{},"Au cœur d'une pipeline: démystifions Vite et ses plugins",[39,7030,7033],{"href":7031,"rel":7032},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F",[77],"Estéban Soubiran",[71,7035,7036,6853,7039],{},[6850,7037,7038],{},"Debugging with IntelliJ IDEA",[39,7040,7043],{"href":7041,"rel":7042},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fantonarhipov\u002F",[77],"Anton Arhipov",[71,7045,7046,7047],{},"Friday, April 17, 2026\n",[1576,7048,7049,7059,7069,7079,7089,7104,7114],{},[71,7050,7051,6853,7054],{},[6850,7052,7053],{},"L'IA au défi de la condition humaine",[39,7055,7058],{"href":7056,"rel":7057},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjganascia\u002F",[77],"Jean-Gabriel Ganascia",[71,7060,7061,6853,7064],{},[6850,7062,7063],{},"La course à l'IA : La France risque-t-elle de devenir un \"territoire servant\"?",[39,7065,7068],{"href":7066,"rel":7067},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floup-cellard-b2502297\u002F",[77],"Loup Cellard",[71,7070,7071,6853,7074],{},[6850,7072,7073],{},"Et si on ouvrait le capot de PostgreSQL ?",[39,7075,7078],{"href":7076,"rel":7077},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcoenenbenjamin\u002F",[77],"Benjamin Coenen",[71,7080,7081,6853,7084],{},[6850,7082,7083],{},"Et si écrire du SQL redevenait cool ?",[39,7085,7088],{"href":7086,"rel":7087},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fs%C3%A9bastien-ferrer-076ba052\u002F",[77],"Sébastien Ferrer",[71,7090,7091,6853,7094,1638,7099],{},[6850,7092,7093],{},"Kubernetes et la JVM",[39,7095,7098],{"href":7096,"rel":7097},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmdoudoux\u002F",[77],"Jean-Michel Doudoux",[39,7100,7103],{"href":7101,"rel":7102},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falainregnier\u002F",[77],"Alain Regnier",[71,7105,7106,6853,7109],{},[6850,7107,7108],{},"Éloge de la simplicité",[39,7110,7113],{"href":7111,"rel":7112},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffleguedois\u002F",[77],"Frédéric Leguédois",[71,7115,7116,6853,7119],{},[6850,7117,7118],{},"Performance de backend Spring : les techniques que tout dev devrait connaître",[39,7120,7123],{"href":7121,"rel":7122},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffbeaufume\u002F",[77],"Florian Beaufumé",[35,7125,7126],{},[1217,7127,7128],{},"I must confess. I could have slept a little during one of the talks. Sorry!",[35,7130,7131,7132],{},"Yes! It's a lot. That could explain why I finished the conference with a headache, feeling tired, and needing to rest alone. ",[1217,7133,7134],{},"But it was worth it.",[35,7136,7137],{},"All replays will be available on the Devoxx France YouTube channel, and I highly encourage you to watch them!",[7139,7140,7145,7149,7152,7203,7207,7210,7216,7219,7225,7231,7244,7252,7256,7259,7265,7268,7274,7277,7324,7327,7330,7336,7342,7348,7351,7357,7379,7385,7401,7425,7428,7434,7436,7439,7441],"callout",{"w-full":13,"icon":7141,"text":7142,"to":7143,"target":7144},"i-simple-icons-youtube","Devoxx France","https:\u002F\u002Fwww.youtube.com\u002F@DevoxxFRvideos","_blank",[51,7146,7148],{"id":7147},"talks-i-loved","Talks I loved",[35,7150,7151],{},"As usual, some talks really stood out to me, and I wanted to highlight them here.",[68,7153,7154,7171,7187],{},[71,7155,7156,6853,7158,7161,7164,7165,7167],{},[6850,7157,7108],{},[39,7159,7113],{"href":7111,"rel":7160},[77],[7162,7163],"br",{},"That might be the best talk I've seen at Devoxx, all editions included. It's a 45-minute talk, but honestly, it doesn't feel like it. Frédéric doesn't use any slides, and he builds the talk like a theater play, with a lot of humor. Despite the feeling of lightness, the talk goes deep into the current state of agility, the overuse of planning, and the importance of rebuilding our relationship with simplicity. Honestly, you should just watch it.",[7162,7166],{},[1690,7168],{"alt":7169,"src":7170},"Frédéric on stage, during his talk about simplicity","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ffrederic-simplicity.webp",[71,7172,7173,6853,7175,7178,7180,7181,7183],{},[6850,7174,6903],{},[39,7176,6908],{"href":6906,"rel":7177},[77],[7162,7179],{},"It's a deeply human-centered talk, and I loved it. Albane talks about our relationship with time, how we perceive it, and how it affects our daily lives and work. \"I don't have time\" is a phrase we often hear, or even say, but have we ever taken the time to dive into it? It's really well written and pleasant to listen to. It's already on my watch list.",[7162,7182],{},[1690,7184],{"alt":7185,"src":7186},"Albane on stage, during her talk about debugging our relationship with time","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Falbane-time.webp",[71,7188,7189,6853,7191,7194,7196,7197,7199],{},[6850,7190,6973],{},[39,7192,6978],{"href":6976,"rel":7193},[77],[7162,7195],{},"I came to this talk without any expectations, and I had a wonderful time. At first, Paul shows that turning on a light can be just 50 lines of code, before explaining all the prerequisites imposed by the EU in terms of security, privacy, and maintenance, which makes the task much more complex, and therefore much more interesting. The real complexity isn't in the bulb itself, but in the surrounding ecosystem needed to make it compliant. This concrete talk can easily serve as an analogy for any project.",[7162,7198],{},[1690,7200],{"alt":7201,"src":7202},"Paul on stage, during his talk about the security of connected objects","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fpaul-connected-objects.webp",[51,7204,7206],{"id":7205},"my-talk","My talk",[35,7208,7209],{},"This year, I gave a talk about Vite and its pipeline at a conference called \"Au cœur d'une pipeline : démystifions Vite et ses plugins\".",[35,7211,7212],{},[1690,7213],{"alt":7214,"src":7215},"Estéban, in front of the announcement panel, before the talk starts","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fvite-talk-panel.webp",[35,7217,7218],{},"It's a 45-minute talk where I explain how Vite works, what its pipeline looks like, and how to write a plugin for it. Through various concrete examples, I try to demystify the inner workings of Vite and show how powerful, yet approachable, it can be when you start writing plugins for it.",[35,7220,7221],{},[1690,7222],{"alt":7223,"src":7224},"Estéban behind the desk, before the talk starts","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fvite-talk-desk.webp",[35,7226,7227],{},[1690,7228],{"alt":7229,"src":7230},"My selfie with the audience, during my talk about Vite","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fvite-talk-selfie.webp",[35,7232,7233,7234,7238,7239,215],{},"I had already given the same talk at ",[39,7235,7237],{"href":7236},".\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026","MiXiT 2026",", and I didn't find the time to improve it for Devoxx. Less than a week is really short to improve a talk. However, a lot of improvements are coming in June, and I'll share more about it on ",[39,7240,7243],{"href":7241,"rel":7242},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25",[77],"LinkedIn",[35,7245,7246,7247,215],{},"You can find the slides of the talk on ",[39,7248,7251],{"href":7249,"rel":7250},"https:\u002F\u002Ftalks.soubiran.dev",[77],"talks.soubiran.dev",[51,7253,7255],{"id":7254},"people-first","People first",[35,7257,7258],{},"For the first time at a conference, I prioritized people over talks.",[35,7260,7261,7262],{},"I'm convinced that the most valuable part of a conference isn't the conference itself. You can always find talks later on YouTube, so the content isn't exclusive to the event. ",[1217,7263,7264],{},"But I understand the value of attending a talk live: it's a different experience, and it forces you to be more present and focused.",[35,7266,7267],{},"But people, from the speakers who make the conference possible to curious attendees eager to learn, and even to the ones who are just there to discover new things, are the real value of a conference. With the recent rise of AI, where we all tend to interact mostly with machines, it makes it even more important to prioritize human interactions.",[35,7269,7270,7271],{},"So, from the speakers' dinner to the Meet and Greet party, and from sponsors to attendees, I've spent a lot of time chatting. I've discovered their stories, how they got into Devoxx, why they speak at conferences or appreciate the company they work for, the latest news from companies, or simply talking about life. It's also a great opportunity to put a face to the name of people you only know from LinkedIn. ",[1217,7272,7273],{},"And to experience the awkwardness of parasocial interactions.",[35,7275,7276],{},"Through this section, I wanted to thank all the people I had the chance to talk with during the conference. It was a true pleasure, and I hope to see you again soon.",[1576,7278,7279,7282,7285,7288,7291,7294,7297,7300,7303,7306,7309,7312,7315,7318],{},[71,7280,7281],{},"Lorenzo,",[71,7283,7284],{},"Octavie and Alexandre,",[71,7286,7287],{},"Julien,",[71,7289,7290],{},"Fanny,",[71,7292,7293],{},"Denis,",[71,7295,7296],{},"Philippe and Loïc,",[71,7298,7299],{},"Benjamin,",[71,7301,7302],{},"Stephen,",[71,7304,7305],{},"Joan,",[71,7307,7308],{},"Damien,",[71,7310,7311],{},"Albane,",[71,7313,7314],{},"Christophe,",[71,7316,7317],{},"the Clever Cloud team I had the chance to talk with,",[71,7319,7320,7321],{},"and many more that I can't remember right now. ",[1217,7322,7323],{},"Sorry, my memory isn't the best.",[35,7325,7326],{},"If you see me at a conference, feel free to come and say hi!",[35,7328,7329],{},"Takima, the company I work for, wasn't a sponsor this year, but they sent three reporters per day to cover the conference. They also sent an engineering manager and a dedicated person to supervise the team, as for some of them it was their first time at a conference, and Devoxx France can be very intimidating.",[35,7331,7332],{},[1690,7333],{"alt":7334,"src":7335},"The Wednesday team of Takima, from left to right: Camille, Joseph, Estéban, Guillaume, and Anaïs","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ftakima-team-day-1.webp",[35,7337,7338],{},[1690,7339],{"alt":7340,"src":7341},"The Thursday team of Takima, from left to right: Camille, Mathilde, Maxime, Delphine, and Estéban","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ftakima-team-day-2.webp",[35,7343,7344],{},[1690,7345],{"alt":7346,"src":7347},"The Friday team of Takima, from left to right: Estéban, Jonathan, Marion, Camille, Camille, and Alexis","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ftakima-team-day-3.webp",[51,7349,7350],{"id":5567},"What's next",[35,7352,7353,7354],{},"May is a relatively quiet month for me, but June is going to be really busy. ",[1217,7355,7356],{},"Gosh, what have I gotten myself into?",[35,7358,7359,7360,3076,7363,3080,7368,106,7373,7378],{},"On June 11 and 12, I'm going to Lille for the DevLille conference. It will be my first time there, and everyone tells me it's a great conference, so I'm really looking forward to it. I'm going to speak about Vite again, and I saw that I'll be in the grand theater. That's a little intimidating. Similar to MiXiT, I'm going to DevLille with ",[39,7361,1775],{"href":1773,"rel":7362},[77],[39,7364,7367],{"href":7365,"rel":7366},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F",[77],"Elise",[39,7369,7372],{"href":7370,"rel":7371},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F",[77],"Victoire",[39,7374,7377],{"href":7375,"rel":7376},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurine-le-net\u002F",[77],"Laurine"," will also join us for the conference! It's good to know that I won't be alone and that I can share this experience with them.",[35,7380,7381,7382],{},"Then, on June 16, I have my talk at Natural Change Leaders Days. I'll talk about agency in a moving world. It's a 100% online conference, so I won't have to travel for this one. This won't be a technical talk, and it will be a new one I have to write. ",[1217,7383,7384],{},"Oh, and you should definitely join us for this 20-day conference. I've never seen a program as good as this one!",[35,7386,7387,7388,3076,7391,3080,7394,7397,7398],{},"Two days later, on June 18 and 19, I'm going to Luxembourg for Voxxed Days Luxembourg. Once again, I'll speak about Vite. It's also a conference I've never attended, so I'm really curious to discover it! I hope to see new faces there! Let's not change a winning team: ",[39,7389,1775],{"href":1773,"rel":7390},[77],[39,7392,7367],{"href":7365,"rel":7393},[77],[39,7395,7372],{"href":7370,"rel":7396},[77]," will also be there. ",[1217,7399,7400],{},"Damn, are you everywhere?",[35,7402,7403,7404,7409,7410,7415,7416,7421,7422],{},"Finally, on June 27, I'm going to La Défense for the Asynconf conference, organized by ",[39,7405,7408],{"href":7406,"rel":7407},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Florenzo-async\u002F",[77],"Lorenzo",". I'll have two talks there, one about Vite and another about signals and frontend reactivity. Looking at the schedule, I'm really asking myself what I'm doing there. ",[39,7411,7414],{"href":7412,"rel":7413},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmathieunebra\u002F",[77],"Mathieu Nebra",", co-founder of OpenClassrooms, ",[39,7417,7420],{"href":7418,"rel":7419},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fanis-ayari\u002F",[77],"Anis Ayari",", content creator, AI engineer, and author, are also speaking at this conference. ",[1217,7423,7424],{},"Oh boy, the impostor syndrome is real for this one.",[35,7426,7427],{},"And that's it for June! Once it's done, you won't hear from me for at least a couple of days. I'll need to recover from all of this.",[35,7429,7430,7431],{},"Then, I'll start working on the second part of the year, attending more conferences and on a new big project that I can't talk about yet. ",[1217,7432,7433],{},"Sorry, where do I buy some sleep?",[1159,7435],{},[35,7437,7438],{},"Have I told you about Mirakl?",[1159,7440],{},[35,7442,7443],{},"For now, all lights are green, so slowing down isn't part of the plan.",{"title":13,"searchDepth":14,"depth":14,"links":7445},[7446,7447,7448,7449,7450],{"id":6821,"depth":14,"text":6822},{"id":7147,"depth":14,"text":7148},{"id":7205,"depth":14,"text":7206},{"id":7254,"depth":14,"text":7255},{"id":5567,"depth":14,"text":7350},"364d3a85-67b8-41e0-92fd-aee9ad2864b9","2026-05-11","AI, AI, and AI. Tech, tech, and tech. But what about people? Conferences are more than just talks and I choose to prioritize people this year.",{},"\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks","---\ncontentId: 364d3a85-67b8-41e0-92fd-aee9ad2864b9\ntitle: \"Devoxx France 2026: Prioritizing People Over Talks\"\ndescription: AI, AI, and AI. Tech, tech, and tech. But what about people? Conferences are more than just talks and I choose to prioritize people this year.\ndate: 2026-05-11\n---\n\nI know, I'm a little late to the party on this one. But since I now do this for most conferences I attend, I wanted to share some of my notes and thoughts about Devoxx France 2026.\n\nDevoxx France is a very different conference because of its size. With 4,500 attendees, 3 days, 14 tracks, and more than 250 talks, it goes far beyond any other technical conference in France. Some speakers only speak at Devoxx, making it the only opportunity to see them.\n\n![The main stage of Devoxx France, during the opening](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fmain-stage.webp)\n\nAt the same time, and that's my personal opinion, the size of the conference makes it very difficult to fully appreciate. Once a talk is over, you're running, literally, to the next one, hoping to find a seat. You're always in a rush. You always have FOMO because of the number of tracks, and you can't just take the time to slow down, talk with people, and enjoy the conference. You can quickly fall into the trap of wanting to see as many talks as possible.\n\nThis year, I really took the time to chat with people. There were some talks I didn't attend to free up some time. For example, I wanted to go to the Dev with AI meetup on Tuesday night, but I was chatting with some friends, didn't see the time pass, and missed it. That's the law of the conference: you have to make choices.\n\n_Please, don't ask me about the food at Devoxx. Just know that I always have some biscuits in my bag._\n\n## My schedule\n\nThis year, for the first time, I looked at the schedule before the conference. I wanted to get an overview of the general atmosphere of the event. These days, everything is about AI. I open X, I see AI. I talk with friends, we talk about AI. I go to work, we push AI even further. Even with my date, my family, or with people I don't know during a carpool, I talk about AI. This topic is everywhere. Devoxx is no exception.\n\nOn top of that, I've spent the last four months writing a talk about the future of developers with [Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux\u002F).\n\nAI saturates so much of our lives, and I wanted a little break from it. So I decided to focus on human-centric talks.\n\nHere is my schedule for the conference:\n\n1. Wednesday, April 15, 2026\n    - **L'IA au service du bien commun : Agir avec raison dans l'incertain** by [Laurence Devillers](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurence-devillers-4041899\u002F)\n    - **Les échecs à l'ère de l'intelligence artificielle** by [Laurent Fressinet](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurent-fressinet-752236188\u002F)\n    - **Neurodiversité & Tech : vos “profils atypiques” sont votre plus grande force** by [Amélie Rigano](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Famelierigano\u002F)\n    - **The evolution of Software engineering** by [Theophane Gregoir](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ftheophane-gregoir\u002F)\n    - **La transactionalité expliquée** by [Benjamin Cavy](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbenjamin-cavy-aa67a2a6\u002F)\n    - **Et si on débuguait notre rapport au temps ?** by [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** by [Anthony Maffert](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fanthonymaffert\u002F) and [Nicolas Fidel](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolas-fidel\u002F)\n    - **Kafka 4, fantastique ?** by [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** by [Julien Briault](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-briault-441539137\u002F)\n2. Thursday, April 16, 2026\n    - **Le développeur face à l'IA : du prototypage rapide à la fin des intermédiaires ?** by [Nicolas Grenié](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolasgrenie\u002F)\n    - **L'IA sur le terrain : l'humain au cœur de la valeur** by [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é** by [Paul Pinault](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpaulpinault\u002F)\n    - **Top 10 Event-Driven Architecture Pitfalls** by [Victor Rentea](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictor-rentea-trainer\u002F)\n    - **Transformer son API Doc en levier de conversion : best practices** by [Anne-Flore Bernard](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fanne-flore-bernard\u002F)\n    - **Clean Code Matters** by [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.** by [Philippe Laumay](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaumay\u002F) and [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** by [Estéban Soubiran](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F)\n    - **Debugging with IntelliJ IDEA** by [Anton Arhipov](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fantonarhipov\u002F)\n3. Friday, April 17, 2026\n    - **L'IA au défi de la condition humaine** by [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\"?** by [Loup Cellard](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floup-cellard-b2502297\u002F)\n    - **Et si on ouvrait le capot de PostgreSQL ?** by [Benjamin Coenen](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcoenenbenjamin\u002F)\n    - **Et si écrire du SQL redevenait cool ?** by [Sébastien Ferrer](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fs%C3%A9bastien-ferrer-076ba052\u002F)\n    - **Kubernetes et la JVM** by [Jean-Michel Doudoux](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmdoudoux\u002F) and [Alain Regnier](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falainregnier\u002F)\n    - **Éloge de la simplicité** by [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** by [Florian Beaufumé](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffbeaufume\u002F)\n\n_I must confess. I could have slept a little during one of the talks. Sorry!_\n\nYes! It's a lot. That could explain why I finished the conference with a headache, feeling tired, and needing to rest alone. _But it was worth it._\n\nAll replays will be available on the Devoxx France YouTube channel, and I highly encourage you to watch them!\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## Talks I loved\n\nAs usual, some talks really stood out to me, and I wanted to highlight them here.\n\n1. **Éloge de la simplicité** by [Frédéric Leguédois](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffleguedois\u002F)\n\n    That might be the best talk I've seen at Devoxx, all editions included. It's a 45-minute talk, but honestly, it doesn't feel like it. Frédéric doesn't use any slides, and he builds the talk like a theater play, with a lot of humor. Despite the feeling of lightness, the talk goes deep into the current state of agility, the overuse of planning, and the importance of rebuilding our relationship with simplicity. Honestly, you should just watch it.\n\n    ![Frédéric on stage, during his talk about simplicity](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ffrederic-simplicity.webp)\n\n2. **Et si on débuguait notre rapport au temps ?** by [Albane Fagot-Veyron](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falbane-fagot-veyron\u002F)\n\n    It's a deeply human-centered talk, and I loved it. Albane talks about our relationship with time, how we perceive it, and how it affects our daily lives and work. \"I don't have time\" is a phrase we often hear, or even say, but have we ever taken the time to dive into it? It's really well written and pleasant to listen to. It's already on my watch list.\n\n    ![Albane on stage, during her talk about debugging our relationship with time](\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é** by [Paul Pinault](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpaulpinault\u002F)\n\n    I came to this talk without any expectations, and I had a wonderful time. At first, Paul shows that turning on a light can be just 50 lines of code, before explaining all the prerequisites imposed by the EU in terms of security, privacy, and maintenance, which makes the task much more complex, and therefore much more interesting. The real complexity isn't in the bulb itself, but in the surrounding ecosystem needed to make it compliant. This concrete talk can easily serve as an analogy for any project.\n\n    ![Paul on stage, during his talk about the security of connected objects](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fpaul-connected-objects.webp)\n\n## My talk\n\nThis year, I gave a talk about Vite and its pipeline at a conference called \"Au cœur d'une pipeline : démystifions Vite et ses plugins\".\n\n![Estéban, in front of the announcement panel, before the talk starts](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fvite-talk-panel.webp)\n\nIt's a 45-minute talk where I explain how Vite works, what its pipeline looks like, and how to write a plugin for it. Through various concrete examples, I try to demystify the inner workings of Vite and show how powerful, yet approachable, it can be when you start writing plugins for it.\n\n![Estéban behind the desk, before the talk starts](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fvite-talk-desk.webp)\n\n![My selfie with the audience, during my talk about Vite](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fvite-talk-selfie.webp)\n\nI had already given the same talk at [MiXiT 2026](.\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026.md), and I didn't find the time to improve it for Devoxx. Less than a week is really short to improve a talk. However, a lot of improvements are coming in June, and I'll share more about it on [LinkedIn](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25).\n\nYou can find the slides of the talk on [talks.soubiran.dev](https:\u002F\u002Ftalks.soubiran.dev).\n\n## People first\n\nFor the first time at a conference, I prioritized people over talks.\n\nI'm convinced that the most valuable part of a conference isn't the conference itself. You can always find talks later on YouTube, so the content isn't exclusive to the event. _But I understand the value of attending a talk live: it's a different experience, and it forces you to be more present and focused._\n\nBut people, from the speakers who make the conference possible to curious attendees eager to learn, and even to the ones who are just there to discover new things, are the real value of a conference. With the recent rise of AI, where we all tend to interact mostly with machines, it makes it even more important to prioritize human interactions.\n\nSo, from the speakers' dinner to the Meet and Greet party, and from sponsors to attendees, I've spent a lot of time chatting. I've discovered their stories, how they got into Devoxx, why they speak at conferences or appreciate the company they work for, the latest news from companies, or simply talking about life. It's also a great opportunity to put a face to the name of people you only know from LinkedIn. _And to experience the awkwardness of parasocial interactions._\n\nThrough this section, I wanted to thank all the people I had the chance to talk with during the conference. It was a true pleasure, and I hope to see you again soon.\n\n- Lorenzo,\n- Octavie and Alexandre,\n- Julien,\n- Fanny,\n- Denis,\n- Philippe and Loïc,\n- Benjamin,\n- Stephen,\n- Joan,\n- Damien,\n- Albane,\n- Christophe,\n- the Clever Cloud team I had the chance to talk with,\n- and many more that I can't remember right now. _Sorry, my memory isn't the best._\n\nIf you see me at a conference, feel free to come and say hi!\n\nTakima, the company I work for, wasn't a sponsor this year, but they sent three reporters per day to cover the conference. They also sent an engineering manager and a dedicated person to supervise the team, as for some of them it was their first time at a conference, and Devoxx France can be very intimidating.\n\n![The Wednesday team of Takima, from left to right: Camille, Joseph, Estéban, Guillaume, and Anaïs](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ftakima-team-day-1.webp)\n\n![The Thursday team of Takima, from left to right: Camille, Mathilde, Maxime, Delphine, and Estéban](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ftakima-team-day-2.webp)\n\n![The Friday team of Takima, from left to right: Estéban, Jonathan, Marion, Camille, Camille, and Alexis](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ftakima-team-day-3.webp)\n\n## What's next\n\nMay is a relatively quiet month for me, but June is going to be really busy. _Gosh, what have I gotten myself into?_\n\nOn June 11 and 12, I'm going to Lille for the DevLille conference. It will be my first time there, and everyone tells me it's a great conference, so I'm really looking forward to it. I'm going to speak about Vite again, and I saw that I'll be in the grand theater. That's a little intimidating. Similar to MiXiT, I'm going to DevLille with [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F), [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F), and [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) will also join us for the conference! It's good to know that I won't be alone and that I can share this experience with them.\n\nThen, on June 16, I have my talk at Natural Change Leaders Days. I'll talk about agency in a moving world. It's a 100% online conference, so I won't have to travel for this one. This won't be a technical talk, and it will be a new one I have to write. _Oh, and you should definitely join us for this 20-day conference. I've never seen a program as good as this one!_\n\nTwo days later, on June 18 and 19, I'm going to Luxembourg for Voxxed Days Luxembourg. Once again, I'll speak about Vite. It's also a conference I've never attended, so I'm really curious to discover it! I hope to see new faces there! Let's not change a winning team: [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F), [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F), and [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F) will also be there. _Damn, are you everywhere?_\n\nFinally, on June 27, I'm going to La Défense for the Asynconf conference, organized by [Lorenzo](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Florenzo-async\u002F). I'll have two talks there, one about Vite and another about signals and frontend reactivity. Looking at the schedule, I'm really asking myself what I'm doing there. [Mathieu Nebra](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmathieunebra\u002F), co-founder of OpenClassrooms, [Anis Ayari](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fanis-ayari\u002F), content creator, AI engineer, and author, are also speaking at this conference. _Oh boy, the impostor syndrome is real for this one._\n\nAnd that's it for June! Once it's done, you won't hear from me for at least a couple of days. I'll need to recover from all of this.\n\nThen, I'll start working on the second part of the year, attending more conferences and on a new big project that I can't talk about yet. _Sorry, where do I buy some sleep?_\n\n---\n\nHave I told you about Mirakl?\n\n---\n\nFor now, all lights are green, so slowing down isn't part of the plan.\n",{"title":6793,"description":7453},{"loc":7455,"images":7459},[7460,7461,7462,7463,7464,7465,7466,7467,7468,7469],{"loc":6807},{"loc":7170},{"loc":7186},{"loc":7202},{"loc":7215},{"loc":7224},{"loc":7230},{"loc":7335},{"loc":7341},{"loc":7347},"posts\u002Fdevoxx-france-2026-prioritizing-people-over-talks","aeeXE-R7pJZFRahEpebC7mF1nJ84FVHsv4SYyrlt8QI",{"id":7473,"title":7474,"body":7475,"contentId":8151,"date":8152,"description":8153,"extension":18,"meta":8154,"navigation":20,"path":8155,"rawbody":8156,"robots":23,"schemaOrg":23,"seo":8157,"sitemap":8158,"stem":8174,"__hash__":8175},"posts\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026.md","The Unexpected Rollercoaster of Feelings at MiXiT 2026",{"type":7,"value":7476,"toc":8141},[7477,7490,7493,7499,7502,7514,7520,7537,7540,7543,7551,7560,7570,7573,7576,7582,7585,7588,7654,7660,7663,7669,7672,7729,7735,7742,7755,7767,7773,7779,7785,7789,7792,7841,7851,7855,7861,7865,7871,7874,7883,7886,7892,7898,7901,7904],[35,7478,7479,7480,106,7485,7489],{},"This was my second time at ",[39,7481,7484],{"href":7482,"rel":7483},"https:\u002F\u002Fmixitconf.org\u002F",[77],"MiXiT",[39,7486,7488],{"href":7487},".\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025","The first time was a year ago",", but it feels like it was yesterday.",[35,7491,7492],{},"MiXiT is a tech conference that took place in Lyon over two days, centered around technology, ethics, and diversity. Compared with last year, the conference was just as warm and welcoming, with more than 600 attendees.",[35,7494,7495],{},[1690,7496],{"alt":7497,"src":7498},"Lyon point of view from Fourvière","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Flyon-point-of-view-from-fourviere.webp",[35,7500,7501],{},"However, this time was different. The context was different.",[35,7503,7504,7505,3076,7508,3080,7511,215],{},"For the first time, I was going to attend a conference with other people, not just anyone, but some wonderful people from Takima, the company I work for. I was going to attend the conference with ",[39,7506,1775],{"href":1773,"rel":7507},[77],[39,7509,7367],{"href":7365,"rel":7510},[77],[39,7512,7372],{"href":7370,"rel":7513},[77],[35,7515,7516],{},[1690,7517],{"alt":7518,"src":7519},"From left to right: Estéban, Victoire, Marion, and Elise, photo by Elise","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fvictoire-elise-marion-and-me.webp",[35,7521,7522,7523,1638,7526,7529,7530,7533,7534],{},"They were both accepted to speak at MiXiT for the first time on stage. ",[39,7524,1775],{"href":1773,"rel":7525},[77],[39,7527,7367],{"href":7365,"rel":7528},[77]," were going to present together on feature flags in production-critical environments. ",[39,7531,7372],{"href":7370,"rel":7532},[77]," was going to talk about mutation testing. They truly deserve it. They put so much work, passion, and love into their talks. ",[1217,7535,7536],{},"Congrats, girls!",[35,7538,7539],{},"For a first conference, they really chose a good one to speak at. Attendees are really nice and understanding, and the organizers are supportive and kind. MiXiT is definitely a good place to start a speaker's journey.",[35,7541,7542],{},"But at the same time, we should have been five speakers, not four.",[35,7544,7545,7546,7550],{},"Since November, I had been preparing a talk about the future of developers with a friend called ",[39,7547,6833],{"href":7548,"rel":7549},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux",[77],". Until now, I had mainly given talks about technical topics. This one was going to be different. First, it was going to be a non-technical talk, written using scientific articles, research, blog posts, books, and interviews with other developers. Second, it was going to be a duo, from the writing all the way to the presentation.",[35,7552,7553,7556,7557],{},[39,7554,6833],{"href":7548,"rel":7555},[77]," was really enthusiastic, yet worried, about it, since it was also her first time giving a talk. Unfortunately, for personal reasons, she had to stop working on the talk a couple of days before the conference, and she couldn't join me at MiXiT. ",[1217,7558,7559],{},"I know she tries so hard to make it work. Another time for a duo talk.",[35,7561,7562,7563],{},"I felt terrible for her. I was this close to canceling the talk, but we were committed to the organizers. I didn't want to let them down. They gave us their trust, and we had to honor it. So, I kept pushing myself to finish the talk until the last minute. ",[1217,7564,7565,7566,7569],{},"To be totally transparent, the week before the conference might have been one of the worst weeks of my life. The conference about Vite was nearly finished, but not quite, the one about the future of developers was less than half done, and ",[39,7567,6833],{"href":7548,"rel":7568},[77]," was not able to join me. Worst feeling.",[51,7571,7237],{"id":7572},"mixit-2026",[35,7574,7575],{},"It all began on Wednesday, April 15, 2026, with the speakers' dinner, held in a chic restaurant near the Saône River. They served cheese, charcuterie, and wine. Honestly, the perfect combination for a great evening. It was also a good opportunity to meet other speakers and to chat about our talks. I love these moments when people try to sell you on their talk. All in all, it's really enjoyable to hear someone talk about their passion. And it helped me plan the next two days!",[35,7577,7578,7579],{},"As with all events, don't ask for photos! What happened at the speakers' dinner stays at the speakers' dinner. ",[1217,7580,7581],{},"I could have spent too much time talking near the drinks.",[35,7583,7584],{},"Around 22:00, we left the restaurant and went to the hotel to rest for the next day. I was really tired, but I had a conference to finish. I worked until 1:30 in the morning trying to finish the talk about the future of developers, and I wrote the LinkedIn post for the next morning.",[35,7586,7587],{},"7 o'clock, time to wake up, continue working on the talk, and get ready for the conference. Not a lot of sleep, but I didn't really have a choice. On the first day, Thursday, April 16, 2026, I saw the following talks:",[1576,7589,7590,7600,7608,7618,7628,7638,7648],{},[71,7591,7592,6853,7595],{},[6850,7593,7594],{},"L'intelligence artificielle générale n'aura pas lieu",[39,7596,7599],{"href":7597,"rel":7598},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthibault-prevost\u002F",[77],"Thibault Prévost",[71,7601,7602,6853,7605],{},[6850,7603,7604],{},"Au cœur d'une pipeline : démystifions Vite et ses plugins",[39,7606,7033],{"href":7031,"rel":7607},[77],[71,7609,7610,6853,7613],{},[6850,7611,7612],{},"Le mythe de la portabilité cloud avec Kubernetes",[39,7614,7617],{"href":7615,"rel":7616},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffederico-fregosi\u002F",[77],"Federico Fregosi",[71,7619,7620,6853,7623],{},[6850,7621,7622],{},"Déconstruisons les frameworks JS pour mieux réinventer le web !",[39,7624,7627],{"href":7625,"rel":7626},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnirinarabeson\u002F",[77],"Nirina Rabeson",[71,7629,7630,6853,7633],{},[6850,7631,7632],{},"Mes trains Jouef passent au numérique, avec TinyGo",[39,7634,7637],{"href":7635,"rel":7636},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fflorian-forestier\u002F",[77],"Florian Forestier",[71,7639,7640,6853,7643],{},[6850,7641,7642],{},"Assistant IA pour le code : comment ça marche ? J'en ai codé un !",[39,7644,7647],{"href":7645,"rel":7646},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fxnopre\u002F",[77],"Xavier Nopre",[71,7649,7650,7653],{},[6850,7651,7652],{},"L'indépendance des médias... indés"," by David Dufresne",[35,7655,7656],{},[1690,7657],{"alt":7658,"src":7659},"Photo of the amphitheater during the introduction word by the organizers","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Famphitheater-during-introduction.webp",[35,7661,7662],{},"Yes, I was the first talk, right after the opening keynote. No time to rest, just plug in the computer, open the slides, jump on stage, and start talking. Thanks to the adrenaline, tiredness was not really a problem during the talk. Afterwards, however, it was really hard and intense.",[35,7664,7665,7666],{},"After the first conference day, MiXiT organized a party at Lyon City Hall. It's a really beautiful place and a unique opportunity to visit it while enjoying some drinks and taking some time to chat with other attendees. Finding time to talk with people is really hard during a conference, so these moments are precious. After a long day and a long party, we went back to the hotel. I wanted to rest, but I had to finish a talk, yes, again. ",[1217,7667,7668],{},"Sleeping at a conference? I should definitely try it! Looks cool!",[35,7670,7671],{},"The next day, same wake-up time: 7 o'clock. At this stage, we don't really talk about tiredness anymore. I was completely exhausted, and even though I worked hard on the talk, it was not finished by the time we left the hotel. I had to finish it during the opening keynote and the first talk. On the second day, Friday, April 17, 2026, I attended the following talks:",[1576,7673,7674,7684,7694,7706,7719],{},[71,7675,7676,6853,7679],{},[6850,7677,7678],{},"Wikipédia à 25 ans : De l'utopie numérique au pilier du savoir commun",[39,7680,7683],{"href":7681,"rel":7682},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fr%C3%A9my-gerbet-511249a1\u002F",[77],"Rémy Gerbet",[71,7685,7686,6853,7689],{},[6850,7687,7688],{},"Conception de logiciels : le match humains vs AI",[39,7690,7693],{"href":7691,"rel":7692},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fazeau\u002F",[77],"Olivier Azeau",[71,7695,7696,6853,7699,1638,7703],{},[6850,7697,7698],{},"Le futur du développeur : IA, compétences et obsolescence",[39,7700,7702],{"href":7548,"rel":7701},[77],"Julie Mouveroux",[39,7704,7033],{"href":7031,"rel":7705},[77],[71,7707,7708,6853,7711,1638,7715],{},[6850,7709,7710],{},"Déployer souvent, stresser moins : feature flags en prod critique",[39,7712,7714],{"href":1773,"rel":7713},[77],"Marion Chineaud",[39,7716,7718],{"href":7365,"rel":7717},[77],"Elise Souvannavong",[71,7720,7721,6853,7724],{},[6850,7722,7723],{},"Pas de managers, pas de réunions, pas de problème",[39,7725,7728],{"href":7726,"rel":7727},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsofialescanocarroll\u002F",[77],"Sofia Lescano Carroll",[35,7730,7731,7732],{},"For my talk, some slides weren't finished, and speaker notes were not written. I never use speaker notes, but I had to for this talk because it was going to be completely improvised. Not my best skill, but I had no choice. Let's make my last thirty talks useful for this one! ",[1217,7733,7734],{},"You may wonder whether I was stressed. I wasn't. Tired, of course, but not stressed. Stress wasn't going to help me or make my talk better, so I just accepted the situation, being behind schedule, and did my best to honor Julie's work on the talk and the trust of the organizers.",[35,7736,7737,7738,7741],{},"At the same time, ",[39,7739,7372],{"href":7370,"rel":7740},[77]," was presenting her talk about mutation testing. I couldn't attend it, but I know it went really well.",[35,7743,7744,7745,1638,7748,7751,7752],{},"Then, we all attended to ",[39,7746,1775],{"href":1773,"rel":7747},[77],[39,7749,7367],{"href":7365,"rel":7750},[77],"'s talk about feature flags. It was really good, and the audience was really into it. I was so proud of them, and I know they were so proud of themselves. They really deserved it. ",[1217,7753,7754],{},"For a first talk, I'm speechless.",[35,7756,7757,7758,3076,7761,3080,7764,215],{},"At the end of the second day, the talks were done and the attendees were happy to have seen both of my talks. Of course, it was a lot of work, but it wouldn't have been possible without the support of ",[39,7759,1775],{"href":1773,"rel":7760},[77],[39,7762,7367],{"href":7365,"rel":7763},[77],[39,7765,7372],{"href":7370,"rel":7766},[77],[35,7768,7769],{},[1690,7770],{"alt":7771,"src":7772},"From left to right: Victoire, Elise, Marion, and Estéban in the middle","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fvictoire-elise-marion-and-me-in-the-middle.webp",[35,7774,7775],{},[1690,7776],{"alt":7777,"src":7778},"Photo of Marion, Elise, and Victoire in the Lyon City Hall during the party","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fmarion-elise-and-victoire-in-lyon-city-hall.webp",[35,7780,7781,7782],{},"It changed everything not to be alone. It was so much fun to share this experience, exchange about the talks, chat about our feelings, and simply support and empower each other. ",[1217,7783,7784],{},"Thank you so much!",[51,7786,7788],{"id":7787},"talks-highlights","Talks highlights",[35,7790,7791],{},"The more conferences I attend, the higher the bar gets for my \"talks highlights.\" I want to share the talks that I really enjoyed and that I think are worth sharing. As always, this is a subjective list, and I encourage you to check the conference program to discover other talks that might interest you.",[68,7793,7794,7820],{},[71,7795,7796,6853,7798,1638,7801,7804,7806,7807,1638,7810,7813,7814,7816],{},[6850,7797,7710],{},[39,7799,7714],{"href":1773,"rel":7800},[77],[39,7802,7718],{"href":7365,"rel":7803},[77],[7162,7805],{},"You may think that I'm a little biased for putting this talk in the first place, but it was the first time I saw it, and I was really impressed. The talk is very complete, covering different types of feature flags, from implementation to usage and use cases. It was well structured, with a good pace and funny jokes when needed. You can feel the chemistry between ",[39,7808,1775],{"href":1773,"rel":7809},[77],[39,7811,7367],{"href":7365,"rel":7812},[77],", as well as the passion they have for the topic.",[7162,7815],{},[1690,7817],{"alt":7818,"src":7819},"Photo of Marion and Elise presenting","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fmarion-and-elise-presenting.webp",[71,7821,7822,6853,7824,7827,7829,7830,7834,7835,7837],{},[6850,7823,7632],{},[39,7825,7637],{"href":7635,"rel":7826},[77],[7162,7828],{},"Not a technical talk, but a true, human-centered, and passionate one. ",[39,7831,7833],{"href":7635,"rel":7832},[77],"Florian"," shared his passion for model trains and how he managed to make them digital and web-controllable, compared with the old analog ones. Beyond the story, the real-world use case of Raspberry Pi with good developer-experience software, TinyGo, was really interesting. I don't know how to make it actionable today, but I know that the day I need it, I'll remember this talk. Listening to passionate people talk about their passion is so pleasant and inspiring.",[7162,7836],{},[1690,7838],{"alt":7839,"src":7840},"Photo of Florian presenting","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fflorian-presenting.webp",[35,7842,7843,7844,7847,7848],{},"I would love to attend ",[39,7845,7372],{"href":7370,"rel":7846},[77],"'s talk, \"Testez vos tests avant qu'ils ne vous trahissent : le mutation testing !\" Unfortunately, it was at the same time as mine. I really hope to be able to attend it at another conference. She worked so hard on it, and I know it's going to be amazing! ",[1217,7849,7850],{},"Can't wait to see it at DevLille in June!",[51,7852,7854],{"id":7853},"vite-future-of-web-developers-and-a-podcast","Vite, future of web developers and a podcast",[35,7856,7857,7858],{},"This year, I had the opportunity to give two talks at MiXiT. Last year, I also gave two talks, but one of them was a launch talk about a very light topic. This year, I had two 45-minute conference talks about very technical topics. It was a real challenge for me, especially with the context of the second talk. ",[1217,7859,7860],{},"I don't recommend building two talks at the same time. It's way too much work.",[1913,7862,7864],{"id":7863},"au-cœur-dune-pipeline-démystifions-vite-et-ses-plugins","\"Au cœur d'une pipeline : démystifions Vite et ses plugins\"",[35,7866,7867],{},[1690,7868],{"alt":7869,"src":7870},"Selfie of Estéban with the audience at the beginning of his talk about Vite","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Festeban-selfie-with-audience-before-vite-talk.webp",[35,7872,7873],{},"This has been the biggest technical talk I have ever given. It's 70 slides with a lot of dynamic content. Its particularity is that the audience is the hero of the talk. People affect the talk by choosing options during it, making each replay different. As you may guess, that means writing multiple paths into the slides and much more content than a normal talk.",[35,7875,7876,7877,7882],{},"I also had to develop all the underlying systems to make it work. Even though it was based on ",[39,7878,7881],{"href":7879,"rel":7880},"https:\u002F\u002Finalia.app",[77],"Inalia",", the SaaS I'm building to make talks more engaging with real-time interactions, I had to develop a lot of new frontend logic to make it fit the talk's needs.",[35,7884,7885],{},"I wrote all my talks using Slidev, a tool that allows you to write your slides in Markdown. The interesting thing about Slidev is that the talk is a Vite and Vue website. So, by using it, you have all the power of Vite and Vue to create your slides. You can create any interactive slide, with any animation, and I took advantage of that to create a really dynamic talk. Good idea, but time consuming! Fortunately, I had the help of AI to develop most of the logic.",[35,7887,7888,7889],{},"Thankfully, I had the opportunity to give this talk at an internal event at L'Atelier in Paris, two weeks before the conference. I also had the chance to give it as a crash test at Takima in front of many of my colleagues. They gave me invaluable feedback to improve it and make it better. ",[1217,7890,7891],{},"And I know it's better now. Thanks to everyone who takes the time to give me feedback on it!",[35,7893,7894],{},[1690,7895],{"alt":7896,"src":7897},"Photo of Estéban during his talk about Vite, photo by Marion","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Festeban-presenting-about-vite.webp",[35,7899,7900],{},"However, I'm far from satisfied with it. I know it could be so much better, even after working on it for months, so I will keep working on it. Let's make it better for DevLille in June!",[35,7902,7903],{},"I will dedicate a full blog post to the making of this talk, with all the technical details and the challenges I faced. There's a lot of hidden work behind it and people asked me a lot of questions!",[7905,7906,7909,7913,7923,7926,7937,7940,7943,7946,7952,7958,7964,7971,7974,7977,7983,7990,8004,8010,8013,8021,8024,8028,8041,8044,8056,8062,8064,8070,8076,8079,8095,8109,8127,8130,8136,8138],"callout-read-more",{"href":7907,"text":7908},"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Ftalks","Check out the talk's repository on GitHub",[1913,7910,7912],{"id":7911},"le-futur-du-développeur-ia-compétences-et-obsolescence","\"Le futur du développeur : IA, compétences et obsolescence\"",[35,7914,7915,7916,7919,7920],{},"The story of this talk starts in November 2025. After going to the theater with ",[39,7917,6833],{"href":7548,"rel":7918},[77],", we sat down at a bar to chat about a lot of things. At some point, I asked her whether she would be interested in creating a talk together. I asked her not to reply immediately, but to think about it and let me know if she was interested. ",[1217,7921,7922],{},"I hate social pressure, and I don't want a \"yes\" just because of the moment. Creating a talk is so much work that I want to be sure the person is truly interested and motivated to do it.",[35,7924,7925],{},"She replied a couple of hours later, saying that she was interested. So happy to read that text message from her!",[35,7927,7928,7929],{},"At this point in the story, you have to know that we didn't know the subject of the talk, Julie had never given a talk before, and I had never given a talk with someone else. To make things even more complicated, we weren't working in the same field and we had a month to submit it to a CFP. Gosh, what a crazy idea! ",[1217,7930,7931,7932,7936],{},"Two rules: things often start at a bar, and ",[39,7933,7935],{"href":7934},".\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things","you can just do things",". You don't have to be ready.",[35,7938,7939],{},"After a couple of brainstorming sessions, we (mainly she) finally found the subject of the talk. We wanted to talk about the future of work from an AI perspective. Because the conferences we submit to are mostly technical, we targeted the future of developers. This also reduced the scope of the talk a little to make it \"easier\" to write.",[35,7941,7942],{},"Then, we started writing the CFP. To do that, we needed a plan for the talk. That meant doing initial research to better understand the trends, the reality, and the direction things were heading. That was not an easy part, but we made it. A couple of days before Christmas, we submitted the CFP to MiXiT and Devoxx France.",[35,7944,7945],{},"Honestly, even if we were rejected by both, we tried hard to make it something we could be proud of.",[35,7947,7948,7949],{},"Even before the acceptance, I started working on it because I knew it was going to be a lot of work. I started reading papers, articles, and books, and watching videos about the topic. That was complicated because you can search \"future of developers\" on Google. No one really took the time to write about it, and the few articles that exist are biased or not relevant. We had to read dozens of articles, extract the relevant information, and make sense of it around the plan we'd defined weeks ago. I spent a lot of Sunday mornings reading articles I would love to never read again because they were so traumatic. ",[1217,7950,7951],{},"Good music is recommended to avoid falling into fear and despair.",[35,7953,7954,7955],{},"We also interviewed developers from the industry to get their point of view on the topic. This was very useful to avoid staying in our bubble and to get a better understanding of the reality. We had really good conversations with them and learned a lot from them. ",[1217,7956,7957],{},"Thank you so much to all the developers who took the time to talk with us!",[35,7959,7960,7961],{},"This process was a real challenge, and I brought a lot of automation to make it work. I won't detail it here because I will dedicate a full blog post to the creation of this talk. This process took absolutely all our time. We regularly met to discuss the content, make sure we were continuously aligned, and simply work on it together. ",[1217,7962,7963],{},"Honestly, we spend more time talking about our lives than about the talk. And that's okay. It's not our job after all.",[35,7965,7966,7967,7970],{},"Unfortunately, a couple of days before the conference, ",[39,7968,6833],{"href":7548,"rel":7969},[77]," had to stop working on the talk for personal reasons. I felt really bad for her, and I was really sad about it. I kept working on the talk until the last minute. During the last couple of days, I was waking up an hour earlier to work on it. I went to work, and after work, I kept working on it until midnight or 1:00 in the morning. Every day.",[35,7972,7973],{},"Then, I continued to work on it during the conference. The day before MiXiT started, less than 50% of the slides were finished. I kept working on them during the conference, before and after the speakers' dinner, after the mid-conference party, and until 10 minutes before the talk, I was still working on speaker notes.",[35,7975,7976],{},"Finally, my time to speak about the future of developers came. I was tired, not ready, and not even sure whether the talk was going to be good. But I was there, on stage, with some slides, 45 minutes ahead of me, and the trust of the organizers. The room was full, and despite a large \"COMPLET\" (\"full\" in English) sign on the door, people were still getting in, crowding together at the back of the room.",[35,7978,7979],{},[1690,7980],{"alt":7981,"src":7982},"The room during the talk about the future of developers, with a lot of people standing at the back of the room","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Froom-during-future-of-developer-talk.webp",[35,7984,7985,7986,7989],{},"I delivered the talk. My biggest fear was rushing through the content by just reading the slides. The opposite happened. I was calm, and I had to speed up the second and third parts to avoid running out of time. And then the talk was over. Whoo, what a relief! During the talk, seeing people nodding or taking photos of the slides was so rewarding because it showed me that we were not completely off track with ",[39,7987,6833],{"href":7548,"rel":7988},[77],". At the end, I got some questions, and afterward, many people came to thank me for the talk. \"Finally a pragmatic talk without tons of bullshit,\" and amazing discussions about the topic that could have lasted for hours.",[35,7991,7992,7993,7998,7999,215],{},"I highly recommend reading the article by ",[39,7994,7997],{"href":7995,"rel":7996},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffanny-klauk\u002F",[77],"Fanny Klauk",", who attended the talk and wrote a magnificent article about it, with lots of insights and takeaways: ",[39,8000,8003],{"href":8001,"rel":8002},"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,8005,8006],{},[1690,8007],{"alt":8008,"src":8009},"Estéban on stage during his talk about the future of developers, photo from Fanny Klauk","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Festeban-presenting-about-future-of-developer.webp",[35,8011,8012],{},"Just to give you an idea, the CFP took us somewhere between 30 and 50 hours, and the talk itself took more than 200 hours. Both were done in less than six months. That's a lot. So the next time you see a talk you appreciate, even if you don't have any questions, just say \"thank you, great talk, loved it!\" to the speaker. It means a lot to them.",[35,8014,8015,8016,215],{},"This was, by far, the worst idea I had for a talk. Never repeat it. Please, ",[39,8017,8020],{"href":8018,"rel":8019},"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],"be prepared before giving a talk",[35,8022,8023],{},"But sometimes, unpredictable things happen. That was it, or I would have had to cancel my talk. However, canceling wasn't an option for me. The organizers gave us their trust, and we had to honor it. So, until the last minute, I kept pushing myself to finish the talk.",[1913,8025,8027],{"id":8026},"mixit-on-air","MiXiT On Air",[35,8029,8030,8031,8035,8036,215],{},"Last year, I had the opportunity to be part of the ",[39,8032,8027],{"href":8033,"rel":8034},"https:\u002F\u002Fpodcast.ausha.co\u002Fmixit",[77]," podcast ",[39,8037,8040],{"href":8038,"rel":8039},"https:\u002F\u002Fpodcast.ausha.co\u002Fmixit\u002Fepisode-12-les-signaux-en-javascript",[77],"to talk about signals in JavaScript",[35,8042,8043],{},"This year, they invited me again to talk about the future of developers. Recording a podcast is a very different experience because it really takes the form of a conversation. You have to be calm and express yourself clearly, with the most interesting and relevant content possible.",[35,8045,8046,8047,8052,8053],{},"Thanks to the beautiful work of the MiXiT On Air team, everything ran smoothly. They even thought to keep a meal going, since the recording was during the lunch break. I really enjoyed the experience, especially the duo with ",[39,8048,8051],{"href":8049,"rel":8050},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcamilleroux\u002F",[77],"Camille",". I hope the episode will be released soon so that you can listen to it. I'll share it on my LinkedIn when it's out. ",[1217,8054,8055],{},"I'm really thinking about starting my own podcast. Should I?",[35,8057,8058],{},[1690,8059],{"alt":8060,"src":8061},"Photo of the MiXiT On Air podcast recording. From left to right: Clément Staron, Hubert Sablonnière, Estéban Soubiran, Charles Bouttaz, and Camille Roux","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fmixit-on-air-podcast-recording.webp",[51,8063,7350],{"id":5567},[35,8065,8066,8067],{},"MiXiT 2026, that's a wrap! It was an amazing experience, with a lot of feelings and a lot of work. I really hope the attendees enjoyed it as much as I did. ",[1217,8068,8069],{},"And slept more than I did!",[35,8071,8072],{},[1690,8073],{"alt":8074,"src":8075},"Photo of the TakiTeam at MiXiT, from left to right: Estéban, Marion, Elise, and Victoire","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Ftakiteam-at-mixit.webp",[35,8077,8078],{},"As I'm writing this, Devoxx France is coming up in a week. I'll be presenting my talk, \"Au cœur d'une pipeline : démystifions Vite et ses plugins.\" I'm really excited about it and curious to discover the other talks and meet people.",[35,8080,8081,8082,3076,8085,3080,8088,106,8091,8094],{},"In June, I'm going to Lille for DevFest Lille with the same team, ",[39,8083,7372],{"href":7370,"rel":8084},[77],[39,8086,7367],{"href":7365,"rel":8087},[77],[39,8089,1775],{"href":1773,"rel":8090},[77],[39,8092,7377],{"href":7375,"rel":8093},[77]," will also join us to talk about micro-frontend. It will be my first DevFest Lille, and I've heard great things about it, so I'm really looking forward to it. It's going to be fun!",[35,8096,8097,8098,8103,8104,8108],{},"Then, at the end of June, I'm going to ",[39,8099,8102],{"href":8100,"rel":8101},"https:\u002F\u002Fapp.itsasync.fr\u002Fconference",[77],"Asynconf"," in Paris. ",[39,8105,8107],{"href":7406,"rel":8106},[77],"Graven"," invited me to speak about frontend reactivity. This is a real honor for me, and famous speakers like Mathieu Nebra, co-founder of OpenClassrooms, will be there.",[35,8110,8111,8112,8117,8118,1638,8123,8126],{},"I'm also participating in ",[39,8113,8116],{"href":8114,"rel":8115},"https:\u002F\u002Fwww.ncldays.fr\u002F",[77],"Natural Change Leaders Days"," in June. It's an event organized by ",[39,8119,8122],{"href":8120,"rel":8121},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Foliviermy\u002F",[77],"Olivier My",[39,8124,6908],{"href":6906,"rel":8125},[77],". It's not a technical conference, and for its first edition, it will be online. It's something new for me, but also really appealing and a little scary. I'll be talking about agency and the importance of staying in motion in a constantly changing world.",[35,8128,8129],{},"So yeah, upcoming schedule is packed!",[35,8131,8132],{},[1690,8133],{"alt":8134,"src":8135},"Lyon by night","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Flyon-by-night.webp",[1159,8137],{},[35,8139,8140],{},"Julie, hope you're doing better now!",{"title":13,"searchDepth":14,"depth":14,"links":8142},[8143,8144,8145,8150],{"id":7572,"depth":14,"text":7237},{"id":7787,"depth":14,"text":7788},{"id":7853,"depth":14,"text":7854,"children":8146},[8147,8148,8149],{"id":7863,"depth":255,"text":7864},{"id":7911,"depth":255,"text":7912},{"id":8026,"depth":255,"text":8027},{"id":5567,"depth":14,"text":7350},"6106cedc-3b20-44be-8994-da3d7da5a98e","2026-04-29","Two conference talks, a missing speaker, way too much work, but three friends to share it with. MiXiT 2026 was shared between a lot of feelings.",{},"\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026","---\ncontentId: 6106cedc-3b20-44be-8994-da3d7da5a98e\ntitle: The Unexpected Rollercoaster of Feelings at MiXiT 2026\ndescription: Two conference talks, a missing speaker, way too much work, but three friends to share it with. MiXiT 2026 was shared between a lot of feelings.\ndate: 2026-04-29\n---\n\nThis was my second time at [MiXiT](https:\u002F\u002Fmixitconf.org\u002F). [The first time was a year ago](.\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025.md), but it feels like it was yesterday.\n\nMiXiT is a tech conference that took place in Lyon over two days, centered around technology, ethics, and diversity. Compared with last year, the conference was just as warm and welcoming, with more than 600 attendees.\n\n![Lyon point of view from Fourvière](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Flyon-point-of-view-from-fourviere.webp)\n\nHowever, this time was different. The context was different.\n\nFor the first time, I was going to attend a conference with other people, not just anyone, but some wonderful people from Takima, the company I work for. I was going to attend the conference with [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F), [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F), and [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F).\n\n![From left to right: Estéban, Victoire, Marion, and Elise, photo by Elise](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fvictoire-elise-marion-and-me.webp)\n\nThey were both accepted to speak at MiXiT for the first time on stage. [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F) and [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F) were going to present together on feature flags in production-critical environments. [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F) was going to talk about mutation testing. They truly deserve it. They put so much work, passion, and love into their talks. _Congrats, girls!_\n\nFor a first conference, they really chose a good one to speak at. Attendees are really nice and understanding, and the organizers are supportive and kind. MiXiT is definitely a good place to start a speaker's journey.\n\nBut at the same time, we should have been five speakers, not four.\n\nSince November, I had been preparing a talk about the future of developers with a friend called [Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux). Until now, I had mainly given talks about technical topics. This one was going to be different. First, it was going to be a non-technical talk, written using scientific articles, research, blog posts, books, and interviews with other developers. Second, it was going to be a duo, from the writing all the way to the presentation.\n\n[Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux) was really enthusiastic, yet worried, about it, since it was also her first time giving a talk. Unfortunately, for personal reasons, she had to stop working on the talk a couple of days before the conference, and she couldn't join me at MiXiT. _I know she tries so hard to make it work. Another time for a duo talk._\n\nI felt terrible for her. I was this close to canceling the talk, but we were committed to the organizers. I didn't want to let them down. They gave us their trust, and we had to honor it. So, I kept pushing myself to finish the talk until the last minute. _To be totally transparent, the week before the conference might have been one of the worst weeks of my life. The conference about Vite was nearly finished, but not quite, the one about the future of developers was less than half done, and [Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux) was not able to join me. Worst feeling._\n\n## MiXiT 2026\n\nIt all began on Wednesday, April 15, 2026, with the speakers' dinner, held in a chic restaurant near the Saône River. They served cheese, charcuterie, and wine. Honestly, the perfect combination for a great evening. It was also a good opportunity to meet other speakers and to chat about our talks. I love these moments when people try to sell you on their talk. All in all, it's really enjoyable to hear someone talk about their passion. And it helped me plan the next two days!\n\nAs with all events, don't ask for photos! What happened at the speakers' dinner stays at the speakers' dinner. _I could have spent too much time talking near the drinks._\n\nAround 22:00, we left the restaurant and went to the hotel to rest for the next day. I was really tired, but I had a conference to finish. I worked until 1:30 in the morning trying to finish the talk about the future of developers, and I wrote the LinkedIn post for the next morning.\n\n7 o'clock, time to wake up, continue working on the talk, and get ready for the conference. Not a lot of sleep, but I didn't really have a choice. On the first day, Thursday, April 16, 2026, I saw the following talks:\n\n  - **L'intelligence artificielle générale n'aura pas lieu** by [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** by [Estéban Soubiran](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F)\n  - **Le mythe de la portabilité cloud avec Kubernetes** by [Federico Fregosi](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffederico-fregosi\u002F)\n  - **Déconstruisons les frameworks JS pour mieux réinventer le web !** by [Nirina Rabeson](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnirinarabeson\u002F)\n  - **Mes trains Jouef passent au numérique, avec TinyGo** by [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 !** by [Xavier Nopre](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fxnopre\u002F)\n  - **L'indépendance des médias... indés** by David Dufresne\n\n![Photo of the amphitheater during the introduction word by the organizers](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Famphitheater-during-introduction.webp)\n\nYes, I was the first talk, right after the opening keynote. No time to rest, just plug in the computer, open the slides, jump on stage, and start talking. Thanks to the adrenaline, tiredness was not really a problem during the talk. Afterwards, however, it was really hard and intense.\n\nAfter the first conference day, MiXiT organized a party at Lyon City Hall. It's a really beautiful place and a unique opportunity to visit it while enjoying some drinks and taking some time to chat with other attendees. Finding time to talk with people is really hard during a conference, so these moments are precious. After a long day and a long party, we went back to the hotel. I wanted to rest, but I had to finish a talk, yes, again. _Sleeping at a conference? I should definitely try it! Looks cool!_\n\nThe next day, same wake-up time: 7 o'clock. At this stage, we don't really talk about tiredness anymore. I was completely exhausted, and even though I worked hard on the talk, it was not finished by the time we left the hotel. I had to finish it during the opening keynote and the first talk. On the second day, Friday, April 17, 2026, I attended the following talks:\n\n  - **Wikipédia à 25 ans : De l'utopie numérique au pilier du savoir commun** by [Rémy Gerbet](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fr%C3%A9my-gerbet-511249a1\u002F)\n  - **Conception de logiciels : le match humains vs AI** by [Olivier Azeau](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fazeau\u002F)\n  - **Le futur du développeur : IA, compétences et obsolescence** by [Julie Mouveroux](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux) and [Estéban Soubiran](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F)\n  - **Déployer souvent, stresser moins : feature flags en prod critique** by [Marion Chineaud](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F) and [Elise Souvannavong](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F)\n  - **Pas de managers, pas de réunions, pas de problème** by [Sofia Lescano Carroll](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsofialescanocarroll\u002F)\n\nFor my talk, some slides weren't finished, and speaker notes were not written. I never use speaker notes, but I had to for this talk because it was going to be completely improvised. Not my best skill, but I had no choice. Let's make my last thirty talks useful for this one! _You may wonder whether I was stressed. I wasn't. Tired, of course, but not stressed. Stress wasn't going to help me or make my talk better, so I just accepted the situation, being behind schedule, and did my best to honor Julie's work on the talk and the trust of the organizers._\n\nAt the same time, [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F) was presenting her talk about mutation testing. I couldn't attend it, but I know it went really well.\n\nThen, we all attended to [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F) and [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F)'s talk about feature flags. It was really good, and the audience was really into it. I was so proud of them, and I know they were so proud of themselves. They really deserved it. _For a first talk, I'm speechless._\n\nAt the end of the second day, the talks were done and the attendees were happy to have seen both of my talks. Of course, it was a lot of work, but it wouldn't have been possible without the support of [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F), [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F), and [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F).\n\n![From left to right: Victoire, Elise, Marion, and Estéban in the middle](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fvictoire-elise-marion-and-me-in-the-middle.webp)\n\n![Photo of Marion, Elise, and Victoire in the Lyon City Hall during the party](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fmarion-elise-and-victoire-in-lyon-city-hall.webp)\n\nIt changed everything not to be alone. It was so much fun to share this experience, exchange about the talks, chat about our feelings, and simply support and empower each other. _Thank you so much!_\n\n## Talks highlights\n\nThe more conferences I attend, the higher the bar gets for my \"talks highlights.\" I want to share the talks that I really enjoyed and that I think are worth sharing. As always, this is a subjective list, and I encourage you to check the conference program to discover other talks that might interest you.\n\n1. **Déployer souvent, stresser moins : feature flags en prod critique** by [Marion Chineaud](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F) and [Elise Souvannavong](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F)\n\n    You may think that I'm a little biased for putting this talk in the first place, but it was the first time I saw it, and I was really impressed. The talk is very complete, covering different types of feature flags, from implementation to usage and use cases. It was well structured, with a good pace and funny jokes when needed. You can feel the chemistry between [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F) and [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F), as well as the passion they have for the topic.\n\n    ![Photo of Marion and Elise presenting](\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** by [Florian Forestier](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fflorian-forestier\u002F)\n\n    Not a technical talk, but a true, human-centered, and passionate one. [Florian](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fflorian-forestier\u002F) shared his passion for model trains and how he managed to make them digital and web-controllable, compared with the old analog ones. Beyond the story, the real-world use case of Raspberry Pi with good developer-experience software, TinyGo, was really interesting. I don't know how to make it actionable today, but I know that the day I need it, I'll remember this talk. Listening to passionate people talk about their passion is so pleasant and inspiring.\n\n    ![Photo of Florian presenting](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fflorian-presenting.webp)\n\nI would love to attend [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F)'s talk, \"Testez vos tests avant qu'ils ne vous trahissent : le mutation testing !\" Unfortunately, it was at the same time as mine. I really hope to be able to attend it at another conference. She worked so hard on it, and I know it's going to be amazing! _Can't wait to see it at DevLille in June!_\n\n## Vite, future of web developers and a podcast\n\nThis year, I had the opportunity to give two talks at MiXiT. Last year, I also gave two talks, but one of them was a launch talk about a very light topic. This year, I had two 45-minute conference talks about very technical topics. It was a real challenge for me, especially with the context of the second talk. _I don't recommend building two talks at the same time. It's way too much work._\n\n### \"Au cœur d'une pipeline : démystifions Vite et ses plugins\"\n\n![Selfie of Estéban with the audience at the beginning of his talk about Vite](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Festeban-selfie-with-audience-before-vite-talk.webp)\n\nThis has been the biggest technical talk I have ever given. It's 70 slides with a lot of dynamic content. Its particularity is that the audience is the hero of the talk. People affect the talk by choosing options during it, making each replay different. As you may guess, that means writing multiple paths into the slides and much more content than a normal talk.\n\nI also had to develop all the underlying systems to make it work. Even though it was based on [Inalia](https:\u002F\u002Finalia.app), the SaaS I'm building to make talks more engaging with real-time interactions, I had to develop a lot of new frontend logic to make it fit the talk's needs.\n\nI wrote all my talks using Slidev, a tool that allows you to write your slides in Markdown. The interesting thing about Slidev is that the talk is a Vite and Vue website. So, by using it, you have all the power of Vite and Vue to create your slides. You can create any interactive slide, with any animation, and I took advantage of that to create a really dynamic talk. Good idea, but time consuming! Fortunately, I had the help of AI to develop most of the logic.\n\nThankfully, I had the opportunity to give this talk at an internal event at L'Atelier in Paris, two weeks before the conference. I also had the chance to give it as a crash test at Takima in front of many of my colleagues. They gave me invaluable feedback to improve it and make it better. _And I know it's better now. Thanks to everyone who takes the time to give me feedback on it!_\n\n![Photo of Estéban during his talk about Vite, photo by Marion](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Festeban-presenting-about-vite.webp)\n\nHowever, I'm far from satisfied with it. I know it could be so much better, even after working on it for months, so I will keep working on it. Let's make it better for DevLille in June!\n\nI will dedicate a full blog post to the making of this talk, with all the technical details and the challenges I faced. There's a lot of hidden work behind it and people asked me a lot of questions!\n\n\u003CCalloutReadMore\n  href=\"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Ftalks\"\n  text=\"Check out the talk's repository on GitHub\"\n\u002F>\n\n### \"Le futur du développeur : IA, compétences et obsolescence\"\n\nThe story of this talk starts in November 2025. After going to the theater with [Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux), we sat down at a bar to chat about a lot of things. At some point, I asked her whether she would be interested in creating a talk together. I asked her not to reply immediately, but to think about it and let me know if she was interested. _I hate social pressure, and I don't want a \"yes\" just because of the moment. Creating a talk is so much work that I want to be sure the person is truly interested and motivated to do it._\n\nShe replied a couple of hours later, saying that she was interested. So happy to read that text message from her!\n\nAt this point in the story, you have to know that we didn't know the subject of the talk, Julie had never given a talk before, and I had never given a talk with someone else. To make things even more complicated, we weren't working in the same field and we had a month to submit it to a CFP. Gosh, what a crazy idea! _Two rules: things often start at a bar, and [you can just do things](.\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things.md). You don't have to be ready._\n\nAfter a couple of brainstorming sessions, we (mainly she) finally found the subject of the talk. We wanted to talk about the future of work from an AI perspective. Because the conferences we submit to are mostly technical, we targeted the future of developers. This also reduced the scope of the talk a little to make it \"easier\" to write.\n\nThen, we started writing the CFP. To do that, we needed a plan for the talk. That meant doing initial research to better understand the trends, the reality, and the direction things were heading. That was not an easy part, but we made it. A couple of days before Christmas, we submitted the CFP to MiXiT and Devoxx France.\n\nHonestly, even if we were rejected by both, we tried hard to make it something we could be proud of.\n\nEven before the acceptance, I started working on it because I knew it was going to be a lot of work. I started reading papers, articles, and books, and watching videos about the topic. That was complicated because you can search \"future of developers\" on Google. No one really took the time to write about it, and the few articles that exist are biased or not relevant. We had to read dozens of articles, extract the relevant information, and make sense of it around the plan we'd defined weeks ago. I spent a lot of Sunday mornings reading articles I would love to never read again because they were so traumatic. _Good music is recommended to avoid falling into fear and despair._\n\nWe also interviewed developers from the industry to get their point of view on the topic. This was very useful to avoid staying in our bubble and to get a better understanding of the reality. We had really good conversations with them and learned a lot from them. _Thank you so much to all the developers who took the time to talk with us!_\n\nThis process was a real challenge, and I brought a lot of automation to make it work. I won't detail it here because I will dedicate a full blog post to the creation of this talk. This process took absolutely all our time. We regularly met to discuss the content, make sure we were continuously aligned, and simply work on it together. _Honestly, we spend more time talking about our lives than about the talk. And that's okay. It's not our job after all._\n\nUnfortunately, a couple of days before the conference, [Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux) had to stop working on the talk for personal reasons. I felt really bad for her, and I was really sad about it. I kept working on the talk until the last minute. During the last couple of days, I was waking up an hour earlier to work on it. I went to work, and after work, I kept working on it until midnight or 1:00 in the morning. Every day.\n\nThen, I continued to work on it during the conference. The day before MiXiT started, less than 50% of the slides were finished. I kept working on them during the conference, before and after the speakers' dinner, after the mid-conference party, and until 10 minutes before the talk, I was still working on speaker notes.\n\nFinally, my time to speak about the future of developers came. I was tired, not ready, and not even sure whether the talk was going to be good. But I was there, on stage, with some slides, 45 minutes ahead of me, and the trust of the organizers. The room was full, and despite a large \"COMPLET\" (\"full\" in English) sign on the door, people were still getting in, crowding together at the back of the room.\n\n![The room during the talk about the future of developers, with a lot of people standing at the back of the room](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Froom-during-future-of-developer-talk.webp)\n\nI delivered the talk. My biggest fear was rushing through the content by just reading the slides. The opposite happened. I was calm, and I had to speed up the second and third parts to avoid running out of time. And then the talk was over. Whoo, what a relief! During the talk, seeing people nodding or taking photos of the slides was so rewarding because it showed me that we were not completely off track with [Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux). At the end, I got some questions, and afterward, many people came to thank me for the talk. \"Finally a pragmatic talk without tons of bullshit,\" and amazing discussions about the topic that could have lasted for hours.\n\nI highly recommend reading the article by [Fanny Klauk](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffanny-klauk\u002F), who attended the talk and wrote a magnificent article about it, with lots of insights and 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 on stage during his talk about the future of developers, photo from Fanny Klauk](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Festeban-presenting-about-future-of-developer.webp)\n\nJust to give you an idea, the CFP took us somewhere between 30 and 50 hours, and the talk itself took more than 200 hours. Both were done in less than six months. That's a lot. So the next time you see a talk you appreciate, even if you don't have any questions, just say \"thank you, great talk, loved it!\" to the speaker. It means a lot to them.\n\nThis was, by far, the worst idea I had for a talk. Never repeat it. Please, [be prepared before giving a 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\nBut sometimes, unpredictable things happen. That was it, or I would have had to cancel my talk. However, canceling wasn't an option for me. The organizers gave us their trust, and we had to honor it. So, until the last minute, I kept pushing myself to finish the talk.\n\n### MiXiT On Air\n\nLast year, I had the opportunity to be part of the [MiXiT On Air](https:\u002F\u002Fpodcast.ausha.co\u002Fmixit) podcast [to talk about signals in JavaScript](https:\u002F\u002Fpodcast.ausha.co\u002Fmixit\u002Fepisode-12-les-signaux-en-javascript).\n\nThis year, they invited me again to talk about the future of developers. Recording a podcast is a very different experience because it really takes the form of a conversation. You have to be calm and express yourself clearly, with the most interesting and relevant content possible.\n\nThanks to the beautiful work of the MiXiT On Air team, everything ran smoothly. They even thought to keep a meal going, since the recording was during the lunch break. I really enjoyed the experience, especially the duo with [Camille](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcamilleroux\u002F). I hope the episode will be released soon so that you can listen to it. I'll share it on my LinkedIn when it's out. _I'm really thinking about starting my own podcast. Should I?_\n\n![Photo of the MiXiT On Air podcast recording. From left to right: Clément Staron, Hubert Sablonnière, Estéban Soubiran, Charles Bouttaz, and Camille Roux](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fmixit-on-air-podcast-recording.webp)\n\n## What's next\n\nMiXiT 2026, that's a wrap! It was an amazing experience, with a lot of feelings and a lot of work. I really hope the attendees enjoyed it as much as I did. _And slept more than I did!_\n\n![Photo of the TakiTeam at MiXiT, from left to right: Estéban, Marion, Elise, and Victoire](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Ftakiteam-at-mixit.webp)\n\nAs I'm writing this, Devoxx France is coming up in a week. I'll be presenting my talk, \"Au cœur d'une pipeline : démystifions Vite et ses plugins.\" I'm really excited about it and curious to discover the other talks and meet people.\n\nIn June, I'm going to Lille for DevFest Lille with the same team, [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), and [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F). [Laurine](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurine-le-net\u002F) will also join us to talk about micro-frontend. It will be my first DevFest Lille, and I've heard great things about it, so I'm really looking forward to it. It's going to be fun!\n\nThen, at the end of June, I'm going to [Asynconf](https:\u002F\u002Fapp.itsasync.fr\u002Fconference) in Paris. [Graven](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Florenzo-async\u002F) invited me to speak about frontend reactivity. This is a real honor for me, and famous speakers like Mathieu Nebra, co-founder of OpenClassrooms, will be there.\n\nI'm also participating in [Natural Change Leaders Days](https:\u002F\u002Fwww.ncldays.fr\u002F) in June. It's an event organized by [Olivier My](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Foliviermy\u002F) and [Albane Fagot-Veyron](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falbane-fagot-veyron\u002F). It's not a technical conference, and for its first edition, it will be online. It's something new for me, but also really appealing and a little scary. I'll be talking about agency and the importance of staying in motion in a constantly changing world.\n\nSo yeah, upcoming schedule is packed!\n\n![Lyon by night](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Flyon-by-night.webp)\n\n---\n\nJulie, hope you're doing better now!\n",{"title":7474,"description":8153},{"loc":8155,"images":8159},[8160,8161,8162,8163,8164,8165,8166,8167,8168,8169,8170,8171,8172,8173],{"loc":7498},{"loc":7519},{"loc":7659},{"loc":7772},{"loc":7778},{"loc":7819},{"loc":7840},{"loc":7870},{"loc":7897},{"loc":7982},{"loc":8009},{"loc":8061},{"loc":8075},{"loc":8135},"posts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026","Kq_X__P3TKi-bT8gMNUPD37TtKdk9zqv7C_o34SFaOY",{"id":8177,"title":1740,"body":8178,"contentId":8746,"date":8747,"description":8748,"extension":18,"meta":8749,"navigation":20,"path":8750,"rawbody":8751,"robots":23,"schemaOrg":23,"seo":8752,"sitemap":8753,"stem":8764,"__hash__":8765},"posts\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon.md",{"type":7,"value":8179,"toc":8738},[8180,8186,8195,8199,8208,8211,8214,8217,8223,8229,8232,8236,8239,8242,8245,8248,8251,8254,8258,8263,8270,8290,8293,8299,8305,8311,8317,8320,8323,8329,8332,8335,8341,8347,8353,8359,8368,8374,8385,8388,8394,8397,8406,8412,8415,8418,8424,8427,8431,8434,8438,8441,8444,8447,8450,8453,8456,8460,8463,8466,8474,8477],[35,8181,8182,8183],{},"Everything started with a tweet. ",[1217,8184,8185],{},"Things often start with a tweet.",[35,8187,8188,8189,8194],{},"On February 10th, 2026, ",[39,8190,8193],{"href":8191,"rel":8192},"https:\u002F\u002Fx.com\u002Fmistralai\u002Fstatus\u002F2021239601503912409",[77],"Mistral AI announced a worldwide hackathon",". I knew seats were limited, so I applied immediately, hoping to be selected, but with no idea what I would do, and not really knowing what I was getting myself into. Just letting myself be surprised by the adventure.",[51,8196,8198],{"id":8197},"for-the-challenge","For the challenge",[35,8200,8201,8202,8207],{},"I've done a few hackathons before, like the ",[39,8203,8206],{"href":8204,"rel":8205},"https:\u002F\u002Fwww.nuitdelinfo.com\u002F",[77],"Nuit de l'Info"," or more entrepreneurial ones, but the last one was years ago. I learned a lot from those experiences, and I'm sure they helped me grow technically. The first hackathon I did opened my eyes to web development, and I never left.",[35,8209,8210],{},"Today, I know a lot more about programming of course, but also more technologies, more about architecting a project, and more about splitting the work so I can have something working in a short amount of time.",[35,8212,8213],{},"In a hackathon, these skills are decisive: the wrong tech choice or the wrong split can completely ruin your chances of having a working project at the end. Having a working prototype (or proof-of-concept) is mandatory. The idea and how it's presented matter, of course, but without a working prototype, you won't even be able to present in front of a jury.",[35,8215,8216],{},"So I really see this hackathon as a great opportunity to challenge myself on these skills. Does everything I've been learning these last few years really help? Am I now able to ship more than a CRUD app in a hackathon?",[35,8218,8219,8220,215],{},"At the same time, AI completely changed the game. Before, people who barely knew how to code didn't have much of a chance to win a hackathon. Now, everyone can build something with AI. Playing with code is becoming a convenience. But this also means that people who do know how to code can produce much more complex and interesting projects. So ",[6850,8221,8222],{},"AI just raised the bar",[35,8224,8225,8226,215],{},"Don't get me wrong, software engineering isn't dead. At some point, without proper architecture, a clear understanding of the code and how to solve problems, and knowing which tool to use (and when), you won't finish the hackathon. ",[6850,8227,8228],{},"Without any guardrails, AI is just a debt generator",[35,8230,8231],{},"The Sunday before the hackathon, I received an email from Mistral AI with the subject \"Registration approved\". I was in! Still no idea and no team, but happy to be part of this, especially with a company like Mistral AI.",[51,8233,8235],{"id":8234},"the-adventure-begins","The adventure begins",[35,8237,8238],{},"The first step was finding a team. The hackathon started on February 28th, 2026. I received the acceptance email on February 22nd, so I had a week to do it.",[35,8240,8241],{},"Mistral opened a dedicated channel on their Discord server so participants could introduce themselves and find teammates. It took me a couple of days, but I finally introduced myself on Discord. I was the first one in the channel. Surprisingly, only three people (including me) introduced themselves.",[35,8243,8244],{},"Another channel was dedicated to finding teammates. I saw a message from someone named \"Godefroy\" saying they were two physics PhD students looking for a teammate with frontend skills and product experience. He also said they practiced vibe coding. I replied immediately: I was interested in joining them.",[35,8246,8247],{},"They already knew each other, so working with them would be easier. And bringing a strong software engineering background to the team felt like a good complement to their skills, their vibe-coding way of working, and their determination to produce the best project possible. Vibe coding in the right direction, using the perfect tools for the job, can make vibe coders insanely productive.",[35,8249,8250],{},"A couple of hours later, he sent me a Discord message explaining their project idea.",[35,8252,8253],{},"Then, we scheduled a call on Friday to discuss all the project ideas we had in mind and choose the one we wanted to work on. We (they) had a couple of ideas. We ended the call with a short list, and we decided to sleep on it and pick the final one before going to the hackathon. At this point, we were four on the team.",[51,8255,8257],{"id":8256},"for-the-win","For the win",[35,8259,8260],{},[1217,8261,8262],{},"Until we heard the name of our project as the winner of the hackathon, we only had the goal to have fun and to learn.",[35,8264,8265,8266,8269],{},"Before arriving at the hackathon, we had a final discussion on WhatsApp about the project idea we wanted to work on. We chose ",[6850,8267,8268],{},"a real-time system to debunk political TV shows",". Way more complicated than it sounds, but we'll discuss the project later.",[35,8271,8272,8273,8278,8279,1638,8284,8289],{},"The hackathon, organized by ",[39,8274,8277],{"href":8275,"rel":8276},"https:\u002F\u002Fiterate.inc\u002F",[77],"Iterate",", started at 9am on February 28th at Raise's offices in Paris, near Invalides. Breakfast was served, and we had the opportunity to meet the other participants (and our team) for the first time. This is where I met ",[39,8280,8283],{"href":8281,"rel":8282},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Femma-grospellier-3491791a1\u002F",[77],"Emma",[39,8285,8288],{"href":8286,"rel":8287},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgodefroymeynard\u002F",[77],"Godefroy",", the two physics PhD students.",[35,8291,8292],{},"The introduction meeting was at 10am, so we had time to chat and get to know each other. The truth is: we were just eager to open our laptops and start hacking. This was also the moment the fourth member of the team decided to join another team. We were now three, but that didn't change our determination to produce the best project possible.",[35,8294,8295],{},[1690,8296],{"alt":8297,"src":8298},"The introduction meeting.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fintro-meeting.webp",[35,8300,8301],{},[1690,8302],{"alt":8303,"src":8304},"Judging criteria for the hackathon. 20% for technicality, 20% for creativity, 20% for usefulness, 20% for the demo, 20% for track alignment.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fjudging-criteria.webp",[35,8306,8307,8308],{},"After an hour of presentations about the hackathon, the sponsor challenges, and the schedule for the weekend, we could finally go look for a place to work in the building. It was a bit chaotic because there were 100 participants and not much space, but we found a small table in the corner of a room. We spent the next 30 hours there, working on our project. We were ideally placed near the eating area, so we could tell when food was served and be first in line. ",[1217,8309,8310],{},"No need to wait, just grab it and keep working.",[35,8312,8313],{},[1690,8314],{"alt":8315,"src":8316},"Our working place for the weekend.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fworking-place.webp",[35,8318,8319],{},"In the first few hours, we structured the project by defining the boundaries: what we wanted to do, how we wanted to do it, and when we would consider the project \"finished\". We also started splitting the work based on our initial understanding of the problem and our respective skills.",[35,8321,8322],{},"Then, we wrote code until the end of the day. Breaks were short and rare, we were really focused on making it work, and it was harder than we expected. Around 10pm, our brains were starting to get tired and irritated. Godefroy and Emma decided to go to sleep, and that was definitely a good idea. I took a long break and went to the kitchen to eat some pizza. A much-needed break before continuing to work on the project.",[35,8324,8325],{},[1690,8326],{"alt":8327,"src":8328},"Eating some pizza to recharge the batteries.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fpizza.webp",[35,8330,8331],{},"I continued to work on the project from 11pm to 1:30am. I refactored a lot of code, trying to simplify it and make it more robust, hoping it would work for the next day. I finally went home around 2am, just in time to catch the last metro. I wasn't really tired, caught up in the adrenaline of the hackathon, so I kept hacking until 6am. I took a nap until 8am, then went back to the hackathon for the second (and last) day.",[35,8333,8334],{},"We had until 3:30pm to finish the project, record a video to showcase it, and prepare the presentation for the jury. Honestly, that was a lot of work because we couldn't make all the components work together. Each part worked well on its own, but once assembled, it didn't work at all.",[35,8336,8337,8338],{},"No choice: we had to make it work. Things started to work around 2:30pm. We filmed the video at 3:22pm, uploaded it at 3:28pm, and submitted the project at 3:29pm. Yes, one minute before the deadline. ",[1217,8339,8340],{},"Business as usual in a hackathon.",[35,8342,8343,8344],{},"We were really stressed, but we made it, and we were really proud to have something working at the end of the hackathon. ",[1217,8345,8346],{},"We made it work! Thank you Godefroy for finding the final solution, and Emma for preparing both the submission and the presentation.",[35,8348,8349,8350],{},"You think it was the end of the adventure? No! We had a presentation, unfinished, and the text wasn't ready. We only had 3 minutes to pitch our project in front of a jury of experts, and we had to do it in English. So, until we presented at 4:30pm, we polished the slides and prepared the live demo. Things went so fast that we didn't even have time to be nervous or stressed. ",[1217,8351,8352],{},"Oh, and the live demo worked like a charm. No problem at all!",[35,8354,8355],{},[1690,8356],{"alt":8357,"src":8358},"A couple of minutes before the pitch.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fpitch-preparation.webp",[35,8360,8361,8362,8367],{},"Three minutes is short, and we had a lot to say, but those are the rules. Choosing the right words to convince the jury that our project deserved to win. Huge thanks to ",[39,8363,8366],{"href":8364,"rel":8365},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Froland-sosin\u002F",[77],"Roland"," for being our timekeeper and for being kind and reassuring before the pitch.",[35,8369,8370],{},[1690,8371],{"alt":8372,"src":8373},"Pitching in front of the jury.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fpitch.webp",[35,8375,8376],{},[1217,8377,8378,8379,8384],{},"Thank you ",[39,8380,8383],{"href":8381,"rel":8382},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsam-rouan-5a3677110\u002F",[77],"Sam"," for photos, and for the long discussion on Saturday afternoon!",[35,8386,8387],{},"Once every team had presented, all the participants went to a room to wait for the results. They announced 12 finalists who had to give a final presentation in front of the jury and all the participants. They announced the first name, then the second. It was us! Whoa, really? We were finalists? Unexpected, and maybe, just maybe, we had a chance to win this hackathon?",[35,8389,8390,8391],{},"We pitched again in front of everyone as best we could, and once again, we waited for the results. ",[1217,8392,8393],{},"We didn't do the live demo this time; we just presented the video.",[35,8395,8396],{},"Five minutes later, the jury announced the winner. They started by announcing third place, then second, and finally the winner. We heard the name of our project, and we couldn't believe it. We looked at each other, making sure we heard the same thing, and yeah, it was our team.",[35,8398,8399,8402,8403],{},[6850,8400,8401],{},"We won the 2026 Mistral AI Paris Hackathon!"," Unreal and unexpected! ",[1217,8404,8405],{},"We did it! 🫶",[35,8407,8408],{},[1690,8409],{"alt":8410,"src":8411},"The team in front of the main screen showing the logo of our project. From left to right: Estéban, Godefroy, and Emma.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fteam.webp",[35,8413,8414],{},"Now we're going to the international final next week! We'll have the opportunity to present our project live, in front of participants from all the hackathons, to try to win the worldwide prize.",[35,8416,8417],{},"An unbelievable adventure that started with a tweet, and ended with a victory. Really proud of the team, the project we built, and the way we worked together. Love hackathons!",[35,8419,8420],{},[1690,8421],{"alt":8422,"src":8423},"All the participants, jury, and organizers of the hackathon forming a M.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fgroup.webp",[35,8425,8426],{},"Now, it's time to rest a bit and prepare the next step.",[51,8428,8430],{"id":8429},"veristral","Veristral",[35,8432,8433],{},"I talked a lot about the hackathon, the atmosphere, the schedule, and how we managed to make it work, but I haven't said a word about the project we built (and that won the hackathon). So, here it is.",[51,8435,8437],{"id":8436},"the-idea","The idea",[35,8439,8440],{},"Our idea was to build a real-time system to debunk political TV shows. We wanted to be able to display, during the show, while the presenter is talking, whether what they're saying is true or not, whether they're dodging the question, and whether they're contradicting themselves. Automatically, a pop-up would appear on the screen to bring additional information to viewers, help them understand the show better, and make up their own minds about what they're watching.",[35,8442,8443],{},"Fake news can be really harmful, and it spreads really fast, way faster than the truth. Sometimes, journalists debunk it later, even days after the show. Unfortunately, that's too late. The damage is done, and the debunking won't reach the same audience as the fake news. Fake news is a real threat to democracy, and it's part of how external powers can destabilize a country.",[35,8445,8446],{},"The real challenge was the real-time constraint. How, in a couple of milliseconds, can we analyze the presenter's speech, understand it, decide whether it's true or not, enrich it with additional information, and display it on screen? Honestly, it's impossible. For now, we don't have the technology to do it end-to-end.",[35,8448,8449],{},"Voxtral, Mistral AI's real-time speech-to-text (STT) model, was announced as sub-200ms, which is impressive, but then we still need to analyze the text, search for information, and send it back to the screen. We quickly hit the latency wall.",[35,8451,8452],{},"There's another issue. The presenter can say something and then correct themselves a couple of seconds later. If we display information as soon as we have it, we can end up with a really bad user experience, and completely discredit the system.",[35,8454,8455],{},"So we had to find another solution to have a real-time system.",[51,8457,8459],{"id":8458},"the-buffer-to-the-rescue","The buffer to the rescue",[35,8461,8462],{},"Most live TV shows aren't actually live. They're delayed by a couple of seconds, and they have a delay buffer so they can cut the stream if something unexpected happens. This delay can be even higher. A problematic French TV show was forced to have a 45-minute buffer to edit the live stream in real time and remove problematic content before it was broadcast.",[35,8464,8465],{},"This teaches us two things:",[68,8467,8468,8471],{},[71,8469,8470],{},"The concept of buffer between the live stream and the broadcast is already used in live TV shows.",[71,8472,8473],{},"The buffer can be really long.",[35,8475,8476],{},"We can use this buffer for our real-time system. Imagine the following situation. At t0, the presenter is talking about something. The stream is delayed by 30 seconds, so viewers are actually watching what the presenter said at t-30s. During these 30 seconds, we can analyze the speech, understand it, decide whether it's true or not, enrich it with additional information, and send it back to the screen. At t+30s, viewers will see the information on screen at the same time as they hear the presenter talking about it. This way, we can have a real-time system without being limited by the technology.",[8478,8479,8480,8483],"veristral-timeline",{},[35,8481,8482],{},"From a workflow point of view, we have the following, simplified, architecture:",[8484,8485,8490,8493,8502,8506,8509,8512,8515,8522,8528,8531,8536,8545,8551,8554,8561,8564,8590,8593,8600],"workflow",{"title":8486,":steps":8487,":relations":8488,":ui":8489},"Veristral's workflow with the buffer","[\n { key: 'microphone', verb: 'capture audio' },\n { key: 'OBS 1', verb: 'send stream with buffer' },\n { key: 'system', verb: 'analyze and enrich' },\n { key: 'OBS 2', verb: 'send enriched stream' },\n { key: 'live show', verb: 'display to viewers' }\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,8491,8492],{},"This buffer solution gives us a large window to analyze the presenter's speech and schedule the information to be displayed at the right time. We can even use the next sentences to improve context and cancel a fact-check if the presenter corrects themselves. That's really powerful and elegant.",[35,8494,8495,8496,106,8499],{},"You may have heard of AI systems to analyze and debunk TV shows before, but this has always been post-show analysis. Our system changes the game by being able to do it in real time and reach the same audience as the fake news. ",[6850,8497,8498],{},"This is a real game-changer in the fight against fake news",[1217,8500,8501],{},"If you're an AI provider or a media company, and you're interested in this project, please reach out to us.",[1913,8503,8505],{"id":8504},"building-the-system","Building the system",[35,8507,8508],{},"Building such a system in 30 hours is challenging. Without a demonstration, we won't be able to present in front of the jury, so we had to make it work at any cost.",[35,8510,8511],{},"The input of the system is the live stream of the TV show, more specifically, its audio. This audio is sent to a speech-to-text model to be transcribed into text. Then, a (somewhat) complex analysis pipeline starts to analyze the text, understand it, fact-check it, and enrich it with additional information. Finally, the information is sent to the overlay to be displayed on the screen at the right time.",[35,8513,8514],{},"Between the input and the output, we used two OBS instances to simulate a live broadcast environment. The first one captured the live stream (a single camera and a microphone in our case) with a 30-second delay to simulate the buffer. The second one captured the output of the first OBS and added the overlay to get a clean and nice design. This second OBS is the one controlled by the system to display information on screen at the right time.",[35,8516,8517,8518,8521],{},"We really struggle to find a way to simulate the live stream with a buffer. On MacOS, starting OBS twice doesn't work. Using ",[156,8519,8520],{},"open -n -a OBS.app"," to start a second instance works, but the second instance was really buggy. To make them communicate, I tried to use MediaMTX, even FFMpeg, with all the possible broadcast formats, but OBS was successfully recalling the stream from the first OBS without any delay. OBS was too smart for us. Finally, we found a workaround by streaming to Internet from a first computer with a 30 seconds delay and capturing this stream on another computer.\nwe had a little more of 30 seconds of delay due to the network and processing time, so we had to adjust our scheduling system.",[35,8523,8524],{},[1690,8525],{"alt":8526,"src":8527},"Working with OBS.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fobs.webp",[35,8529,8530],{},"Looks like an instable setup but it worked!",[35,8532,8533],{},[1217,8534,8535],{},"Things are going to become technical now, feel free to skip this part if you're not interested in the technical details.",[35,8537,8538,8539,8544],{},"For speech-to-text, we used Mistral AI models, specifically ",[39,8540,8543],{"href":8541,"rel":8542},"https:\u002F\u002Fmistral.ai\u002Fnews\u002Fvoxtral-transcribe-2",[77],"Voxtral Realtime",", released a couple of weeks before the hackathon. We waited until we had a complete sentence before starting the analysis. This part could be improved a lot: we could have a more intelligent system, and we could keep more than one sentence to get more context.",[35,8546,8547,8548],{},"For the hackathon, we just added a step to sanitize the text, because speech-to-text can make mistakes. ",[1217,8549,8550],{},"The more you reduce the delay, the less accurate the transcription is.",[35,8552,8553],{},"For the analysis pipeline, we used a combination of different Mistral AI models assembled in a way to have a workflow.",[35,8555,8556,8557,8560],{},"Everything starts with a ",[6850,8558,8559],{},"router agent"," that receives sentences. This router analyzes them and determines which agents to call next using structured output. This allows us to have a dynamic workflow that can adapt to the content of each sentence.",[35,8562,8563],{},"The following agents are the ones that can run after the router:",[68,8565,8566,8572,8578,8584],{},[71,8567,8568,8571],{},[6850,8569,8570],{},"Stats Agent",": this agent is called if the sentence contains some numbers, like a budget, a percentage, or any quantification. This agent will try to verify if the numbers are correct and to find the source of these numbers.",[71,8573,8574,8577],{},[6850,8575,8576],{},"Rhetoric Agent",": this agent is called if the sentence contains a question. It will analyze if the presenter is trying to escape the question, and if yes, it will try to find the question that is being evaded and to find the answer to this question.",[71,8579,8580,8583],{},[6850,8581,8582],{},"Personal Coherence Agent",": this agent is called if the sentence contains a declaration that can be verified by looking at the previous declarations of the presenter. It will analyze if the presenter is coherent with himself, or if he contradicts himself.",[71,8585,8586,8589],{},[6850,8587,8588],{},"Context Agent",": this agent is called if the sentence contains a declaration about an event, a law, or any contextual information that can be verified. It will try to find the relevant information about this declaration, and to verify if it's correct or not.",[35,8591,8592],{},"Both the Stats Agent and the Context Agent had access to the internet via a web search tool, so they could find relevant information to verify the presenter's claims.",[35,8594,8595,8596,8599],{},"Then, all these agents' outputs (depending on which ones are called) are aggregated in an ",[6850,8597,8598],{},"Editor Agent",". This agent writes a readable, understandable text for viewers. Its output is also structured, and it's what we rendered in OBS via the web overlay.",[8484,8601,8605,8608,8611,8614,8621,8624,8627,8630,8633,8636],{"title":8602,":steps":8603,":relations":8604},"Veristral's analysis workflow","[\n { id: 'router', key: 'router', verb: 'determine next 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: 'write final text' }\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,8606,8607],{},"Honestly, this is a very simple pipeline. It worked well for the hackathon, but in a real-world product, it should be a lot more complex, with more agents and more features. For example, we explored a system where the workflow waits for a signal before going to sleep. That signal would be sent by the STT some time after the initial sentence to provide more context, and to determine whether the presenter corrects themselves or not.",[35,8609,8610],{},"This is part of a Temporal workflow. Once done, we instruct Temporal to sleep until the right time to display the information on screen. We know that time because we know t0 (the moment right before the STT responds with a sentence) and the size of the buffer. For example, if t0 is 14:15:13, we know the workflow has to sleep until 14:15:43. No matter how long the analysis takes, the information will be displayed at the right time.",[35,8612,8613],{},"Before sleeping, we check whether the current time is still before the scheduled time; if not, we discard the information because it's too late to display. Once the workflow wakes up, it sends the information to another dedicated application.",[35,8615,8616,8617,8620],{},"Temporal allows us to parallelize the analysis without complexity, and to schedule information to be displayed at the right time without dealing with a database and a custom scheduler. ",[1217,8618,8619],{},"This could have been done using task scheduling in Laravel, but Emma and Godefroy were more familiar with Python."," Another advantage of Temporal (and the \"trigger a workflow\" approach) is that we completely decouple analysis from scheduling.",[35,8622,8623],{},"The interface between Godefroy, who was working on STT, and Emma, who was working on the analysis, was Temporal: a well-documented and robust system. No need to reinvent the wheel, and that's really important in a hackathon to save time and avoid blocking bugs.",[35,8625,8626],{},"To control the second OBS, I built a Laravel application. This application has an endpoint that receives the information to be displayed from the Temporal workflow. Then, it sends the new data to the frontend using Reverb (Laravel's WebSocket system). Finally, the endpoint triggers a scene change in the second OBS to display the information on screen.",[35,8628,8629],{},"You may struggle to understand that part. OBS is software for live streaming. You have scenes where you can stack different sources, like a camera, a screen capture, or a browser source. The last one is really interesting because it lets us use a web page as a source in OBS.",[35,8631,8632],{},"So, the application I built contains both the backend (to receive the information and send it to the frontend) and a frontend that listens to the WebSocket and updates the content of the web page. This frontend is integrated into the second OBS as a browser source with a transparent background, so you can still see the presenter behind it. We had two scenes in the second OBS: one with a large camera source to show the presenter when there was no information to display, and one with a smaller camera source to make room for the information on screen.",[35,8634,8635],{},"From a deeper point of view, the whole system looks like this:",[8484,8637,8642,8648,8652,8655,8658,8672,8678,8681,8684,8688,8691,8694,8697,8702,8706,8709,8712,8715,8718,8721,8724,8727,8729,8732,8735],{"title":8638,":steps":8639,":relations":8640,":ui":8641},"Veristral's overall workflow","[\n { key: 'microphone', verb: 'capture audio' },\n { key: 'OBS 1', verb: 'send stream with buffer' },\n { key: 'router', verb: 'determine next 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: 'trigger an overlay update' },\n { key: 'app', verb: 'update overlay' },\n { key: 'OBS 2', verb: 'send enriched stream' },\n { key: 'live show', verb: 'display to viewers' }\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,8643,8644,8645],{},"All our source code is available on GitHub: ",[1582,8646],{"repo":8647},"Barbapapazes\u002Fhackathon-paris",[1913,8649,8651],{"id":8650},"quick-note-on-temporal","Quick note on Temporal",[35,8653,8654],{},"Temporal might seem overkill, but it saved our project. Take some time to find a solution to our parallelizing and scheduling problem without Temporal, and you'll quickly realize how complicated it is to build a robust scheduling system that scales. With Temporal, it just works.",[35,8656,8657],{},"We used it for two reasons:",[68,8659,8660,8669],{},[71,8661,8662,8663,8668],{},"Temporal organized a meetup in Paris on November 5th, 2025, and I attended it. One of the talks was \"Combining Temporal's durability with streaming of LLM responses using NATS Jetstream\" by ",[39,8664,8667],{"href":8665,"rel":8666},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolas-moreau-26060614b\u002F",[77],"Nicolas Moreau",", software engineer at Mistral AI.",[71,8670,8671],{},"Scheduling, or waiting for a specific time to do something, is really a workflow thing. Temporal is a workflow engine.",[35,8673,8674,8675],{},"These two points were enough for me to decide to use Temporal. ",[1217,8676,8677],{},"If we use the same technology as Mistral AI, maybe it will make the project easier to understand and feel more interesting and realistic for the jury. I don't know how this influenced the jury's perception, but we talked about it with some Mistral AI engineers who were present.",[35,8679,8680],{},"At work, we use Temporal in our backend infrastructure. This is so powerful that I put everything I can in Temporal.",[35,8682,8683],{},"For personal projects, I'm mostly using Cloudflare Workflows. Under the hood, it's really different, but from a usage point of view, it feels the same. Once you have the power of a workflow, everything in your life becomes a workflow. The funny thing is that I'm currently automating my life, mostly using Cloudflare Workflows, so using them during the hackathon was natural for me.",[1913,8685,8687],{"id":8686},"going-to-the-real-world","Going to the real world",[35,8689,8690],{},"Trusting the system from the beginning could be really hard, so we could easily imagine a setup where the system does most of the job, with a human in the loop to validate, verify, and correct the information before it's displayed on screen. This is a feasible solution as a first step.",[35,8692,8693],{},"But a fully autonomous system is the real goal because removing the human in the loop, especially in political shows, is really important to reduce bias and to make sure all political parties are treated the same way.",[35,8695,8696],{},"Now that the hackathon is over, there are so many things we can do to keep improving the project, and I heard from reliable sources that Mistral AI was interested, and maybe even building a similar system.",[35,8698,8699],{},[1217,8700,8701],{},"We went a little further during the hackathon, but we couldn't make it work. We started building a system that collects everything we fact-checked during the live show and makes it available on a centralized website. That way, viewers could just scan a QR code on the screen to access the full fact-check, since space is limited on the overlay.",[51,8703,8705],{"id":8704},"about-the-extensive-use-of-ai","About the extensive use of AI",[35,8707,8708],{},"During the two days of the hackathon, we extensively used AI to write code.",[35,8710,8711],{},"Emma mainly relied on Gemini Pro through its chat UI to write code, while Godefroy used both Gemini Pro through Antigravity and the chat, and Codex from OpenAI. For my part, I used GitHub Copilot Pro through VS Code, and I mainly used Sonnet 4.6 and GPT-5.3-Codex.",[35,8713,8714],{},"Even with its paid plan, Emma reached the limit of Gemini Pro, and she had to wait a couple of hours before being able to use it again. Godefroy had to alternate between Antigravity and Codex to avoid reaching the limit of one of them. With GitHub Copilot Pro, there is no hard limit since we pay per request. To give you an idea, I made 94 requests in a weekend. The Pro plan includes 300 requests per month, so I burned through about a third of the monthly limit in a weekend.",[35,8716,8717],{},"It's interesting to see how we each used AI differently. Emma used a Jupyter notebook to write the debunking algorithm. She alternated between asking Gemini Pro via chat and copy\u002Fpasting the code into the notebook. Godefroy used Antigravity Agent or Codex to directly edit and commit code. It was a very vibe-coding way of working.",[35,8719,8720],{},"For both of them, I was really impressed by the speed at which they generated working code. Even to set up OBS, Godefroy asked Gemini Pro how to do it, and in a couple of minutes, he had a working configuration.",[35,8722,8723],{},"However, as I mentioned at the beginning of the article, without understanding the code, clear organization, and a well-structured project, things become a mess really quickly. And then restructuring the code to make it cleaner, extensible, and robust becomes a nightmare.",[35,8725,8726],{},"I've spent a lot of time between Saturday and Sunday refactoring both the debunking algorithm and the workflow code to make it more robust for upcoming features.",[1159,8728],{},[35,8730,8731],{},"With AI, losing control of the code is really easy. Is it bad? I still don't know.",[35,8733,8734],{},"Maybe we're just creating a higher-level abstraction above code, where we don't care about the implementation details. Maybe it's okay most of the time. Maybe it's the worst abstraction, and code as a commodity is an illusion for non-software engineers.",[35,8736,8737],{},"One thing I'm pretty sure about, though: creating complex applications still requires good architecture, the ability to split complex problems into smaller ones (adapted to AI capabilities, not human capabilities), and a good understanding of the code.",{"title":13,"searchDepth":14,"depth":14,"links":8739},[8740,8741,8742,8743,8744,8745],{"id":8197,"depth":14,"text":8198},{"id":8234,"depth":14,"text":8235},{"id":8256,"depth":14,"text":8257},{"id":8429,"depth":14,"text":8430},{"id":8436,"depth":14,"text":8437},{"id":8458,"depth":14,"text":8459},"68b658da-5c2a-426b-88ab-534564a6894b","2026-03-02","How we built Veristral and won the 2026 Mistral AI Paris Hackathon: real-time fact-checking for political TV made possible thanks to the broadcast buffer.",{},"\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon","---\ncontentId: 68b658da-5c2a-426b-88ab-534564a6894b\ntitle: \"From Idea to Victory: the 2026 Mistral AI Paris Hackathon\"\ndescription: \"How we built Veristral and won the 2026 Mistral AI Paris Hackathon: real-time fact-checking for political TV made possible thanks to the broadcast buffer.\"\ndate: 2026-03-02\n---\n\nEverything started with a tweet. _Things often start with a tweet._\n\nOn February 10th, 2026, [Mistral AI announced a worldwide hackathon](https:\u002F\u002Fx.com\u002Fmistralai\u002Fstatus\u002F2021239601503912409). I knew seats were limited, so I applied immediately, hoping to be selected, but with no idea what I would do, and not really knowing what I was getting myself into. Just letting myself be surprised by the adventure.\n\n## For the challenge\n\nI've done a few hackathons before, like the [Nuit de l'Info](https:\u002F\u002Fwww.nuitdelinfo.com\u002F) or more entrepreneurial ones, but the last one was years ago. I learned a lot from those experiences, and I'm sure they helped me grow technically. The first hackathon I did opened my eyes to web development, and I never left.\n\nToday, I know a lot more about programming of course, but also more technologies, more about architecting a project, and more about splitting the work so I can have something working in a short amount of time.\n\nIn a hackathon, these skills are decisive: the wrong tech choice or the wrong split can completely ruin your chances of having a working project at the end. Having a working prototype (or proof-of-concept) is mandatory. The idea and how it's presented matter, of course, but without a working prototype, you won't even be able to present in front of a jury.\n\nSo I really see this hackathon as a great opportunity to challenge myself on these skills. Does everything I've been learning these last few years really help? Am I now able to ship more than a CRUD app in a hackathon?\n\nAt the same time, AI completely changed the game. Before, people who barely knew how to code didn't have much of a chance to win a hackathon. Now, everyone can build something with AI. Playing with code is becoming a convenience. But this also means that people who do know how to code can produce much more complex and interesting projects. So **AI just raised the bar**.\n\nDon't get me wrong, software engineering isn't dead. At some point, without proper architecture, a clear understanding of the code and how to solve problems, and knowing which tool to use (and when), you won't finish the hackathon. **Without any guardrails, AI is just a debt generator**.\n\nThe Sunday before the hackathon, I received an email from Mistral AI with the subject \"Registration approved\". I was in! Still no idea and no team, but happy to be part of this, especially with a company like Mistral AI.\n\n## The adventure begins\n\nThe first step was finding a team. The hackathon started on February 28th, 2026. I received the acceptance email on February 22nd, so I had a week to do it.\n\nMistral opened a dedicated channel on their Discord server so participants could introduce themselves and find teammates. It took me a couple of days, but I finally introduced myself on Discord. I was the first one in the channel. Surprisingly, only three people (including me) introduced themselves.\n\nAnother channel was dedicated to finding teammates. I saw a message from someone named \"Godefroy\" saying they were two physics PhD students looking for a teammate with frontend skills and product experience. He also said they practiced vibe coding. I replied immediately: I was interested in joining them.\n\nThey already knew each other, so working with them would be easier. And bringing a strong software engineering background to the team felt like a good complement to their skills, their vibe-coding way of working, and their determination to produce the best project possible. Vibe coding in the right direction, using the perfect tools for the job, can make vibe coders insanely productive.\n\nA couple of hours later, he sent me a Discord message explaining their project idea.\n\nThen, we scheduled a call on Friday to discuss all the project ideas we had in mind and choose the one we wanted to work on. We (they) had a couple of ideas. We ended the call with a short list, and we decided to sleep on it and pick the final one before going to the hackathon. At this point, we were four on the team.\n\n## For the win\n\n_Until we heard the name of our project as the winner of the hackathon, we only had the goal to have fun and to learn._\n\nBefore arriving at the hackathon, we had a final discussion on WhatsApp about the project idea we wanted to work on. We chose **a real-time system to debunk political TV shows**. Way more complicated than it sounds, but we'll discuss the project later.\n\nThe hackathon, organized by [Iterate](https:\u002F\u002Fiterate.inc\u002F), started at 9am on February 28th at Raise's offices in Paris, near Invalides. Breakfast was served, and we had the opportunity to meet the other participants (and our team) for the first time. This is where I met [Emma](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Femma-grospellier-3491791a1\u002F) and [Godefroy](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgodefroymeynard\u002F), the two physics PhD students.\n\nThe introduction meeting was at 10am, so we had time to chat and get to know each other. The truth is: we were just eager to open our laptops and start hacking. This was also the moment the fourth member of the team decided to join another team. We were now three, but that didn't change our determination to produce the best project possible.\n\n![The introduction meeting.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fintro-meeting.webp)\n\n![Judging criteria for the hackathon. 20% for technicality, 20% for creativity, 20% for usefulness, 20% for the demo, 20% for track alignment.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fjudging-criteria.webp)\n\nAfter an hour of presentations about the hackathon, the sponsor challenges, and the schedule for the weekend, we could finally go look for a place to work in the building. It was a bit chaotic because there were 100 participants and not much space, but we found a small table in the corner of a room. We spent the next 30 hours there, working on our project. We were ideally placed near the eating area, so we could tell when food was served and be first in line. _No need to wait, just grab it and keep working._\n\n![Our working place for the weekend.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fworking-place.webp)\n\nIn the first few hours, we structured the project by defining the boundaries: what we wanted to do, how we wanted to do it, and when we would consider the project \"finished\". We also started splitting the work based on our initial understanding of the problem and our respective skills.\n\nThen, we wrote code until the end of the day. Breaks were short and rare, we were really focused on making it work, and it was harder than we expected. Around 10pm, our brains were starting to get tired and irritated. Godefroy and Emma decided to go to sleep, and that was definitely a good idea. I took a long break and went to the kitchen to eat some pizza. A much-needed break before continuing to work on the project.\n\n![Eating some pizza to recharge the batteries.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fpizza.webp)\n\nI continued to work on the project from 11pm to 1:30am. I refactored a lot of code, trying to simplify it and make it more robust, hoping it would work for the next day. I finally went home around 2am, just in time to catch the last metro. I wasn't really tired, caught up in the adrenaline of the hackathon, so I kept hacking until 6am. I took a nap until 8am, then went back to the hackathon for the second (and last) day.\n\nWe had until 3:30pm to finish the project, record a video to showcase it, and prepare the presentation for the jury. Honestly, that was a lot of work because we couldn't make all the components work together. Each part worked well on its own, but once assembled, it didn't work at all.\n\nNo choice: we had to make it work. Things started to work around 2:30pm. We filmed the video at 3:22pm, uploaded it at 3:28pm, and submitted the project at 3:29pm. Yes, one minute before the deadline. _Business as usual in a hackathon._\n\nWe were really stressed, but we made it, and we were really proud to have something working at the end of the hackathon. _We made it work! Thank you Godefroy for finding the final solution, and Emma for preparing both the submission and the presentation._\n\nYou think it was the end of the adventure? No! We had a presentation, unfinished, and the text wasn't ready. We only had 3 minutes to pitch our project in front of a jury of experts, and we had to do it in English. So, until we presented at 4:30pm, we polished the slides and prepared the live demo. Things went so fast that we didn't even have time to be nervous or stressed. _Oh, and the live demo worked like a charm. No problem at all!_\n\n![A couple of minutes before the pitch.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fpitch-preparation.webp)\n\nThree minutes is short, and we had a lot to say, but those are the rules. Choosing the right words to convince the jury that our project deserved to win. Huge thanks to [Roland](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Froland-sosin\u002F) for being our timekeeper and for being kind and reassuring before the pitch.\n\n![Pitching in front of the jury.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fpitch.webp)\n\n_Thank you [Sam](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsam-rouan-5a3677110\u002F) for photos, and for the long discussion on Saturday afternoon!_\n\nOnce every team had presented, all the participants went to a room to wait for the results. They announced 12 finalists who had to give a final presentation in front of the jury and all the participants. They announced the first name, then the second. It was us! Whoa, really? We were finalists? Unexpected, and maybe, just maybe, we had a chance to win this hackathon?\n\nWe pitched again in front of everyone as best we could, and once again, we waited for the results. _We didn't do the live demo this time; we just presented the video._\n\nFive minutes later, the jury announced the winner. They started by announcing third place, then second, and finally the winner. We heard the name of our project, and we couldn't believe it. We looked at each other, making sure we heard the same thing, and yeah, it was our team.\n\n**We won the 2026 Mistral AI Paris Hackathon!** Unreal and unexpected! _We did it! 🫶_\n\n![The team in front of the main screen showing the logo of our project. From left to right: Estéban, Godefroy, and Emma.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fteam.webp)\n\nNow we're going to the international final next week! We'll have the opportunity to present our project live, in front of participants from all the hackathons, to try to win the worldwide prize.\n\nAn unbelievable adventure that started with a tweet, and ended with a victory. Really proud of the team, the project we built, and the way we worked together. Love hackathons!\n\n![All the participants, jury, and organizers of the hackathon forming a M.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fgroup.webp)\n\nNow, it's time to rest a bit and prepare the next step.\n\n## Veristral\n\nI talked a lot about the hackathon, the atmosphere, the schedule, and how we managed to make it work, but I haven't said a word about the project we built (and that won the hackathon). So, here it is.\n\n## The idea\n\nOur idea was to build a real-time system to debunk political TV shows. We wanted to be able to display, during the show, while the presenter is talking, whether what they're saying is true or not, whether they're dodging the question, and whether they're contradicting themselves. Automatically, a pop-up would appear on the screen to bring additional information to viewers, help them understand the show better, and make up their own minds about what they're watching.\n\nFake news can be really harmful, and it spreads really fast, way faster than the truth. Sometimes, journalists debunk it later, even days after the show. Unfortunately, that's too late. The damage is done, and the debunking won't reach the same audience as the fake news. Fake news is a real threat to democracy, and it's part of how external powers can destabilize a country.\n\nThe real challenge was the real-time constraint. How, in a couple of milliseconds, can we analyze the presenter's speech, understand it, decide whether it's true or not, enrich it with additional information, and display it on screen? Honestly, it's impossible. For now, we don't have the technology to do it end-to-end.\n\nVoxtral, Mistral AI's real-time speech-to-text (STT) model, was announced as sub-200ms, which is impressive, but then we still need to analyze the text, search for information, and send it back to the screen. We quickly hit the latency wall.\n\nThere's another issue. The presenter can say something and then correct themselves a couple of seconds later. If we display information as soon as we have it, we can end up with a really bad user experience, and completely discredit the system.\n\nSo we had to find another solution to have a real-time system.\n\n## The buffer to the rescue\n\nMost live TV shows aren't actually live. They're delayed by a couple of seconds, and they have a delay buffer so they can cut the stream if something unexpected happens. This delay can be even higher. A problematic French TV show was forced to have a 45-minute buffer to edit the live stream in real time and remove problematic content before it was broadcast.\n\nThis teaches us two things:\n\n1. The concept of buffer between the live stream and the broadcast is already used in live TV shows.\n2. The buffer can be really long.\n\nWe can use this buffer for our real-time system. Imagine the following situation. At t0, the presenter is talking about something. The stream is delayed by 30 seconds, so viewers are actually watching what the presenter said at t-30s. During these 30 seconds, we can analyze the speech, understand it, decide whether it's true or not, enrich it with additional information, and send it back to the screen. At t+30s, viewers will see the information on screen at the same time as they hear the presenter talking about it. This way, we can have a real-time system without being limited by the technology.\n\n\u003CVeristralTimeline \u002F>\n\nFrom a workflow point of view, we have the following, simplified, architecture:\n\n\u003CWorkflow\n  title=\"Veristral's workflow with the buffer\"\n  :steps=\"[\n    { key: 'microphone', verb: 'capture audio' },\n    { key: 'OBS 1', verb: 'send stream with buffer' },\n    { key: 'system', verb: 'analyze and enrich' },\n    { key: 'OBS 2', verb: 'send enriched stream' },\n    { key: 'live show', verb: 'display to viewers' }\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\nThis buffer solution gives us a large window to analyze the presenter's speech and schedule the information to be displayed at the right time. We can even use the next sentences to improve context and cancel a fact-check if the presenter corrects themselves. That's really powerful and elegant.\n\nYou may have heard of AI systems to analyze and debunk TV shows before, but this has always been post-show analysis. Our system changes the game by being able to do it in real time and reach the same audience as the fake news. **This is a real game-changer in the fight against fake news**. _If you're an AI provider or a media company, and you're interested in this project, please reach out to us._\n\n### Building the system\n\nBuilding such a system in 30 hours is challenging. Without a demonstration, we won't be able to present in front of the jury, so we had to make it work at any cost.\n\nThe input of the system is the live stream of the TV show, more specifically, its audio. This audio is sent to a speech-to-text model to be transcribed into text. Then, a (somewhat) complex analysis pipeline starts to analyze the text, understand it, fact-check it, and enrich it with additional information. Finally, the information is sent to the overlay to be displayed on the screen at the right time.\n\nBetween the input and the output, we used two OBS instances to simulate a live broadcast environment. The first one captured the live stream (a single camera and a microphone in our case) with a 30-second delay to simulate the buffer. The second one captured the output of the first OBS and added the overlay to get a clean and nice design. This second OBS is the one controlled by the system to display information on screen at the right time.\n\nWe really struggle to find a way to simulate the live stream with a buffer. On MacOS, starting OBS twice doesn't work. Using `open -n -a OBS.app` to start a second instance works, but the second instance was really buggy. To make them communicate, I tried to use MediaMTX, even FFMpeg, with all the possible broadcast formats, but OBS was successfully recalling the stream from the first OBS without any delay. OBS was too smart for us. Finally, we found a workaround by streaming to Internet from a first computer with a 30 seconds delay and capturing this stream on another computer.\nwe had a little more of 30 seconds of delay due to the network and processing time, so we had to adjust our scheduling system.\n\n![Working with OBS.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fobs.webp)\n\nLooks like an instable setup but it worked!\n\n_Things are going to become technical now, feel free to skip this part if you're not interested in the technical details._\n\nFor speech-to-text, we used Mistral AI models, specifically [Voxtral Realtime](https:\u002F\u002Fmistral.ai\u002Fnews\u002Fvoxtral-transcribe-2), released a couple of weeks before the hackathon. We waited until we had a complete sentence before starting the analysis. This part could be improved a lot: we could have a more intelligent system, and we could keep more than one sentence to get more context.\n\nFor the hackathon, we just added a step to sanitize the text, because speech-to-text can make mistakes. _The more you reduce the delay, the less accurate the transcription is._\n\nFor the analysis pipeline, we used a combination of different Mistral AI models assembled in a way to have a workflow.\n\nEverything starts with a **router agent** that receives sentences. This router analyzes them and determines which agents to call next using structured output. This allows us to have a dynamic workflow that can adapt to the content of each sentence.\n\nThe following agents are the ones that can run after the router:\n\n1. **Stats Agent**: this agent is called if the sentence contains some numbers, like a budget, a percentage, or any quantification. This agent will try to verify if the numbers are correct and to find the source of these numbers.\n2. **Rhetoric Agent**: this agent is called if the sentence contains a question. It will analyze if the presenter is trying to escape the question, and if yes, it will try to find the question that is being evaded and to find the answer to this question.\n3. **Personal Coherence Agent**: this agent is called if the sentence contains a declaration that can be verified by looking at the previous declarations of the presenter. It will analyze if the presenter is coherent with himself, or if he contradicts himself.\n4. **Context Agent**: this agent is called if the sentence contains a declaration about an event, a law, or any contextual information that can be verified. It will try to find the relevant information about this declaration, and to verify if it's correct or not.\n\nBoth the Stats Agent and the Context Agent had access to the internet via a web search tool, so they could find relevant information to verify the presenter's claims.\n\nThen, all these agents' outputs (depending on which ones are called) are aggregated in an **Editor Agent**. This agent writes a readable, understandable text for viewers. Its output is also structured, and it's what we rendered in OBS via the web overlay.\n\n\u003CWorkflow\n  title=\"Veristral's analysis workflow\"\n  :steps=\"[\n    { id: 'router', key: 'router', verb: 'determine next 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: 'write final text' }\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\nHonestly, this is a very simple pipeline. It worked well for the hackathon, but in a real-world product, it should be a lot more complex, with more agents and more features. For example, we explored a system where the workflow waits for a signal before going to sleep. That signal would be sent by the STT some time after the initial sentence to provide more context, and to determine whether the presenter corrects themselves or not.\n\nThis is part of a Temporal workflow. Once done, we instruct Temporal to sleep until the right time to display the information on screen. We know that time because we know t0 (the moment right before the STT responds with a sentence) and the size of the buffer. For example, if t0 is 14:15:13, we know the workflow has to sleep until 14:15:43. No matter how long the analysis takes, the information will be displayed at the right time.\n\nBefore sleeping, we check whether the current time is still before the scheduled time; if not, we discard the information because it's too late to display. Once the workflow wakes up, it sends the information to another dedicated application.\n\nTemporal allows us to parallelize the analysis without complexity, and to schedule information to be displayed at the right time without dealing with a database and a custom scheduler. _This could have been done using task scheduling in Laravel, but Emma and Godefroy were more familiar with Python._ Another advantage of Temporal (and the \"trigger a workflow\" approach) is that we completely decouple analysis from scheduling.\n\nThe interface between Godefroy, who was working on STT, and Emma, who was working on the analysis, was Temporal: a well-documented and robust system. No need to reinvent the wheel, and that's really important in a hackathon to save time and avoid blocking bugs.\n\nTo control the second OBS, I built a Laravel application. This application has an endpoint that receives the information to be displayed from the Temporal workflow. Then, it sends the new data to the frontend using Reverb (Laravel's WebSocket system). Finally, the endpoint triggers a scene change in the second OBS to display the information on screen.\n\nYou may struggle to understand that part. OBS is software for live streaming. You have scenes where you can stack different sources, like a camera, a screen capture, or a browser source. The last one is really interesting because it lets us use a web page as a source in OBS.\n\nSo, the application I built contains both the backend (to receive the information and send it to the frontend) and a frontend that listens to the WebSocket and updates the content of the web page. This frontend is integrated into the second OBS as a browser source with a transparent background, so you can still see the presenter behind it. We had two scenes in the second OBS: one with a large camera source to show the presenter when there was no information to display, and one with a smaller camera source to make room for the information on screen.\n\nFrom a deeper point of view, the whole system looks like this:\n\n\u003CWorkflow\n  title=\"Veristral's overall workflow\"\n  :steps=\"[\n    { key: 'microphone', verb: 'capture audio' },\n    { key: 'OBS 1', verb: 'send stream with buffer' },\n    { key: 'router', verb: 'determine next 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: 'trigger an overlay update' },\n    { key: 'app', verb: 'update overlay' },\n    { key: 'OBS 2', verb: 'send enriched stream' },\n    { key: 'live show', verb: 'display to viewers' }\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\nAll our source code is available on GitHub: \u003CGitHubLink repo=\"Barbapapazes\u002Fhackathon-paris\" \u002F>\n\n### Quick note on Temporal\n\nTemporal might seem overkill, but it saved our project. Take some time to find a solution to our parallelizing and scheduling problem without Temporal, and you'll quickly realize how complicated it is to build a robust scheduling system that scales. With Temporal, it just works.\n\nWe used it for two reasons:\n\n1. Temporal organized a meetup in Paris on November 5th, 2025, and I attended it. One of the talks was \"Combining Temporal's durability with streaming of LLM responses using NATS Jetstream\" by [Nicolas Moreau](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolas-moreau-26060614b\u002F), software engineer at Mistral AI.\n2. Scheduling, or waiting for a specific time to do something, is really a workflow thing. Temporal is a workflow engine.\n\nThese two points were enough for me to decide to use Temporal. _If we use the same technology as Mistral AI, maybe it will make the project easier to understand and feel more interesting and realistic for the jury. I don't know how this influenced the jury's perception, but we talked about it with some Mistral AI engineers who were present._\n\nAt work, we use Temporal in our backend infrastructure. This is so powerful that I put everything I can in Temporal.\n\nFor personal projects, I'm mostly using Cloudflare Workflows. Under the hood, it's really different, but from a usage point of view, it feels the same. Once you have the power of a workflow, everything in your life becomes a workflow. The funny thing is that I'm currently automating my life, mostly using Cloudflare Workflows, so using them during the hackathon was natural for me.\n\n### Going to the real world\n\nTrusting the system from the beginning could be really hard, so we could easily imagine a setup where the system does most of the job, with a human in the loop to validate, verify, and correct the information before it's displayed on screen. This is a feasible solution as a first step.\n\nBut a fully autonomous system is the real goal because removing the human in the loop, especially in political shows, is really important to reduce bias and to make sure all political parties are treated the same way.\n\nNow that the hackathon is over, there are so many things we can do to keep improving the project, and I heard from reliable sources that Mistral AI was interested, and maybe even building a similar system.\n\n_We went a little further during the hackathon, but we couldn't make it work. We started building a system that collects everything we fact-checked during the live show and makes it available on a centralized website. That way, viewers could just scan a QR code on the screen to access the full fact-check, since space is limited on the overlay._\n\n## About the extensive use of AI\n\nDuring the two days of the hackathon, we extensively used AI to write code.\n\nEmma mainly relied on Gemini Pro through its chat UI to write code, while Godefroy used both Gemini Pro through Antigravity and the chat, and Codex from OpenAI. For my part, I used GitHub Copilot Pro through VS Code, and I mainly used Sonnet 4.6 and GPT-5.3-Codex.\n\nEven with its paid plan, Emma reached the limit of Gemini Pro, and she had to wait a couple of hours before being able to use it again. Godefroy had to alternate between Antigravity and Codex to avoid reaching the limit of one of them. With GitHub Copilot Pro, there is no hard limit since we pay per request. To give you an idea, I made 94 requests in a weekend. The Pro plan includes 300 requests per month, so I burned through about a third of the monthly limit in a weekend.\n\nIt's interesting to see how we each used AI differently. Emma used a Jupyter notebook to write the debunking algorithm. She alternated between asking Gemini Pro via chat and copy\u002Fpasting the code into the notebook. Godefroy used Antigravity Agent or Codex to directly edit and commit code. It was a very vibe-coding way of working.\n\nFor both of them, I was really impressed by the speed at which they generated working code. Even to set up OBS, Godefroy asked Gemini Pro how to do it, and in a couple of minutes, he had a working configuration.\n\nHowever, as I mentioned at the beginning of the article, without understanding the code, clear organization, and a well-structured project, things become a mess really quickly. And then restructuring the code to make it cleaner, extensible, and robust becomes a nightmare.\n\nI've spent a lot of time between Saturday and Sunday refactoring both the debunking algorithm and the workflow code to make it more robust for upcoming features.\n\n---\n\nWith AI, losing control of the code is really easy. Is it bad? I still don't know.\n\nMaybe we're just creating a higher-level abstraction above code, where we don't care about the implementation details. Maybe it's okay most of the time. Maybe it's the worst abstraction, and code as a commodity is an illusion for non-software engineers.\n\nOne thing I'm pretty sure about, though: creating complex applications still requires good architecture, the ability to split complex problems into smaller ones (adapted to AI capabilities, not human capabilities), and a good understanding of the code.\n",{"title":1740,"description":8748},{"loc":8750,"images":8754},[8755,8756,8757,8758,8759,8760,8761,8762,8763],{"loc":8298},{"loc":8304},{"loc":8316},{"loc":8328},{"loc":8358},{"loc":8373},{"loc":8411},{"loc":8423},{"loc":8527},"posts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon","RKeNL6VWb4sEKZRSxbVStG_9i_sMNVTvRYZiLmqPOP0",{"id":8767,"title":8768,"body":8769,"contentId":9399,"date":9400,"description":9401,"extension":18,"meta":9402,"navigation":20,"path":9403,"rawbody":9404,"robots":23,"schemaOrg":23,"seo":9405,"sitemap":9406,"stem":9421,"__hash__":9422},"posts\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action.md","2025: A Turning Point from Slow Steps to Clear Action",{"type":7,"value":8770,"toc":9391},[8771,8774,8777,8789,8797,8803,8806,8844,8847,8851,8857,8877,8880,8888,8894,8899,8902,8908,8912,8919,8925,8931,8934,8937,8942,8946,8954,8959,8962,8968,8976,8982,8985,8995,9001,9012,9018,9021,9043,9049,9055,9061,9067,9070,9076,9140,9143,9149,9155,9158,9169,9179,9183,9197,9207,9214,9228,9231,9235,9238,9241,9277,9280,9307,9310,9327,9330,9333,9337,9340,9346,9355,9361,9364,9367,9370,9373,9376,9379,9382,9385],[35,8772,8773],{},"Hey,",[35,8775,8776],{},"Hope you're doing well on this last day of 2025.",[35,8778,8779,8780],{},"This is the last post of the year, and I wanted to take a moment to look back. It's the third year I've been writing this kind of post, following ",[3029,8781,1638,8783,8788],{"id":8782},"d86a42c6-47d0-4078-b33c-805f44046b17",[39,8784,8787],{"href":8785,"rel":8786},"https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Frecap-2023",[77],"Récap 2023"," (only available in French). I find them important for at least two reasons:",[68,8790,8791,8794],{},[71,8792,8793],{},"When you work on long-term goals, it's easy to lose track of the progress you've made and everything you've accomplished. You always want to do more. Taking the time to record the journey helps you see all the progress made in a single year.",[71,8795,8796],{},"From time to time, I love going back to these posts to see how my goals, my hopes, and my desires have evolved over time.",[35,8798,8799],{},[1690,8800],{"alt":8801,"src":8802},"Estéban during a sunset between trees in front of the sea.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fsunset-trees-sea.webp",[35,8804,8805],{},"Before jumping into the details, I want to talk about numbers. I love numbers because they can give an overview of the year at a glance.",[1576,8807,8808,8811,8814,8817,8820,8823,8826,8829,8832,8835,8838,8841],{},[71,8809,8810],{},"20 posts written, including this one. This is a new post every 2.7 weeks.",[71,8812,8813],{},"1 series of 5 posts about AI agents and an MCP server. Fewer series than last year, but it's so much work, and I've been busy giving talks.",[71,8815,8816],{},"19 talks given at 11 different events (6 conferences and 5 meetups). The talk \"La réactivité et les signaux : démystifions la magie du frontend\" (Reactivity and Signals: Demystifying the Frontend's Magic) has been given 8 times this year.",[71,8818,8819],{},"9 cities visited for conferences and meetups: Paris, Bagneux, Rennes, Nantes, Bordeaux, Toulouse, Lyon, Dijon, and Strasbourg.",[71,8821,8822],{},"+1,000 people attended my talks this year, with a peak of +200 people for my talk at Devoxx France about Reactivity and Signals.",[71,8824,8825],{},"+711 euros spent on trains and hotels for meetups and conferences. I was honestly expecting way more. This represents +3,000 km, +35 hours of train (without delays), and 9 nights in hotels.",[71,8827,8828],{},"1,518 followers on X at the end of 2025 and 8,153 posts shared.",[71,8830,8831],{},"1,800 followers on LinkedIn at the end of 2025. Compared to last year, with a growth of 63.7%, this is more than 700 new followers this year. I've reached +34,000 members for a total of +182,000 impressions over 124 posts. I definitely wrote a lot on LinkedIn this year.",[71,8833,8834],{},"+240 followers on Twitch for +140 hours of live streaming. It's just for fun, but I really enjoy it. I'll continue in 2026 for sure.",[71,8836,8837],{},"+1,600 hours spent on VS Code, with a peak of 14 hours and 1 minute on January 24, 2025 (which is way too much to have a healthy life). This represents an average of 4.4 hours per day, 7 days a week.",[71,8839,8840],{},"143 reactions and 45 comments on my posts on soubiran.dev.",[71,8842,8843],{},"25 different pastries created. More about it in 2026.",[35,8845,8846],{},"That's a lot of numbers for a single year. In hindsight, it's amazing how much is possible to achieve in just 12 months.",[51,8848,8850],{"id":8849},"back-to-2025-goals","Back to 2025 goals",[35,8852,8853,8854],{},"So many things happened in 2025. From changing jobs to giving talks, I haven't stopped for a second. ",[1217,8855,8856],{},"Sometimes, I went too fast and too far.",[35,8858,8859,8860,8865,8866,8869,8870,8873,8874],{},"Last year, I bought the course ",[39,8861,8864],{"href":8862,"rel":8863},"https:\u002F\u002Fthreejs-journey.com\u002F",[77],"Three.js Journey",". I really wanted to learn 3D, but I never found the time to do it. ",[1217,8867,8868],{},"Maybe in 2026."," My talks for Vue.js Amsterdam weren't accepted, but I submitted new ones this year. ",[1217,8871,8872],{},"This is the fourth time. Fingers crossed for 2026."," Finally, I didn't take the time to learn more about infrastructure. However, I started a project moving in this direction. ",[1217,8875,8876],{},"More about it in the coming months.",[35,8878,8879],{},"As every year, a lot of unexpected things happened. I think it's important to embrace them and to adapt the roadmap as you go.",[1576,8881,8882],{},[71,8883,8884,8885],{},"I have discovered a passion for baking. Every weekend, I try a new recipe, buy new equipment and new books. This keeps me away from screens and coding. It also gave me the opportunity to bake for Jordan, a close friend's birthday, and for Thanksgiving with amazing people. ",[1217,8886,8887],{},"My colleagues really appreciate it, as I often bring pastries on Mondays.",[35,8889,8890],{},[1690,8891],{"alt":8892,"src":8893},"Fig tartlet with white chocolate and port syrup.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Ffig-tartlet.webp",[1576,8895,8896],{},[71,8897,8898],{},"I met amazing people, new friends, with whom I'm building projects, conferences, and discovering new things. Putting humans at the center of everything, creating moments together, has become really important to me.",[35,8900,8901],{},"With a group of friends, we started playing darts regularly. From a simple game of darts, it's truly amazing how many things can happen, from playing with unknown people to meet new friends, and create memorable moments together.",[35,8903,8904],{},[1690,8905],{"alt":8906,"src":8907},"A dart in a dart.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdart.webp",[51,8909,8911],{"id":8910},"life-changes","Life changes",[35,8913,8914,8915,215],{},"In December 2024, I started a new job within a company called MaiaSpace. MaiaSpace is building a space launcher called Maia. I was working as a software engineer in the avionics team, building tools to automate some of the work done by engineers. I met truly amazing people and learned a lot. It was ",[39,8916,8918],{"href":8917},"\u002Fposts\u002Fstarting-a-new-chapter-new-ambitions-and-new-job","an incredible opportunity that ended a year later",[35,8920,8921,8922],{},"After weeks of thinking, I decided to leave MaiaSpace to join a new company called Takima. It's a consulting firm specializing in Java and infrastructure. This is a big change for me, as I know nothing about Java, so I have everything to learn. This is a really long-term investment, but I believe that learning Java will open many doors. Currently, it's just a lot of work. ",[1217,8923,8924],{},"But getting out of the comfort zone is the only way to grow.",[35,8926,8927,8928],{},"This job change also involves moving to a new home, closer to the office and my friends. In January 2026, I'll move to a new apartment, closer to Paris. It's smaller than the current one, but it's way more practical for my daily life. ",[1217,8929,8930],{},"Less time commuting means more time for myself and my projects.",[35,8932,8933],{},"In April 2025, I started the first stream of the year. While I was in Switzerland, I started streaming on Twitch, but a performance issue with my hardware stopped me after a few streams. After buying a new computer, the streams were back. It was still heavily experimental, as I'm really trying to find my style in this format. Since September, I haven't been very active. Conferences took up a lot of my time, and issues at work made it hard to find the motivation to stream. In 2026, I should be more consistent, because I've automated a lot of things.",[35,8935,8936],{},"Speaking of automation, I'm revamping my entire content creation workflow to automate as much as possible. AI will be used for boring tasks, never for creative ones. These changes should reduce the time spent creating content, allowing me to focus on what matters most.",[35,8938,8939],{},[1217,8940,8941],{},"Organizing things well is so hard.",[51,8943,8945],{"id":8944},"meetups-and-conferences","Meetups and conferences",[35,8947,8948,8949,8953],{},"In my ",[39,8950,8952],{"href":8951},"\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next","last year recap",", I said:",[939,8955,8956],{},[35,8957,8958],{},"I really hope this year will be as rich as 2024",[35,8960,8961],{},"My hope has been more than fulfilled. With 19 talks given in 2025, compared to 6 in 2024, and 1 in 2023, this year has been intense. Really intense.",[35,8963,8964,8965],{},"19 talks at 11 different events means that I spoke almost every five weeks. It's a lot, and I never imagined reaching this number so quickly. The growth is honestly crazy, and I secretly hope to slow it down in 2026 to find a better balance. ",[1217,8966,8967],{},"Keeping the same pace in 2026 would mean giving more than 30 talks, which is honestly impossible.",[35,8969,8970,8971,8975],{},"The year started with ",[39,8972,8974],{"href":8973},".\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special","Devoxx France in April",", where I had the opportunity to give two talks: \"La réactivité et les signaux : démystifions la magie du frontend\" (Reactivity and Signals: Demystifying the Frontend's Magic) and \"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). For a conference of this size, with more than 4,500 attendees and only 281 talks accepted from more than 1,100 submissions, it's really an honor to be selected twice.",[35,8977,8978],{},[1690,8979],{"alt":8980,"src":8981},"Esteban Soubiran presenting at Devoxx France 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdevoxx-france-2025.webp",[35,8983,8984],{},"These two talks were submitted before Christmas 2024, but I only received the acceptance in February 2025. After that, I started working hard to prepare them. It took me two months to have something ready. Understanding Alien Signals took a lot of time, and to make it understandable for a large audience, I had to be truly creative. Today I'm really proud of the result, and the overall feedback for both talks has been really positive.",[35,8986,8987,8988,8990,8991,8994],{},"Two weeks later, I was at ",[39,8989,7484],{"href":7487}," in Lyon to also give two talks. During this conference, I met a lot of new and amazing people. I really appreciated the smaller size of the conference compared to Devoxx France. It's easier to connect with people, and there is less stress when going to see talks. ",[1217,8992,8993],{},"Will I have a seat?"," For the anecdote, to have a seat at Devoxx France for the keynotes, I had to arrive 1 hour before the start. I thought it was the same for MiXiT, so I arrived 45 minutes before the start. Completely unnecessary, because everyone could enter the main room without any problem.",[35,8996,8997],{},[1690,8998],{"alt":8999,"src":9000},"Esteban Soubiran presenting at MiXiT 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fmixit-2025.webp",[35,9002,9003,9004,9008,9009],{},"Then, in June, I went to Rennes for the ",[39,9005,9007],{"href":9006},".\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker","BreizhCamp",". I think you can guess what happened: I gave two talks again, the same ones as at Devoxx France and MiXiT. ",[1217,9010,9011],{},"You might wonder why I give the same talks again and again. After spending two months preparing them, you want to give them as much as possible, to reach the largest audience possible. Also, reusing the same talks allows me to improve both the content and the delivery. I'm now comfortable with them, and I can really enjoy giving them. The first time, it's more stress than pleasure.",[35,9013,9014],{},[1690,9015],{"alt":9016,"src":9017},"Esteban Soubiran presenting at BreizhCamp 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fbreizhcamp-2025.webp",[35,9019,9020],{},"After the summer break, and up to December, I went from one conference to another.",[1576,9022,9023,9030,9037,9040],{},[71,9024,9025,9029],{},[39,9026,9028],{"href":9027},".\u002Fmy-adventure-to-nantes-for-europes-largest-devfest","DevFest Nantes, October 17, 2025",", where I gave my talk on JavaScript packages. This conference is the largest DevFest in Europe. The party was really pleasant, and the closing keynote, an improvisational theatre performance, was really exceptional (and so original). This could be my favorite conference of the year.",[71,9031,9032,9036],{},[39,9033,9035],{"href":9034},".\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux","BDX I\u002FO, November 7, 2025",", where I gave my two talks. The conference was really well organized, and the venue was superb. I took the opportunity to visit the NuxtLabs office in Bordeaux and meet Sébastien and Benjamin.",[71,9038,9039],{},"DevFest Toulouse, November 13, 2025, where I gave my talk on reactivity and signals for the seventh time this year. The speaker dinner was really exceptional. We were in a superb restaurant in the city center.",[71,9041,9042],{},"DevFest Dijon, December 5, 2025, where I gave my talk on reactivity and signals for the eighth time this year. The speaker gift was so nice: a bag with local products from Dijon and the region.",[35,9044,9045],{},[1690,9046],{"alt":9047,"src":9048},"Estéban presenting at DevFest Nantes 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdevfest-nantes-2025.webp",[35,9050,9051],{},[1690,9052],{"alt":9053,"src":9054},"Estéban presenting at BDX I\u002FO 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fbdx-io-2025.webp",[35,9056,9057],{},[1690,9058],{"alt":9059,"src":9060},"Estéban, Sébastien, and Benjamin at NuxtLabs.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fnuxtlabs-bordeaux-2025.webp",[35,9062,9063],{},[1690,9064],{"alt":9065,"src":9066},"Estéban's selfie at DevFest Dijon 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdevfest-dijon-2025.webp",[35,9068,9069],{},"The speaker world is quite small, and you often meet the same people again and again. This alleviated the loneliness I felt in 2023 when I gave my first talk at Devoxx France.",[35,9071,9072,9073],{},"Before and in between these conferences, I gave several talks at meetups in Paris and Strasbourg. ",[1217,9074,9075],{},"Yes, I went and came back to Strasbourg just for an evening talk. Unreasonable, but fun.",[1576,9077,9078,9086,9094,9102,9110,9117,9125,9132],{},[71,9079,9080,9081],{},"Paris TypeScript, January 21, 2025, ",[39,9082,9085],{"href":9083,"rel":9084},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-01-21\u002Fparis-typescript\u002F1",[77],"Devins full-stack avec TypeScript",[71,9087,9088,9089],{},"Node.js Paris, March 5, 2025, ",[39,9090,9093],{"href":9091,"rel":9092},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-03-05\u002Fnodejs-paris\u002F1",[77],"Vous n'avez pas besoin de ces nouvelles API Node.js",[71,9095,9096,9097],{},"HumanTalks Paris, April 8, 2025, ",[39,9098,9101],{"href":9099,"rel":9100},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-04-08\u002Fhuman-talks\u002F1",[77],"Ship Nuxt apps that scale.",[71,9103,9104,9105],{},"HumanTalks Paris, September 9, 2025, ",[39,9106,9109],{"href":9107,"rel":9108},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-09-09\u002Fhumantalks-paris\u002F1",[77],"Inalia: Un Anneau pour les gouverner tous",[71,9111,9112,9113],{},"Node.js Paris, September 17, 2025, ",[39,9114,9109],{"href":9115,"rel":9116},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-09-17\u002Fnodejs-paris\u002F1",[77],[71,9118,9119,9120],{},"Paris TypeScript, October 14, 2025, ",[39,9121,9124],{"href":9122,"rel":9123},"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,9126,9127,9128],{},"StrasbourgJS, October 21, 2025, ",[39,9129,9124],{"href":9130,"rel":9131},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-10-21\u002Fstrasbourgjs\u002F1",[77],[71,9133,9134,9135],{},"Paris TypeScript, December 9, 2025, ",[39,9136,9139],{"href":9137,"rel":9138},"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,9141,9142],{},"I really enjoyed giving talks at meetups. The atmosphere is way more relaxed than at conferences, and it's easier to interact with the audience. But make no mistake: it can be more stressful, because the audience is really close to you, and you can easily see them.",[35,9144,9145],{},[1690,9146],{"alt":9147,"src":9148},"Estéban presenting at StrasbourgJS meetup in October 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fstrasbourgjs-october-2025.webp",[35,9150,9151],{},[1690,9152],{"alt":9153,"src":9154},"Estéban's selfie at Paris TypeScript meetup in December 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fparis-typescript-december-2025.webp",[35,9156,9157],{},"This was an exceptional year for talks and conferences, surpassing all my expectations. Thank you to all the people I met and all the organizers who trusted me to give talks at their events.",[35,9159,9160,9161,9165,9166],{},"Find all my talks on my ",[39,9162,9164],{"href":9163},"..\u002Ftalks","talks page",". I added a map showing all the places where I gave talks since 2023 and some statistics about my talks. You can find the slides on ",[1582,9167,215],{"repo":9168},"barbapapazes\u002Ftalks",[35,9170,9171],{},[1217,9172,9173,9174,215],{},"I'm not receiving any compensation for my talks. All the trips, hotels, and time spent preparing them are at my own expense. I do it because I love sharing my knowledge and experiences with the community. However, it's not viable in the long term. If you want to support me, consider becoming a sponsor on ",[39,9175,9178],{"href":9176,"rel":9177},"https:\u002F\u002Fgithub.com\u002Fsponsors\u002Fbarbapapazes",[77],"GitHub Sponsors",[51,9180,9182],{"id":9181},"open-source-journey","Open source journey",[35,9184,9185,9186,9191,9192,9196],{},"This year, I mainly contributed to ",[39,9187,9190],{"href":9188,"rel":9189},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui",[77],"Nuxt UI",". Nuxt UI is my go-to component library, whether I'm building a Nuxt or a Vue application. I use it for all my projects. As I needed to deeply dive into it to ",[39,9193,9195],{"href":9194},".\u002Fsoftware-engineering-a-human-journey-before-anything-else","maintain a fork",", I know it very well now. So each time I found an issue or a way to improve it, I submitted a pull request.",[35,9198,9199,9200],{},"I also created a Markdown Exit plugin that leverages its async capabilities to automatically optimize images and generate blurhash placeholders. You can find it on ",[1582,9201,9203,9204],{"repo":9202},"barbapapazes\u002Fmarkdown-exit-image",". I also created a VitePress plugin to automatically generate OG images. You can find it on ",[1582,9205,215],{"repo":9206},"barbapapazes\u002Fvitepress-plugin-og",[35,9208,9209,9210,9213],{},"I continued to build ",[39,9211,7881],{"href":7879,"rel":9212},[77],". I really hope to open a public beta in the first half of 2026, but there is still a lot of work to do, and other projects are taking up my time.",[35,9215,9216,9217,9222,9223,215],{},"Throughout the year, I also continued to maintain other open source projects like ",[39,9218,9221],{"href":9219,"rel":9220},"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fnuxt-authorization",[77],"Nuxt Authorization",", and most of my projects are open source. You can find all my open source projects on my ",[39,9224,9227],{"href":9225,"rel":9226},"https:\u002F\u002Fgithub.com\u002Fbarbapapazes",[77],"GitHub profile",[35,9229,9230],{},"I've started using AI a lot to write code. GitHub Copilot Agent from the web UI is really powerful, and I'm using it daily. Whenever I have an idea, wherever I am, I open the mobile app, write a short prompt, and start an agent. At the end of the day, I review the generated PRs, make adjustments, and merge them. This has really improved my productivity, allowing me to focus on more complex tasks.",[51,9232,9234],{"id":9233},"content-creation-and-writing","Content creation and writing",[35,9236,9237],{},"This year, I wrote 20 posts and one series. To give you an idea, a single post takes me between 5 and 10 hours to write. But counting hours doesn't make sense for an activity as important as writing. Writing helps me to clarify my thoughts, free my mind, and grow my knowledge. I highly recommend it to everyone, and I won't stop doing it. However, I have an important rule: I only write when I have something to share. This means I don't follow a writing schedule.",[35,9239,9240],{},"This year, I wrote technical posts about JavaScript, Vite, Markdown It, and Laravel:",[1576,9242,9243,9248,9253,9258,9262,9267,9272],{},[71,9244,9245],{},[3029,9246],{"id":9247},"0b59cb4d-f145-4011-9d8c-ef32e2770273",[71,9249,9250],{},[3029,9251],{"id":9252},"769fccfd-feb9-4164-a73f-f80f0ad2bb8d",[71,9254,9255],{},[3029,9256],{"id":9257},"67862d90-d724-476f-9667-3f35683445ed",[71,9259,9260],{},[3029,9261],{"id":4263},[71,9263,9264],{},[3029,9265],{"id":9266},"9eb990da-ce9f-42ea-9836-5bb1fca28691",[71,9268,9269],{},[3029,9270],{"id":9271},"264d08e1-fb05-40c0-b298-0ba1dbbd3f14",[71,9273,9274],{},[3029,9275],{"id":9276},"d64b06de-1e87-41a9-a93f-aa6e42c6bdbc",[35,9278,9279],{},"I also wrote posts about my journey as a speaker:",[1576,9281,9282,9287,9292,9297,9302],{},[71,9283,9284],{},[3029,9285],{"id":9286},"38a49fbc-ac6d-4307-ae17-e662a855d897",[71,9288,9289],{},[3029,9290],{"id":9291},"1e785fe3-5952-4ba4-9406-c1c3c073c825",[71,9293,9294],{},[3029,9295],{"id":9296},"d1269015-72b0-45ae-86d8-f898a5fd35ee",[71,9298,9299],{},[3029,9300],{"id":9301},"71b437c6-68f6-42e8-bf6e-b657a5b89948",[71,9303,9304],{},[3029,9305],{"id":9306},"0c7194ba-a648-424f-824c-52d079b6fa72",[35,9308,9309],{},"And I also wrote a bit more personal posts about me and my life:",[1576,9311,9312,9317,9322],{},[71,9313,9314],{},[3029,9315],{"id":9316},"a0605bb8-6058-496d-920a-561d1f5f2ce6",[71,9318,9319],{},[3029,9320],{"id":9321},"03ce2599-d5a8-45f6-92a6-dd95ce2fc4e6",[71,9323,9324],{},[3029,9325],{"id":9326},"93da817a-f293-412c-8696-befcb4f95a8f",[35,9328,9329],{},"All of them are posts I would have loved to read when I started my journey as a developer. Being a software engineer isn't just about code; it's not always easy, and when it's also a passion, it can become overwhelming. Sharing my experiences and thoughts helps me first, and I hope it helps other people too.",[35,9331,9332],{},"I also refactored the backend of this website to handle the future websites I'm building. This also unlocked new features that should arrive in 2026. However, it will take time, because I will have to migrate this website from VitePress to pure Vite with Vue. That's a lot of work, and I already migrated it last year when moving from a custom UI to a rewritten Nuxt UI version. Being able to migrate instead of rewriting from scratch is something I'm really proud of. It shows that things are evolving without breaking the continuity.",[51,9334,9336],{"id":9335},"what-comes-next","What comes next",[35,9338,9339],{},"As you see, 2025 has been a significant year. I'm really proud of everything I achieved, but I finished the year really tired.",[35,9341,9342,9343],{},"However, I have a lot of exciting plans for 2026, so slowing down isn't in my plans. ",[1217,9344,9345],{},"Don't worry, I've learned to understand my limits, and I'm able to reduce the pace when needed. Burning out isn't an option either.",[35,9347,9348,9349,9354],{},"First, ",[39,9350,9353],{"href":9351,"rel":9352},"https:\u002F\u002Fx.com\u002Fhugorcd",[77],"Hugo",". Hugo is someone I've only talked to online. We share the same passion for web development, open source, and Nuxt, and when he started sharing content on X, I was so curious about his work. He has a unique approach to beauty, using animation, transitions, and elegant assets that I found really inspiring. He brought something new to my feed. Now, he is working for Vercel from London. So, I'm planning to visit him. In 2026, I want to go to London, meet him, and explore the city a bit.",[35,9356,9357,9358],{},"Speaking of going abroad, I want to attend more European and maybe even international conferences. I want to continue to explore these amazing events, meet passionate people, and share my love for web development. I already have a few conferences in mind where I want to submit talks. I've already submitted to Vue.js Amsterdam and MadVue. ",[1217,9359,9360],{},"I should take some courses to improve my English speaking skills.",[35,9362,9363],{},"I wrote two new talks during the last month of 2025. For now, I plan to present them mainly in France, as I did this year. One of them is about Vite plugins, written differently from any talk you've seen before. I won't spoil the surprise, but if I can make it work, it will be a lot of fun to attend and present. The second one is co-written with a friend, Julie, whom I really appreciate. I'm really excited about this new talk. It's the first time I've written a talk with someone else. We don't have the same background, and it's a topic I haven't completely mastered. This collaboration is a real challenge. It's way more than just a talk. It consumes a lot of time and takes us out of our comfort zone, but I strongly believe it will be worth it. The current state is really promising.",[35,9365,9366],{},"Apart from that, I want to continue writing as I have for two years now. Writing helps me to clarify my thoughts, share my experiences, and dive into topics I thought I mastered but that always have more to reveal. I want to keep pushing my boundaries and exploring new ideas through my writing. I also want to continue streaming on Twitch. Since September, I haven't had much time, but I hope that will change in 2026.",[35,9368,9369],{},"For my content, I will continue to write about JavaScript and Laravel, but I'll have to start writing more about Java, as my new job is mainly focused on it. I'm not completely sure how I'll do it, as I'm also building a new website that serves as a second brain. You can expect more information about it in the coming months. A few weeks ago, I shared on X another website I'm building to track my infrastructure. It's still in its early days, but I hope to share more about it as soon as possible. This requires me to write a lot, and writing takes time, so I won't give a date, but expect it before summer 2026.",[35,9371,9372],{},"As AI takes more and more space in my daily life, I've accepted that I can delegate some tasks to it. But there's a red line I won't cross: I will keep writing all my posts by myself, and I'll never use AI to generate content for me. If that were to happen, I would just stop working in tech and start a whole new career. This is non-negotiable for me, both personally and professionally. I am also strongly opposed to anyone doing the opposite.",[35,9374,9375],{},"Baking will be part of 2026. It's so good to make things with my hands, away from screens. Sharing it with someone is also an amazing activity. Highly recommended.",[35,9377,9378],{},"All of this is the main plan for 2026. But as change is the only constant, unexpected things will happen, and I'll adapt accordingly.",[35,9380,9381],{},"This was an amazing year. 2026 will be even better.",[35,9383,9384],{},"Take care of yourself, spend time with your loved ones, live life to the fullest, and happy new year! 🫶",[35,9386,9387],{},[1690,9388],{"alt":9389,"src":9390},"Estéban flying to new horizon on a wingfoil.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fhappy-new-year-2026.webp",{"title":13,"searchDepth":14,"depth":14,"links":9392},[9393,9394,9395,9396,9397,9398],{"id":8849,"depth":14,"text":8850},{"id":8910,"depth":14,"text":8911},{"id":8944,"depth":14,"text":8945},{"id":9181,"depth":14,"text":9182},{"id":9233,"depth":14,"text":9234},{"id":9335,"depth":14,"text":9336},"429cc748-628b-4620-96fc-9dcaa4eb3249","2025-12-31","2025 comes to an end. It's time to look back at an intense year filled with talks, new experiences, and personal growth.",{},"\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action","---\ncontentId: 429cc748-628b-4620-96fc-9dcaa4eb3249\ntitle: \"2025: A Turning Point from Slow Steps to Clear Action\"\ndescription: 2025 comes to an end. It's time to look back at an intense year filled with talks, new experiences, and personal growth.\ndate: 2025-12-31\n---\n\nHey,\n\nHope you're doing well on this last day of 2025.\n\nThis is the last post of the year, and I wanted to take a moment to look back. It's the third year I've been writing this kind of post, following \u003CLinkToArticle id=\"d86a42c6-47d0-4078-b33c-805f44046b17\" \u002F> and [Récap 2023](https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Frecap-2023) (only available in French). I find them important for at least two reasons:\n\n1. When you work on long-term goals, it's easy to lose track of the progress you've made and everything you've accomplished. You always want to do more. Taking the time to record the journey helps you see all the progress made in a single year.\n2. From time to time, I love going back to these posts to see how my goals, my hopes, and my desires have evolved over time.\n\n![Estéban during a sunset between trees in front of the sea.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fsunset-trees-sea.webp)\n\nBefore jumping into the details, I want to talk about numbers. I love numbers because they can give an overview of the year at a glance.\n\n- 20 posts written, including this one. This is a new post every 2.7 weeks.\n- 1 series of 5 posts about AI agents and an MCP server. Fewer series than last year, but it's so much work, and I've been busy giving talks.\n- 19 talks given at 11 different events (6 conferences and 5 meetups). The talk \"La réactivité et les signaux : démystifions la magie du frontend\" (Reactivity and Signals: Demystifying the Frontend's Magic) has been given 8 times this year.\n- 9 cities visited for conferences and meetups: Paris, Bagneux, Rennes, Nantes, Bordeaux, Toulouse, Lyon, Dijon, and Strasbourg.\n- +1,000 people attended my talks this year, with a peak of +200 people for my talk at Devoxx France about Reactivity and Signals.\n- +711 euros spent on trains and hotels for meetups and conferences. I was honestly expecting way more. This represents +3,000 km, +35 hours of train (without delays), and 9 nights in hotels.\n- 1,518 followers on X at the end of 2025 and 8,153 posts shared.\n- 1,800 followers on LinkedIn at the end of 2025. Compared to last year, with a growth of 63.7%, this is more than 700 new followers this year. I've reached +34,000 members for a total of +182,000 impressions over 124 posts. I definitely wrote a lot on LinkedIn this year.\n- +240 followers on Twitch for +140 hours of live streaming. It's just for fun, but I really enjoy it. I'll continue in 2026 for sure.\n- +1,600 hours spent on VS Code, with a peak of 14 hours and 1 minute on January 24, 2025 (which is way too much to have a healthy life). This represents an average of 4.4 hours per day, 7 days a week.\n- 143 reactions and 45 comments on my posts on soubiran.dev.\n- 25 different pastries created. More about it in 2026.\n\nThat's a lot of numbers for a single year. In hindsight, it's amazing how much is possible to achieve in just 12 months.\n\n## Back to 2025 goals\n\nSo many things happened in 2025. From changing jobs to giving talks, I haven't stopped for a second. _Sometimes, I went too fast and too far._\n\nLast year, I bought the course [Three.js Journey](https:\u002F\u002Fthreejs-journey.com\u002F). I really wanted to learn 3D, but I never found the time to do it. _Maybe in 2026._ My talks for Vue.js Amsterdam weren't accepted, but I submitted new ones this year. _This is the fourth time. Fingers crossed for 2026._ Finally, I didn't take the time to learn more about infrastructure. However, I started a project moving in this direction. _More about it in the coming months._\n\nAs every year, a lot of unexpected things happened. I think it's important to embrace them and to adapt the roadmap as you go.\n\n- I have discovered a passion for baking. Every weekend, I try a new recipe, buy new equipment and new books. This keeps me away from screens and coding. It also gave me the opportunity to bake for Jordan, a close friend's birthday, and for Thanksgiving with amazing people. _My colleagues really appreciate it, as I often bring pastries on Mondays._\n\n![Fig tartlet with white chocolate and port syrup.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Ffig-tartlet.webp)\n\n- I met amazing people, new friends, with whom I'm building projects, conferences, and discovering new things. Putting humans at the center of everything, creating moments together, has become really important to me.\n\nWith a group of friends, we started playing darts regularly. From a simple game of darts, it's truly amazing how many things can happen, from playing with unknown people to meet new friends, and create memorable moments together.\n\n![A dart in a dart.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdart.webp)\n\n## Life changes\n\nIn December 2024, I started a new job within a company called MaiaSpace. MaiaSpace is building a space launcher called Maia. I was working as a software engineer in the avionics team, building tools to automate some of the work done by engineers. I met truly amazing people and learned a lot. It was [an incredible opportunity that ended a year later](\u002Fposts\u002Fstarting-a-new-chapter-new-ambitions-and-new-job).\n\nAfter weeks of thinking, I decided to leave MaiaSpace to join a new company called Takima. It's a consulting firm specializing in Java and infrastructure. This is a big change for me, as I know nothing about Java, so I have everything to learn. This is a really long-term investment, but I believe that learning Java will open many doors. Currently, it's just a lot of work. _But getting out of the comfort zone is the only way to grow._\n\nThis job change also involves moving to a new home, closer to the office and my friends. In January 2026, I'll move to a new apartment, closer to Paris. It's smaller than the current one, but it's way more practical for my daily life. _Less time commuting means more time for myself and my projects._\n\nIn April 2025, I started the first stream of the year. While I was in Switzerland, I started streaming on Twitch, but a performance issue with my hardware stopped me after a few streams. After buying a new computer, the streams were back. It was still heavily experimental, as I'm really trying to find my style in this format. Since September, I haven't been very active. Conferences took up a lot of my time, and issues at work made it hard to find the motivation to stream. In 2026, I should be more consistent, because I've automated a lot of things.\n\nSpeaking of automation, I'm revamping my entire content creation workflow to automate as much as possible. AI will be used for boring tasks, never for creative ones. These changes should reduce the time spent creating content, allowing me to focus on what matters most.\n\n_Organizing things well is so hard._\n\n## Meetups and conferences\n\nIn my [last year recap](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next), I said:\n\n> I really hope this year will be as rich as 2024\n\nMy hope has been more than fulfilled. With 19 talks given in 2025, compared to 6 in 2024, and 1 in 2023, this year has been intense. Really intense.\n\n19 talks at 11 different events means that I spoke almost every five weeks. It's a lot, and I never imagined reaching this number so quickly. The growth is honestly crazy, and I secretly hope to slow it down in 2026 to find a better balance. _Keeping the same pace in 2026 would mean giving more than 30 talks, which is honestly impossible._\n\nThe year started with [Devoxx France in April](.\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special.md), where I had the opportunity to give two talks: \"La réactivité et les signaux : démystifions la magie du frontend\" (Reactivity and Signals: Demystifying the Frontend's Magic) and \"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). For a conference of this size, with more than 4,500 attendees and only 281 talks accepted from more than 1,100 submissions, it's really an honor to be selected twice.\n\n![Esteban Soubiran presenting at Devoxx France 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdevoxx-france-2025.webp)\n\nThese two talks were submitted before Christmas 2024, but I only received the acceptance in February 2025. After that, I started working hard to prepare them. It took me two months to have something ready. Understanding Alien Signals took a lot of time, and to make it understandable for a large audience, I had to be truly creative. Today I'm really proud of the result, and the overall feedback for both talks has been really positive.\n\nTwo weeks later, I was at [MiXiT](.\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025.md) in Lyon to also give two talks. During this conference, I met a lot of new and amazing people. I really appreciated the smaller size of the conference compared to Devoxx France. It's easier to connect with people, and there is less stress when going to see talks. _Will I have a seat?_ For the anecdote, to have a seat at Devoxx France for the keynotes, I had to arrive 1 hour before the start. I thought it was the same for MiXiT, so I arrived 45 minutes before the start. Completely unnecessary, because everyone could enter the main room without any problem.\n\n![Esteban Soubiran presenting at MiXiT 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fmixit-2025.webp)\n\nThen, in June, I went to Rennes for the [BreizhCamp](.\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker.md). I think you can guess what happened: I gave two talks again, the same ones as at Devoxx France and MiXiT. _You might wonder why I give the same talks again and again. After spending two months preparing them, you want to give them as much as possible, to reach the largest audience possible. Also, reusing the same talks allows me to improve both the content and the delivery. I'm now comfortable with them, and I can really enjoy giving them. The first time, it's more stress than pleasure._\n\n![Esteban Soubiran presenting at BreizhCamp 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fbreizhcamp-2025.webp)\n\nAfter the summer break, and up to December, I went from one conference to another.\n\n- [DevFest Nantes, October 17, 2025](.\u002Fmy-adventure-to-nantes-for-europes-largest-devfest.md), where I gave my talk on JavaScript packages. This conference is the largest DevFest in Europe. The party was really pleasant, and the closing keynote, an improvisational theatre performance, was really exceptional (and so original). This could be my favorite conference of the year.\n- [BDX I\u002FO, November 7, 2025](.\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux.md), where I gave my two talks. The conference was really well organized, and the venue was superb. I took the opportunity to visit the NuxtLabs office in Bordeaux and meet Sébastien and Benjamin.\n- DevFest Toulouse, November 13, 2025, where I gave my talk on reactivity and signals for the seventh time this year. The speaker dinner was really exceptional. We were in a superb restaurant in the city center.\n- DevFest Dijon, December 5, 2025, where I gave my talk on reactivity and signals for the eighth time this year. The speaker gift was so nice: a bag with local products from Dijon and the region.\n\n![Estéban presenting at DevFest Nantes 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdevfest-nantes-2025.webp)\n\n![Estéban presenting at 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, and Benjamin at NuxtLabs.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fnuxtlabs-bordeaux-2025.webp)\n\n![Estéban's selfie at DevFest Dijon 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdevfest-dijon-2025.webp)\n\nThe speaker world is quite small, and you often meet the same people again and again. This alleviated the loneliness I felt in 2023 when I gave my first talk at Devoxx France.\n\nBefore and in between these conferences, I gave several talks at meetups in Paris and Strasbourg. _Yes, I went and came back to Strasbourg just for an evening talk. Unreasonable, but fun._\n\n- Paris TypeScript, January 21, 2025, [Devins full-stack avec TypeScript](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-01-21\u002Fparis-typescript\u002F1)\n- Node.js Paris, March 5, 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, April 8, 2025, [Ship Nuxt apps that scale.](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-04-08\u002Fhuman-talks\u002F1)\n- HumanTalks Paris, September 9, 2025, [Inalia: Un Anneau pour les gouverner tous](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-09-09\u002Fhumantalks-paris\u002F1)\n- Node.js Paris, September 17, 2025, [Inalia: Un Anneau pour les gouverner tous](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-09-17\u002Fnodejs-paris\u002F1)\n- Paris TypeScript, October 14, 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, October 21, 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, December 9, 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\nI really enjoyed giving talks at meetups. The atmosphere is way more relaxed than at conferences, and it's easier to interact with the audience. But make no mistake: it can be more stressful, because the audience is really close to you, and you can easily see them.\n\n![Estéban presenting at StrasbourgJS meetup in October 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fstrasbourgjs-october-2025.webp)\n\n![Estéban's selfie at Paris TypeScript meetup in December 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fparis-typescript-december-2025.webp)\n\nThis was an exceptional year for talks and conferences, surpassing all my expectations. Thank you to all the people I met and all the organizers who trusted me to give talks at their events.\n\nFind all my talks on my [talks page](..\u002Ftalks.md). I added a map showing all the places where I gave talks since 2023 and some statistics about my talks. You can find the slides on \u003CGitHubLink repo=\"barbapapazes\u002Ftalks\" \u002F>.\n\n_I'm not receiving any compensation for my talks. All the trips, hotels, and time spent preparing them are at my own expense. I do it because I love sharing my knowledge and experiences with the community. However, it's not viable in the long term. If you want to support me, consider becoming a sponsor on [GitHub Sponsors](https:\u002F\u002Fgithub.com\u002Fsponsors\u002Fbarbapapazes)._\n\n## Open source journey\n\nThis year, I mainly contributed to [Nuxt UI](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui). Nuxt UI is my go-to component library, whether I'm building a Nuxt or a Vue application. I use it for all my projects. As I needed to deeply dive into it to [maintain a fork](.\u002Fsoftware-engineering-a-human-journey-before-anything-else.md), I know it very well now. So each time I found an issue or a way to improve it, I submitted a pull request.\n\nI also created a Markdown Exit plugin that leverages its async capabilities to automatically optimize images and generate blurhash placeholders. You can find it on \u003CGitHubLink repo=\"barbapapazes\u002Fmarkdown-exit-image\" \u002F>. I also created a VitePress plugin to automatically generate OG images. You can find it on \u003CGitHubLink repo=\"barbapapazes\u002Fvitepress-plugin-og\" \u002F>.\n\nI continued to build [Inalia](https:\u002F\u002Finalia.app). I really hope to open a public beta in the first half of 2026, but there is still a lot of work to do, and other projects are taking up my time.\n\nThroughout the year, I also continued to maintain other open source projects like [Nuxt Authorization](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fnuxt-authorization), and most of my projects are open source. You can find all my open source projects on my [GitHub profile](https:\u002F\u002Fgithub.com\u002Fbarbapapazes).\n\nI've started using AI a lot to write code. GitHub Copilot Agent from the web UI is really powerful, and I'm using it daily. Whenever I have an idea, wherever I am, I open the mobile app, write a short prompt, and start an agent. At the end of the day, I review the generated PRs, make adjustments, and merge them. This has really improved my productivity, allowing me to focus on more complex tasks.\n\n## Content creation and writing\n\nThis year, I wrote 20 posts and one series. To give you an idea, a single post takes me between 5 and 10 hours to write. But counting hours doesn't make sense for an activity as important as writing. Writing helps me to clarify my thoughts, free my mind, and grow my knowledge. I highly recommend it to everyone, and I won't stop doing it. However, I have an important rule: I only write when I have something to share. This means I don't follow a writing schedule.\n\nThis year, I wrote technical posts about JavaScript, Vite, Markdown It, and Laravel:\n\n- \u003CLinkToArticle id=\"0b59cb4d-f145-4011-9d8c-ef32e2770273\" \u002F>\n- \u003CLinkToArticle id=\"769fccfd-feb9-4164-a73f-f80f0ad2bb8d\" \u002F>\n- \u003CLinkToArticle id=\"67862d90-d724-476f-9667-3f35683445ed\" \u002F>\n- \u003CLinkToArticle id=\"4ff51ee2-926d-4924-86f5-97cd9e378fa7\" \u002F>\n- \u003CLinkToArticle id=\"9eb990da-ce9f-42ea-9836-5bb1fca28691\" \u002F>\n- \u003CLinkToArticle id=\"264d08e1-fb05-40c0-b298-0ba1dbbd3f14\" \u002F>\n- \u003CLinkToArticle id=\"d64b06de-1e87-41a9-a93f-aa6e42c6bdbc\" \u002F>\n\nI also wrote posts about my journey as a speaker:\n\n- \u003CLinkToArticle id=\"38a49fbc-ac6d-4307-ae17-e662a855d897\" \u002F>\n- \u003CLinkToArticle id=\"1e785fe3-5952-4ba4-9406-c1c3c073c825\" \u002F>\n- \u003CLinkToArticle id=\"d1269015-72b0-45ae-86d8-f898a5fd35ee\" \u002F>\n- \u003CLinkToArticle id=\"71b437c6-68f6-42e8-bf6e-b657a5b89948\" \u002F>\n- \u003CLinkToArticle id=\"0c7194ba-a648-424f-824c-52d079b6fa72\" \u002F>\n\nAnd I also wrote a bit more personal posts about me and my life:\n\n- \u003CLinkToArticle id=\"a0605bb8-6058-496d-920a-561d1f5f2ce6\" \u002F>\n- \u003CLinkToArticle id=\"03ce2599-d5a8-45f6-92a6-dd95ce2fc4e6\" \u002F>\n- \u003CLinkToArticle id=\"93da817a-f293-412c-8696-befcb4f95a8f\" \u002F>\n\nAll of them are posts I would have loved to read when I started my journey as a developer. Being a software engineer isn't just about code; it's not always easy, and when it's also a passion, it can become overwhelming. Sharing my experiences and thoughts helps me first, and I hope it helps other people too.\n\nI also refactored the backend of this website to handle the future websites I'm building. This also unlocked new features that should arrive in 2026. However, it will take time, because I will have to migrate this website from VitePress to pure Vite with Vue. That's a lot of work, and I already migrated it last year when moving from a custom UI to a rewritten Nuxt UI version. Being able to migrate instead of rewriting from scratch is something I'm really proud of. It shows that things are evolving without breaking the continuity.\n\n## What comes next\n\nAs you see, 2025 has been a significant year. I'm really proud of everything I achieved, but I finished the year really tired.\n\nHowever, I have a lot of exciting plans for 2026, so slowing down isn't in my plans. _Don't worry, I've learned to understand my limits, and I'm able to reduce the pace when needed. Burning out isn't an option either._\n\nFirst, [Hugo](https:\u002F\u002Fx.com\u002Fhugorcd). Hugo is someone I've only talked to online. We share the same passion for web development, open source, and Nuxt, and when he started sharing content on X, I was so curious about his work. He has a unique approach to beauty, using animation, transitions, and elegant assets that I found really inspiring. He brought something new to my feed. Now, he is working for Vercel from London. So, I'm planning to visit him. In 2026, I want to go to London, meet him, and explore the city a bit.\n\nSpeaking of going abroad, I want to attend more European and maybe even international conferences. I want to continue to explore these amazing events, meet passionate people, and share my love for web development. I already have a few conferences in mind where I want to submit talks. I've already submitted to Vue.js Amsterdam and MadVue. _I should take some courses to improve my English speaking skills._\n\nI wrote two new talks during the last month of 2025. For now, I plan to present them mainly in France, as I did this year. One of them is about Vite plugins, written differently from any talk you've seen before. I won't spoil the surprise, but if I can make it work, it will be a lot of fun to attend and present. The second one is co-written with a friend, Julie, whom I really appreciate. I'm really excited about this new talk. It's the first time I've written a talk with someone else. We don't have the same background, and it's a topic I haven't completely mastered. This collaboration is a real challenge. It's way more than just a talk. It consumes a lot of time and takes us out of our comfort zone, but I strongly believe it will be worth it. The current state is really promising.\n\nApart from that, I want to continue writing as I have for two years now. Writing helps me to clarify my thoughts, share my experiences, and dive into topics I thought I mastered but that always have more to reveal. I want to keep pushing my boundaries and exploring new ideas through my writing. I also want to continue streaming on Twitch. Since September, I haven't had much time, but I hope that will change in 2026.\n\nFor my content, I will continue to write about JavaScript and Laravel, but I'll have to start writing more about Java, as my new job is mainly focused on it. I'm not completely sure how I'll do it, as I'm also building a new website that serves as a second brain. You can expect more information about it in the coming months. A few weeks ago, I shared on X another website I'm building to track my infrastructure. It's still in its early days, but I hope to share more about it as soon as possible. This requires me to write a lot, and writing takes time, so I won't give a date, but expect it before summer 2026.\n\nAs AI takes more and more space in my daily life, I've accepted that I can delegate some tasks to it. But there's a red line I won't cross: I will keep writing all my posts by myself, and I'll never use AI to generate content for me. If that were to happen, I would just stop working in tech and start a whole new career. This is non-negotiable for me, both personally and professionally. I am also strongly opposed to anyone doing the opposite.\n\nBaking will be part of 2026. It's so good to make things with my hands, away from screens. Sharing it with someone is also an amazing activity. Highly recommended.\n\nAll of this is the main plan for 2026. But as change is the only constant, unexpected things will happen, and I'll adapt accordingly.\n\nThis was an amazing year. 2026 will be even better.\n\nTake care of yourself, spend time with your loved ones, live life to the fullest, and happy new year! 🫶\n\n![Estéban flying to new horizon on a wingfoil.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fhappy-new-year-2026.webp)\n",{"title":8768,"description":9401},{"loc":9403,"images":9407},[9408,9409,9410,9411,9412,9413,9414,9415,9416,9417,9418,9419,9420],{"loc":8802},{"loc":8893},{"loc":8907},{"loc":8981},{"loc":9000},{"loc":9017},{"loc":9048},{"loc":9054},{"loc":9060},{"loc":9066},{"loc":9148},{"loc":9154},{"loc":9390},"posts\u002F2025-a-turning-point-from-slow-steps-to-clear-action","6xFQbm4xSlO0MGvuKwaTyFmXY8Pvr1O8BG1SUVwCACI",{"id":9424,"title":9425,"body":9426,"contentId":9642,"date":9643,"description":9644,"extension":18,"meta":9645,"navigation":20,"path":8917,"rawbody":9646,"robots":23,"schemaOrg":23,"seo":9647,"sitemap":9648,"stem":9649,"__hash__":9650},"posts\u002Fposts\u002Fstarting-a-new-chapter-new-ambitions-and-new-job.md","Starting a New Chapter: New Ambitions and a New Job",{"type":7,"value":9427,"toc":9636},[9428,9434,9438,9441,9447,9453,9456,9462,9465,9468,9471,9475,9482,9492,9495,9498,9501,9504,9512,9517,9523,9526,9529,9533,9540,9543,9546,9549,9552,9555,9558,9561,9565,9568,9577,9586,9594,9597,9600,9603,9609,9611,9617,9619,9622,9625,9628,9631,9633],[35,9429,9430,9431],{},"It's been a heartbreaking decision, but I have decided to leave, or more accurately, not to renew my contract. ",[1217,9432,9433],{},"I was a software engineer working in an aerospace company called MaiaSpace.",[51,9435,9437],{"id":9436},"everything-comes-to-an-end","Everything comes to an end",[35,9439,9440],{},"It was my first job, but not necessarily my dream job, or the job I had dreamed of.",[35,9442,9443,9444],{},"I never thought I could work in aerospace, and I'm not particularly passionate about rockets. I really appreciate watching the launches and all the new technologies developed, like the landing of the Falcon 9's booster and first stage or following the Starship development. But not more than that. ",[1217,9445,9446],{},"The Starship's first stage catch between the chopsticks was really impressive!",[35,9448,9449,9450],{},"At the end of August 2024, I posted on LinkedIn to share I was looking for a new opportunity, without a lot of expectations. But a friend saw my post and reached out to me about a position at his company. They were looking for someone with my skills in web development. Great! ",[1217,9451,9452],{},"I can safely say it now, but I never applied anywhere else. All my hope was pinned on this opportunity.",[35,9454,9455],{},"I asked him where he was working. He told me he was building a reusable rocket within a startup called MaiaSpace. I didn't really ask for more. I was simply thrilled by the project and the idea of working on an industrial project that was concrete and would have an impact on France. To join the company, I had two one-hour interviews, one with the team, and the other with the PO and CTO. Everything went well, and I was hired for a one-year contract.",[35,9457,9458,9459],{},"I was the first, and only, web developer in the company for nearly all of that time. So, I had the opportunity to build from scratch, with the technologies I wanted. The only constraint was to have a working product as fast as possible. I chose a stack simple enough to be able to manage it alone but powerful enough to handle the requirements of the project. ",[1217,9460,9461],{},"Laravel, Vue.js, and Inertia.",[35,9463,9464],{},"Tech has never been the complicated part of my mission.",[35,9466,9467],{},"When you're arriving in a field you're completely unfamiliar with, writing code is, by far, the simplest task you can do. Understanding the business and the domain, when most words are acronyms and nobody understands your job, is really hard. Honestly, it took me months to really grasp enough of the domain to start working effectively, and to ship a useful product. Once you have it, everything becomes easier, and you start shipping features that really empower users.",[35,9469,9470],{},"I really loved everything I was involved in, and really appreciated everyone on the team I was part of.",[51,9472,9474],{"id":9473},"heartbreaking-to-leave","Heartbreaking to leave",[35,9476,9477,9478,9481],{},"I developed the software alone for nearly a year, spending more than 845 hours in VS Code. I've written and thought about each line. I saw it born, die, and born again. Then, I saw it growing and evolving, from a simple idea to a complex system, from a white paper to a ",[1217,9479,9480],{},"deployed"," application, interconnected with various components and services. Over time, it became a crucial part of the team workflow.",[35,9483,9484,9485],{},"It even inspired some articles I've written: ",[3029,9486,3076,9487],{"id":9247},[3029,9488,3080,9489],{"id":9266},[3029,9490,215],{"id":9491},"aefb6e0d-6566-43ca-a39d-e26ae877ce56",[35,9493,9494],{},"The opportunity to work on such a project was incredible, and it has been a unique opportunity. There are no two MaiaSpace on Earth, and there are not many companies working on reusable rockets.",[35,9496,9497],{},"The truth is, even with such a unique opportunity, I couldn't find my place within the company. I wasn't aligned with the company's vision, values, and mindset. The importance of the industrial project wasn't enough to keep me motivated.",[35,9499,9500],{},"The European aerospace industry is a small one, very closed-minded, despite the long statements about innovation and disruption. Embracing new ideas is a challenge for them. Some of them have been working in the industry for more than 20 years. It worked for Ariane 5 and Ariane 6, so why change? There's no need.",[35,9502,9503],{},"They forgot one thing, an important one. The goal of MaiaSpace isn't just to build a launcher. It's all about reducing costs and increasing accessibility to space, which is a fundamental shift. We know how to build a rocket. Ariane 6 is going well, and we don't need an Ariane 6 bis. The real challenge is to get rid of the bureaucracy layer, to increase velocity, to innovate, to make it with more intelligence. That's honestly a huge objective, nearly a cultural change.",[35,9505,9506,9507,9511],{},"The team I was part of was incredibly talented and passionate. They understood the importance of my work, creating software to facilitate, accelerate and automate the workflow. Unfortunately, beyond that scope, the company leadership saw my work as just a cost center, rather than a value driver. Day to day, it meant that ",[39,9508,9510],{"href":9509},".\u002Fbeing-lost-an-essay-to-find-myself-within-uncertainty","every little action requires me to move mountains",", dealing with internal political battles that I should never have had to face.",[35,9513,9514],{},[1217,9515,9516],{},"Never forget that youth and a fresh perspective can bring way more value than experience. Embrace feedback.",[35,9518,9519,9520],{},"Also, no clear career path was offered, and when you're just at the beginning of the journey, it's hard when you can't see a growth trajectory. At the same time, I love writing talks and giving them at meetups and conferences. I dedicate a lot of my free time to this activity, but the company never saw the value. More than half of my vacation days were spent going to conferences. ",[1217,9521,9522],{},"Can't really call them \"vacation\" days.",[35,9524,9525],{},"Finally, being alone means that you inevitably reach a plateau where you stop learning and growing. This creates frustration, and a year after my arrival, I felt I reached a point of stagnation.",[35,9527,9528],{},"There were pros to staying, as well as to leaving.",[51,9530,9532],{"id":9531},"the-mourning-process","The mourning process",[35,9534,9535,9536,9539],{},"I didn't leave on a whim. ",[39,9537,9538],{"href":9509},"It took me weeks"," to come to this conclusion.",[35,9541,9542],{},"In September, I went through a significant period of reflection and reevaluation. Before considering leaving, I tried to find ways to improve my situation within the company. I discussed my concerns with multiple colleagues to find solutions. After weeks of trying a lot of different approaches, I had to accept the facts. I couldn't change anything, and considering leaving was the only remaining option.",[35,9544,9545],{},"At first, I really wanted to stay, and I questioned myself. Am I the issue? What should I do to improve the situation? Also, I was persuading myself that things could get better. People I talked to told me that they were discussing how to resolve the situation. I believed them.",[35,9547,9548],{},"During the wait, I was at my lowest point. I completely lost the joy of working, of creating, both personally and professionally. The only thing I wanted was to stay on the sofa, scrolling through my phone, waiting for tomorrow. The reality was that the situation wouldn't improve magically.",[35,9550,9551],{},"After some weeks of waiting, nothing changed at work. Some days were even worse than others. Of all the options I considered, only one was left: leaving. I wouldn't accept it at first, but after talking with many friends and people outside the situation, I realized it was the only acceptable option.",[35,9553,9554],{},"It took me a few days to admit it, but they were right.",[35,9556,9557],{},"Knowing that I wanted to leave was a crucial step because I could start finding ways to get back up. What do I want next? Where do I go from here? I can explore absolutely anything. Once I found my direction and a new job, life became smoother, and work stopped being a pain.",[35,9559,9560],{},"In hindsight, it was really a mourning process that took place in September. I understood it after the fact, but I'm sure that understanding it while I was going through it would have helped to know how to resolve it faster.",[51,9562,9564],{"id":9563},"moving-forward","Moving forward",[35,9566,9567],{},"That was the past, and now it's time to look ahead.",[35,9569,9570,9571,9576],{},"I'm starting a new chapter at a company called ",[39,9572,9575],{"href":9573,"rel":9574},"https:\u002F\u002Fwww.takima.fr\u002F",[77],"Takima",". They're a consulting firm specialized in Angular, Java, and everything related to infrastructure. I don't know exactly what I will work on yet, but I'm excited to start working with them.",[35,9578,9579,9580,9585],{},"I met Takima two years ago, at Devoxx France 2023. The first talk I saw was one from ",[39,9581,9584],{"href":9582,"rel":9583},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floicortola\u002F",[77],"Loïc",", current CTO, and Aurélien, former consultant, about building a GitOps from scratch. I found it amazing, and even if I didn't understand everything, I knew I wanted to be able to do like them one day.",[35,9587,9588,9589,9593],{},"In 2024, when I went back to Devoxx France, I looked for talks from Takima. They are well written, you learn a lot, and you have a good time. Later in the year, ",[39,9590,8051],{"href":9591,"rel":9592},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcamille-pillot-455517177\u002F",[77],", consultant at Takima and organizer of tech events, invited me to talk at one of their meetups called TakiMeet.",[35,9595,9596],{},"In 2025, still at Devoxx France, I spent most of my time at their booth, chatting, discussing, and learning from the team. Since then, I had multiple opportunities to meet them at conferences.",[35,9598,9599],{},"Through all of these interactions, I discovered more than just a company. I found passionate people who care about their work, who love to share their knowledge, with a vision and a tech culture I really appreciate. I was aligned with them. Also, I will be surrounded by people who do the same work. I won't be the only one. One of my hopes is to find fulfillment in this environment.",[35,9601,9602],{},"So, when the opportunity arose to join them, I didn't hesitate.",[35,9604,9605,9606],{},"Now, it's time to learn Java! ",[1217,9607,9608],{},"I have never really written Java before, so this will be a new challenge for me. I will be paid for writing Java, so I have to be good.",[1159,9610],{},[35,9612,9613,9614],{},"What's your job? I'm building a reusable space rocket. ",[1217,9615,9616],{},"Was that really the truth? Who knows? It doesn't really matter now, I can no longer say that.",[1159,9618],{},[35,9620,9621],{},"What do I really want?",[35,9623,9624],{},"This question really haunted me. At first, I wasn't able to find an answer. I wanted to find a fixed point, a clear direction that wouldn't change over time.",[35,9626,9627],{},"I'm now convinced that it's an impossible quest.",[35,9629,9630],{},"The answer to this question inevitably fluctuates, and I needed to accept that it's okay for my goals and desires to evolve, along with my experiences and the people I surround myself with. This acceptance really reduced my mental burden and made me feel more peaceful.",[1159,9632],{},[35,9634,9635],{},"Change is the only constant.",{"title":13,"searchDepth":14,"depth":14,"links":9637},[9638,9639,9640,9641],{"id":9436,"depth":14,"text":9437},{"id":9473,"depth":14,"text":9474},{"id":9531,"depth":14,"text":9532},{"id":9563,"depth":14,"text":9564},"c3bdd596-a106-4ce6-975c-834b1aac2222","2025-12-02","I leave my job at MaiaSpace after weeks of reflection and consideration to join a more human-centered company called Takima.",{},"---\ncontentId: c3bdd596-a106-4ce6-975c-834b1aac2222\ntitle: \"Starting a New Chapter: New Ambitions and a New Job\"\ndescription: \"I leave my job at MaiaSpace after weeks of reflection and consideration to join a more human-centered company called Takima.\"\ndate: 2025-12-02\n---\n\nIt's been a heartbreaking decision, but I have decided to leave, or more accurately, not to renew my contract. _I was a software engineer working in an aerospace company called MaiaSpace._\n\n## Everything comes to an end\n\nIt was my first job, but not necessarily my dream job, or the job I had dreamed of.\n\nI never thought I could work in aerospace, and I'm not particularly passionate about rockets. I really appreciate watching the launches and all the new technologies developed, like the landing of the Falcon 9's booster and first stage or following the Starship development. But not more than that. _The Starship's first stage catch between the chopsticks was really impressive!_\n\nAt the end of August 2024, I posted on LinkedIn to share I was looking for a new opportunity, without a lot of expectations. But a friend saw my post and reached out to me about a position at his company. They were looking for someone with my skills in web development. Great! _I can safely say it now, but I never applied anywhere else. All my hope was pinned on this opportunity._\n\nI asked him where he was working. He told me he was building a reusable rocket within a startup called MaiaSpace. I didn't really ask for more. I was simply thrilled by the project and the idea of working on an industrial project that was concrete and would have an impact on France. To join the company, I had two one-hour interviews, one with the team, and the other with the PO and CTO. Everything went well, and I was hired for a one-year contract.\n\nI was the first, and only, web developer in the company for nearly all of that time. So, I had the opportunity to build from scratch, with the technologies I wanted. The only constraint was to have a working product as fast as possible. I chose a stack simple enough to be able to manage it alone but powerful enough to handle the requirements of the project. _Laravel, Vue.js, and Inertia._\n\nTech has never been the complicated part of my mission.\n\nWhen you're arriving in a field you're completely unfamiliar with, writing code is, by far, the simplest task you can do. Understanding the business and the domain, when most words are acronyms and nobody understands your job, is really hard. Honestly, it took me months to really grasp enough of the domain to start working effectively, and to ship a useful product. Once you have it, everything becomes easier, and you start shipping features that really empower users.\n\nI really loved everything I was involved in, and really appreciated everyone on the team I was part of.\n\n## Heartbreaking to leave\n\nI developed the software alone for nearly a year, spending more than 845 hours in VS Code. I've written and thought about each line. I saw it born, die, and born again. Then, I saw it growing and evolving, from a simple idea to a complex system, from a white paper to a _deployed_ application, interconnected with various components and services. Over time, it became a crucial part of the team workflow.\n\nIt even inspired some articles I've written: \u003CLinkToArticle id=\"0b59cb4d-f145-4011-9d8c-ef32e2770273\" \u002F>, \u003CLinkToArticle id=\"9eb990da-ce9f-42ea-9836-5bb1fca28691\" \u002F>, and \u003CLinkToArticle id=\"aefb6e0d-6566-43ca-a39d-e26ae877ce56\" \u002F>.\n\nThe opportunity to work on such a project was incredible, and it has been a unique opportunity. There are no two MaiaSpace on Earth, and there are not many companies working on reusable rockets.\n\nThe truth is, even with such a unique opportunity, I couldn't find my place within the company. I wasn't aligned with the company's vision, values, and mindset. The importance of the industrial project wasn't enough to keep me motivated.\n\nThe European aerospace industry is a small one, very closed-minded, despite the long statements about innovation and disruption. Embracing new ideas is a challenge for them. Some of them have been working in the industry for more than 20 years. It worked for Ariane 5 and Ariane 6, so why change? There's no need.\n\nThey forgot one thing, an important one. The goal of MaiaSpace isn't just to build a launcher. It's all about reducing costs and increasing accessibility to space, which is a fundamental shift. We know how to build a rocket. Ariane 6 is going well, and we don't need an Ariane 6 bis. The real challenge is to get rid of the bureaucracy layer, to increase velocity, to innovate, to make it with more intelligence. That's honestly a huge objective, nearly a cultural change.\n\nThe team I was part of was incredibly talented and passionate. They understood the importance of my work, creating software to facilitate, accelerate and automate the workflow. Unfortunately, beyond that scope, the company leadership saw my work as just a cost center, rather than a value driver. Day to day, it meant that [every little action requires me to move mountains](.\u002Fbeing-lost-an-essay-to-find-myself-within-uncertainty.md), dealing with internal political battles that I should never have had to face.\n\n_Never forget that youth and a fresh perspective can bring way more value than experience. Embrace feedback._\n\nAlso, no clear career path was offered, and when you're just at the beginning of the journey, it's hard when you can't see a growth trajectory. At the same time, I love writing talks and giving them at meetups and conferences. I dedicate a lot of my free time to this activity, but the company never saw the value. More than half of my vacation days were spent going to conferences. _Can't really call them \"vacation\" days._\n\nFinally, being alone means that you inevitably reach a plateau where you stop learning and growing. This creates frustration, and a year after my arrival, I felt I reached a point of stagnation.\n\nThere were pros to staying, as well as to leaving.\n\n## The mourning process\n\nI didn't leave on a whim. [It took me weeks](.\u002Fbeing-lost-an-essay-to-find-myself-within-uncertainty.md) to come to this conclusion.\n\nIn September, I went through a significant period of reflection and reevaluation. Before considering leaving, I tried to find ways to improve my situation within the company. I discussed my concerns with multiple colleagues to find solutions. After weeks of trying a lot of different approaches, I had to accept the facts. I couldn't change anything, and considering leaving was the only remaining option.\n\nAt first, I really wanted to stay, and I questioned myself. Am I the issue? What should I do to improve the situation? Also, I was persuading myself that things could get better. People I talked to told me that they were discussing how to resolve the situation. I believed them.\n\nDuring the wait, I was at my lowest point. I completely lost the joy of working, of creating, both personally and professionally. The only thing I wanted was to stay on the sofa, scrolling through my phone, waiting for tomorrow. The reality was that the situation wouldn't improve magically.\n\nAfter some weeks of waiting, nothing changed at work. Some days were even worse than others. Of all the options I considered, only one was left: leaving. I wouldn't accept it at first, but after talking with many friends and people outside the situation, I realized it was the only acceptable option.\n\nIt took me a few days to admit it, but they were right.\n\nKnowing that I wanted to leave was a crucial step because I could start finding ways to get back up. What do I want next? Where do I go from here? I can explore absolutely anything. Once I found my direction and a new job, life became smoother, and work stopped being a pain.\n\nIn hindsight, it was really a mourning process that took place in September. I understood it after the fact, but I'm sure that understanding it while I was going through it would have helped to know how to resolve it faster.\n\n## Moving forward\n\nThat was the past, and now it's time to look ahead.\n\nI'm starting a new chapter at a company called [Takima](https:\u002F\u002Fwww.takima.fr\u002F). They're a consulting firm specialized in Angular, Java, and everything related to infrastructure. I don't know exactly what I will work on yet, but I'm excited to start working with them.\n\nI met Takima two years ago, at Devoxx France 2023. The first talk I saw was one from [Loïc](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floicortola\u002F), current CTO, and Aurélien, former consultant, about building a GitOps from scratch. I found it amazing, and even if I didn't understand everything, I knew I wanted to be able to do like them one day.\n\nIn 2024, when I went back to Devoxx France, I looked for talks from Takima. They are well written, you learn a lot, and you have a good time. Later in the year, [Camille](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcamille-pillot-455517177\u002F), consultant at Takima and organizer of tech events, invited me to talk at one of their meetups called TakiMeet.\n\nIn 2025, still at Devoxx France, I spent most of my time at their booth, chatting, discussing, and learning from the team. Since then, I had multiple opportunities to meet them at conferences.\n\nThrough all of these interactions, I discovered more than just a company. I found passionate people who care about their work, who love to share their knowledge, with a vision and a tech culture I really appreciate. I was aligned with them. Also, I will be surrounded by people who do the same work. I won't be the only one. One of my hopes is to find fulfillment in this environment.\n\nSo, when the opportunity arose to join them, I didn't hesitate.\n\nNow, it's time to learn Java! _I have never really written Java before, so this will be a new challenge for me. I will be paid for writing Java, so I have to be good._\n\n---\n\nWhat's your job? I'm building a reusable space rocket. _Was that really the truth? Who knows? It doesn't really matter now, I can no longer say that._\n\n---\n\nWhat do I really want?\n\nThis question really haunted me. At first, I wasn't able to find an answer. I wanted to find a fixed point, a clear direction that wouldn't change over time.\n\nI'm now convinced that it's an impossible quest.\n\nThe answer to this question inevitably fluctuates, and I needed to accept that it's okay for my goals and desires to evolve, along with my experiences and the people I surround myself with. This acceptance really reduced my mental burden and made me feel more peaceful.\n\n---\n\nChange is the only constant.\n",{"title":9425,"description":9644},{"loc":8917},"posts\u002Fstarting-a-new-chapter-new-ambitions-and-new-job","BN2jqCh5KDwUTz6XhEUJzrs45lk6eyzh1J3y9StRIRs",{"id":9652,"title":9653,"body":9654,"contentId":9276,"date":11286,"description":11287,"extension":18,"meta":11288,"navigation":20,"path":11289,"rawbody":11290,"robots":23,"schemaOrg":23,"seo":11291,"sitemap":11292,"stem":11293,"__hash__":11294},"posts\u002Fposts\u002Fmarkdown-exit-breaks-the-rules-with-async-rendering.md","Markdown Exit Breaks the Rules With Async Rendering",{"type":7,"value":9655,"toc":11279},[9656,9662,9665,9667,9676,9685,9688,9692,9695,9721,11276],[35,9657,9658,9659],{},"This article is the continuation of ",[3029,9660,9661],{"id":9271}," and focuses on a more concrete use case: automatically optimizing images in Markdown files.",[35,9663,9664],{},"It is highly technical, and unless you're dealing with Markdown files, this could be a little abstract. Nevertheless, I highly encourage you to read both the previous and the current article, as it could give you some nice ideas.",[1159,9666],{},[35,9668,9669,9670,9675],{},"Why write a dedicated article about it? Because, until now, this wasn't possible. The main Markdown parser in the JavaScript world is ",[39,9671,9674],{"href":9672,"rel":9673},"https:\u002F\u002Fgithub.com\u002Fmarkdown-it\u002Fmarkdown-it",[77],"markdown-it",", and it is fully synchronous.",[35,9677,9678,9679,9684],{},"This might not be an issue, but it means that ",[39,9680,9683],{"href":9681,"rel":9682},"https:\u002F\u002Fgithub.com\u002Fmarkdown-it\u002Fmarkdown-it\u002Fblob\u002Fmaster\u002Fdocs\u002Fdevelopment.md#i-need-async-rule-how-to-do-it",[77],"all rules must be synchronous"," as well. You can't have asynchronous rules. You can't perform a fetch within a rule, and you can't parallelize anything.",[35,9686,9687],{},"A little recap is needed to make sure we're all on the same page.",[51,9689,9691],{"id":9690},"writing-a-markdown-parser","Writing a Markdown parser",[35,9693,9694],{},"I won't go into details, but essentially, the parser will split the Markdown text into tokens and then apply rules, which are basically functions, to those tokens to generate the HTML.",[35,9696,9697,9698,9701,9702,9705,9706,9709,9710,9713,9714,9716,9717,9720],{},"For example, the following text ",[156,9699,9700],{},"**bold text**"," will be tokenized into: ",[156,9703,9704],{},"['strong_open', 'text', 'strong_close']"," and the corresponding rules will generate the HTML: ",[156,9707,9708],{},"\u003Cstrong>bold text\u003C\u002Fstrong>",". The token ",[156,9711,9712],{},"strong_open"," will be processed by the ",[156,9715,9712],{}," rule, which returns ",[156,9718,9719],{},"\u003Cstrong>",", and so on.",[7905,9722,9725,9728,9739,9942,9953,9957,9960,9973,9976,9985,9989,9997,10000,10007,10014,10017,10030,10033,10067,10070,10197,10201,10209,10212,10341,10344,10401,10404,10410,10418,10649,10655,10658,10666,10683,10691,10825,10828,10895,10898,10904,11025,11028,11160,11167,11204,11211,11218,11222,11225,11228,11265,11270,11273],{"text":9723,"href":9724},"Explore the emphasis rule implementation in markdown-exit","https:\u002F\u002Fgithub.com\u002Fserkodev\u002Fmarkdown-exit\u002Fblob\u002F3e0cfeb7926355f6896fcd4dd7afd797e7f6689f\u002Fpackages\u002Fmarkdown-exit\u002Fsrc\u002Fparser\u002Finline\u002Frules\u002Femphasis.ts",[35,9726,9727],{},"The rule we've been talking about since the beginning transforms a token into its corresponding HTML representation. The interesting part is that you can attach your own rules to the parser to extend its functionality.",[35,9729,9730,9731,9734,9735,9738],{},"Imagine you want to automatically apply the ",[156,9732,9733],{},"loading=\"lazy\""," attribute to all images in your Markdown files. You could write a rule that targets the ",[156,9736,9737],{},"image"," rule.",[192,9740,9743],{"className":9741,"code":9742,"language":1373,"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,9744,9745,9766,9795,9844,9864,9868,9900,9904,9932,9936],{"__ignoreMap":13},[200,9746,9747,9749,9751,9754,9756,9758,9760,9762,9764],{"class":202,"line":203},[200,9748,18],{"class":210},[200,9750,215],{"class":214},[200,9752,9753],{"class":223},"use",[200,9755,227],{"class":210},[200,9757,227],{"class":214},[200,9759,18],{"class":386},[200,9761,2089],{"class":214},[200,9763,393],{"class":379},[200,9765,280],{"class":214},[200,9767,9768,9770,9773,9775,9778,9780,9783,9785,9788,9790,9792],{"class":202,"line":14},[200,9769,3723],{"class":379},[200,9771,9772],{"class":210}," imageRule",[200,9774,510],{"class":214},[200,9776,9777],{"class":210}," md",[200,9779,215],{"class":214},[200,9781,9782],{"class":210},"renderer",[200,9784,215],{"class":214},[200,9786,9787],{"class":210},"rules",[200,9789,215],{"class":214},[200,9791,9737],{"class":210},[200,9793,9794],{"class":214},"!\n",[200,9796,9797,9800,9802,9804,9806,9808,9810,9812,9814,9816,9819,9821,9824,9826,9829,9831,9833,9835,9838,9840,9842],{"class":202,"line":255},[200,9798,9799],{"class":210},"  md",[200,9801,215],{"class":214},[200,9803,9782],{"class":210},[200,9805,215],{"class":214},[200,9807,9787],{"class":210},[200,9809,215],{"class":214},[200,9811,9737],{"class":223},[200,9813,510],{"class":214},[200,9815,1095],{"class":214},[200,9817,9818],{"class":386},"tokens",[200,9820,550],{"class":214},[200,9822,9823],{"class":386}," idx",[200,9825,550],{"class":214},[200,9827,9828],{"class":386}," options",[200,9830,550],{"class":214},[200,9832,4877],{"class":386},[200,9834,550],{"class":214},[200,9836,9837],{"class":386}," self",[200,9839,2089],{"class":214},[200,9841,393],{"class":379},[200,9843,280],{"class":214},[200,9845,9846,9848,9851,9853,9856,9859,9862],{"class":202,"line":272},[200,9847,1999],{"class":379},[200,9849,9850],{"class":210}," token",[200,9852,510],{"class":214},[200,9854,9855],{"class":210}," tokens",[200,9857,9858],{"class":235},"[",[200,9860,9861],{"class":210},"idx",[200,9863,4496],{"class":235},[200,9865,9866],{"class":202,"line":283},[200,9867,529],{"emptyLinePlaceholder":20},[200,9869,9872,9875,9877,9880,9882,9884,9887,9889,9891,9893,9896,9898],{"class":9870,"line":300},[202,9871],"highlighted",[200,9873,9874],{"class":210},"    token",[200,9876,215],{"class":214},[200,9878,9879],{"class":223},"attrSet",[200,9881,227],{"class":235},[200,9883,249],{"class":214},[200,9885,9886],{"class":245},"loading",[200,9888,249],{"class":214},[200,9890,550],{"class":214},[200,9892,242],{"class":214},[200,9894,9895],{"class":245},"lazy",[200,9897,249],{"class":214},[200,9899,5267],{"class":235},[200,9901,9902],{"class":202,"line":310},[200,9903,529],{"emptyLinePlaceholder":20},[200,9905,9906,9908,9910,9912,9914,9916,9918,9920,9922,9924,9926,9928,9930],{"class":202,"line":336},[200,9907,3296],{"class":206},[200,9909,9772],{"class":223},[200,9911,227],{"class":235},[200,9913,9818],{"class":210},[200,9915,550],{"class":214},[200,9917,9823],{"class":210},[200,9919,550],{"class":214},[200,9921,9828],{"class":210},[200,9923,550],{"class":214},[200,9925,4877],{"class":210},[200,9927,550],{"class":214},[200,9929,9837],{"class":210},[200,9931,416],{"class":235},[200,9933,9934],{"class":202,"line":342},[200,9935,4490],{"class":214},[200,9937,9938,9940],{"class":202,"line":365},[200,9939,413],{"class":214},[200,9941,416],{"class":210},[35,9943,9944,9945,9948,9949,9952],{},"Now, the following Markdown ",[156,9946,9947],{},"![alt text](image.jpg)"," will be rendered as ",[156,9950,9951],{},"\u003Cimg src=\"image.jpg\" alt=\"alt text\" loading=\"lazy\" \u002F>",". This is a dream: automatically apply changes to files without touching them.",[51,9954,9956],{"id":9955},"synchronous-limitations","Synchronous limitations",[35,9958,9959],{},"This is a dream until you realize that all of this happens synchronously. Rules are applied one by one, without any way to parallelize the process. For the highlighting rule, this can significantly reduce performance because you can't render all the code blocks at once—it's one at a time.",[939,9961,9962],{},[35,9963,9964,9966,9967,9972],{},[200,9965,945],{},"\nAnthony Fu was well aware of this and he created ",[39,9968,9971],{"href":9969,"rel":9970},"https:\u002F\u002Fgithub.com\u002Fantfu\u002Fmarkdown-it-async",[77],"markdown-it-async"," as a thin wrapper to make it possible.",[35,9974,9975],{},"Apart from parallelizing the process, synchronous rules mean that you can't perform any asynchronous operations within a rule. For example, you can't fetch data from an API. This can quickly become limiting.",[35,9977,9978,9979,9984],{},"But, with ",[39,9980,9983],{"href":9981,"rel":9982},"https:\u002F\u002Fgithub.com\u002Fserkodev\u002Fmarkdown-exit",[77],"markdown-exit",", a drop-in replacement and complete rewrite of markdown-it using TypeScript, this is now possible. Asynchronous rules are now a reality.",[51,9986,9988],{"id":9987},"optimizing-images-on-the-fly","Optimizing images on the fly",[35,9990,9991,9992,9996],{},"In ",[39,9993,9995],{"href":9994},"\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite","the previous article",", we leveraged a Vite plugin to automatically apply optimizations to images in our HTML. This was a good and useful start.",[35,9998,9999],{},"For me, this wasn't enough because I mostly deal with Markdown files, and building a custom rule is a better choice. That's what we'll see now.",[35,10001,10002,10003],{},"All the source code is available on GitHub: ",[1582,10004,10006],{"repo":10005},"Barbapapazes\u002Fmarkdown-exit-unpic",". To keep this article simple, I won't show unrelated code like the project setup.",[35,10008,10009,10010,10013],{},"Let's imagine we have a ",[156,10011,10012],{},"src\u002Findex.ts"," file. This file is our main script, and the goal is to render raw Markdown to HTML while optimizing images.",[35,10015,10016],{},"We want to run the following command:",[192,10018,10020],{"className":1355,"code":10019,"language":1358,"meta":13,"style":13},"node src\u002Findex.ts\n",[156,10021,10022],{"__ignoreMap":13},[200,10023,10024,10027],{"class":202,"line":203},[200,10025,10026],{"class":1978},"node",[200,10028,10029],{"class":245}," src\u002Findex.ts\n",[35,10031,10032],{},"with this Markdown content:",[192,10034,10036],{"className":5891,"code":10035,"language":18,"meta":13,"style":13},"# Hello World\n\n![This is a placeholder](https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666)\n",[156,10037,10038,10046,10050],{"__ignoreMap":13},[200,10039,10040,10043],{"class":202,"line":203},[200,10041,10042],{"class":214},"# ",[200,10044,10045],{"class":1978},"Hello World\n",[200,10047,10048],{"class":202,"line":14},[200,10049,529],{"emptyLinePlaceholder":20},[200,10051,10052,10055,10058,10061,10065],{"class":202,"line":255},[200,10053,10054],{"class":214},"![",[200,10056,10057],{"class":245},"This is a placeholder",[200,10059,10060],{"class":214},"](",[200,10062,10064],{"class":10063},"sQLHv","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666",[200,10066,416],{"class":214},[35,10068,10069],{},"and have the following output:",[192,10071,10073],{"className":591,"code":10072,"language":593,"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,10074,10075,10093],{"__ignoreMap":13},[200,10076,10077,10079,10082,10084,10087,10089,10091],{"class":202,"line":203},[200,10078,600],{"class":214},[200,10080,10081],{"class":235},"h1",[200,10083,675],{"class":214},[200,10085,10086],{"class":210},"Hello World",[200,10088,681],{"class":214},[200,10090,10081],{"class":235},[200,10092,648],{"class":214},[200,10094,10095,10097,10099,10102,10104,10107,10109,10111,10114,10117,10120,10122,10125,10127,10130,10132,10135,10137,10139,10141,10143,10146,10148,10150,10152,10154,10157,10159,10161,10164,10166,10169,10171,10173,10176,10178,10181,10183,10185,10188,10190,10193,10195],{"class":202,"line":14},[200,10096,600],{"class":214},[200,10098,35],{"class":235},[200,10100,10101],{"class":214},">\u003C",[200,10103,1690],{"class":235},[200,10105,10106],{"class":379}," src",[200,10108,449],{"class":214},[200,10110,611],{"class":214},[200,10112,10113],{"class":245},"https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666?auto=format",[200,10115,10116],{"class":210},"&amp;",[200,10118,10119],{"class":245},"fit=crop",[200,10121,10116],{"class":210},[200,10123,10124],{"class":245},"q=80",[200,10126,10116],{"class":210},[200,10128,10129],{"class":245},"w=800",[200,10131,611],{"class":214},[200,10133,10134],{"class":379}," alt",[200,10136,449],{"class":214},[200,10138,611],{"class":214},[200,10140,10057],{"class":245},[200,10142,611],{"class":214},[200,10144,10145],{"class":379}," loading",[200,10147,449],{"class":214},[200,10149,611],{"class":214},[200,10151,9895],{"class":245},[200,10153,611],{"class":214},[200,10155,10156],{"class":379}," width",[200,10158,449],{"class":214},[200,10160,611],{"class":214},[200,10162,10163],{"class":245},"2567",[200,10165,611],{"class":214},[200,10167,10168],{"class":379}," height",[200,10170,449],{"class":214},[200,10172,611],{"class":214},[200,10174,10175],{"class":245},"2000",[200,10177,611],{"class":214},[200,10179,10180],{"class":379}," style",[200,10182,449],{"class":214},[200,10184,611],{"class":214},[200,10186,10187],{"class":245},"background-size: cover; background-image: url(data:image\u002Fbmp;base64,Qk32BAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAMAAAAATCwAAEwsAAAAAAAAAAAAACQYICgcIDAoKEQ8PFBISFRISEQ4OCgYGCAYGDAoKExESGhgYHRsbHBoaFhMTDAgICAcGDgwMGBcXIB8fIyIiISAgGhgYDgoLCQcGDw0NGRgYIiEhJiUlJCIiHBoaDwsMCgcHDgwMFxYWHx4eIyIiISAgGhgYDgsLCQYGDAkJEhAQGBcXGxoaGxkZFRMTDAgICAUECQYGDAkJEA0OEhAQEhAQDwwMCQYFBwUEBwUECQYGCwcJDQoKDQsKCwgHCQUE);",[200,10189,611],{"class":214},[200,10191,10192],{"class":214},">\u003C\u002F",[200,10194,35],{"class":235},[200,10196,648],{"class":214},[51,10198,10200],{"id":10199},"building-a-custom-async-rule","Building a custom async rule",[35,10202,10203,10204,215],{},"The core logic of the rule won't differ from what we've built in the Vite plugin. Instead of reading the file from the local filesystem, we will fetch it from Unsplash to simulate a remote bucket. Then, we will retrieve the width and height, and generate the blurhash. At the end, we will also apply some custom query parameters to avoid the client loading the full image, like you can do with ",[39,10205,10208],{"href":10206,"rel":10207},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fimages\u002Ftransform-images\u002Ftransform-via-url\u002F",[77],"Cloudflare Images",[35,10210,10211],{},"First, we need to create a new markdown-exit instance and render the Markdown. This is really straightforward.",[192,10213,10215],{"className":1370,"code":10214,"language":1373,"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,10216,10217,10236,10240,10253,10257,10270,10295,10299,10308,10312,10326,10330,10334],{"__ignoreMap":13},[200,10218,10219,10221,10223,10226,10228,10230,10232,10234],{"class":202,"line":203},[200,10220,973],{"class":206},[200,10222,318],{"class":214},[200,10224,10225],{"class":210}," createMarkdownExit",[200,10227,981],{"class":214},[200,10229,984],{"class":206},[200,10231,242],{"class":214},[200,10233,9983],{"class":245},[200,10235,992],{"class":214},[200,10237,10238],{"class":202,"line":14},[200,10239,529],{"emptyLinePlaceholder":20},[200,10241,10242,10244,10247,10249,10251],{"class":202,"line":255},[200,10243,443],{"class":379},[200,10245,10246],{"class":210}," md ",[200,10248,449],{"class":214},[200,10250,10225],{"class":223},[200,10252,466],{"class":210},[200,10254,10255],{"class":202,"line":272},[200,10256,529],{"emptyLinePlaceholder":20},[200,10258,10259,10261,10263,10266,10268],{"class":202,"line":283},[200,10260,3711],{"class":379},[200,10262,4987],{"class":379},[200,10264,10265],{"class":223}," render",[200,10267,1532],{"class":214},[200,10269,280],{"class":214},[200,10271,10272,10274,10277,10279,10281,10283,10285,10288,10290,10292],{"class":202,"line":300},[200,10273,3723],{"class":379},[200,10275,10276],{"class":210}," html",[200,10278,510],{"class":214},[200,10280,452],{"class":206},[200,10282,9777],{"class":210},[200,10284,215],{"class":214},[200,10286,10287],{"class":223},"renderAsync",[200,10289,227],{"class":235},[200,10291,3318],{"class":214},[200,10293,10294],{"class":245},"# Hello World\n",[200,10296,10297],{"class":202,"line":310},[200,10298,529],{"emptyLinePlaceholder":20},[200,10300,10301,10304,10306],{"class":202,"line":336},[200,10302,10303],{"class":245},"![This is a placeholder](https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666)",[200,10305,3318],{"class":214},[200,10307,416],{"class":235},[200,10309,10310],{"class":202,"line":342},[200,10311,529],{"emptyLinePlaceholder":20},[200,10313,10314,10316,10318,10320,10322,10324],{"class":202,"line":365},[200,10315,1113],{"class":210},[200,10317,215],{"class":214},[200,10319,1118],{"class":223},[200,10321,227],{"class":235},[200,10323,593],{"class":210},[200,10325,416],{"class":235},[200,10327,10328],{"class":202,"line":371},[200,10329,1130],{"class":214},[200,10331,10332],{"class":202,"line":398},[200,10333,529],{"emptyLinePlaceholder":20},[200,10335,10336,10339],{"class":202,"line":405},[200,10337,10338],{"class":223},"render",[200,10340,466],{"class":210},[35,10342,10343],{},"Nothing fancy in this script. However, the output is the following:",[192,10345,10347],{"className":591,"code":10346,"language":593,"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,10348,10349,10365],{"__ignoreMap":13},[200,10350,10351,10353,10355,10357,10359,10361,10363],{"class":202,"line":203},[200,10352,600],{"class":214},[200,10354,10081],{"class":235},[200,10356,675],{"class":214},[200,10358,10086],{"class":210},[200,10360,681],{"class":214},[200,10362,10081],{"class":235},[200,10364,648],{"class":214},[200,10366,10367,10369,10371,10373,10375,10377,10379,10381,10383,10385,10387,10389,10391,10393,10395,10397,10399],{"class":202,"line":14},[200,10368,600],{"class":214},[200,10370,35],{"class":235},[200,10372,10101],{"class":214},[200,10374,1690],{"class":235},[200,10376,10106],{"class":379},[200,10378,449],{"class":214},[200,10380,611],{"class":214},[200,10382,10064],{"class":245},[200,10384,611],{"class":214},[200,10386,10134],{"class":379},[200,10388,449],{"class":214},[200,10390,611],{"class":214},[200,10392,10057],{"class":245},[200,10394,611],{"class":214},[200,10396,10192],{"class":214},[200,10398,35],{"class":235},[200,10400,648],{"class":214},[35,10402,10403],{},"There are some missing parts, but that's a good start.",[35,10405,10406,10407,10409],{},"To change the image rendering behavior, we will hook into the ",[156,10408,9737],{}," rule, adjust the token to our needs, and use the default implementation to generate the output.",[35,10411,10412,10413,10415,10416,9738],{},"To create a new rule, we can use the ",[156,10414,9753],{}," function. It's like creating a Vue plugin. Then, bind the function we want to the ",[156,10417,9737],{},[192,10419,10421],{"className":1370,"code":10420,"language":1373,"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,10422,10423,10444,10462,10466,10478,10482,10506,10530,10576,10604,10608,10614,10618,10630,10635,10639,10643],{"__ignoreMap":13},[200,10424,10425,10427,10429,10431,10434,10436,10438,10440,10442],{"class":202,"line":203},[200,10426,973],{"class":206},[200,10428,321],{"class":206},[200,10430,318],{"class":214},[200,10432,10433],{"class":210}," MarkdownExit",[200,10435,981],{"class":214},[200,10437,984],{"class":206},[200,10439,242],{"class":214},[200,10441,9983],{"class":245},[200,10443,992],{"class":214},[200,10445,10446,10448,10450,10452,10454,10456,10458,10460],{"class":202,"line":14},[200,10447,973],{"class":206},[200,10449,318],{"class":214},[200,10451,10225],{"class":210},[200,10453,981],{"class":214},[200,10455,984],{"class":206},[200,10457,242],{"class":214},[200,10459,9983],{"class":245},[200,10461,992],{"class":214},[200,10463,10464],{"class":202,"line":255},[200,10465,529],{"emptyLinePlaceholder":20},[200,10467,10468,10470,10472,10474,10476],{"class":202,"line":272},[200,10469,443],{"class":379},[200,10471,10246],{"class":210},[200,10473,449],{"class":214},[200,10475,10225],{"class":223},[200,10477,466],{"class":210},[200,10479,10480],{"class":202,"line":283},[200,10481,529],{"emptyLinePlaceholder":20},[200,10483,10484,10486,10488,10490,10492,10494,10496,10498,10500,10502,10504],{"class":202,"line":300},[200,10485,18],{"class":210},[200,10487,215],{"class":214},[200,10489,9753],{"class":223},[200,10491,227],{"class":210},[200,10493,227],{"class":214},[200,10495,18],{"class":386},[200,10497,239],{"class":214},[200,10499,10433],{"class":1978},[200,10501,2089],{"class":214},[200,10503,393],{"class":379},[200,10505,280],{"class":214},[200,10507,10508,10510,10512,10514,10516,10518,10520,10522,10524,10526,10528],{"class":202,"line":310},[200,10509,3723],{"class":379},[200,10511,9772],{"class":210},[200,10513,510],{"class":214},[200,10515,9777],{"class":210},[200,10517,215],{"class":214},[200,10519,9782],{"class":210},[200,10521,215],{"class":214},[200,10523,9787],{"class":210},[200,10525,215],{"class":214},[200,10527,9737],{"class":210},[200,10529,9794],{"class":214},[200,10531,10532,10534,10536,10538,10540,10542,10544,10546,10548,10550,10552,10554,10556,10558,10560,10562,10564,10566,10568,10570,10572,10574],{"class":202,"line":336},[200,10533,9799],{"class":210},[200,10535,215],{"class":214},[200,10537,9782],{"class":210},[200,10539,215],{"class":214},[200,10541,9787],{"class":210},[200,10543,215],{"class":214},[200,10545,9737],{"class":223},[200,10547,510],{"class":214},[200,10549,380],{"class":379},[200,10551,1095],{"class":214},[200,10553,9818],{"class":386},[200,10555,550],{"class":214},[200,10557,9823],{"class":386},[200,10559,550],{"class":214},[200,10561,9828],{"class":386},[200,10563,550],{"class":214},[200,10565,4877],{"class":386},[200,10567,550],{"class":214},[200,10569,9837],{"class":386},[200,10571,2089],{"class":214},[200,10573,393],{"class":379},[200,10575,280],{"class":214},[200,10577,10578,10580,10582,10584,10586,10588,10590,10592,10594,10596,10598,10600,10602],{"class":202,"line":342},[200,10579,3296],{"class":206},[200,10581,9772],{"class":223},[200,10583,227],{"class":235},[200,10585,9818],{"class":210},[200,10587,550],{"class":214},[200,10589,9823],{"class":210},[200,10591,550],{"class":214},[200,10593,9828],{"class":210},[200,10595,550],{"class":214},[200,10597,4877],{"class":210},[200,10599,550],{"class":214},[200,10601,9837],{"class":210},[200,10603,416],{"class":235},[200,10605,10606],{"class":202,"line":365},[200,10607,4490],{"class":214},[200,10609,10610,10612],{"class":202,"line":371},[200,10611,413],{"class":214},[200,10613,416],{"class":210},[200,10615,10616],{"class":202,"line":398},[200,10617,529],{"emptyLinePlaceholder":20},[200,10619,10620,10622,10624,10626,10628],{"class":202,"line":405},[200,10621,3711],{"class":379},[200,10623,4987],{"class":379},[200,10625,10265],{"class":223},[200,10627,1532],{"class":214},[200,10629,280],{"class":214},[200,10631,10632],{"class":202,"line":410},[200,10633,10634],{"class":401},"  \u002F\u002F ...\n",[200,10636,10637],{"class":202,"line":861},[200,10638,1130],{"class":214},[200,10640,10641],{"class":202,"line":890},[200,10642,529],{"emptyLinePlaceholder":20},[200,10644,10645,10647],{"class":202,"line":900},[200,10646,10338],{"class":223},[200,10648,466],{"class":210},[35,10650,10651,10652,10654],{},"Our function does nothing apart from calling the original ",[156,10653,9737],{}," rule, which is already a good step because we don't want to reimplement it ourselves.",[35,10656,10657],{},"Then, we can grab the current token and try to add an attribute to it.",[35,10659,419,10660,10662,10663,10665],{},[156,10661,9818],{}," variable contains all available tokens. It's an array of tokens. To access the current one, we need to use the ",[156,10664,9861],{}," variable, which is the current index of the token.",[192,10667,10669],{"className":1370,"code":10668,"language":1373,"meta":13,"style":13},"const token = tokens[idx]\n",[156,10670,10671],{"__ignoreMap":13},[200,10672,10673,10675,10678,10680],{"class":202,"line":203},[200,10674,443],{"class":379},[200,10676,10677],{"class":210}," token ",[200,10679,449],{"class":214},[200,10681,10682],{"class":210}," tokens[idx]\n",[35,10684,10685,10686,10688,10689,215],{},"Now, we can modify the token. For example, we can add a ",[156,10687,9886],{}," attribute with the value ",[156,10690,9895],{},[192,10692,10694],{"className":1370,"code":10693,"language":1373,"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,10695,10696,10742,10758,10762,10789,10793,10821],{"__ignoreMap":13},[200,10697,10698,10700,10702,10704,10706,10708,10710,10712,10714,10716,10718,10720,10722,10724,10726,10728,10730,10732,10734,10736,10738,10740],{"class":202,"line":203},[200,10699,18],{"class":210},[200,10701,215],{"class":214},[200,10703,9782],{"class":210},[200,10705,215],{"class":214},[200,10707,9787],{"class":210},[200,10709,215],{"class":214},[200,10711,9737],{"class":223},[200,10713,510],{"class":214},[200,10715,380],{"class":379},[200,10717,1095],{"class":214},[200,10719,9818],{"class":386},[200,10721,550],{"class":214},[200,10723,9823],{"class":386},[200,10725,550],{"class":214},[200,10727,9828],{"class":386},[200,10729,550],{"class":214},[200,10731,4877],{"class":386},[200,10733,550],{"class":214},[200,10735,9837],{"class":386},[200,10737,2089],{"class":214},[200,10739,393],{"class":379},[200,10741,280],{"class":214},[200,10743,10744,10746,10748,10750,10752,10754,10756],{"class":202,"line":14},[200,10745,3723],{"class":379},[200,10747,9850],{"class":210},[200,10749,510],{"class":214},[200,10751,9855],{"class":210},[200,10753,9858],{"class":235},[200,10755,9861],{"class":210},[200,10757,4496],{"class":235},[200,10759,10760],{"class":202,"line":255},[200,10761,529],{"emptyLinePlaceholder":20},[200,10763,10764,10767,10769,10771,10773,10775,10777,10779,10781,10783,10785,10787],{"class":202,"line":272},[200,10765,10766],{"class":210},"  token",[200,10768,215],{"class":214},[200,10770,9879],{"class":223},[200,10772,227],{"class":235},[200,10774,249],{"class":214},[200,10776,9886],{"class":245},[200,10778,249],{"class":214},[200,10780,550],{"class":214},[200,10782,242],{"class":214},[200,10784,9895],{"class":245},[200,10786,249],{"class":214},[200,10788,416],{"class":235},[200,10790,10791],{"class":202,"line":283},[200,10792,529],{"emptyLinePlaceholder":20},[200,10794,10795,10797,10799,10801,10803,10805,10807,10809,10811,10813,10815,10817,10819],{"class":202,"line":300},[200,10796,3743],{"class":206},[200,10798,9772],{"class":223},[200,10800,227],{"class":235},[200,10802,9818],{"class":210},[200,10804,550],{"class":214},[200,10806,9823],{"class":210},[200,10808,550],{"class":214},[200,10810,9828],{"class":210},[200,10812,550],{"class":214},[200,10814,4877],{"class":210},[200,10816,550],{"class":214},[200,10818,9837],{"class":210},[200,10820,416],{"class":235},[200,10822,10823],{"class":202,"line":310},[200,10824,1130],{"class":214},[35,10826,10827],{},"The output of our script is now the following:",[192,10829,10831],{"className":591,"code":10830,"language":593,"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,10832,10833,10849],{"__ignoreMap":13},[200,10834,10835,10837,10839,10841,10843,10845,10847],{"class":202,"line":203},[200,10836,600],{"class":214},[200,10838,10081],{"class":235},[200,10840,675],{"class":214},[200,10842,10086],{"class":210},[200,10844,681],{"class":214},[200,10846,10081],{"class":235},[200,10848,648],{"class":214},[200,10850,10851,10853,10855,10857,10859,10861,10863,10865,10867,10869,10871,10873,10875,10877,10879,10881,10883,10885,10887,10889,10891,10893],{"class":202,"line":14},[200,10852,600],{"class":214},[200,10854,35],{"class":235},[200,10856,10101],{"class":214},[200,10858,1690],{"class":235},[200,10860,10106],{"class":379},[200,10862,449],{"class":214},[200,10864,611],{"class":214},[200,10866,10064],{"class":245},[200,10868,611],{"class":214},[200,10870,10134],{"class":379},[200,10872,449],{"class":214},[200,10874,611],{"class":214},[200,10876,10057],{"class":245},[200,10878,611],{"class":214},[200,10880,10145],{"class":379},[200,10882,449],{"class":214},[200,10884,611],{"class":214},[200,10886,9895],{"class":245},[200,10888,611],{"class":214},[200,10890,10192],{"class":214},[200,10892,35],{"class":235},[200,10894,648],{"class":214},[35,10896,10897],{},"Easy, right?",[35,10899,10900,10901],{},"Based on our expected behavior, we need to add the width, height, blurhash, and some query parameters to the image. To do so, we will use the same logic as in ",[3029,10902,10903],{"id":9271},", except that we will fetch the image from Unsplash, as you could do with any remote bucket.",[192,10905,10907],{"className":1370,"code":10906,"language":1373,"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,10908,10909,10948,10965,10969],{"__ignoreMap":13},[200,10910,10911,10913,10916,10918,10920,10922,10925,10927,10930,10932,10935,10937,10940,10942,10945],{"class":202,"line":203},[200,10912,443],{"class":379},[200,10914,10915],{"class":210}," img ",[200,10917,449],{"class":214},[200,10919,452],{"class":206},[200,10921,3299],{"class":223},[200,10923,10924],{"class":210},"(src)",[200,10926,215],{"class":214},[200,10928,10929],{"class":223},"then",[200,10931,227],{"class":210},[200,10933,10934],{"class":386},"res",[200,10936,393],{"class":379},[200,10938,10939],{"class":210}," res",[200,10941,215],{"class":214},[200,10943,10944],{"class":223},"bytes",[200,10946,10947],{"class":210},"())\n",[200,10949,10950,10952,10955,10957,10959,10962],{"class":202,"line":14},[200,10951,443],{"class":379},[200,10953,10954],{"class":210}," data ",[200,10956,449],{"class":214},[200,10958,452],{"class":206},[200,10960,10961],{"class":223}," getPixels",[200,10963,10964],{"class":210},"(img)\n",[200,10966,10967],{"class":202,"line":255},[200,10968,529],{"emptyLinePlaceholder":20},[200,10970,10971,10973,10976,10978,10981,10984,10986,10988,10991,10993,10996,10998,11000,11002,11005,11007,11009,11011,11014,11016,11019,11021,11023],{"class":202,"line":272},[200,10972,443],{"class":379},[200,10974,10975],{"class":210}," blurhash ",[200,10977,449],{"class":214},[200,10979,10980],{"class":223}," encode",[200,10982,10983],{"class":210},"(Uint8ClampedArray",[200,10985,215],{"class":214},[200,10987,1472],{"class":223},[200,10989,10990],{"class":210},"(data",[200,10992,215],{"class":214},[200,10994,10995],{"class":210},"data)",[200,10997,550],{"class":214},[200,10999,5041],{"class":210},[200,11001,215],{"class":214},[200,11003,11004],{"class":210},"width",[200,11006,550],{"class":214},[200,11008,5041],{"class":210},[200,11010,215],{"class":214},[200,11012,11013],{"class":210},"height",[200,11015,550],{"class":214},[200,11017,11018],{"class":2045}," 4",[200,11020,550],{"class":214},[200,11022,11018],{"class":2045},[200,11024,416],{"class":210},[35,11026,11027],{},"Now, we can set the width, height, and style attributes on the token.",[192,11029,11031],{"className":1370,"code":11030,"language":1373,"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,11032,11033,11053,11057,11089,11119],{"__ignoreMap":13},[200,11034,11035,11037,11039,11042,11044,11046,11048,11051],{"class":202,"line":203},[200,11036,973],{"class":206},[200,11038,318],{"class":214},[200,11040,11041],{"class":210}," blurhashToDataUri",[200,11043,981],{"class":214},[200,11045,984],{"class":206},[200,11047,242],{"class":214},[200,11049,11050],{"class":245},"@unpic\u002Fplaceholder",[200,11052,992],{"class":214},[200,11054,11055],{"class":202,"line":14},[200,11056,529],{"emptyLinePlaceholder":20},[200,11058,11059,11062,11064,11066,11068,11070,11072,11074,11076,11078,11080,11082,11084,11087],{"class":202,"line":255},[200,11060,11061],{"class":210},"token",[200,11063,215],{"class":214},[200,11065,9879],{"class":223},[200,11067,227],{"class":210},[200,11069,249],{"class":214},[200,11071,11004],{"class":245},[200,11073,249],{"class":214},[200,11075,550],{"class":214},[200,11077,5041],{"class":210},[200,11079,215],{"class":214},[200,11081,11004],{"class":210},[200,11083,215],{"class":214},[200,11085,11086],{"class":223},"toString",[200,11088,10947],{"class":210},[200,11090,11091,11093,11095,11097,11099,11101,11103,11105,11107,11109,11111,11113,11115,11117],{"class":202,"line":272},[200,11092,11061],{"class":210},[200,11094,215],{"class":214},[200,11096,9879],{"class":223},[200,11098,227],{"class":210},[200,11100,249],{"class":214},[200,11102,11013],{"class":245},[200,11104,249],{"class":214},[200,11106,550],{"class":214},[200,11108,5041],{"class":210},[200,11110,215],{"class":214},[200,11112,11013],{"class":210},[200,11114,215],{"class":214},[200,11116,11086],{"class":223},[200,11118,10947],{"class":210},[200,11120,11121,11123,11125,11127,11129,11131,11133,11135,11137,11140,11143,11145,11148,11151,11153,11156,11158],{"class":202,"line":283},[200,11122,11061],{"class":210},[200,11124,215],{"class":214},[200,11126,9879],{"class":223},[200,11128,227],{"class":210},[200,11130,249],{"class":214},[200,11132,1171],{"class":245},[200,11134,249],{"class":214},[200,11136,550],{"class":214},[200,11138,11139],{"class":214}," `",[200,11141,11142],{"class":245},"background-size: cover; background-image: url(",[200,11144,4853],{"class":214},[200,11146,11147],{"class":223},"blurhashToDataUri",[200,11149,11150],{"class":210},"(blurhash)",[200,11152,413],{"class":214},[200,11154,11155],{"class":245},");",[200,11157,3318],{"class":214},[200,11159,416],{"class":210},[35,11161,11162,11163,11166],{},"Finally, we can adjust the ",[156,11164,11165],{},"src"," attribute to add some query parameters.",[192,11168,11170],{"className":1370,"code":11169,"language":1373,"meta":13,"style":13},"token.attrSet('src', `${src}?auto=format&fit=crop&q=80&w=800`)\n",[156,11171,11172],{"__ignoreMap":13},[200,11173,11174,11176,11178,11180,11182,11184,11186,11188,11190,11193,11195,11197,11200,11202],{"class":202,"line":203},[200,11175,11061],{"class":210},[200,11177,215],{"class":214},[200,11179,9879],{"class":223},[200,11181,227],{"class":210},[200,11183,249],{"class":214},[200,11185,11165],{"class":245},[200,11187,249],{"class":214},[200,11189,550],{"class":214},[200,11191,11192],{"class":214}," `${",[200,11194,11165],{"class":210},[200,11196,413],{"class":214},[200,11198,11199],{"class":245},"?auto=format&fit=crop&q=80&w=800",[200,11201,3318],{"class":214},[200,11203,416],{"class":210},[35,11205,11206,11207,11210],{},"These queries are specific to Unsplash, but you could do the same with ",[39,11208,10208],{"href":10206,"rel":11209},[77]," or any image optimization service.",[35,11212,11213,11214,11217],{},"A quick note on this approach: it works fine. Parallelism slightly improves performance because most of the computation happens in the ",[156,11215,11216],{},"encode"," function that transforms the image into a blurhash synchronously. On my computer, fetching the image takes about 100ms, while encoding it takes about 3 seconds.",[51,11219,11221],{"id":11220},"going-further","Going further",[35,11223,11224],{},"The current implementation is simple, and that's intentional. Now that you know how to optimize your images and create custom rules, you can easily adapt this approach to your needs, and even create new ones that fit your specific use cases.",[35,11226,11227],{},"To deal with the performance bottleneck, you have many possibilities. Three of them are:",[68,11229,11230,11236,11245],{},[71,11231,11232,11235],{},[6850,11233,11234],{},"Local pre-processing and local storage",": If you store images within your code source, you can create a script that will go through all images and generate a JSON file containing the width, height, and the blurhash. Then, in the rule, you'll just have to read this file, and no more computation is needed. This will reduce the build time as well.",[71,11237,11238,11241,11242],{},[6850,11239,11240],{},"Local pre-processing and remote storage",": If your images are stored remotely, you can create a similar script that will fetch the images, process them, and generate the JSON file. This file can be pushed to a bucket for later use. In the rule, instead of reading a file, you can fetch it from the bucket. ",[1217,11243,11244],{},"Without asynchronous rules, this wasn't possible!",[71,11246,11247,11250,11251],{},[6850,11248,11249],{},"Remote pre-processing and remote storage",": If your images are stored remotely, you can create a workflow that will be triggered each time a new file is added. The workflow generates the JSON file and stores it in the same bucket. In the rule, you can fetch the JSON file from the bucket and use it directly. ",[1217,11252,11253,11254,1638,11259,11264],{},"The maximal optimization. With ",[39,11255,11258],{"href":11256,"rel":11257},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fr2\u002Fbuckets\u002Fevent-notifications\u002F",[77],"R2 notifications",[39,11260,11263],{"href":11261,"rel":11262},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fqueues\u002Fevent-subscriptions\u002Fevents-schemas\u002F#r2",[77],"Queues",", this is simple to implement.",[35,11266,11267],{},[1217,11268,11269],{},"You could also create a worker pool using Node threads, but it is overkill for this use case.",[35,11271,11272],{},"I'm sure there are a lot of other ways to automate image optimization. Now, it's up to you to find the best approach that fits your workflow.",[35,11274,11275],{},"Hope you enjoyed the article! If you implement something similar, keep me posted!",[1171,11277,11278],{},"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 .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 .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}",{"title":13,"searchDepth":14,"depth":14,"links":11280},[11281,11282,11283,11284,11285],{"id":9690,"depth":14,"text":9691},{"id":9955,"depth":14,"text":9956},{"id":9987,"depth":14,"text":9988},{"id":10199,"depth":14,"text":10200},{"id":11220,"depth":14,"text":11221},"2025-11-19","Previously markdown-it required synchronous rendering and rules. markdown-exit supports async rendering and rules, enabling richer features and parallelism.",{},"\u002Fposts\u002Fmarkdown-exit-breaks-the-rules-with-async-rendering","---\ncontentId: d64b06de-1e87-41a9-a93f-aa6e42c6bdbc\ntitle: Markdown Exit Breaks the Rules With Async Rendering\ndescription: Previously markdown-it required synchronous rendering and rules. markdown-exit supports async rendering and rules, enabling richer features and parallelism.\ndate: 2025-11-19\n---\n\nThis article is the continuation of \u003CLinkToArticle id=\"264d08e1-fb05-40c0-b298-0ba1dbbd3f14\" \u002F> and focuses on a more concrete use case: automatically optimizing images in Markdown files.\n\nIt is highly technical, and unless you're dealing with Markdown files, this could be a little abstract. Nevertheless, I highly encourage you to read both the previous and the current article, as it could give you some nice ideas.\n\n---\n\nWhy write a dedicated article about it? Because, until now, this wasn't possible. The main Markdown parser in the JavaScript world is [markdown-it](https:\u002F\u002Fgithub.com\u002Fmarkdown-it\u002Fmarkdown-it), and it is fully synchronous.\n\nThis might not be an issue, but it means that [all rules must be synchronous](https:\u002F\u002Fgithub.com\u002Fmarkdown-it\u002Fmarkdown-it\u002Fblob\u002Fmaster\u002Fdocs\u002Fdevelopment.md#i-need-async-rule-how-to-do-it) as well. You can't have asynchronous rules. You can't perform a fetch within a rule, and you can't parallelize anything.\n\nA little recap is needed to make sure we're all on the same page.\n\n## Writing a Markdown parser\n\nI won't go into details, but essentially, the parser will split the Markdown text into tokens and then apply rules, which are basically functions, to those tokens to generate the HTML.\n\nFor example, the following text `**bold text**` will be tokenized into: `['strong_open', 'text', 'strong_close']` and the corresponding rules will generate the HTML: `\u003Cstrong>bold text\u003C\u002Fstrong>`. The token `strong_open` will be processed by the `strong_open` rule, which returns `\u003Cstrong>`, and so on.\n\n\u003CCalloutReadMore\n  text=\"Explore the emphasis rule implementation in 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\nThe rule we've been talking about since the beginning transforms a token into its corresponding HTML representation. The interesting part is that you can attach your own rules to the parser to extend its functionality.\n\nImagine you want to automatically apply the `loading=\"lazy\"` attribute to all images in your Markdown files. You could write a rule that targets the `image` rule.\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\nNow, the following Markdown `![alt text](image.jpg)` will be rendered as `\u003Cimg src=\"image.jpg\" alt=\"alt text\" loading=\"lazy\" \u002F>`. This is a dream: automatically apply changes to files without touching them.\n\n## Synchronous limitations\n\nThis is a dream until you realize that all of this happens synchronously. Rules are applied one by one, without any way to parallelize the process. For the highlighting rule, this can significantly reduce performance because you can't render all the code blocks at once—it's one at a time.\n\n> [!NOTE]\n> Anthony Fu was well aware of this and he created [markdown-it-async](https:\u002F\u002Fgithub.com\u002Fantfu\u002Fmarkdown-it-async) as a thin wrapper to make it possible.\n\nApart from parallelizing the process, synchronous rules mean that you can't perform any asynchronous operations within a rule. For example, you can't fetch data from an API. This can quickly become limiting.\n\nBut, with [markdown-exit](https:\u002F\u002Fgithub.com\u002Fserkodev\u002Fmarkdown-exit), a drop-in replacement and complete rewrite of markdown-it using TypeScript, this is now possible. Asynchronous rules are now a reality.\n\n## Optimizing images on the fly\n\nIn [the previous article](\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite), we leveraged a Vite plugin to automatically apply optimizations to images in our HTML. This was a good and useful start.\n\nFor me, this wasn't enough because I mostly deal with Markdown files, and building a custom rule is a better choice. That's what we'll see now.\n\nAll the source code is available on GitHub: \u003CGitHubLink repo=\"Barbapapazes\u002Fmarkdown-exit-unpic\" \u002F>. To keep this article simple, I won't show unrelated code like the project setup.\n\nLet's imagine we have a `src\u002Findex.ts` file. This file is our main script, and the goal is to render raw Markdown to HTML while optimizing images.\n\nWe want to run the following command:\n\n```sh\nnode src\u002Findex.ts\n```\n\nwith this Markdown content:\n\n```md\n# Hello World\n\n![This is a placeholder](https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666)\n```\n\nand have the following output:\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## Building a custom async rule\n\nThe core logic of the rule won't differ from what we've built in the Vite plugin. Instead of reading the file from the local filesystem, we will fetch it from Unsplash to simulate a remote bucket. Then, we will retrieve the width and height, and generate the blurhash. At the end, we will also apply some custom query parameters to avoid the client loading the full image, like you can do with [Cloudflare Images](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fimages\u002Ftransform-images\u002Ftransform-via-url\u002F).\n\nFirst, we need to create a new markdown-exit instance and render the Markdown. This is really straightforward.\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\nNothing fancy in this script. However, the output is the following:\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\nThere are some missing parts, but that's a good start.\n\nTo change the image rendering behavior, we will hook into the `image` rule, adjust the token to our needs, and use the default implementation to generate the output.\n\nTo create a new rule, we can use the `use` function. It's like creating a Vue plugin. Then, bind the function we want to the `image` rule.\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\nOur function does nothing apart from calling the original `image` rule, which is already a good step because we don't want to reimplement it ourselves.\n\nThen, we can grab the current token and try to add an attribute to it.\n\nThe `tokens` variable contains all available tokens. It's an array of tokens. To access the current one, we need to use the `idx` variable, which is the current index of the token.\n\n```ts\nconst token = tokens[idx]\n```\n\nNow, we can modify the token. For example, we can add a `loading` attribute with the value `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\nThe output of our script is now the following:\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\nEasy, right?\n\nBased on our expected behavior, we need to add the width, height, blurhash, and some query parameters to the image. To do so, we will use the same logic as in \u003CLinkToArticle id=\"264d08e1-fb05-40c0-b298-0ba1dbbd3f14\" \u002F>, except that we will fetch the image from Unsplash, as you could do with any remote bucket.\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\nNow, we can set the width, height, and style attributes on the 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\nFinally, we can adjust the `src` attribute to add some query parameters.\n\n```ts\ntoken.attrSet('src', `${src}?auto=format&fit=crop&q=80&w=800`)\n```\n\nThese queries are specific to Unsplash, but you could do the same with [Cloudflare Images](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fimages\u002Ftransform-images\u002Ftransform-via-url\u002F) or any image optimization service.\n\nA quick note on this approach: it works fine. Parallelism slightly improves performance because most of the computation happens in the `encode` function that transforms the image into a blurhash synchronously. On my computer, fetching the image takes about 100ms, while encoding it takes about 3 seconds.\n\n## Going further\n\nThe current implementation is simple, and that's intentional. Now that you know how to optimize your images and create custom rules, you can easily adapt this approach to your needs, and even create new ones that fit your specific use cases.\n\nTo deal with the performance bottleneck, you have many possibilities. Three of them are:\n\n1. **Local pre-processing and local storage**: If you store images within your code source, you can create a script that will go through all images and generate a JSON file containing the width, height, and the blurhash. Then, in the rule, you'll just have to read this file, and no more computation is needed. This will reduce the build time as well.\n2. **Local pre-processing and remote storage**: If your images are stored remotely, you can create a similar script that will fetch the images, process them, and generate the JSON file. This file can be pushed to a bucket for later use. In the rule, instead of reading a file, you can fetch it from the bucket. _Without asynchronous rules, this wasn't possible!_\n3. **Remote pre-processing and remote storage**: If your images are stored remotely, you can create a workflow that will be triggered each time a new file is added. The workflow generates the JSON file and stores it in the same bucket. In the rule, you can fetch the JSON file from the bucket and use it directly. _The maximal optimization. With [R2 notifications](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fr2\u002Fbuckets\u002Fevent-notifications\u002F) and [Queues](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fqueues\u002Fevent-subscriptions\u002Fevents-schemas\u002F#r2), this is simple to implement._\n\n_You could also create a worker pool using Node threads, but it is overkill for this use case._\n\nI'm sure there are a lot of other ways to automate image optimization. Now, it's up to you to find the best approach that fits your workflow.\n\nHope you enjoyed the article! If you implement something similar, keep me posted!\n",{"title":9653,"description":11287},{"loc":11289},"posts\u002Fmarkdown-exit-breaks-the-rules-with-async-rendering","BwjYq7Gh-KwV5efwECmlp_1CvWKrmk2JkIsPjVpWmgk",{"id":11296,"title":11297,"body":11298,"contentId":9271,"date":13770,"description":13771,"extension":18,"meta":13772,"navigation":20,"path":9994,"rawbody":13773,"robots":23,"schemaOrg":23,"seo":13774,"sitemap":13775,"stem":13778,"__hash__":13779},"posts\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite.md","Erase CLS by Automatically Optimizing Images with Vite",{"type":7,"value":11299,"toc":13763},[11300,11303,11306,11312,11314,11317,11320,11323,11333,11339,11356,11360,11363,11369,11372,11375,11427,11430,11545,11548,11554,11561,11565,11568,11581,11588,11591,11605,11608,11611,11623,11628,11756,11759,11772,11915,11918,12115,12124,13760],[35,11301,11302],{},"I love Vite, not only because it's fast, but also because of its amazing plugin system.",[35,11304,11305],{},"At its core, a Vite plugin lets you edit, on the fly, any file that is imported, whether it exists or not, in your project.",[35,11307,11308,11309],{},"Once you understand that, everything becomes possible. ",[1217,11310,11311],{},"Everything as a Vite plugin!",[1159,11313],{},[35,11315,11316],{},"For years now, I've been building content-first websites where the content is written in Markdown. I also love using images to illustrate my content.",[35,11318,11319],{},"However, images are tricky. To improve performance, you want to lazy‑load them, but lazy loading inevitably causes Cumulative Layout Shift (CLS) if you don't reserve space. To reserve space, the browser needs to know the image dimensions beforehand. But you don't always want the same dimensions everywhere, so for each image, you need its dimensions for the browser to reserve the right amount of space.",[35,11321,11322],{},"Beyond that, images can quickly become large, and hosting them in your repository isn't ideal. So you might want to transfer them to a dedicated bucket. This means they will be served from a different domain, which, combined with lazy loading, even with the correct dimensions, will leave a blank space until they are fully loaded.",[130,11324,132,11325,132,11330],{},[134,11326,136,11327,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,11328],{"src":11329,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite\u002Fcls-issue.mp4",[143,11331,11332],{},"Example of Cumulative Layout Shift (CLS) issue when lazy-loading an image without reserved space.",[35,11334,11335,11336],{},"This is a solvable problem, but it requires a lot of manual work. ",[1217,11337,11338],{},"I hate manual and repetitive work that can be automated!",[939,11340,11341],{},[35,11342,11343,11345,11346,11350,11351,215],{},[200,11344,945],{},"\nThis article is dedicated to HTML content. ",[39,11347,11349],{"href":11348},".\u002Fmarkdown-exit-breaks-the-rules-with-async-rendering","Another article"," covers how to handle Markdown content using ",[39,11352,11355],{"href":11353,"rel":11354},"https:\u002F\u002Fmarkdown-exit.pages.dev\u002F",[77],"Markdown Exit",[51,11357,11359],{"id":11358},"vite-to-the-rescue","Vite to the rescue",[35,11361,11362],{},"The first question that comes to mind is: How do you know whether a Vite plugin is the right tool for the job?",[35,11364,11365,11366],{},"This is an important question. I could just give you the solution, but you won't be able to apply a similar approach to your own problems. ",[1217,11367,11368],{},"Sadly, most tutorials miss this step.",[35,11370,11371],{},"Let's analyze the problem by looking at its inputs and outputs.",[35,11373,11374],{},"The input is probably a raw HTML snippet like this one:",[192,11376,11378],{"className":591,"code":11377,"language":593,"meta":13,"style":13},"\u003Cdiv>\n  \u003Cimg src=\"\u002Fpath\u002Fto\u002Fimage.jpg\" alt=\"An image\">\n\u003C\u002Fdiv>\n",[156,11379,11380,11389,11419],{"__ignoreMap":13},[200,11381,11382,11384,11387],{"class":202,"line":203},[200,11383,600],{"class":214},[200,11385,11386],{"class":235},"div",[200,11388,648],{"class":214},[200,11390,11391,11393,11395,11397,11399,11401,11404,11406,11408,11410,11412,11415,11417],{"class":202,"line":14},[200,11392,657],{"class":214},[200,11394,1690],{"class":235},[200,11396,10106],{"class":379},[200,11398,449],{"class":214},[200,11400,611],{"class":214},[200,11402,11403],{"class":245},"\u002Fpath\u002Fto\u002Fimage.jpg",[200,11405,611],{"class":214},[200,11407,10134],{"class":379},[200,11409,449],{"class":214},[200,11411,611],{"class":214},[200,11413,11414],{"class":245},"An image",[200,11416,611],{"class":214},[200,11418,648],{"class":214},[200,11420,11421,11423,11425],{"class":202,"line":255},[200,11422,681],{"class":214},[200,11424,11386],{"class":235},[200,11426,648],{"class":214},[35,11428,11429],{},"The output should be an optimized HTML snippet like this one:",[192,11431,11433],{"className":591,"code":11432,"language":593,"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,11434,11435,11443,11450,11464,11477,11491,11505,11518,11532,11537],{"__ignoreMap":13},[200,11436,11437,11439,11441],{"class":202,"line":203},[200,11438,600],{"class":214},[200,11440,11386],{"class":235},[200,11442,648],{"class":214},[200,11444,11445,11447],{"class":202,"line":14},[200,11446,657],{"class":214},[200,11448,11449],{"class":235},"img\n",[200,11451,11452,11455,11457,11459,11462],{"class":202,"line":255},[200,11453,11454],{"class":379},"    src",[200,11456,449],{"class":214},[200,11458,611],{"class":214},[200,11460,11461],{"class":245},"https:\u002F\u002Fcdn.example.com\u002Fpath\u002Fto\u002Fimage-optimized.jpg",[200,11463,617],{"class":214},[200,11465,11466,11469,11471,11473,11475],{"class":202,"line":272},[200,11467,11468],{"class":379},"    alt",[200,11470,449],{"class":214},[200,11472,611],{"class":214},[200,11474,11414],{"class":245},[200,11476,617],{"class":214},[200,11478,11479,11482,11484,11486,11489],{"class":202,"line":283},[200,11480,11481],{"class":379},"    width",[200,11483,449],{"class":214},[200,11485,611],{"class":214},[200,11487,11488],{"class":245},"600",[200,11490,617],{"class":214},[200,11492,11493,11496,11498,11500,11503],{"class":202,"line":300},[200,11494,11495],{"class":379},"    height",[200,11497,449],{"class":214},[200,11499,611],{"class":214},[200,11501,11502],{"class":245},"400",[200,11504,617],{"class":214},[200,11506,11507,11510,11512,11514,11516],{"class":202,"line":310},[200,11508,11509],{"class":379},"    loading",[200,11511,449],{"class":214},[200,11513,611],{"class":214},[200,11515,9895],{"class":245},[200,11517,617],{"class":214},[200,11519,11520,11523,11525,11527,11530],{"class":202,"line":336},[200,11521,11522],{"class":379},"    style",[200,11524,449],{"class":214},[200,11526,611],{"class":214},[200,11528,11529],{"class":245},"background-image: url('data:image\u002Fsvg+xml;base64,...'); background-size: cover;",[200,11531,617],{"class":214},[200,11533,11534],{"class":202,"line":342},[200,11535,11536],{"class":214},"  >\n",[200,11538,11539,11541,11543],{"class":202,"line":365},[200,11540,681],{"class":214},[200,11542,11386],{"class":235},[200,11544,648],{"class":214},[35,11546,11547],{},"Vite is built around a pipeline that processes files as they're imported. This means we can hook into the pipeline, detect when the code contains an image, and transform it accordingly.",[35,11549,11550,11551],{},"Our use case fits perfectly with Vite's capabilities. ",[1217,11552,11553],{},"Vite plugins can have side effects on the imported files. They unlock really powerful use cases like automatic image compression, resizing, ...",[939,11555,11556],{},[35,11557,11558,11560],{},[200,11559,945],{},"\nThis article assumes images are available locally. Handling remote images won't be covered here, but I'll give a few hints at the end of the article.",[51,11562,11564],{"id":11563},"building-the-plugin","Building the plugin",[35,11566,11567],{},"What's the plan? It's great to have an idea of what we want to achieve, but how do we get there?",[35,11569,11570,11571,11574,11575,11577,11578,11580],{},"We need to hook into Vite's pipeline and look for Vue templates. Then, we need to find all ",[156,11572,11573],{},"\u003Cimg>"," tags, extract their ",[156,11576,11165],{}," attributes, load the images, get their dimensions, generate the blurred placeholder, and finally replace the original ",[156,11579,11573],{}," tags with optimized ones.",[939,11582,11583],{},[35,11584,11585,11587],{},[200,11586,945],{},"\nI'm using Vue for all my projects, but the same approach can be applied to any frontend framework supported by Vite.",[35,11589,11590],{},"Sounds easier than it is, especially when you don't know where to start and which tool to use.",[35,11592,11593,11594,11599,11600,215],{},"To handle all the image processing, I use ",[39,11595,11598],{"href":11596,"rel":11597},"https:\u002F\u002Funpic.pics\u002F",[77],"unpic",". It's a fantastic set of primitives to handle everything related to images. To manipulate code and generate source maps, I use ",[39,11601,11604],{"href":11602,"rel":11603},"https:\u002F\u002Fgithub.com\u002FRich-Harris\u002Fmagic-string",[77],"MagicString",[35,11606,11607],{},"That's all we need!",[35,11609,11610],{},"Now, we can create a Vite plugin that does exactly what we want.",[939,11612,11613],{},[35,11614,11615,11617,11618,215],{},[200,11616,945],{},"\nYou can use a Vite + Vue project as a starting point. The source code of the final plugin is available on ",[39,11619,11622],{"href":11620,"rel":11621},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fvite-vue-unpic",[77],"GitHub",[35,11624,2908,11625,11627],{},[156,11626,1460],{}," file of our project, let's first create a plugin:",[192,11629,11631],{"className":1370,"code":11630,"filename":1460,"language":1373,"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,11632,11633,11649,11667,11671,11683,11691,11700,11711,11718,11733,11737,11744,11750],{"__ignoreMap":13},[200,11634,11635,11637,11640,11642,11644,11647],{"class":202,"line":203},[200,11636,973],{"class":206},[200,11638,11639],{"class":210}," vue ",[200,11641,1472],{"class":206},[200,11643,242],{"class":214},[200,11645,11646],{"class":245},"@vitejs\u002Fplugin-vue",[200,11648,992],{"class":214},[200,11650,11651,11653,11655,11657,11659,11661,11663,11665],{"class":202,"line":14},[200,11652,973],{"class":206},[200,11654,318],{"class":214},[200,11656,1488],{"class":210},[200,11658,981],{"class":214},[200,11660,984],{"class":206},[200,11662,242],{"class":214},[200,11664,1497],{"class":245},[200,11666,992],{"class":214},[200,11668,11669],{"class":202,"line":255},[200,11670,529],{"emptyLinePlaceholder":20},[200,11672,11673,11675,11677,11679,11681],{"class":202,"line":272},[200,11674,1380],{"class":206},[200,11676,1383],{"class":206},[200,11678,1488],{"class":223},[200,11680,227],{"class":210},[200,11682,230],{"class":214},[200,11684,11685,11687,11689],{"class":202,"line":283},[200,11686,1520],{"class":235},[200,11688,239],{"class":214},[200,11690,1400],{"class":210},[200,11692,11693,11696,11698],{"class":202,"line":300},[200,11694,11695],{"class":223},"    vue",[200,11697,1532],{"class":210},[200,11699,252],{"class":214},[200,11701,11702,11705,11707,11709],{"class":202,"line":310},[200,11703,11704],{"class":210},"    (",[200,11706,1532],{"class":214},[200,11708,393],{"class":379},[200,11710,280],{"class":214},[200,11712,11713,11716],{"class":202,"line":336},[200,11714,11715],{"class":206},"      return",[200,11717,280],{"class":214},[200,11719,11720,11723,11725,11727,11729,11731],{"class":202,"line":342},[200,11721,11722],{"class":235},"        name",[200,11724,239],{"class":214},[200,11726,242],{"class":214},[200,11728,11598],{"class":245},[200,11730,249],{"class":214},[200,11732,252],{"class":214},[200,11734,11735],{"class":202,"line":365},[200,11736,4478],{"class":214},[200,11738,11739,11741],{"class":202,"line":371},[200,11740,2096],{"class":214},[200,11742,11743],{"class":210},")()\n",[200,11745,11746,11748],{"class":202,"line":398},[200,11747,1417],{"class":210},[200,11749,252],{"class":214},[200,11751,11752,11754],{"class":202,"line":405},[200,11753,413],{"class":214},[200,11755,416],{"class":210},[35,11757,11758],{},"At a minimum, a Vite plugin must have a name. Our plugin is an IIFE (Immediately Invoked Function Expression). This allows us to have a context to store state without relying on global variables.",[35,11760,11761,11762,11767,11768,11771],{},"Then, we need to hook into the right part of the pipeline. There are ",[39,11763,11766],{"href":11764,"rel":11765},"https:\u002F\u002Frollupjs.org\u002Fplugin-development\u002F#build-hooks",[77],"a lot of hooks"," available, but in practice, the most useful one is ",[156,11769,11770],{},"transform",". This hook is called for each file that is imported in the project. It receives the file's code and its id (path). We only want to process Vue files, so we can filter by file extension.",[192,11773,11775],{"className":9741,"code":11774,"filename":1460,"language":1373,"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,11776,11777,11787,11793,11808,11828,11856,11861,11865,11870,11874,11880,11887,11897,11901,11905,11909],{"__ignoreMap":13},[200,11778,11779,11781,11783,11785],{"class":202,"line":203},[200,11780,227],{"class":210},[200,11782,1532],{"class":214},[200,11784,393],{"class":379},[200,11786,280],{"class":214},[200,11788,11789,11791],{"class":202,"line":14},[200,11790,3743],{"class":206},[200,11792,280],{"class":214},[200,11794,11795,11798,11800,11802,11804,11806],{"class":202,"line":255},[200,11796,11797],{"class":235},"    name",[200,11799,239],{"class":214},[200,11801,242],{"class":214},[200,11803,11598],{"class":245},[200,11805,249],{"class":214},[200,11807,252],{"class":214},[200,11809,11810,11813,11816,11818,11820,11822,11824,11826],{"class":202,"line":272},[200,11811,11812],{"class":379},"    async",[200,11814,11815],{"class":235}," transform",[200,11817,227],{"class":214},[200,11819,156],{"class":386},[200,11821,550],{"class":214},[200,11823,3224],{"class":386},[200,11825,2089],{"class":214},[200,11827,280],{"class":214},[200,11829,11831,11833,11835,11838,11840,11842,11845,11847,11849,11852,11854],{"class":11830,"line":283},[202,9871],[200,11832,6511],{"class":206},[200,11834,1095],{"class":235},[200,11836,11837],{"class":214},"!",[200,11839,3280],{"class":210},[200,11841,215],{"class":214},[200,11843,11844],{"class":223},"endsWith",[200,11846,227],{"class":235},[200,11848,249],{"class":214},[200,11850,11851],{"class":245},".vue",[200,11853,249],{"class":214},[200,11855,6466],{"class":235},[200,11857,11858],{"class":202,"line":300},[200,11859,11860],{"class":206},"        return\n",[200,11862,11863],{"class":202,"line":310},[200,11864,529],{"emptyLinePlaceholder":20},[200,11866,11867],{"class":202,"line":336},[200,11868,11869],{"class":401},"      \u002F\u002F Some magic will happen here\n",[200,11871,11872],{"class":202,"line":342},[200,11873,529],{"emptyLinePlaceholder":20},[200,11875,11876,11878],{"class":202,"line":365},[200,11877,11715],{"class":206},[200,11879,280],{"class":214},[200,11881,11882,11885],{"class":202,"line":371},[200,11883,11884],{"class":210},"        code",[200,11886,252],{"class":214},[200,11888,11889,11892,11894],{"class":202,"line":398},[200,11890,11891],{"class":235},"        map",[200,11893,239],{"class":214},[200,11895,11896],{"class":214}," null,\n",[200,11898,11899],{"class":202,"line":405},[200,11900,4478],{"class":214},[200,11902,11903],{"class":202,"line":410},[200,11904,4962],{"class":214},[200,11906,11907],{"class":202,"line":861},[200,11908,4490],{"class":214},[200,11910,11911,11913],{"class":202,"line":890},[200,11912,413],{"class":214},[200,11914,11743],{"class":210},[35,11916,11917],{},"But we can make it even more performant. Calling our plugin on every file is unnecessary, especially with Rolldown, where the communication between Rust and JavaScript incurs a small overhead. To avoid that, we can use some filters to only process files that match certain criteria. In our case, we only want to process Vue files.",[192,11919,11921],{"className":9741,"code":11920,"filename":1460,"language":1373,"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,11922,11923,11933,11989,11993,11999,12013,12028,12036,12045,12068,12079,12084,12101,12105,12109],{"__ignoreMap":13},[200,11924,11925,11927,11929,11931],{"class":202,"line":203},[200,11926,227],{"class":210},[200,11928,1532],{"class":214},[200,11930,393],{"class":379},[200,11932,280],{"class":214},[200,11934,11936,11938,11941,11943,11946,11949,11952,11954,11957,11960,11962,11965,11968,11970,11973,11975,11978,11980,11982,11984,11986],{"class":11935,"line":14},[202,9871],[200,11937,3723],{"class":379},[200,11939,11940],{"class":210}," imgTagRegex",[200,11942,510],{"class":214},[200,11944,11945],{"class":214}," \u002F",[200,11947,11948],{"class":245},"\u003Cimg\\s",[200,11950,11951],{"class":214},"[^",[200,11953,675],{"class":245},[200,11955,11956],{"class":214},"]*",[200,11958,11959],{"class":245},"src=",[200,11961,9858],{"class":214},[200,11963,11964],{"class":245},"\"'",[200,11966,11967],{"class":214},"]([^",[200,11969,11964],{"class":245},[200,11971,11972],{"class":214},"]+)[",[200,11974,11964],{"class":245},[200,11976,11977],{"class":214},"][^",[200,11979,675],{"class":245},[200,11981,11956],{"class":214},[200,11983,675],{"class":245},[200,11985,2882],{"class":214},[200,11987,11988],{"class":2045},"g",[200,11990,11991],{"class":202,"line":255},[200,11992,529],{"emptyLinePlaceholder":20},[200,11994,11995,11997],{"class":202,"line":272},[200,11996,3743],{"class":206},[200,11998,280],{"class":214},[200,12000,12001,12003,12005,12007,12009,12011],{"class":202,"line":283},[200,12002,11797],{"class":235},[200,12004,239],{"class":214},[200,12006,242],{"class":214},[200,12008,11598],{"class":245},[200,12010,249],{"class":214},[200,12012,252],{"class":214},[200,12014,12015,12018,12020,12022,12024,12026],{"class":202,"line":300},[200,12016,12017],{"class":235},"    enforce",[200,12019,239],{"class":214},[200,12021,242],{"class":214},[200,12023,192],{"class":245},[200,12025,249],{"class":214},[200,12027,252],{"class":214},[200,12029,12030,12032,12034],{"class":202,"line":310},[200,12031,5036],{"class":235},[200,12033,239],{"class":214},[200,12035,280],{"class":214},[200,12037,12038,12041,12043],{"class":202,"line":336},[200,12039,12040],{"class":235},"      filter",[200,12042,239],{"class":214},[200,12044,280],{"class":214},[200,12046,12048,12051,12053,12055,12058,12061,12064,12066],{"class":12047,"line":342},[202,9871],[200,12049,12050],{"class":235},"        id",[200,12052,239],{"class":214},[200,12054,11945],{"class":214},[200,12056,12057],{"class":210},"\\.",[200,12059,12060],{"class":245},"vue",[200,12062,12063],{"class":206},"$",[200,12065,2882],{"class":214},[200,12067,550],{"class":214},[200,12069,12071,12073,12075,12077],{"class":12070,"line":365},[202,9871],[200,12072,11884],{"class":235},[200,12074,239],{"class":214},[200,12076,11940],{"class":210},[200,12078,550],{"class":214},[200,12080,12081],{"class":202,"line":371},[200,12082,12083],{"class":214},"      },\n",[200,12085,12086,12089,12092,12094,12096,12098],{"class":202,"line":398},[200,12087,12088],{"class":379},"      async",[200,12090,12091],{"class":235}," handler",[200,12093,227],{"class":214},[200,12095,156],{"class":386},[200,12097,2089],{"class":214},[200,12099,12100],{"class":214}," {},\n",[200,12102,12103],{"class":202,"line":405},[200,12104,4962],{"class":214},[200,12106,12107],{"class":202,"line":410},[200,12108,4490],{"class":214},[200,12110,12111,12113],{"class":202,"line":861},[200,12112,413],{"class":214},[200,12114,11743],{"class":210},[35,12116,12117,12118,12120,12121,12123],{},"This ",[156,12119,3758],{}," ensures that our plugin only runs on Vue files that contain at least one ",[156,12122,11573],{}," tag. So much better!",[7905,12125,12128,12131,12147,12224,12230,12234,12240,12274,12277,12304,12307,12313,12346,12354,12425,12429,12507,12513,12640,12648,12767,12770,13513,13527,13533,13536,13546,13558,13562,13570,13586,13592,13681,13684,13696,13702,13709,13715,13717,13720,13723,13746,13749,13755,13757],{"text":12126,"href":12127},"Plugin hook filters","https:\u002F\u002Frolldown.rs\u002Fapis\u002Fplugin-hook-filters",[35,12129,12130],{},"One last thing before we can start the magic: we need to make sure the plugin runs before the Vue plugin. Vue transforms Vue files into JavaScript files, and Single‑File Component (SFC) files are much simpler to process. To do so, we have two options:",[68,12132,12133,12140],{},[71,12134,12135,12136,12139],{},"Place our plugin before the Vue plugin in the ",[156,12137,12138],{},"plugins"," array.",[71,12141,12142,12143,12146],{},"Use the ",[156,12144,12145],{},"enforce: 'pre'"," option in our plugin definition.",[192,12148,12150],{"className":9741,"code":12149,"filename":1460,"language":1373,"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,12151,12152,12162,12168,12182,12197,12205,12210,12214,12218],{"__ignoreMap":13},[200,12153,12154,12156,12158,12160],{"class":202,"line":203},[200,12155,227],{"class":210},[200,12157,1532],{"class":214},[200,12159,393],{"class":379},[200,12161,280],{"class":214},[200,12163,12164,12166],{"class":202,"line":14},[200,12165,3743],{"class":206},[200,12167,280],{"class":214},[200,12169,12170,12172,12174,12176,12178,12180],{"class":202,"line":255},[200,12171,11797],{"class":235},[200,12173,239],{"class":214},[200,12175,242],{"class":214},[200,12177,11598],{"class":245},[200,12179,249],{"class":214},[200,12181,252],{"class":214},[200,12183,12185,12187,12189,12191,12193,12195],{"class":12184,"line":272},[202,9871],[200,12186,12017],{"class":235},[200,12188,239],{"class":214},[200,12190,242],{"class":214},[200,12192,192],{"class":245},[200,12194,249],{"class":214},[200,12196,550],{"class":214},[200,12198,12199,12201,12203],{"class":202,"line":283},[200,12200,5036],{"class":235},[200,12202,239],{"class":214},[200,12204,280],{"class":214},[200,12206,12207],{"class":202,"line":300},[200,12208,12209],{"class":401},"      \u002F\u002F ...\n",[200,12211,12212],{"class":202,"line":310},[200,12213,4962],{"class":214},[200,12215,12216],{"class":202,"line":336},[200,12217,4490],{"class":214},[200,12219,12220,12222],{"class":202,"line":342},[200,12221,413],{"class":214},[200,12223,11743],{"class":210},[35,12225,12226,12227,12229],{},"Now, we can start processing the Vue files to enhance the ",[156,12228,11573],{}," tags.",[51,12231,12233],{"id":12232},"transforming-the-code","Transforming the code",[35,12235,12236,12237,12239],{},"Within the ",[156,12238,11770],{}," hook, the following needs to be done:",[68,12241,12242,12247,12256,12259,12265],{},[71,12243,12244,12245,12229],{},"Parse the code to find all ",[156,12246,11573],{},[71,12248,12249,12250,12252,12253,12255],{},"For each ",[156,12251,11573],{}," tag, extract the ",[156,12254,11165],{}," attribute.",[71,12257,12258],{},"Read the file from the filesystem.",[71,12260,12261,12262,12264],{},"Process the image with ",[156,12263,11598],{}," to get its dimensions and a blurred placeholder.",[71,12266,12267,12268,12270,12271,12273],{},"Generate and replace the original ",[156,12269,11573],{}," tag with the optimized ",[156,12272,11573],{}," tag with all the necessary attributes.",[35,12275,12276],{},"Before we start, we need to install the dependencies:",[192,12278,12280],{"className":2151,"code":12279,"language":2153,"meta":13,"style":13},"pnpm install -D @unpic\u002Fpixels @unpic\u002Fplaceholder blurhash magic-string\n",[156,12281,12282],{"__ignoreMap":13},[200,12283,12284,12286,12289,12292,12295,12298,12301],{"class":202,"line":203},[200,12285,2160],{"class":1978},[200,12287,12288],{"class":245}," install",[200,12290,12291],{"class":245}," -D",[200,12293,12294],{"class":245}," @unpic\u002Fpixels",[200,12296,12297],{"class":245}," @unpic\u002Fplaceholder",[200,12299,12300],{"class":245}," blurhash",[200,12302,12303],{"class":245}," magic-string\n",[35,12305,12306],{},"Let's implement this step by step.",[68,12308,12309],{},[71,12310,12244,12311,12229],{},[156,12312,11573],{},[192,12314,12316],{"className":1370,"code":12315,"filename":1460,"language":1373,"meta":13,"style":13},"\u002F\u002F eslint-disable-next-line prefer-const\nlet match = imgTagRegex.exec(code) \u002F\u002F We reuse the regex defined earlier\n",[156,12317,12318,12323],{"__ignoreMap":13},[200,12319,12320],{"class":202,"line":203},[200,12321,12322],{"class":401},"\u002F\u002F eslint-disable-next-line prefer-const\n",[200,12324,12325,12328,12331,12333,12335,12337,12340,12343],{"class":202,"line":14},[200,12326,12327],{"class":379},"let",[200,12329,12330],{"class":210}," match ",[200,12332,449],{"class":214},[200,12334,11940],{"class":210},[200,12336,215],{"class":214},[200,12338,12339],{"class":223},"exec",[200,12341,12342],{"class":210},"(code) ",[200,12344,12345],{"class":401},"\u002F\u002F We reuse the regex defined earlier\n",[68,12347,12348],{"start":14},[71,12349,12249,12350,12252,12352,12255],{},[156,12351,11573],{},[156,12353,11165],{},[192,12355,12357],{"className":1370,"code":12356,"filename":1460,"language":1373,"meta":13,"style":13},"do {\n  const srcValue = match[1]\n\n  match = imgTagRegex.exec(code)\n} while (match !== null)\n",[156,12358,12359,12366,12384,12388,12407],{"__ignoreMap":13},[200,12360,12361,12364],{"class":202,"line":203},[200,12362,12363],{"class":206},"do",[200,12365,280],{"class":214},[200,12367,12368,12370,12373,12375,12378,12380,12382],{"class":202,"line":14},[200,12369,3723],{"class":379},[200,12371,12372],{"class":210}," srcValue",[200,12374,510],{"class":214},[200,12376,12377],{"class":210}," match",[200,12379,9858],{"class":235},[200,12381,3188],{"class":2045},[200,12383,4496],{"class":235},[200,12385,12386],{"class":202,"line":255},[200,12387,529],{"emptyLinePlaceholder":20},[200,12389,12390,12393,12395,12397,12399,12401,12403,12405],{"class":202,"line":272},[200,12391,12392],{"class":210},"  match",[200,12394,510],{"class":214},[200,12396,11940],{"class":210},[200,12398,215],{"class":214},[200,12400,12339],{"class":223},[200,12402,227],{"class":235},[200,12404,156],{"class":210},[200,12406,416],{"class":235},[200,12408,12409,12411,12414,12417,12420,12423],{"class":202,"line":283},[200,12410,413],{"class":214},[200,12412,12413],{"class":206}," while",[200,12415,12416],{"class":210}," (match ",[200,12418,12419],{"class":214},"!==",[200,12421,12422],{"class":214}," null",[200,12424,416],{"class":210},[68,12426,12427],{"start":255},[71,12428,12258],{},[192,12430,12432],{"className":1370,"code":12431,"filename":1460,"language":1373,"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,12433,12434,12454,12474,12478],{"__ignoreMap":13},[200,12435,12436,12438,12440,12443,12445,12447,12449,12452],{"class":202,"line":203},[200,12437,973],{"class":206},[200,12439,318],{"class":214},[200,12441,12442],{"class":210}," readFile",[200,12444,981],{"class":214},[200,12446,984],{"class":206},[200,12448,242],{"class":214},[200,12450,12451],{"class":245},"node:fs\u002Fpromises",[200,12453,992],{"class":214},[200,12455,12456,12458,12460,12463,12465,12467,12469,12472],{"class":202,"line":14},[200,12457,973],{"class":206},[200,12459,318],{"class":214},[200,12461,12462],{"class":210}," join",[200,12464,981],{"class":214},[200,12466,984],{"class":206},[200,12468,242],{"class":214},[200,12470,12471],{"class":245},"node:path",[200,12473,992],{"class":214},[200,12475,12476],{"class":202,"line":255},[200,12477,529],{"emptyLinePlaceholder":20},[200,12479,12480,12482,12484,12486,12488,12490,12492,12494,12497,12499,12502,12504],{"class":202,"line":272},[200,12481,443],{"class":379},[200,12483,10915],{"class":210},[200,12485,449],{"class":214},[200,12487,452],{"class":206},[200,12489,12442],{"class":223},[200,12491,227],{"class":210},[200,12493,6190],{"class":223},[200,12495,12496],{"class":210},"(resolvedConfig",[200,12498,215],{"class":214},[200,12500,12501],{"class":210},"publicDir",[200,12503,550],{"class":214},[200,12505,12506],{"class":210}," srcValue))\n",[68,12508,12509],{"start":272},[71,12510,12261,12511,12264],{},[156,12512,11598],{},[192,12514,12516],{"className":1370,"code":12515,"filename":1460,"language":1373,"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,12517,12518,12537,12555,12574,12578,12592],{"__ignoreMap":13},[200,12519,12520,12522,12524,12526,12528,12530,12532,12535],{"class":202,"line":203},[200,12521,973],{"class":206},[200,12523,318],{"class":214},[200,12525,10961],{"class":210},[200,12527,981],{"class":214},[200,12529,984],{"class":206},[200,12531,242],{"class":214},[200,12533,12534],{"class":245},"@unpic\u002Fpixels",[200,12536,992],{"class":214},[200,12538,12539,12541,12543,12545,12547,12549,12551,12553],{"class":202,"line":14},[200,12540,973],{"class":206},[200,12542,318],{"class":214},[200,12544,11041],{"class":210},[200,12546,981],{"class":214},[200,12548,984],{"class":206},[200,12550,242],{"class":214},[200,12552,11050],{"class":245},[200,12554,992],{"class":214},[200,12556,12557,12559,12561,12563,12565,12567,12569,12572],{"class":202,"line":255},[200,12558,973],{"class":206},[200,12560,318],{"class":214},[200,12562,10980],{"class":210},[200,12564,981],{"class":214},[200,12566,984],{"class":206},[200,12568,242],{"class":214},[200,12570,12571],{"class":245},"blurhash",[200,12573,992],{"class":214},[200,12575,12576],{"class":202,"line":272},[200,12577,529],{"emptyLinePlaceholder":20},[200,12579,12580,12582,12584,12586,12588,12590],{"class":202,"line":283},[200,12581,443],{"class":379},[200,12583,10954],{"class":210},[200,12585,449],{"class":214},[200,12587,452],{"class":206},[200,12589,10961],{"class":223},[200,12591,10964],{"class":210},[200,12593,12594,12596,12598,12600,12602,12604,12606,12608,12610,12612,12614,12616,12618,12620,12622,12624,12626,12628,12630,12632,12634,12636,12638],{"class":202,"line":300},[200,12595,443],{"class":379},[200,12597,10975],{"class":210},[200,12599,449],{"class":214},[200,12601,10980],{"class":223},[200,12603,10983],{"class":210},[200,12605,215],{"class":214},[200,12607,1472],{"class":223},[200,12609,10990],{"class":210},[200,12611,215],{"class":214},[200,12613,10995],{"class":210},[200,12615,550],{"class":214},[200,12617,5041],{"class":210},[200,12619,215],{"class":214},[200,12621,11004],{"class":210},[200,12623,550],{"class":214},[200,12625,5041],{"class":210},[200,12627,215],{"class":214},[200,12629,11013],{"class":210},[200,12631,550],{"class":214},[200,12633,11018],{"class":2045},[200,12635,550],{"class":214},[200,12637,11018],{"class":2045},[200,12639,416],{"class":210},[68,12641,12642],{"start":283},[71,12643,12267,12644,12270,12646,12273],{},[156,12645,11573],{},[156,12647,11573],{},[192,12649,12651],{"className":1370,"code":12650,"filename":1460,"language":1373,"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,12652,12653,12671,12675,12697,12714,12763],{"__ignoreMap":13},[200,12654,12655,12657,12659,12661,12663,12665,12667,12669],{"class":202,"line":203},[200,12656,973],{"class":206},[200,12658,318],{"class":214},[200,12660,11041],{"class":210},[200,12662,981],{"class":214},[200,12664,984],{"class":206},[200,12666,242],{"class":214},[200,12668,11050],{"class":245},[200,12670,992],{"class":214},[200,12672,12673],{"class":202,"line":14},[200,12674,529],{"emptyLinePlaceholder":20},[200,12676,12677,12679,12682,12684,12687,12689,12691,12693,12695],{"class":202,"line":255},[200,12678,443],{"class":379},[200,12680,12681],{"class":210}," newImgTag ",[200,12683,449],{"class":214},[200,12685,12686],{"class":210}," match[",[200,12688,3933],{"class":2045},[200,12690,360],{"class":210},[200,12692,215],{"class":214},[200,12694,6136],{"class":223},[200,12696,3115],{"class":210},[200,12698,12699,12702,12705,12707,12709,12712],{"class":202,"line":272},[200,12700,12701],{"class":214},"  \u002F",[200,12703,12704],{"class":245},"\u003Cimg",[200,12706,227],{"class":214},[200,12708,6151],{"class":245},[200,12710,12711],{"class":214},"+)\u002F",[200,12713,252],{"class":214},[200,12715,12716,12719,12722,12724,12727,12729,12731,12733,12736,12738,12740,12742,12744,12746,12749,12751,12753,12755,12757,12760],{"class":202,"line":283},[200,12717,12718],{"class":214},"  `",[200,12720,12721],{"class":245},"\u003Cimg$1width=\"",[200,12723,4853],{"class":214},[200,12725,12726],{"class":210},"data",[200,12728,215],{"class":214},[200,12730,11004],{"class":210},[200,12732,413],{"class":214},[200,12734,12735],{"class":245},"\" height=\"",[200,12737,4853],{"class":214},[200,12739,12726],{"class":210},[200,12741,215],{"class":214},[200,12743,11013],{"class":210},[200,12745,413],{"class":214},[200,12747,12748],{"class":245},"\" style=\"background-size: cover; background-image: url(",[200,12750,4853],{"class":214},[200,12752,11147],{"class":223},[200,12754,11150],{"class":210},[200,12756,413],{"class":214},[200,12758,12759],{"class":245},");\" loading=\"lazy\" ",[200,12761,12762],{"class":214},"`\n",[200,12764,12765],{"class":202,"line":300},[200,12766,416],{"class":210},[35,12768,12769],{},"After implementing all these steps, our final plugin looks like this:",[192,12771,12773],{"className":1370,"code":12772,"filename":1460,"language":1373,"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,12774,12775,12785,12798,12802,12847,12851,12857,12871,12885,12893,12901,12919,12963,12967,12985,13007,13011,13032,13036,13043,13062,13066,13098,13116,13171,13175,13192,13221,13225,13251,13267,13311,13317,13322,13348,13353,13373,13393,13398,13406,13424,13454,13460,13465,13470,13485,13496,13501,13506],{"__ignoreMap":13},[200,12776,12777,12779,12781,12783],{"class":202,"line":203},[200,12778,227],{"class":210},[200,12780,1532],{"class":214},[200,12782,393],{"class":379},[200,12784,280],{"class":214},[200,12786,12787,12790,12793,12795],{"class":202,"line":14},[200,12788,12789],{"class":379},"  let",[200,12791,12792],{"class":210}," resolvedConfig",[200,12794,239],{"class":214},[200,12796,12797],{"class":1978}," ResolvedConfig\n",[200,12799,12800],{"class":202,"line":255},[200,12801,529],{"emptyLinePlaceholder":20},[200,12803,12804,12806,12808,12810,12812,12814,12816,12818,12820,12822,12824,12826,12828,12830,12832,12834,12836,12838,12840,12842,12844],{"class":202,"line":272},[200,12805,3723],{"class":379},[200,12807,11940],{"class":210},[200,12809,510],{"class":214},[200,12811,11945],{"class":214},[200,12813,11948],{"class":245},[200,12815,11951],{"class":214},[200,12817,675],{"class":245},[200,12819,11956],{"class":214},[200,12821,11959],{"class":245},[200,12823,9858],{"class":214},[200,12825,11964],{"class":245},[200,12827,11967],{"class":214},[200,12829,11964],{"class":245},[200,12831,11972],{"class":214},[200,12833,11964],{"class":245},[200,12835,11977],{"class":214},[200,12837,675],{"class":245},[200,12839,11956],{"class":214},[200,12841,675],{"class":245},[200,12843,2882],{"class":214},[200,12845,12846],{"class":2045},"g\n",[200,12848,12849],{"class":202,"line":283},[200,12850,529],{"emptyLinePlaceholder":20},[200,12852,12853,12855],{"class":202,"line":300},[200,12854,3743],{"class":206},[200,12856,280],{"class":214},[200,12858,12859,12861,12863,12865,12867,12869],{"class":202,"line":310},[200,12860,11797],{"class":235},[200,12862,239],{"class":214},[200,12864,242],{"class":214},[200,12866,11598],{"class":245},[200,12868,249],{"class":214},[200,12870,252],{"class":214},[200,12872,12873,12875,12877,12879,12881,12883],{"class":202,"line":336},[200,12874,12017],{"class":235},[200,12876,239],{"class":214},[200,12878,242],{"class":214},[200,12880,192],{"class":245},[200,12882,249],{"class":214},[200,12884,252],{"class":214},[200,12886,12887,12889,12891],{"class":202,"line":342},[200,12888,5036],{"class":235},[200,12890,239],{"class":214},[200,12892,280],{"class":214},[200,12894,12895,12897,12899],{"class":202,"line":365},[200,12896,12040],{"class":235},[200,12898,239],{"class":214},[200,12900,280],{"class":214},[200,12902,12903,12905,12907,12909,12911,12913,12915,12917],{"class":202,"line":371},[200,12904,12050],{"class":235},[200,12906,239],{"class":214},[200,12908,11945],{"class":214},[200,12910,12057],{"class":210},[200,12912,12060],{"class":245},[200,12914,12063],{"class":206},[200,12916,2882],{"class":214},[200,12918,252],{"class":214},[200,12920,12921,12923,12925,12927,12929,12931,12933,12935,12937,12939,12941,12943,12945,12947,12949,12951,12953,12955,12957,12959,12961],{"class":202,"line":398},[200,12922,11884],{"class":235},[200,12924,239],{"class":214},[200,12926,11945],{"class":214},[200,12928,11948],{"class":245},[200,12930,11951],{"class":214},[200,12932,675],{"class":245},[200,12934,11956],{"class":214},[200,12936,11959],{"class":245},[200,12938,9858],{"class":214},[200,12940,11964],{"class":245},[200,12942,11967],{"class":214},[200,12944,11964],{"class":245},[200,12946,11972],{"class":214},[200,12948,11964],{"class":245},[200,12950,11977],{"class":214},[200,12952,675],{"class":245},[200,12954,11956],{"class":214},[200,12956,675],{"class":245},[200,12958,2882],{"class":214},[200,12960,11988],{"class":2045},[200,12962,252],{"class":214},[200,12964,12965],{"class":202,"line":405},[200,12966,12083],{"class":214},[200,12968,12969,12971,12973,12975,12977,12979,12981,12983],{"class":202,"line":410},[200,12970,12088],{"class":379},[200,12972,12091],{"class":235},[200,12974,227],{"class":214},[200,12976,156],{"class":386},[200,12978,550],{"class":214},[200,12980,3224],{"class":386},[200,12982,2089],{"class":214},[200,12984,280],{"class":214},[200,12986,12987,12990,12993,12995,12998,13001,13003,13005],{"class":202,"line":861},[200,12988,12989],{"class":379},"        const",[200,12991,12992],{"class":210}," s",[200,12994,510],{"class":214},[200,12996,12997],{"class":214}," new",[200,12999,13000],{"class":223}," MagicString",[200,13002,227],{"class":235},[200,13004,156],{"class":210},[200,13006,416],{"class":235},[200,13008,13009],{"class":202,"line":890},[200,13010,529],{"emptyLinePlaceholder":20},[200,13012,13013,13016,13018,13020,13022,13024,13026,13028,13030],{"class":202,"line":900},[200,13014,13015],{"class":379},"        let",[200,13017,12377],{"class":210},[200,13019,510],{"class":214},[200,13021,11940],{"class":210},[200,13023,215],{"class":214},[200,13025,12339],{"class":223},[200,13027,227],{"class":235},[200,13029,156],{"class":210},[200,13031,416],{"class":235},[200,13033,13034],{"class":202,"line":905},[200,13035,529],{"emptyLinePlaceholder":20},[200,13037,13038,13041],{"class":202,"line":931},[200,13039,13040],{"class":206},"        do",[200,13042,280],{"class":214},[200,13044,13045,13048,13050,13052,13054,13056,13058,13060],{"class":202,"line":3640},[200,13046,13047],{"class":379},"          const",[200,13049,12372],{"class":210},[200,13051,510],{"class":214},[200,13053,12377],{"class":210},[200,13055,11837],{"class":214},[200,13057,9858],{"class":235},[200,13059,3188],{"class":2045},[200,13061,4496],{"class":235},[200,13063,13064],{"class":202,"line":3645},[200,13065,529],{"emptyLinePlaceholder":20},[200,13067,13068,13070,13073,13075,13077,13079,13081,13083,13085,13088,13090,13092,13094,13096],{"class":202,"line":4020},[200,13069,13047],{"class":379},[200,13071,13072],{"class":210}," img",[200,13074,510],{"class":214},[200,13076,452],{"class":206},[200,13078,12442],{"class":223},[200,13080,227],{"class":235},[200,13082,6190],{"class":223},[200,13084,227],{"class":235},[200,13086,13087],{"class":210},"resolvedConfig",[200,13089,215],{"class":214},[200,13091,12501],{"class":210},[200,13093,550],{"class":214},[200,13095,12372],{"class":210},[200,13097,585],{"class":235},[200,13099,13100,13102,13104,13106,13108,13110,13112,13114],{"class":202,"line":4475},[200,13101,13047],{"class":379},[200,13103,5041],{"class":210},[200,13105,510],{"class":214},[200,13107,452],{"class":206},[200,13109,10961],{"class":223},[200,13111,227],{"class":235},[200,13113,1690],{"class":210},[200,13115,416],{"class":235},[200,13117,13118,13120,13122,13124,13126,13128,13131,13133,13135,13137,13139,13141,13143,13145,13147,13149,13151,13153,13155,13157,13159,13161,13163,13165,13167,13169],{"class":202,"line":4481},[200,13119,13047],{"class":379},[200,13121,12300],{"class":210},[200,13123,510],{"class":214},[200,13125,10980],{"class":223},[200,13127,227],{"class":235},[200,13129,13130],{"class":210},"Uint8ClampedArray",[200,13132,215],{"class":214},[200,13134,1472],{"class":223},[200,13136,227],{"class":235},[200,13138,12726],{"class":210},[200,13140,215],{"class":214},[200,13142,12726],{"class":210},[200,13144,2089],{"class":235},[200,13146,550],{"class":214},[200,13148,5041],{"class":210},[200,13150,215],{"class":214},[200,13152,11004],{"class":210},[200,13154,550],{"class":214},[200,13156,5041],{"class":210},[200,13158,215],{"class":214},[200,13160,11013],{"class":210},[200,13162,550],{"class":214},[200,13164,11018],{"class":2045},[200,13166,550],{"class":214},[200,13168,11018],{"class":2045},[200,13170,416],{"class":235},[200,13172,13173],{"class":202,"line":4487},[200,13174,529],{"emptyLinePlaceholder":20},[200,13176,13177,13179,13182,13184,13186,13189],{"class":202,"line":4493},[200,13178,13047],{"class":379},[200,13180,13181],{"class":210}," imgTagStart",[200,13183,510],{"class":214},[200,13185,12377],{"class":210},[200,13187,13188],{"class":214},"!.",[200,13190,13191],{"class":210},"index\n",[200,13193,13194,13196,13199,13201,13203,13206,13208,13210,13212,13214,13216,13218],{"class":202,"line":5454},[200,13195,13047],{"class":379},[200,13197,13198],{"class":210}," imgTagEnd",[200,13200,510],{"class":214},[200,13202,13181],{"class":210},[200,13204,13205],{"class":214}," +",[200,13207,12377],{"class":210},[200,13209,11837],{"class":214},[200,13211,9858],{"class":235},[200,13213,3933],{"class":2045},[200,13215,360],{"class":235},[200,13217,215],{"class":214},[200,13219,13220],{"class":210},"length\n",[200,13222,13223],{"class":202,"line":5459},[200,13224,529],{"emptyLinePlaceholder":20},[200,13226,13228,13230,13233,13235,13237,13239,13241,13243,13245,13247,13249],{"class":202,"line":13227},29,[200,13229,13047],{"class":379},[200,13231,13232],{"class":210}," newImgTag",[200,13234,510],{"class":214},[200,13236,12377],{"class":210},[200,13238,11837],{"class":214},[200,13240,9858],{"class":235},[200,13242,3933],{"class":2045},[200,13244,360],{"class":235},[200,13246,215],{"class":214},[200,13248,6136],{"class":223},[200,13250,3115],{"class":235},[200,13252,13254,13257,13259,13261,13263,13265],{"class":202,"line":13253},30,[200,13255,13256],{"class":214},"            \u002F",[200,13258,12704],{"class":245},[200,13260,227],{"class":214},[200,13262,6151],{"class":245},[200,13264,12711],{"class":214},[200,13266,252],{"class":214},[200,13268,13270,13273,13275,13277,13279,13281,13283,13285,13287,13289,13291,13293,13295,13297,13299,13301,13303,13305,13307,13309],{"class":202,"line":13269},31,[200,13271,13272],{"class":214},"            `",[200,13274,12721],{"class":245},[200,13276,4853],{"class":214},[200,13278,12726],{"class":210},[200,13280,215],{"class":214},[200,13282,11004],{"class":210},[200,13284,413],{"class":214},[200,13286,12735],{"class":245},[200,13288,4853],{"class":214},[200,13290,12726],{"class":210},[200,13292,215],{"class":214},[200,13294,11013],{"class":210},[200,13296,413],{"class":214},[200,13298,12748],{"class":245},[200,13300,4853],{"class":214},[200,13302,11147],{"class":223},[200,13304,11150],{"class":210},[200,13306,413],{"class":214},[200,13308,12759],{"class":245},[200,13310,12762],{"class":214},[200,13312,13314],{"class":202,"line":13313},32,[200,13315,13316],{"class":235},"          )\n",[200,13318,13320],{"class":202,"line":13319},33,[200,13321,529],{"emptyLinePlaceholder":20},[200,13323,13325,13328,13330,13333,13335,13338,13340,13342,13344,13346],{"class":202,"line":13324},34,[200,13326,13327],{"class":210},"          s",[200,13329,215],{"class":214},[200,13331,13332],{"class":223},"overwrite",[200,13334,227],{"class":235},[200,13336,13337],{"class":210},"imgTagStart",[200,13339,550],{"class":214},[200,13341,13198],{"class":210},[200,13343,550],{"class":214},[200,13345,13232],{"class":210},[200,13347,416],{"class":235},[200,13349,13351],{"class":202,"line":13350},35,[200,13352,529],{"emptyLinePlaceholder":20},[200,13354,13356,13359,13361,13363,13365,13367,13369,13371],{"class":202,"line":13355},36,[200,13357,13358],{"class":210},"          match",[200,13360,510],{"class":214},[200,13362,11940],{"class":210},[200,13364,215],{"class":214},[200,13366,12339],{"class":223},[200,13368,227],{"class":235},[200,13370,156],{"class":210},[200,13372,416],{"class":235},[200,13374,13376,13379,13381,13383,13386,13389,13391],{"class":202,"line":13375},37,[200,13377,13378],{"class":214},"        }",[200,13380,12413],{"class":206},[200,13382,1095],{"class":235},[200,13384,13385],{"class":210},"match",[200,13387,13388],{"class":214}," !==",[200,13390,12422],{"class":214},[200,13392,416],{"class":235},[200,13394,13396],{"class":202,"line":13395},38,[200,13397,529],{"emptyLinePlaceholder":20},[200,13399,13401,13404],{"class":202,"line":13400},39,[200,13402,13403],{"class":206},"        return",[200,13405,280],{"class":214},[200,13407,13409,13412,13414,13416,13418,13420,13422],{"class":202,"line":13408},40,[200,13410,13411],{"class":235},"          code",[200,13413,239],{"class":214},[200,13415,12992],{"class":210},[200,13417,215],{"class":214},[200,13419,11086],{"class":223},[200,13421,1532],{"class":235},[200,13423,252],{"class":214},[200,13425,13427,13430,13432,13434,13436,13439,13441,13443,13446,13448,13450,13452],{"class":202,"line":13426},41,[200,13428,13429],{"class":235},"          map",[200,13431,239],{"class":214},[200,13433,12992],{"class":210},[200,13435,215],{"class":214},[200,13437,13438],{"class":223},"generateMap",[200,13440,227],{"class":235},[200,13442,2752],{"class":214},[200,13444,13445],{"class":235}," hires",[200,13447,239],{"class":214},[200,13449,2424],{"class":2423},[200,13451,981],{"class":214},[200,13453,416],{"class":235},[200,13455,13457],{"class":202,"line":13456},42,[200,13458,13459],{"class":214},"        }\n",[200,13461,13463],{"class":202,"line":13462},43,[200,13464,4478],{"class":214},[200,13466,13468],{"class":202,"line":13467},44,[200,13469,339],{"class":214},[200,13471,13473,13476,13478,13481,13483],{"class":202,"line":13472},45,[200,13474,13475],{"class":235},"    configResolved",[200,13477,227],{"class":214},[200,13479,13480],{"class":386},"config",[200,13482,2089],{"class":214},[200,13484,280],{"class":214},[200,13486,13488,13491,13493],{"class":202,"line":13487},46,[200,13489,13490],{"class":210},"      resolvedConfig",[200,13492,510],{"class":214},[200,13494,13495],{"class":210}," config\n",[200,13497,13499],{"class":202,"line":13498},47,[200,13500,4962],{"class":214},[200,13502,13504],{"class":202,"line":13503},48,[200,13505,4490],{"class":214},[200,13507,13509,13511],{"class":202,"line":13508},49,[200,13510,413],{"class":214},[200,13512,11743],{"class":210},[939,13514,13515],{},[35,13516,13517,13519,13520,13523,13524,13526],{},[200,13518,945],{},"\nThe ",[156,13521,13522],{},"configResolved"," hook is used to get the resolved Vite configuration, which we need to access the ",[156,13525,12501],{}," where the images are stored.",[35,13528,13529,13530],{},"The complete source code is also available on ",[1582,13531,215],{"repo":13532},"Barbapapazes\u002Fvite-vue-unpic",[35,13534,13535],{},"With this plugin, we now provide a better user experience without adding any manual work to our content creation process.",[130,13537,132,13538,132,13543],{},[134,13539,136,13540,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,13541],{"src":13542,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite\u002Foptimized-image.mp4",[143,13544,13545],{},"Example of optimized image with reserved space and lazy-loading.",[35,13547,13548,13549,1638,13551,13553,13554,13557],{},"When the page loads, the image space is already reserved thanks to the ",[156,13550,11004],{},[156,13552,11013],{}," attributes, and a blurred placeholder is displayed using the ",[156,13555,13556],{},"background-image"," style until the full image is loaded. This effectively eliminates CLS issues related to lazy‑loaded images and improves the overall user experience by providing a visual cue while the image is loading.",[51,13559,13561],{"id":13560},"observing-the-pipeline","Observing the pipeline",[35,13563,13564,13565,215],{},"We can observe how our plugin interacts with the Vite pipeline using ",[39,13566,13569],{"href":13567,"rel":13568},"https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-plugin-inspect",[77],"vite-plugin-inspect",[192,13571,13573],{"className":1355,"code":13572,"language":1358,"meta":13,"style":13},"pnpm add -D vite-plugin-inspect\n",[156,13574,13575],{"__ignoreMap":13},[200,13576,13577,13579,13581,13583],{"class":202,"line":203},[200,13578,2160],{"class":1978},[200,13580,2163],{"class":245},[200,13582,12291],{"class":245},[200,13584,13585],{"class":245}," vite-plugin-inspect\n",[35,13587,13588,13589,13591],{},"Then, add it to the ",[156,13590,1460],{}," file:",[192,13593,13595],{"className":1370,"code":13594,"filename":1460,"language":1373,"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,13596,13597,13615,13630,13634,13646,13654,13661,13665,13670,13675],{"__ignoreMap":13},[200,13598,13599,13601,13603,13605,13607,13609,13611,13613],{"class":202,"line":203},[200,13600,973],{"class":206},[200,13602,318],{"class":214},[200,13604,1488],{"class":210},[200,13606,981],{"class":214},[200,13608,984],{"class":206},[200,13610,242],{"class":214},[200,13612,1497],{"class":245},[200,13614,992],{"class":214},[200,13616,13617,13619,13622,13624,13626,13628],{"class":202,"line":14},[200,13618,973],{"class":206},[200,13620,13621],{"class":210}," inspect ",[200,13623,1472],{"class":206},[200,13625,242],{"class":214},[200,13627,13569],{"class":245},[200,13629,992],{"class":214},[200,13631,13632],{"class":202,"line":255},[200,13633,529],{"emptyLinePlaceholder":20},[200,13635,13636,13638,13640,13642,13644],{"class":202,"line":272},[200,13637,1380],{"class":206},[200,13639,1383],{"class":206},[200,13641,1488],{"class":223},[200,13643,227],{"class":210},[200,13645,230],{"class":214},[200,13647,13648,13650,13652],{"class":202,"line":283},[200,13649,1520],{"class":235},[200,13651,239],{"class":214},[200,13653,1400],{"class":210},[200,13655,13656,13659],{"class":202,"line":300},[200,13657,13658],{"class":223},"    inspect",[200,13660,466],{"class":210},[200,13662,13663],{"class":202,"line":310},[200,13664,529],{"emptyLinePlaceholder":20},[200,13666,13667],{"class":202,"line":336},[200,13668,13669],{"class":401},"    \u002F\u002F ...\n",[200,13671,13672],{"class":202,"line":342},[200,13673,13674],{"class":210},"  ]\n",[200,13676,13677,13679],{"class":202,"line":365},[200,13678,413],{"class":214},[200,13680,416],{"class":210},[35,13682,13683],{},"Then, run the development server:",[192,13685,13687],{"className":1355,"code":13686,"language":1358,"meta":13,"style":13},"pnpm dev\n",[156,13688,13689],{"__ignoreMap":13},[200,13690,13691,13693],{"class":202,"line":203},[200,13692,2160],{"class":1978},[200,13694,13695],{"class":245}," dev\n",[35,13697,13698,13699,215],{},"And open the inspection panel at ",[156,13700,13701],{},"http:\u002F\u002Flocalhost:5173\u002F__inspect\u002F",[35,13703,13704,13705,13708],{},"You can see all the files being processed by Vite. By clicking on ",[156,13706,13707],{},"App.vue",", you can see how our plugin transformed the code step by step.",[35,13710,13711],{},[1690,13712],{"alt":13713,"src":13714},"Vite Plugin Inspect - Image Transformation Steps","\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite\u002Fvite-plugin-inspect.webp",[51,13716,11221],{"id":11220},[35,13718,13719],{},"Through this relatively simple yet useful example, I hope you learned about the power of Vite plugins.",[35,13721,13722],{},"Once you understand the mindset and the basics, you can automate nearly everything within your project. Since I learned how to create Vite plugins, I use them all the time to solve problems I didn't even know were solvable in another way.",[35,13724,13725,13726,13731,13732,1638,13737,106,13742,13745],{},"To learn more about plugins, I highly recommend reading the source code of popular Vite plugins like ",[39,13727,13730],{"href":13728,"rel":13729},"https:\u002F\u002Fgithub.com\u002Fvitejs\u002Fvite-plugin-vue",[77],"vite-plugin-vue"," or ",[39,13733,13736],{"href":13734,"rel":13735},"https:\u002F\u002Fgithub.com\u002Funplugin\u002Funplugin-icons",[77],"unplugin-icons",[39,13738,13741],{"href":13739,"rel":13740},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fvitepress\u002Ftree\u002Fmain\u002Fsrc\u002Fnode\u002Fplugins",[77],"those from VitePress",[39,13743,13569],{"href":13567,"rel":13744},[77]," really helps to understand how plugins interact with each other and modify the code.",[35,13747,13748],{},"Also, this is a straightforward example. If you have a lot of images, you might want to add caching and parallel processing to speed up the build time. You could also handle remote images by downloading them before processing them.",[35,13750,13751,13752,13754],{},"Speaking of remote images, instead of reading the filesystem, you could fetch the images from your bucket, process them, and prefix the ",[156,13753,11165],{}," attribute with your CDN URL. This avoids having to commit images to your repository and keeps the CDN URL in a single place.",[1159,13756],{},[35,13758,13759],{},"I'm considering creating a small talk about Vite plugins. They are underappreciated and underdocumented. Please let me know if you would be interested!",[1171,13761,13762],{},"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":13764},[13765,13766,13767,13768,13769],{"id":11358,"depth":14,"text":11359},{"id":11563,"depth":14,"text":11564},{"id":12232,"depth":14,"text":12233},{"id":13560,"depth":14,"text":13561},{"id":11220,"depth":14,"text":11221},"2025-11-11","Cumulative Layout Shift, when related to images, can be tricky to handle. Learn how to completely remove CLS issues without any manual work.",{},"---\ncontentId: 264d08e1-fb05-40c0-b298-0ba1dbbd3f14\ntitle: Erase CLS by Automatically Optimizing Images with Vite\ndescription: Cumulative Layout Shift, when related to images, can be tricky to handle. Learn how to completely remove CLS issues without any manual work.\ndate: 2025-11-11\n---\n\nI love Vite, not only because it's fast, but also because of its amazing plugin system.\n\nAt its core, a Vite plugin lets you edit, on the fly, any file that is imported, whether it exists or not, in your project.\n\nOnce you understand that, everything becomes possible. _Everything as a Vite plugin!_\n\n---\n\nFor years now, I've been building content-first websites where the content is written in Markdown. I also love using images to illustrate my content.\n\nHowever, images are tricky. To improve performance, you want to lazy‑load them, but lazy loading inevitably causes Cumulative Layout Shift (CLS) if you don't reserve space. To reserve space, the browser needs to know the image dimensions beforehand. But you don't always want the same dimensions everywhere, so for each image, you need its dimensions for the browser to reserve the right amount of space.\n\nBeyond that, images can quickly become large, and hosting them in your repository isn't ideal. So you might want to transfer them to a dedicated bucket. This means they will be served from a different domain, which, combined with lazy loading, even with the correct dimensions, will leave a blank space until they are fully loaded.\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>Example of Cumulative Layout Shift (CLS) issue when lazy-loading an image without reserved space.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nThis is a solvable problem, but it requires a lot of manual work. _I hate manual and repetitive work that can be automated!_\n\n> [!NOTE]\n> This article is dedicated to HTML content. [Another article](.\u002Fmarkdown-exit-breaks-the-rules-with-async-rendering.md) covers how to handle Markdown content using [Markdown Exit](https:\u002F\u002Fmarkdown-exit.pages.dev\u002F).\n\n## Vite to the rescue\n\nThe first question that comes to mind is: How do you know whether a Vite plugin is the right tool for the job?\n\nThis is an important question. I could just give you the solution, but you won't be able to apply a similar approach to your own problems. _Sadly, most tutorials miss this step._\n\nLet's analyze the problem by looking at its inputs and outputs.\n\nThe input is probably a raw HTML snippet like this one:\n\n```html\n\u003Cdiv>\n  \u003Cimg src=\"\u002Fpath\u002Fto\u002Fimage.jpg\" alt=\"An image\">\n\u003C\u002Fdiv>\n```\n\nThe output should be an optimized HTML snippet like this one:\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 is built around a pipeline that processes files as they're imported. This means we can hook into the pipeline, detect when the code contains an image, and transform it accordingly.\n\nOur use case fits perfectly with Vite's capabilities. _Vite plugins can have side effects on the imported files. They unlock really powerful use cases like automatic image compression, resizing, ..._\n\n> [!NOTE]\n> This article assumes images are available locally. Handling remote images won't be covered here, but I'll give a few hints at the end of the article.\n\n## Building the plugin\n\nWhat's the plan? It's great to have an idea of what we want to achieve, but how do we get there?\n\nWe need to hook into Vite's pipeline and look for Vue templates. Then, we need to find all `\u003Cimg>` tags, extract their `src` attributes, load the images, get their dimensions, generate the blurred placeholder, and finally replace the original `\u003Cimg>` tags with optimized ones.\n\n> [!NOTE]\n> I'm using Vue for all my projects, but the same approach can be applied to any frontend framework supported by Vite.\n\nSounds easier than it is, especially when you don't know where to start and which tool to use.\n\nTo handle all the image processing, I use [unpic](https:\u002F\u002Funpic.pics\u002F). It's a fantastic set of primitives to handle everything related to images. To manipulate code and generate source maps, I use [MagicString](https:\u002F\u002Fgithub.com\u002FRich-Harris\u002Fmagic-string).\n\nThat's all we need!\n\nNow, we can create a Vite plugin that does exactly what we want.\n\n> [!NOTE]\n> You can use a Vite + Vue project as a starting point. The source code of the final plugin is available on [GitHub](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fvite-vue-unpic).\n\nIn the `vite.config.ts` file of our project, let's first create a 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\nAt a minimum, a Vite plugin must have a name. Our plugin is an IIFE (Immediately Invoked Function Expression). This allows us to have a context to store state without relying on global variables.\n\nThen, we need to hook into the right part of the pipeline. There are [a lot of hooks](https:\u002F\u002Frollupjs.org\u002Fplugin-development\u002F#build-hooks) available, but in practice, the most useful one is `transform`. This hook is called for each file that is imported in the project. It receives the file's code and its id (path). We only want to process Vue files, so we can filter by file extension.\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\nBut we can make it even more performant. Calling our plugin on every file is unnecessary, especially with Rolldown, where the communication between Rust and JavaScript incurs a small overhead. To avoid that, we can use some filters to only process files that match certain criteria. In our case, we only want to process Vue files.\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\nThis `filter` ensures that our plugin only runs on Vue files that contain at least one `\u003Cimg>` tag. So much better!\n\n\u003CCalloutReadMore text=\"Plugin hook filters\" href=\"https:\u002F\u002Frolldown.rs\u002Fapis\u002Fplugin-hook-filters\" \u002F>\n\nOne last thing before we can start the magic: we need to make sure the plugin runs before the Vue plugin. Vue transforms Vue files into JavaScript files, and Single‑File Component (SFC) files are much simpler to process. To do so, we have two options:\n\n1. Place our plugin before the Vue plugin in the `plugins` array.\n2. Use the `enforce: 'pre'` option in our plugin definition.\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\nNow, we can start processing the Vue files to enhance the `\u003Cimg>` tags.\n\n## Transforming the code\n\nWithin the `transform` hook, the following needs to be done:\n\n1. Parse the code to find all `\u003Cimg>` tags.\n2. For each `\u003Cimg>` tag, extract the `src` attribute.\n3. Read the file from the filesystem.\n4. Process the image with `unpic` to get its dimensions and a blurred placeholder.\n5. Generate and replace the original `\u003Cimg>` tag with the optimized `\u003Cimg>` tag with all the necessary attributes.\n\nBefore we start, we need to install the dependencies:\n\n```bash\npnpm install -D @unpic\u002Fpixels @unpic\u002Fplaceholder blurhash magic-string\n```\n\nLet's implement this step by step.\n\n1. Parse the code to find all `\u003Cimg>` tags.\n\n  ```ts [vite.config.ts]\n  \u002F\u002F eslint-disable-next-line prefer-const\n  let match = imgTagRegex.exec(code) \u002F\u002F We reuse the regex defined earlier\n  ```\n\n2. For each `\u003Cimg>` tag, extract the `src` attribute.\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. Read the file from the filesystem.\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. Process the image with `unpic` to get its dimensions and a blurred placeholder.\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. Generate and replace the original `\u003Cimg>` tag with the optimized `\u003Cimg>` tag with all the necessary attributes.\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\nAfter implementing all these steps, our final plugin looks like this:\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> The `configResolved` hook is used to get the resolved Vite configuration, which we need to access the `publicDir` where the images are stored.\n\nThe complete source code is also available on \u003CGitHubLink repo=\"Barbapapazes\u002Fvite-vue-unpic\" \u002F>.\n\nWith this plugin, we now provide a better user experience without adding any manual work to our content creation process.\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>Example of optimized image with reserved space and lazy-loading.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nWhen the page loads, the image space is already reserved thanks to the `width` and `height` attributes, and a blurred placeholder is displayed using the `background-image` style until the full image is loaded. This effectively eliminates CLS issues related to lazy‑loaded images and improves the overall user experience by providing a visual cue while the image is loading.\n\n## Observing the pipeline\n\nWe can observe how our plugin interacts with the Vite pipeline using [vite-plugin-inspect](https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-plugin-inspect).\n\n```sh\npnpm add -D vite-plugin-inspect\n```\n\nThen, add it to the `vite.config.ts` file:\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\nThen, run the development server:\n\n```sh\npnpm dev\n```\n\nAnd open the inspection panel at `http:\u002F\u002Flocalhost:5173\u002F__inspect\u002F`.\n\nYou can see all the files being processed by Vite. By clicking on `App.vue`, you can see how our plugin transformed the code step by step.\n\n![Vite Plugin Inspect - Image Transformation Steps](\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite\u002Fvite-plugin-inspect.webp)\n\n## Going further\n\nThrough this relatively simple yet useful example, I hope you learned about the power of Vite plugins.\n\nOnce you understand the mindset and the basics, you can automate nearly everything within your project. Since I learned how to create Vite plugins, I use them all the time to solve problems I didn't even know were solvable in another way.\n\nTo learn more about plugins, I highly recommend reading the source code of popular Vite plugins like [vite-plugin-vue](https:\u002F\u002Fgithub.com\u002Fvitejs\u002Fvite-plugin-vue) or [unplugin-icons](https:\u002F\u002Fgithub.com\u002Funplugin\u002Funplugin-icons) and [those from VitePress](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fvitepress\u002Ftree\u002Fmain\u002Fsrc\u002Fnode\u002Fplugins). [vite-plugin-inspect](https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-plugin-inspect) really helps to understand how plugins interact with each other and modify the code.\n\nAlso, this is a straightforward example. If you have a lot of images, you might want to add caching and parallel processing to speed up the build time. You could also handle remote images by downloading them before processing them.\n\nSpeaking of remote images, instead of reading the filesystem, you could fetch the images from your bucket, process them, and prefix the `src` attribute with your CDN URL. This avoids having to commit images to your repository and keeps the CDN URL in a single place.\n\n---\n\nI'm considering creating a small talk about Vite plugins. They are underappreciated and underdocumented. Please let me know if you would be interested!\n",{"title":11297,"description":13771},{"loc":9994,"images":13776},[13777],{"loc":13714},"posts\u002Ferase-cls-by-automatically-optimizing-images-with-vite","YTvAM3AnTaxt6NIN2wx29JiXYPBa2PomiaQ7q5fwBy8",{"id":13781,"title":13782,"body":13783,"contentId":9306,"date":14109,"description":14110,"extension":18,"meta":14111,"navigation":20,"path":14112,"rawbody":14113,"robots":23,"schemaOrg":23,"seo":14114,"sitemap":14115,"stem":14123,"__hash__":14124},"posts\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux.md","Twice in One Day: Speaking at BDX I\u002FO 2025 in Bordeaux",{"type":7,"value":13784,"toc":14104},[13785,13794,13797,13803,13806,13810,13813,13821,13830,13833,13839,13848,13860,13874,13880,13884,13887,13893,13901,13907,13910,13913,13916,13991,13994,14000,14004,14007,14013,14016,14041,14047,14050,14056,14062,14065,14068,14070,14073,14080,14083,14092,14095,14098],[35,13786,13787,13788,13793],{},"This year, and for the first time, I attended ",[39,13789,13792],{"href":13790,"rel":13791},"https:\u002F\u002Fbdxio.fr",[77],"BDX I\u002FO",", a developer conference taking place in Bordeaux, France. I submitted two talks, and both were accepted! I was really happy to have this opportunity until I realized the conference was only one day long.",[35,13795,13796],{},"I had to give both talks the same day, even the same afternoon! The first one was at 13:55, and the second one at 15:10. A tight schedule for a true challenge.",[35,13798,13799,13802],{},[1217,13800,13801],{},"Don't try this at home. It's made by a professional!"," Jokes aside, you have to know your presentation on your fingertips. Context switching is hard, and you don't want to mess up. Once the first talk was done, I had to completely forget about it and refocus on the second one.",[35,13804,13805],{},"Let me tell you more about my trip to Bordeaux.",[51,13807,13809],{"id":13808},"where-nuxt-is-made","Where Nuxt is made",[35,13811,13812],{},"BDX I\u002FO took place in Bordeaux. You may know the city for many things, like its wine.",[35,13814,13815,13816,13820],{},"But did you know it's also where ",[39,13817,2918],{"href":13818,"rel":13819},"https:\u002F\u002Fnuxt.com",[77],", the framework, is born? In October 2016, Alexandre and Sébastien Chopin started working on Nuxt to make server-side rendering with Vue.js easier.",[35,13822,13823,13824,13829],{},"The headquarters of NuxtLabs, the company behind Nuxt, now ",[39,13825,13828],{"href":13826,"rel":13827},"https:\u002F\u002Fnuxtlabs.com\u002F",[77],"bought by Vercel",", is located in Bordeaux. I took the opportunity of my trip to see them, for real this time. I'm talking with the team mainly online, through Discord and GitHub.",[35,13831,13832],{},"In Bordeaux, only Sébastien and Benjamin are remaining. Most of the team is now operating remotely. So, after going to my hostel, I headed to their office.",[35,13834,13835],{},[1690,13836],{"alt":13837,"src":13838},"NuxtLabs Office in Bordeaux","\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fnuxtlabs-office-bordeaux.webp",[35,13840,13841,13842,13847],{},"I found the famous Nuxt plate but not the ",[39,13843,13846],{"href":13844,"rel":13845},"https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1986454979763138802",[77],"doorbell",". I sent a message to Sébastien but got no answer.",[35,13849,13850,13851,13856,13857],{},"So, I continued to visit the area around and after half an hour, I decided to try again. This time, ",[39,13852,13855],{"href":13853,"rel":13854},"https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1986462410585055599",[77],"I saw the doorbell"," and rang it. A few seconds later, Sébastien opened the door and welcomed me inside. ",[1217,13858,13859],{},"Never give up!",[35,13861,13862,13863,1638,13868,13873],{},"With ",[39,13864,13867],{"href":13865,"rel":13866},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fatinux\u002F",[77],"Sébastien",[39,13869,13872],{"href":13870,"rel":13871},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbenjamincanac\u002F",[77],"Benjamin",", we talked two hours about Nuxt, Studio, Hub, the ecosystem, Vercel, ... Really appreciated meeting them in person finally, after all those years of online interactions.",[35,13875,13876],{},[1690,13877],{"alt":13878,"src":13879},"From Left to Right: 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,13881,13883],{"id":13882},"bdx-io-2025","BDX I\u002FO 2025",[35,13885,13886],{},"BDX I\u002FO is a one-day conference for developers in Bordeaux since 2014. This year, it took place at the Palais des Congrès of Bordeaux Lac. It attracts around 1,000 attendees, 50 conferences across 8 tracks, and more than 20 sponsors.",[35,13888,13889],{},[1690,13890],{"alt":13891,"src":13892},"The main stage at BDX I\u002FO","\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fmain-stage-bdx-io.webp",[35,13894,13895,13896,13900],{},"The place was really pleasant, with large amphitheaters, lots of light, and a great view of the lake. A large room was dedicated to sponsors, with food and drinks available all day long. I saw ",[39,13897,13899],{"href":6941,"rel":13898},[77],"Julien"," from Deezer mixing. They successfully created a dance floor in the sponsor area! And an artist draw me a caricature. Really cool!",[35,13902,13903],{},[1690,13904],{"alt":13905,"src":13906},"Sponsor and launch area at BDX I\u002FO","\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fsponsor-area-bdx-io.webp",[35,13908,13909],{},"The day before the conference, there was a speakers' dinner at a Doppio Malto near the convention center. As usual, it's always a pleasure to meet other speakers, ask them about their talks, or just chat about tech and life.",[35,13911,13912],{},"This time, I tried to attend talks not directly related to technique. I wanted to explore more of the world around development.",[35,13914,13915],{},"So here are the talks I attended:",[1576,13917,13918],{},[71,13919,13920,13923],{},[6850,13921,13922],{},"Friday, November 7, 2025",[1576,13924,13925,13935,13945,13955,13961,13976,13981],{},[71,13926,13927,6853,13930],{},[6850,13928,13929],{},"Prédire l'imprévisible : comment penser le futur à l'ère de l'IA",[39,13931,13934],{"href":13932,"rel":13933},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fludoviccinquin\u002F",[77],"Ludovic Cinquin",[71,13936,13937,6853,13940],{},[6850,13938,13939],{},"Réécrire le rôle : développeur à l'ère des LLM",[39,13941,13944],{"href":13942,"rel":13943},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fhoraciogonzalez\u002F",[77],"Horacio Gonzalez",[71,13946,13947,6853,13950],{},[6850,13948,13949],{},"Onboarding 2.0 : Réinventer l'intégration des devs",[39,13951,13954],{"href":13952,"rel":13953},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fhafsa-el-maizi\u002F",[77],"Hafsa EL MAIZI",[71,13956,13957,13960],{},[6850,13958,13959],{},"En JavaScript, il y a un paquet pour tout... et souvent pour rien."," by Estéban Soubiran (it's me!)",[71,13962,13963,6853,13966,1638,13971],{},[6850,13964,13965],{},"Rex : innover les transports en commun pour accueillir les Jeux Olympiques 2024",[39,13967,13970],{"href":13968,"rel":13969},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fghislain-mokolomboka-3bb86236\u002F",[77],"Ghislain MOKOLOMBOKA",[39,13972,13975],{"href":13973,"rel":13974},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolasjozwiak\u002F",[77],"Nicolas Jozwiak",[71,13977,13978,13980],{},[6850,13979,9124],{}," by Estéban Soubiran (it's me again!)",[71,13982,13983,6853,13986],{},[6850,13984,13985],{},"Parlons d'entretiens techniques",[39,13987,13990],{"href":13988,"rel":13989},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fane-diaz-de-tuesta-37785252\u002F",[77],"Ane Diaz De Tuesta",[35,13992,13993],{},"With 8 tracks in parallel, I know I missed a lot of great talks, so I'll have to wait for the videos to be published.",[35,13995,13996],{},[1690,13997],{"alt":13998,"src":13999},"BDX I\u002FO organizers group photo","\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fbdx-io-organizers-group-photo.webp",[51,14001,14003],{"id":14002},"speaking-twice","Speaking twice",[35,14005,14006],{},"Giving two talks is an opportunity. It shows the trust of the organizers in my presentations. I already had this opportunity during Devoxx 2025, MiXiT 2025, and BreizhCamp 2025. But this time, it was different because both talks were on the same day, with only 1 hour between.",[35,14008,14009,14010],{},"When I submitted my talks to BDX I\u002FO, I didn't expect both to be accepted. I never imagined having to speak twice in a row. So, when I received two acceptance emails, I was both excited to speak at a new conference and a bit stressed about the schedule. I took it as a challenge, and I love challenges. ",[1217,14011,14012],{},"Let's explore the unknown!",[35,14014,14015],{},"So, on November 7, 2025, I gave my two talks:",[1576,14017,14018,14030],{},[71,14019,14020,14022,14023,215],{},[6850,14021,13959],{}," at 13:55, fifteen minutes to explore ecosystem performance and show tools to explore ",[39,14024,14027],{"href":14025,"rel":14026},"https:\u002F\u002Fe18e.dev\u002F",[77],[156,14028,14029],{},"node_modules",[71,14031,14032,14034,14035,14040],{},[6850,14033,9124],{}," at 15:10, fourty-five minutes to explain signals through the amazing ",[39,14036,14039],{"href":14037,"rel":14038},"https:\u002F\u002Fgithub.com\u002Fstackblitz\u002Falien-signals",[77],"Alien Signals"," library.",[35,14042,14043],{},[1690,14044],{"alt":14045,"src":14046},"Me Speaking at BDX I\u002FO","\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fspeaking-bdx-io.webp",[35,14048,14049],{},"Both talks went well, and I received positive feedback from the audience.",[35,14051,14052,14053],{},"It was the sixth time I gave them, so I really know them by heart. I know exactly what to say, when to pause, and what's coming when I press the remote control. This familiarity helped me manage the tight schedule. Honestly, I wouldn't recommend this if you don't perfectly master your talks. ",[1217,14054,14055],{},"Training is key!",[35,14057,14058,14059,14061],{},"Once the first talk was over, I had to completely switch context, forget about JavaScript ",[156,14060,14029],{},", and focus on signals. To avoid stress, I went to a talk in another room, as if I were an attendee. It helped me think about something else entirely.",[35,14063,14064],{},"Then, I went to the room where I had to give my second talk, set up my laptop, and waited for the time to start. I didn't even have time to be nervous or stressed.",[35,14066,14067],{},"When I finished, I was completely exhausted with a headache. I went to a last talk, where I nearly fell asleep sitting, listened to the final keynote, and went back to the train station to go home. I slept 12+ hours the next day to recover. That's how intense it was!",[1159,14069],{},[35,14071,14072],{},"You may think that everything was under control. But no!",[35,14074,14075,14076,14079],{},"A couple of minutes before my first talk, I pushed some code to production on ",[39,14077,7881],{"href":7879,"rel":14078},[77],", the tool I use to make my slides interactive. I also had to create questions on Inalia to integrate them into my slides.",[35,14081,14082],{},"Why not do it earlier? Because I honestly had so many things to do before leaving for Bordeaux that I didn't find the time. I saw a friend and we went to the theater, attended a meetup, started preparing talks, wrote and published the communication, and more.",[35,14084,14085,14086,14091],{},"The night before the conference, I had to finish ",[39,14087,14090],{"href":14088,"rel":14089},"https:\u002F\u002Fwww.linkedin.com\u002Ffeed\u002Fupdate\u002Furn:li:activity:7392459559274983425\u002F",[77],"the blog post about NuxtLabs"," and my visit to their office. I slept less than 6 hours that night. The conference day was hard, and I took a small nap during the lunch break to survive the afternoon. At the end of the day, I was only thinking about going back home and sleeping, but I still had a three-hour train ride.",[35,14093,14094],{},"When you have to deal with everything alone, it's unfortunately inevitable.",[35,14096,14097],{},"From imagining and preparing the talks, to communicating about them, traveling to conferences, and speaking, it's all done during my free time, evenings and weekends. There's no company behind me to help.",[35,14099,14100,14101],{},"So you may think it's glamorous to speak at conferences, but it's a lot of work and little rest. ",[1217,14102,14103],{},"Most people never talk about that side of the story.",{"title":13,"searchDepth":14,"depth":14,"links":14105},[14106,14107,14108],{"id":13808,"depth":14,"text":13809},{"id":13882,"depth":14,"text":13883},{"id":14002,"depth":14,"text":14003},"2025-11-10","I had the opportunity to speak twice in one day at BDX I\u002FO. It was a unique challenge that required perfect mastery of my presentations.",{},"\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux","---\ncontentId: 0c7194ba-a648-424f-824c-52d079b6fa72\ntitle: \"Twice in One Day: Speaking at BDX I\u002FO 2025 in Bordeaux\"\ndescription: I had the opportunity to speak twice in one day at BDX I\u002FO. It was a unique challenge that required perfect mastery of my presentations.\ndate: 2025-11-10\n---\n\nThis year, and for the first time, I attended [BDX I\u002FO](https:\u002F\u002Fbdxio.fr), a developer conference taking place in Bordeaux, France. I submitted two talks, and both were accepted! I was really happy to have this opportunity until I realized the conference was only one day long.\n\nI had to give both talks the same day, even the same afternoon! The first one was at 13:55, and the second one at 15:10. A tight schedule for a true challenge.\n\n_Don't try this at home. It's made by a professional!_ Jokes aside, you have to know your presentation on your fingertips. Context switching is hard, and you don't want to mess up. Once the first talk was done, I had to completely forget about it and refocus on the second one.\n\nLet me tell you more about my trip to Bordeaux.\n\n## Where Nuxt is made\n\nBDX I\u002FO took place in Bordeaux. You may know the city for many things, like its wine.\n\nBut did you know it's also where [Nuxt](https:\u002F\u002Fnuxt.com), the framework, is born? In October 2016, Alexandre and Sébastien Chopin started working on Nuxt to make server-side rendering with Vue.js easier.\n\nThe headquarters of NuxtLabs, the company behind Nuxt, now [bought by Vercel](https:\u002F\u002Fnuxtlabs.com\u002F), is located in Bordeaux. I took the opportunity of my trip to see them, for real this time. I'm talking with the team mainly online, through Discord and GitHub.\n\nIn Bordeaux, only Sébastien and Benjamin are remaining. Most of the team is now operating remotely. So, after going to my hostel, I headed to their office.\n\n![NuxtLabs Office in Bordeaux](\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fnuxtlabs-office-bordeaux.webp)\n\nI found the famous Nuxt plate but not the [doorbell](https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1986454979763138802). I sent a message to Sébastien but got no answer.\n\nSo, I continued to visit the area around and after half an hour, I decided to try again. This time, [I saw the doorbell](https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1986462410585055599) and rang it. A few seconds later, Sébastien opened the door and welcomed me inside. _Never give up!_\n\nWith [Sébastien](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fatinux\u002F) and [Benjamin](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbenjamincanac\u002F), we talked two hours about Nuxt, Studio, Hub, the ecosystem, Vercel, ... Really appreciated meeting them in person finally, after all those years of online interactions.\n\n![From Left to Right: 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 is a one-day conference for developers in Bordeaux since 2014. This year, it took place at the Palais des Congrès of Bordeaux Lac. It attracts around 1,000 attendees, 50 conferences across 8 tracks, and more than 20 sponsors.\n\n![The main stage at BDX I\u002FO](\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fmain-stage-bdx-io.webp)\n\nThe place was really pleasant, with large amphitheaters, lots of light, and a great view of the lake. A large room was dedicated to sponsors, with food and drinks available all day long. I saw [Julien](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-briault-441539137\u002F) from Deezer mixing. They successfully created a dance floor in the sponsor area! And an artist draw me a caricature. Really cool!\n\n![Sponsor and launch area at BDX I\u002FO](\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fsponsor-area-bdx-io.webp)\n\nThe day before the conference, there was a speakers' dinner at a Doppio Malto near the convention center. As usual, it's always a pleasure to meet other speakers, ask them about their talks, or just chat about tech and life.\n\nThis time, I tried to attend talks not directly related to technique. I wanted to explore more of the world around development.\n\nSo here are the talks I attended:\n\n- **Friday, November 7, 2025**\n  - **Prédire l'imprévisible : comment penser le futur à l'ère de l'IA** by [Ludovic Cinquin](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fludoviccinquin\u002F)\n  - **Réécrire le rôle : développeur à l'ère des LLM** by [Horacio Gonzalez](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fhoraciogonzalez\u002F)\n  - **Onboarding 2.0 : Réinventer l'intégration des devs** by [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.** by Estéban Soubiran (it's me!)\n  - **Rex : innover les transports en commun pour accueillir les Jeux Olympiques 2024** by [Ghislain MOKOLOMBOKA](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fghislain-mokolomboka-3bb86236\u002F) and [Nicolas Jozwiak](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolasjozwiak\u002F)\n  - **La réactivité et les signaux : démystifions la magie du frontend** by Estéban Soubiran (it's me again!)\n  - **Parlons d'entretiens techniques** by [Ane Diaz De Tuesta](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fane-diaz-de-tuesta-37785252\u002F)\n\nWith 8 tracks in parallel, I know I missed a lot of great talks, so I'll have to wait for the videos to be published.\n\n![BDX I\u002FO organizers group photo](\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fbdx-io-organizers-group-photo.webp)\n\n## Speaking twice\n\nGiving two talks is an opportunity. It shows the trust of the organizers in my presentations. I already had this opportunity during Devoxx 2025, MiXiT 2025, and BreizhCamp 2025. But this time, it was different because both talks were on the same day, with only 1 hour between.\n\nWhen I submitted my talks to BDX I\u002FO, I didn't expect both to be accepted. I never imagined having to speak twice in a row. So, when I received two acceptance emails, I was both excited to speak at a new conference and a bit stressed about the schedule. I took it as a challenge, and I love challenges. _Let's explore the unknown!_\n\nSo, on November 7, 2025, I gave my two talks:\n\n- **En JavaScript, il y a un paquet pour tout... et souvent pour rien.** at 13:55, fifteen minutes to explore ecosystem performance and show tools to explore [`node_modules`](https:\u002F\u002Fe18e.dev\u002F).\n- **La réactivité et les signaux : démystifions la magie du frontend** at 15:10, fourty-five minutes to explain signals through the amazing [Alien Signals](https:\u002F\u002Fgithub.com\u002Fstackblitz\u002Falien-signals) library.\n\n![Me Speaking at BDX I\u002FO](\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fspeaking-bdx-io.webp)\n\nBoth talks went well, and I received positive feedback from the audience.\n\nIt was the sixth time I gave them, so I really know them by heart. I know exactly what to say, when to pause, and what's coming when I press the remote control. This familiarity helped me manage the tight schedule. Honestly, I wouldn't recommend this if you don't perfectly master your talks. _Training is key!_\n\nOnce the first talk was over, I had to completely switch context, forget about JavaScript `node_modules`, and focus on signals. To avoid stress, I went to a talk in another room, as if I were an attendee. It helped me think about something else entirely.\n\nThen, I went to the room where I had to give my second talk, set up my laptop, and waited for the time to start. I didn't even have time to be nervous or stressed.\n\nWhen I finished, I was completely exhausted with a headache. I went to a last talk, where I nearly fell asleep sitting, listened to the final keynote, and went back to the train station to go home. I slept 12+ hours the next day to recover. That's how intense it was!\n\n---\n\nYou may think that everything was under control. But no!\n\nA couple of minutes before my first talk, I pushed some code to production on [Inalia](https:\u002F\u002Finalia.app), the tool I use to make my slides interactive. I also had to create questions on Inalia to integrate them into my slides.\n\nWhy not do it earlier? Because I honestly had so many things to do before leaving for Bordeaux that I didn't find the time. I saw a friend and we went to the theater, attended a meetup, started preparing talks, wrote and published the communication, and more.\n\nThe night before the conference, I had to finish [the blog post about NuxtLabs](https:\u002F\u002Fwww.linkedin.com\u002Ffeed\u002Fupdate\u002Furn:li:activity:7392459559274983425\u002F) and my visit to their office. I slept less than 6 hours that night. The conference day was hard, and I took a small nap during the lunch break to survive the afternoon. At the end of the day, I was only thinking about going back home and sleeping, but I still had a three-hour train ride.\n\nWhen you have to deal with everything alone, it's unfortunately inevitable.\n\nFrom imagining and preparing the talks, to communicating about them, traveling to conferences, and speaking, it's all done during my free time, evenings and weekends. There's no company behind me to help.\n\nSo you may think it's glamorous to speak at conferences, but it's a lot of work and little rest. _Most people never talk about that side of the story._\n",{"title":13782,"description":14110},{"loc":14112,"images":14116},[14117,14118,14119,14120,14121,14122],{"loc":13838},{"loc":13879},{"loc":13892},{"loc":13906},{"loc":13999},{"loc":14046},"posts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux","FPPzLd-T2nLIgDVaAQBaXWcruqHG6Zj3YN-c4qI1BCY",{"id":14126,"title":14127,"body":14128,"contentId":9326,"date":16364,"description":16365,"extension":18,"meta":16366,"navigation":20,"path":2971,"rawbody":16367,"robots":23,"schemaOrg":23,"seo":16368,"sitemap":16369,"stem":16372,"__hash__":16373},"posts\u002Fposts\u002Fwant-to-contribute-to-open-source-youre-doing-it-wrong.md","Want to Contribute to Open Source? You're Doing It Wrong",{"type":7,"value":14129,"toc":16358},[14130,14133,14136,14139,14142,14145,14147,14159,14166,14169,14192,14195,14198,14209,14213,14216,14224,14227,14230,14233,14362,14372,14379,14525,14528,14531,14541,14544,14547,14553,14569,14576,15302,15305,15314,15320,15335,15339,15342,15345,15348,15595,15602,15609,15612,15615,15731,15738,15741,15748,15751,15754,15760,15763,15767,15770,15773,15776,15783,15786,16085,16088,16091,16096,16102,16114,16199,16206,16213,16355],[35,14131,14132],{},"I have been actively contributing to open-source since 2020. Since then, I've spoken with many developers who want to contribute but don't know where to start, often asking for an issue to work on.",[35,14134,14135],{},"I'm deeply convinced that open-source is more than that and, more importantly, doesn't work like that.",[35,14137,14138],{},"To me, open-source, at its core, is a mindset. It's about the freedom to explore, understand, modify, and improve the source code of the software you want. Of course, this model encourages collaboration and sharing by allowing anyone to contribute, but it doesn't require you to.",[35,14140,14141],{},"Open-source shouldn't, and should never, be a goal in itself. It's part of the journey, but it's not the destination. Otherwise, you'll end up feeling frustrated and unfulfilled by never being able to contribute.",[35,14143,14144],{},"Let me tell you a story to illustrate this point.",[1159,14146],{},[35,14148,14149,14150,14155,14156],{},"I'm building a new website called ",[39,14151,14154],{"href":14152,"rel":14153},"https:\u002F\u002Finfra.soubiran.dev",[77],"infra.soubiran.dev"," to document my own infrastructure. It's a personal wiki but open to everyone. ",[1217,14157,14158],{},"I won't go into details; a dedicated post will come later.",[35,14160,14161,14162,14165],{},"To build it, I needed to find the right tools. Until now, I was using ",[39,14163,14164],{"href":4700},"VitePress with a custom theme for my personal blog",", the one you're reading right now. It works great, but I've nearly reached its limits, and with all the ideas I have, I know it could be time to move on.",[35,14167,14168],{},"So, I started looking into alternatives. Honestly, there are two options:",[68,14170,14171,14183],{},[71,14172,14173,14174,14177,14178],{},"Using ",[39,14175,2918],{"href":13818,"rel":14176},[77]," with ",[39,14179,14182],{"href":14180,"rel":14181},"https:\u002F\u002Fcontent.nuxt.com",[77],"Nuxt Content",[71,14184,14185,14186,14191],{},"Using Vite with Vue and some ",[39,14187,14190],{"href":14188,"rel":14189},"https:\u002F\u002Funplugin.unjs.io\u002F",[77],"unplugins"," to handle markdown files.",[35,14193,14194],{},"I went for the second option. Funny enough, before moving to VitePress, I had the same dilemma: Nuxt Content, VitePress, or Vite with Vue. At that time, I chose VitePress.",[35,14196,14197],{},"Anyway, I started building the website.",[35,14199,14200,14201,14205,14206],{},"For the past couple of months, there's one tech I can't live without: ",[39,14202,9190],{"href":14203,"rel":14204},"https:\u002F\u002Fui.nuxt.com",[77],". It's an incredible component library made by the Nuxt team. All my projects that use Vue or Nuxt use Nuxt UI, and I regularly push it to its limits. ",[1217,14207,14208],{},"This is also a reason why I decided to move away from VitePress, which is not compatible with Nuxt UI.",[51,14210,14212],{"id":14211},"building-with-open-source-software","Building with open-source software",[35,14214,14215],{},"The website I'm building is content-driven. This means that the content is primarily text-based and, in my case, all the pages are written in Markdown. To update the website, I edit the Markdown files and then rebuild the site.",[35,14217,14218,14219,215],{},"The pages are generated and pre-rendered at build time. There is no need to generate them at runtime, as every user will receive the same pre-rendered content. To make it work, I use ",[39,14220,14223],{"href":14221,"rel":14222},"https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-ssg",[77],"Vite SSG",[35,14225,14226],{},"It's a drop-in replacement for Vite when building the website. It automatically builds the client and the server, then uses the server to pre-render each page, as if a user were requesting it.",[35,14228,14229],{},"After installing it, everything worked as expected.",[35,14231,14232],{},"So I continued to build the website. I installed Nuxt UI and started configuring it. To use Nuxt UI within a Vue application, you have to use a plugin.",[192,14234,14237],{"className":9741,"code":14235,"filename":14236,"language":1373,"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,14238,14239,14255,14275,14291,14295,14311,14318,14323,14337,14354,14358],{"__ignoreMap":13},[200,14240,14241,14243,14246,14248,14250,14253],{"class":202,"line":203},[200,14242,973],{"class":206},[200,14244,14245],{"class":210}," ui ",[200,14247,1472],{"class":206},[200,14249,242],{"class":214},[200,14251,14252],{"class":245},"@nuxt\u002Fui\u002Fvue-plugin",[200,14254,992],{"class":214},[200,14256,14257,14259,14261,14264,14266,14268,14270,14273],{"class":202,"line":14},[200,14258,973],{"class":206},[200,14260,318],{"class":214},[200,14262,14263],{"class":210}," ViteSSG",[200,14265,981],{"class":214},[200,14267,984],{"class":206},[200,14269,242],{"class":214},[200,14271,14272],{"class":245},"vite-ssg",[200,14274,992],{"class":214},[200,14276,14277,14279,14282,14284,14286,14289],{"class":202,"line":255},[200,14278,973],{"class":206},[200,14280,14281],{"class":210}," App ",[200,14283,1472],{"class":206},[200,14285,242],{"class":214},[200,14287,14288],{"class":245},".\u002FApp.vue",[200,14290,992],{"class":214},[200,14292,14293],{"class":202,"line":272},[200,14294,529],{"emptyLinePlaceholder":20},[200,14296,14297,14299,14302,14305,14307,14309],{"class":202,"line":283},[200,14298,1380],{"class":206},[200,14300,14301],{"class":379}," const",[200,14303,14304],{"class":210}," createApp ",[200,14306,449],{"class":214},[200,14308,14263],{"class":223},[200,14310,3115],{"class":210},[200,14312,14313,14316],{"class":202,"line":300},[200,14314,14315],{"class":210},"  App",[200,14317,252],{"class":214},[200,14319,14320],{"class":202,"line":310},[200,14321,14322],{"class":214},"  {},\n",[200,14324,14325,14328,14331,14333,14335],{"class":202,"line":336},[200,14326,14327],{"class":214},"  ({",[200,14329,14330],{"class":386}," app",[200,14332,390],{"class":214},[200,14334,393],{"class":379},[200,14336,280],{"class":214},[200,14338,14340,14343,14345,14347,14349,14352],{"class":14339,"line":342},[202,9871],[200,14341,14342],{"class":210},"    app",[200,14344,215],{"class":214},[200,14346,9753],{"class":223},[200,14348,227],{"class":235},[200,14350,14351],{"class":210},"ui",[200,14353,5267],{"class":235},[200,14355,14356],{"class":202,"line":365},[200,14357,368],{"class":214},[200,14359,14360],{"class":202,"line":371},[200,14361,416],{"class":210},[35,14363,14364,14365,14367,14368,14371],{},"And then, everything broke. During the pre-rendering, something tried to access ",[156,14366,422],{}," to create a style: ",[156,14369,14370],{},"const style = document.createElement('style')",". Really strange.",[35,14373,14374,14375,14378],{},"When pre-rendering, Vite SSG injects ",[156,14376,14377],{},"import.meta.env.SSR"," to detect if it's running in SSR mode. It also allows the bundler to tree-shake unused code effectively. So, to fix the bug, I wrapped the plugin usage in a conditional check for SSR mode.",[192,14380,14382],{"className":9741,"code":14381,"filename":14236,"language":1373,"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,14383,14384,14398,14416,14430,14434,14448,14454,14458,14470,14497,14512,14517,14521],{"__ignoreMap":13},[200,14385,14386,14388,14390,14392,14394,14396],{"class":202,"line":203},[200,14387,973],{"class":206},[200,14389,14245],{"class":210},[200,14391,1472],{"class":206},[200,14393,242],{"class":214},[200,14395,14252],{"class":245},[200,14397,992],{"class":214},[200,14399,14400,14402,14404,14406,14408,14410,14412,14414],{"class":202,"line":14},[200,14401,973],{"class":206},[200,14403,318],{"class":214},[200,14405,14263],{"class":210},[200,14407,981],{"class":214},[200,14409,984],{"class":206},[200,14411,242],{"class":214},[200,14413,14272],{"class":245},[200,14415,992],{"class":214},[200,14417,14418,14420,14422,14424,14426,14428],{"class":202,"line":255},[200,14419,973],{"class":206},[200,14421,14281],{"class":210},[200,14423,1472],{"class":206},[200,14425,242],{"class":214},[200,14427,14288],{"class":245},[200,14429,992],{"class":214},[200,14431,14432],{"class":202,"line":272},[200,14433,529],{"emptyLinePlaceholder":20},[200,14435,14436,14438,14440,14442,14444,14446],{"class":202,"line":283},[200,14437,1380],{"class":206},[200,14439,14301],{"class":379},[200,14441,14304],{"class":210},[200,14443,449],{"class":214},[200,14445,14263],{"class":223},[200,14447,3115],{"class":210},[200,14449,14450,14452],{"class":202,"line":300},[200,14451,14315],{"class":210},[200,14453,252],{"class":214},[200,14455,14456],{"class":202,"line":310},[200,14457,14322],{"class":214},[200,14459,14460,14462,14464,14466,14468],{"class":202,"line":336},[200,14461,14327],{"class":214},[200,14463,14330],{"class":386},[200,14465,390],{"class":214},[200,14467,393],{"class":379},[200,14469,280],{"class":214},[200,14471,14473,14475,14477,14479,14481,14483,14485,14488,14490,14493,14495],{"class":14472,"line":342},[202,9871],[200,14474,5252],{"class":206},[200,14476,1095],{"class":235},[200,14478,973],{"class":206},[200,14480,215],{"class":214},[200,14482,2015],{"class":210},[200,14484,215],{"class":214},[200,14486,14487],{"class":210},"env",[200,14489,215],{"class":214},[200,14491,14492],{"class":210},"SSR",[200,14494,5267],{"class":235},[200,14496,2752],{"class":214},[200,14498,14499,14502,14504,14506,14508,14510],{"class":202,"line":365},[200,14500,14501],{"class":210},"      app",[200,14503,215],{"class":214},[200,14505,9753],{"class":223},[200,14507,227],{"class":235},[200,14509,14351],{"class":210},[200,14511,416],{"class":235},[200,14513,14515],{"class":14514,"line":371},[202,9871],[200,14516,2096],{"class":214},[200,14518,14519],{"class":202,"line":398},[200,14520,368],{"class":214},[200,14522,14523],{"class":202,"line":405},[200,14524,416],{"class":210},[35,14526,14527],{},"Now, pre-rendering worked as expected, or so I thought.",[35,14529,14530],{},"I wrote some pages, created components, and pushed everything to production. It built successfully, so I opened the website and saw the following:",[130,14532,132,14533,132,14538],{},[134,14534,136,14535,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,14536],{"src":14537,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fwhant-to-contribute-to-open-source-youre-doing-it-wrong\u002Finfra-soubiran-dev-fouc.mp4",[143,14539,14540],{},"Loading of my new website",[35,14542,14543],{},"Can you see it?",[35,14545,14546],{},"Before going further, and if you noticed it, would you have fixed it? You know it will take time, and you won't be able to continue writing content. That's a real dilemma. Taking time for something you might consider a detail, or continuing to work on your content.",[35,14548,14549,14552],{},[6850,14550,14551],{},"Stunning websites are made of details",". For me, details are very important, and it's essential to pay attention to them. They can transform the user experience, improve user comfort, and increase loyalty. Never underestimate them.",[35,14554,14555,14556,14561,14562,14565,14566,14568],{},"For those who don't see it, that's called a ",[39,14557,14560],{"href":14558,"rel":14559},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FFlash_of_unstyled_content",[77],"FOUC",", a flash of unstyled content. The content appears before all the style is available. It usually happens when the script needs to add a ",[156,14563,14564],{},"dark"," class to the HTML element. The website loads with the default theme (light mode), appears, and then the JavaScript is parsed and executed. The script adds the ",[156,14567,14564],{}," class and the theme switches to dark mode. The light mode lasts a few hundred milliseconds, but it's enough to be noticed. This is all related to the way a browser renders a page.",[35,14570,14571,14572,14575],{},"It can be very tricky to fix. Usually, it happens when tags in your ",[156,14573,14574],{},"head"," aren't correctly ordered or when the script is not executed at the right time. So, I jumped into the head of the generated page:",[192,14577,14580],{"className":591,"code":14578,"filename":14579,"language":593,"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,14581,14582,14602,14610,14655,14731,14751,14780,14810,14827,14857,14886,14895,14909,14951,14989,15030,15064,15070,15079,15120,15155,15191,15226,15257,15286,15294],{"__ignoreMap":13},[200,14583,14584,14586,14588,14591,14593,14595,14598,14600],{"class":202,"line":203},[200,14585,600],{"class":214},[200,14587,593],{"class":235},[200,14589,14590],{"class":379}," lang",[200,14592,449],{"class":214},[200,14594,611],{"class":214},[200,14596,14597],{"class":245},"en",[200,14599,611],{"class":214},[200,14601,648],{"class":214},[200,14603,14604,14606,14608],{"class":202,"line":14},[200,14605,657],{"class":214},[200,14607,14574],{"class":235},[200,14609,648],{"class":214},[200,14611,14612,14614,14617,14620,14622,14624,14627,14629,14632,14634,14636,14639,14641,14644,14646,14648,14651,14653],{"class":202,"line":255},[200,14613,804],{"class":214},[200,14615,14616],{"class":235},"link",[200,14618,14619],{"class":379}," rel",[200,14621,449],{"class":214},[200,14623,611],{"class":214},[200,14625,14626],{"class":245},"preconnect",[200,14628,611],{"class":214},[200,14630,14631],{"class":379}," href",[200,14633,449],{"class":214},[200,14635,611],{"class":214},[200,14637,14638],{"class":245},"https:\u002F\u002Ffonts.gstatic.com\u002F",[200,14640,611],{"class":214},[200,14642,14643],{"class":379}," crossorigin",[200,14645,449],{"class":214},[200,14647,611],{"class":214},[200,14649,14650],{"class":245},"anonymous",[200,14652,611],{"class":214},[200,14654,648],{"class":214},[200,14656,14657,14659,14661,14663,14665,14667,14670,14672,14675,14677,14679,14681,14683,14686,14688,14691,14694,14697,14700,14703,14705,14707,14709,14712,14714,14717,14719,14722,14724,14727,14729],{"class":202,"line":272},[200,14658,804],{"class":214},[200,14660,14616],{"class":235},[200,14662,14619],{"class":379},[200,14664,449],{"class":214},[200,14666,611],{"class":214},[200,14668,14669],{"class":245},"preload",[200,14671,611],{"class":214},[200,14673,14674],{"class":379}," as",[200,14676,449],{"class":214},[200,14678,611],{"class":214},[200,14680,1171],{"class":245},[200,14682,611],{"class":214},[200,14684,14685],{"class":379}," onload",[200,14687,449],{"class":214},[200,14689,14690],{"class":214},"\"this.",[200,14692,14693],{"class":210},"rel",[200,14695,14696],{"class":214},"='",[200,14698,14699],{"class":245},"stylesheet",[200,14701,14702],{"class":214},"'\"",[200,14704,14631],{"class":379},[200,14706,449],{"class":214},[200,14708,611],{"class":214},[200,14710,14711],{"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,14713,10116],{"class":210},[200,14715,14716],{"class":245},"family=DM Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500",[200,14718,10116],{"class":210},[200,14720,14721],{"class":245},"family=Sofia Sans:ital,wght@0,1..1000;1,1..1000",[200,14723,10116],{"class":210},[200,14725,14726],{"class":245},"display=swap",[200,14728,611],{"class":214},[200,14730,648],{"class":214},[200,14732,14733,14735,14737,14740,14742,14744,14747,14749],{"class":202,"line":283},[200,14734,804],{"class":214},[200,14736,2015],{"class":235},[200,14738,14739],{"class":379}," charset",[200,14741,449],{"class":214},[200,14743,611],{"class":214},[200,14745,14746],{"class":245},"utf-8",[200,14748,611],{"class":214},[200,14750,648],{"class":214},[200,14752,14753,14755,14757,14759,14761,14763,14766,14768,14770,14772,14774,14776,14778],{"class":202,"line":300},[200,14754,804],{"class":214},[200,14756,2015],{"class":235},[200,14758,701],{"class":379},[200,14760,449],{"class":214},[200,14762,611],{"class":214},[200,14764,14765],{"class":245},"author",[200,14767,611],{"class":214},[200,14769,6041],{"class":379},[200,14771,449],{"class":214},[200,14773,611],{"class":214},[200,14775,7033],{"class":245},[200,14777,611],{"class":214},[200,14779,648],{"class":214},[200,14781,14782,14784,14786,14788,14790,14792,14795,14797,14799,14801,14803,14806,14808],{"class":202,"line":310},[200,14783,804],{"class":214},[200,14785,2015],{"class":235},[200,14787,701],{"class":379},[200,14789,449],{"class":214},[200,14791,611],{"class":214},[200,14793,14794],{"class":245},"viewport",[200,14796,611],{"class":214},[200,14798,6041],{"class":379},[200,14800,449],{"class":214},[200,14802,611],{"class":214},[200,14804,14805],{"class":245},"width=device-width, initial-scale=1",[200,14807,611],{"class":214},[200,14809,648],{"class":214},[200,14811,14812,14814,14816,14818,14821,14823,14825],{"class":202,"line":336},[200,14813,804],{"class":214},[200,14815,3566],{"class":235},[200,14817,675],{"class":214},[200,14819,14820],{"class":210},"Estéban's Infra · Estéban Soubiran",[200,14822,681],{"class":214},[200,14824,3566],{"class":235},[200,14826,648],{"class":214},[200,14828,14829,14831,14833,14835,14837,14839,14842,14844,14846,14848,14850,14853,14855],{"class":202,"line":342},[200,14830,804],{"class":214},[200,14832,2015],{"class":235},[200,14834,701],{"class":379},[200,14836,449],{"class":214},[200,14838,611],{"class":214},[200,14840,14841],{"class":245},"msapplication-TileColor",[200,14843,611],{"class":214},[200,14845,6041],{"class":379},[200,14847,449],{"class":214},[200,14849,611],{"class":214},[200,14851,14852],{"class":245},"#ffffff",[200,14854,611],{"class":214},[200,14856,648],{"class":214},[200,14858,14859,14861,14863,14865,14867,14869,14872,14874,14876,14878,14880,14882,14884],{"class":202,"line":365},[200,14860,804],{"class":214},[200,14862,2015],{"class":235},[200,14864,701],{"class":379},[200,14866,449],{"class":214},[200,14868,611],{"class":214},[200,14870,14871],{"class":245},"theme-color",[200,14873,611],{"class":214},[200,14875,6041],{"class":379},[200,14877,449],{"class":214},[200,14879,611],{"class":214},[200,14881,14852],{"class":245},[200,14883,611],{"class":214},[200,14885,648],{"class":214},[200,14887,14888,14890,14893],{"class":202,"line":371},[200,14889,804],{"class":214},[200,14891,14892],{"class":235},"script",[200,14894,648],{"class":214},[200,14896,14897,14900,14902,14905,14907],{"class":202,"line":398},[200,14898,14899],{"class":214},"      ;",[200,14901,227],{"class":210},[200,14903,14904],{"class":379},"function",[200,14906,3714],{"class":214},[200,14908,280],{"class":214},[200,14910,14911,14913,14916,14918,14921,14923,14926,14929,14931,14933,14935,14937,14939,14942,14944,14946,14948],{"class":202,"line":405},[200,14912,12989],{"class":379},[200,14914,14915],{"class":210}," prefersDark",[200,14917,510],{"class":214},[200,14919,14920],{"class":210}," window",[200,14922,215],{"class":214},[200,14924,14925],{"class":210},"matchMedia",[200,14927,14928],{"class":214}," &&",[200,14930,14920],{"class":210},[200,14932,215],{"class":214},[200,14934,14925],{"class":223},[200,14936,227],{"class":235},[200,14938,249],{"class":214},[200,14940,14941],{"class":245},"(prefers-color-scheme: dark)",[200,14943,249],{"class":214},[200,14945,2089],{"class":235},[200,14947,215],{"class":214},[200,14949,14950],{"class":210},"matches\n",[200,14952,14953,14955,14958,14960,14963,14965,14968,14970,14972,14975,14977,14979,14982,14984,14987],{"class":202,"line":410},[200,14954,12989],{"class":379},[200,14956,14957],{"class":210}," setting",[200,14959,510],{"class":214},[200,14961,14962],{"class":210}," localStorage",[200,14964,215],{"class":214},[200,14966,14967],{"class":223},"getItem",[200,14969,227],{"class":235},[200,14971,249],{"class":214},[200,14973,14974],{"class":245},"vueuse-color-scheme",[200,14976,249],{"class":214},[200,14978,5267],{"class":235},[200,14980,14981],{"class":214},"||",[200,14983,242],{"class":214},[200,14985,14986],{"class":245},"auto",[200,14988,992],{"class":214},[200,14990,14991,14994,14996,14999,15001,15003,15005,15007,15010,15012,15015,15017,15019,15021,15023,15026,15028],{"class":202,"line":861},[200,14992,14993],{"class":206},"        if",[200,14995,1095],{"class":235},[200,14997,14998],{"class":210},"setting",[200,15000,3283],{"class":214},[200,15002,242],{"class":214},[200,15004,14564],{"class":245},[200,15006,249],{"class":214},[200,15008,15009],{"class":214}," ||",[200,15011,1095],{"class":235},[200,15013,15014],{"class":210},"prefersDark",[200,15016,14928],{"class":214},[200,15018,14957],{"class":210},[200,15020,13388],{"class":214},[200,15022,242],{"class":214},[200,15024,15025],{"class":245},"light",[200,15027,249],{"class":214},[200,15029,585],{"class":235},[200,15031,15032,15035,15037,15040,15042,15045,15047,15050,15052,15054,15056,15058,15060,15062],{"class":202,"line":890},[200,15033,15034],{"class":210},"          document",[200,15036,215],{"class":214},[200,15038,15039],{"class":210},"documentElement",[200,15041,215],{"class":214},[200,15043,15044],{"class":210},"classList",[200,15046,215],{"class":214},[200,15048,15049],{"class":223},"toggle",[200,15051,227],{"class":235},[200,15053,249],{"class":214},[200,15055,14564],{"class":245},[200,15057,249],{"class":214},[200,15059,550],{"class":214},[200,15061,2424],{"class":2423},[200,15063,416],{"class":235},[200,15065,15066,15068],{"class":202,"line":900},[200,15067,2632],{"class":214},[200,15069,11743],{"class":210},[200,15071,15072,15075,15077],{"class":202,"line":905},[200,15073,15074],{"class":214},"    \u003C\u002F",[200,15076,14892],{"class":235},[200,15078,648],{"class":214},[200,15080,15081,15083,15085,15087,15089,15091,15094,15096,15098,15100,15103,15105,15107,15109,15112,15114,15116,15118],{"class":202,"line":931},[200,15082,804],{"class":214},[200,15084,14892],{"class":235},[200,15086,321],{"class":379},[200,15088,449],{"class":214},[200,15090,611],{"class":214},[200,15092,15093],{"class":245},"module",[200,15095,611],{"class":214},[200,15097,14643],{"class":379},[200,15099,449],{"class":214},[200,15101,15102],{"class":214},"\"\"",[200,15104,10106],{"class":379},[200,15106,449],{"class":214},[200,15108,611],{"class":214},[200,15110,15111],{"class":245},"\u002Fassets\u002Fapp-IQH-kuhC.js",[200,15113,611],{"class":214},[200,15115,10192],{"class":214},[200,15117,14892],{"class":235},[200,15119,648],{"class":214},[200,15121,15122,15124,15126,15128,15130,15132,15134,15136,15138,15140,15142,15144,15146,15148,15151,15153],{"class":202,"line":3640},[200,15123,804],{"class":214},[200,15125,14616],{"class":235},[200,15127,14619],{"class":379},[200,15129,449],{"class":214},[200,15131,611],{"class":214},[200,15133,14699],{"class":245},[200,15135,611],{"class":214},[200,15137,14643],{"class":379},[200,15139,449],{"class":214},[200,15141,15102],{"class":214},[200,15143,14631],{"class":379},[200,15145,449],{"class":214},[200,15147,611],{"class":214},[200,15149,15150],{"class":245},"\u002Fassets\u002Fapp-B27WEjTb.css",[200,15152,611],{"class":214},[200,15154,648],{"class":214},[200,15156,15157,15159,15161,15163,15165,15167,15170,15172,15174,15176,15178,15180,15182,15184,15187,15189],{"class":202,"line":3645},[200,15158,804],{"class":214},[200,15160,14616],{"class":235},[200,15162,14619],{"class":379},[200,15164,449],{"class":214},[200,15166,611],{"class":214},[200,15168,15169],{"class":245},"modulepreload",[200,15171,611],{"class":214},[200,15173,14643],{"class":379},[200,15175,449],{"class":214},[200,15177,15102],{"class":214},[200,15179,14631],{"class":379},[200,15181,449],{"class":214},[200,15183,611],{"class":214},[200,15185,15186],{"class":245},"\u002Fassets\u002Fpages-DeF1Ma75.js",[200,15188,611],{"class":214},[200,15190,648],{"class":214},[200,15192,15193,15195,15197,15199,15201,15203,15205,15207,15209,15211,15213,15215,15217,15219,15222,15224],{"class":202,"line":4020},[200,15194,804],{"class":214},[200,15196,14616],{"class":235},[200,15198,14619],{"class":379},[200,15200,449],{"class":214},[200,15202,611],{"class":214},[200,15204,15169],{"class":245},[200,15206,611],{"class":214},[200,15208,14643],{"class":379},[200,15210,449],{"class":214},[200,15212,15102],{"class":214},[200,15214,14631],{"class":379},[200,15216,449],{"class":214},[200,15218,611],{"class":214},[200,15220,15221],{"class":245},"\u002Fassets\u002FWrapperContent-BVl8okbX.js",[200,15223,611],{"class":214},[200,15225,648],{"class":214},[200,15227,15228,15230,15232,15235,15237,15239,15242,15244,15246,15248,15250,15253,15255],{"class":202,"line":4475},[200,15229,804],{"class":214},[200,15231,2015],{"class":235},[200,15233,15234],{"class":379}," property",[200,15236,449],{"class":214},[200,15238,611],{"class":214},[200,15240,15241],{"class":245},"og:title",[200,15243,611],{"class":214},[200,15245,6041],{"class":379},[200,15247,449],{"class":214},[200,15249,611],{"class":214},[200,15251,15252],{"class":245},"Estéban's Infra",[200,15254,611],{"class":214},[200,15256,648],{"class":214},[200,15258,15259,15261,15263,15265,15267,15269,15272,15274,15276,15278,15280,15282,15284],{"class":202,"line":4481},[200,15260,804],{"class":214},[200,15262,2015],{"class":235},[200,15264,701],{"class":379},[200,15266,449],{"class":214},[200,15268,611],{"class":214},[200,15270,15271],{"class":245},"twitter:title",[200,15273,611],{"class":214},[200,15275,6041],{"class":379},[200,15277,449],{"class":214},[200,15279,611],{"class":214},[200,15281,15252],{"class":245},[200,15283,611],{"class":214},[200,15285,648],{"class":214},[200,15287,15288,15290,15292],{"class":202,"line":4487},[200,15289,893],{"class":214},[200,15291,14574],{"class":235},[200,15293,648],{"class":214},[200,15295,15296,15298,15300],{"class":202,"line":4493},[200,15297,681],{"class":214},[200,15299,593],{"class":235},[200,15301,648],{"class":214},[35,15303,15304],{},"And I tried to understand what was happening.",[35,15306,15307,15308,15313],{},"To know if the head is correctly sorted, you can use ",[39,15309,15312],{"href":15310,"rel":15311},"https:\u002F\u002Frviscomi.github.io\u002Fcapo.js\u002F",[77],"capo.js",". After a quick inspection, I had the following result:",[35,15315,15316],{},[1690,15317],{"alt":15318,"src":15319},"capo.js result showing head issues","\u002Fposts\u002Fwhant-to-contribute-to-open-source-youre-doing-it-wrong\u002Fcapo-js-result.webp",[35,15321,15322,15323,15328,15329,15334],{},"In the Vite SSG documentation, it says to use ",[39,15324,15327],{"href":15325,"rel":15326},"https:\u002F\u002Funhead.unjs.io",[77],"Unhead",", so I read all the documentation about it. I discovered that Unhead automatically ",[39,15330,15333],{"href":15331,"rel":15332},"https:\u002F\u002Funhead.unjs.io\u002Fdocs\u002Fvue\u002Fhead\u002Fguides\u002Fcore-concepts\u002Fpositions#sort-order",[77],"sorts the head"," elements, but if it's already used, why isn't the head correctly sorted?",[51,15336,15338],{"id":15337},"everything-didnt-go-as-planned","Everything didn't go as planned",[35,15340,15341],{},"If Unhead is supposed to sort the head elements, could the problem be in Vite SSG?",[35,15343,15344],{},"Only one way to find out: debugging.",[35,15346,15347],{},"After reading the whole source code of Vite SSG, I found an interesting part:",[192,15349,15352],{"className":9741,"code":15350,"filename":15351,"language":1373,"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,15353,15354,15359,15376,15380,15385,15432,15436,15441,15450,15485,15489,15507,15542,15549,15569,15573],{"__ignoreMap":13},[200,15355,15356],{"class":202,"line":203},[200,15357,15358],{"class":401},"\u002F\u002F create jsdom from renderedHTML\n",[200,15360,15361,15363,15366,15368,15370,15373],{"class":202,"line":14},[200,15362,443],{"class":379},[200,15364,15365],{"class":210}," jsdom ",[200,15367,449],{"class":214},[200,15369,12997],{"class":214},[200,15371,15372],{"class":223}," JSDOM",[200,15374,15375],{"class":210},"(renderedHTML)\n",[200,15377,15378],{"class":202,"line":255},[200,15379,529],{"emptyLinePlaceholder":20},[200,15381,15382],{"class":202,"line":272},[200,15383,15384],{"class":401},"\u002F\u002F render current page's preloadLinks\n",[200,15386,15387,15390,15393,15395,15398,15400,15402,15404,15407,15409,15412,15414,15416,15419,15421,15423,15425,15427,15429],{"class":202,"line":283},[200,15388,15389],{"class":223},"renderPreloadLinks",[200,15391,15392],{"class":210},"(jsdom",[200,15394,215],{"class":214},[200,15396,15397],{"class":210},"window",[200,15399,215],{"class":214},[200,15401,422],{"class":210},[200,15403,550],{"class":214},[200,15405,15406],{"class":210}," ctx",[200,15408,215],{"class":214},[200,15410,15411],{"class":210},"modules ",[200,15413,14981],{"class":214},[200,15415,12997],{"class":214},[200,15417,15418],{"class":223}," Set",[200,15420,600],{"class":214},[200,15422,328],{"class":1978},[200,15424,675],{"class":214},[200,15426,1532],{"class":210},[200,15428,550],{"class":214},[200,15430,15431],{"class":210}," ssrManifest)\n",[200,15433,15434],{"class":202,"line":300},[200,15435,529],{"emptyLinePlaceholder":20},[200,15437,15438],{"class":202,"line":310},[200,15439,15440],{"class":401},"\u002F\u002F render head\n",[200,15442,15444,15447],{"class":15443,"line":336},[202,9871],[200,15445,15446],{"class":206},"if",[200,15448,15449],{"class":210}," (head) ",[200,15451,15453,15455,15458,15461,15463,15465,15467,15469,15472,15474,15476,15478,15481,15483],{"class":15452,"line":342},[202,9871],[200,15454,6165],{"class":206},[200,15456,15457],{"class":223}," renderDOMHead",[200,15459,15460],{"class":210},"(head",[200,15462,550],{"class":214},[200,15464,318],{"class":214},[200,15466,211],{"class":235},[200,15468,239],{"class":214},[200,15470,15471],{"class":210}," jsdom",[200,15473,215],{"class":214},[200,15475,15397],{"class":210},[200,15477,215],{"class":214},[200,15479,15480],{"class":210},"document ",[200,15482,413],{"class":214},[200,15484,5267],{"class":210},[200,15486,15487],{"class":202,"line":365},[200,15488,529],{"emptyLinePlaceholder":20},[200,15490,15491,15493,15496,15498,15500,15502,15505],{"class":202,"line":371},[200,15492,443],{"class":379},[200,15494,15495],{"class":210}," html ",[200,15497,449],{"class":214},[200,15499,15471],{"class":210},[200,15501,215],{"class":214},[200,15503,15504],{"class":223},"serialize",[200,15506,466],{"class":210},[200,15508,15509,15511,15514,15516,15518,15520,15523,15525,15528,15530,15532,15534,15537,15539],{"class":202,"line":398},[200,15510,12327],{"class":379},[200,15512,15513],{"class":210}," transformed ",[200,15515,449],{"class":214},[200,15517,1095],{"class":210},[200,15519,207],{"class":206},[200,15521,15522],{"class":223}," onPageRendered",[200,15524,6524],{"class":214},[200,15526,15527],{"class":210},"(route",[200,15529,550],{"class":214},[200,15531,10276],{"class":210},[200,15533,550],{"class":214},[200,15535,15536],{"class":210}," appCtx)) ",[200,15538,14981],{"class":214},[200,15540,15541],{"class":210}," html\n",[200,15543,15544,15546],{"class":202,"line":405},[200,15545,15446],{"class":206},[200,15547,15548],{"class":210}," (beasties)\n",[200,15550,15551,15554,15556,15558,15561,15563,15566],{"class":202,"line":410},[200,15552,15553],{"class":210},"  transformed ",[200,15555,449],{"class":214},[200,15557,452],{"class":206},[200,15559,15560],{"class":210}," beasties",[200,15562,215],{"class":214},[200,15564,15565],{"class":223},"process",[200,15567,15568],{"class":210},"(transformed)\n",[200,15570,15571],{"class":202,"line":861},[200,15572,529],{"emptyLinePlaceholder":20},[200,15574,15575,15577,15580,15582,15584,15587,15590,15592],{"class":202,"line":890},[200,15576,443],{"class":379},[200,15578,15579],{"class":210}," formatted ",[200,15581,449],{"class":214},[200,15583,452],{"class":206},[200,15585,15586],{"class":223}," formatHtml",[200,15588,15589],{"class":210},"(transformed",[200,15591,550],{"class":214},[200,15593,15594],{"class":210}," formatting)\n",[35,15596,15597,15598,15601],{},"This part is responsible for generating the HTML before being saved. The interesting line is ",[156,15599,15600],{},"await renderDOMHead(head, { document: jsdom.window.document })",". This is the only part where the head is used and rendered inside the HTML.",[35,15603,15604,15605,15608],{},"More interestingly, the function comes from ",[156,15606,15607],{},"@unhead\u002Fdom",". Strange, because we're using SSR, so having a front-end function on the server looks suspicious.",[35,15610,15611],{},"And I was right. This function doesn't optimize the head. It's supposed to change the head elements once the page is loaded, on the client. Bringing the whole sorting logic to the client side is completely pointless.",[35,15613,15614],{},"While reading the Unhead documentation, I discovered the recommended way to use it with SSR.",[192,15616,15619],{"className":9741,"code":15617,"filename":15618,"language":1373,"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,15620,15621,15642,15646,15662,15666,15682,15693,15727],{"__ignoreMap":13},[200,15622,15624,15626,15628,15631,15633,15635,15637,15640],{"class":15623,"line":203},[202,9871],[200,15625,973],{"class":206},[200,15627,318],{"class":214},[200,15629,15630],{"class":210}," transformHtmlTemplate",[200,15632,981],{"class":214},[200,15634,984],{"class":206},[200,15636,242],{"class":214},[200,15638,15639],{"class":245},"@unhead\u002Fvue\u002Fserver",[200,15641,249],{"class":214},[200,15643,15644],{"class":202,"line":14},[200,15645,529],{"emptyLinePlaceholder":20},[200,15647,15648,15650,15653,15655,15657,15659],{"class":202,"line":255},[200,15649,443],{"class":379},[200,15651,15652],{"class":210}," rendered ",[200,15654,449],{"class":214},[200,15656,452],{"class":206},[200,15658,10265],{"class":223},[200,15660,15661],{"class":210},"(url)\n",[200,15663,15664],{"class":202,"line":272},[200,15665,529],{"emptyLinePlaceholder":20},[200,15667,15669,15671,15673,15675,15677,15679],{"class":15668,"line":283},[202,9871],[200,15670,443],{"class":379},[200,15672,15495],{"class":210},[200,15674,449],{"class":214},[200,15676,452],{"class":206},[200,15678,15630],{"class":223},[200,15680,15681],{"class":210},"( ",[200,15683,15684,15687,15689,15691],{"class":202,"line":300},[200,15685,15686],{"class":210},"  rendered",[200,15688,215],{"class":214},[200,15690,14574],{"class":210},[200,15692,252],{"class":214},[200,15694,15695,15698,15700,15702,15704,15706,15709,15711,15713,15716,15718,15721,15723,15725],{"class":202,"line":310},[200,15696,15697],{"class":210},"  template",[200,15699,215],{"class":214},[200,15701,6136],{"class":223},[200,15703,227],{"class":210},[200,15705,3318],{"class":214},[200,15707,15708],{"class":245},"\u003C!--app-html-->",[200,15710,3318],{"class":214},[200,15712,550],{"class":214},[200,15714,15715],{"class":210}," rendered",[200,15717,215],{"class":214},[200,15719,15720],{"class":210},"html ",[200,15722,3821],{"class":214},[200,15724,3894],{"class":214},[200,15726,416],{"class":210},[200,15728,15729],{"class":202,"line":336},[200,15730,416],{"class":210},[35,15732,15733,15734,15737],{},"The function ",[156,15735,15736],{},"transformHtmlTemplate"," extracts the current head elements from the template and injects them into the head before re-rendering the HTML and injecting it into the page. This ensures that the head is correctly sorted before sending it to the client.",[35,15739,15740],{},"It looked like a good solution to fix the head sorting issue. This led me to create a PR to Vite SSG to improve the integration with Unhead.",[35,15742,15743],{},[39,15744,15747],{"href":15745,"rel":15746},"https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-ssg\u002Fpull\u002F464",[77],"refactor: uses unhead\u002Fserver to optimize head",[35,15749,15750],{},"In order to quickly use this fix in my project, I patched Vite SSG locally. This allowed me to continue developing my project without waiting for my PR to be merged and then for a release.",[35,15752,15753],{},"But not everything went as planned.",[35,15755,15756,15757],{},"The head was, nearly, well-ordered, but after that fix, the FOUC was still there. ",[1217,15758,15759],{},"Sad.",[35,15761,15762],{},"It means that the problem isn't related to the head sorting.",[51,15764,15766],{"id":15765},"deeper-investigation","Deeper investigation",[35,15768,15769],{},"The Vite SSG patch isn't useless, as it improves the head sorting. However, it didn't solve the FOUC issue. I had to dig deeper.",[35,15771,15772],{},"I continued to look for the root cause. I tried to analyze the application performance to identify any bottlenecks, race conditions, or other issues that could be causing the FOUC. I didn't find anything.",[35,15774,15775],{},"I inspected the CSS and found that the styles from the prose were correctly applied, but not the ones from Nuxt UI. If you look closely at the video, you'll see the title color change from black to white, but the text underneath is already white. The CSS variables for Nuxt UI aren't available on the first render but are applied when the JavaScript loads.",[35,15777,15778,15779,15782],{},"To confirm this suspicion, I checked the Nuxt UI source code where I found a Vue plugin named ",[156,15780,15781],{},"colors.ts",". The same that caused the build issue at the beginning.",[35,15784,15785],{},"Everything was leading to this plugin, so I took the time to understand it, and I found the root cause of both the FOUC and the build issue:",[192,15787,15790],{"className":1370,"code":15788,"filename":15789,"language":1373,"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,15791,15792,15808,15819,15840,15854,15868,15874,15878,15882,15930,15955,15959,15977,16001,16021,16025,16040,16063,16069,16073,16077],{"__ignoreMap":13},[200,15793,15794,15796,15799,15801,15804,15806],{"class":202,"line":203},[200,15795,443],{"class":379},[200,15797,15798],{"class":210}," headData",[200,15800,239],{"class":214},[200,15802,15803],{"class":1978}," UseHeadInput",[200,15805,510],{"class":214},[200,15807,280],{"class":214},[200,15809,15810,15813,15815,15817],{"class":202,"line":14},[200,15811,15812],{"class":235},"  style",[200,15814,239],{"class":214},[200,15816,350],{"class":210},[200,15818,230],{"class":214},[200,15820,15821,15824,15826,15828,15830,15833,15835,15838],{"class":202,"line":255},[200,15822,15823],{"class":223},"    innerHTML",[200,15825,239],{"class":214},[200,15827,3714],{"class":214},[200,15829,393],{"class":379},[200,15831,15832],{"class":210}," root",[200,15834,215],{"class":214},[200,15836,15837],{"class":210},"value",[200,15839,252],{"class":214},[200,15841,15842,15845,15847,15850,15852],{"class":202,"line":272},[200,15843,15844],{"class":235},"    tagPriority",[200,15846,239],{"class":214},[200,15848,15849],{"class":214}," -",[200,15851,4782],{"class":2045},[200,15853,252],{"class":214},[200,15855,15856,15859,15861,15863,15866],{"class":202,"line":283},[200,15857,15858],{"class":235},"    id",[200,15860,239],{"class":214},[200,15862,242],{"class":214},[200,15864,15865],{"class":245},"nuxt-ui-colors",[200,15867,992],{"class":214},[200,15869,15870,15872],{"class":202,"line":300},[200,15871,5080],{"class":214},[200,15873,4496],{"class":210},[200,15875,15876],{"class":202,"line":310},[200,15877,1130],{"class":214},[200,15879,15880],{"class":202,"line":336},[200,15881,529],{"emptyLinePlaceholder":20},[200,15883,15884,15886,15888,15890,15892,15894,15896,15899,15902,15905,15907,15910,15912,15915,15918,15920,15923,15925,15928],{"class":202,"line":342},[200,15885,15446],{"class":206},[200,15887,1095],{"class":210},[200,15889,973],{"class":206},[200,15891,215],{"class":214},[200,15893,2015],{"class":210},[200,15895,215],{"class":214},[200,15897,15898],{"class":210},"client ",[200,15900,15901],{"class":214},"&&",[200,15903,15904],{"class":210}," nuxtApp",[200,15906,215],{"class":214},[200,15908,15909],{"class":210},"isHydrating ",[200,15911,15901],{"class":214},[200,15913,15914],{"class":214}," !",[200,15916,15917],{"class":210},"nuxtApp",[200,15919,215],{"class":214},[200,15921,15922],{"class":210},"payload",[200,15924,215],{"class":214},[200,15926,15927],{"class":210},"serverRendered) ",[200,15929,230],{"class":214},[200,15931,15932,15934,15936,15938,15940,15942,15945,15947,15949,15951,15953],{"class":202,"line":365},[200,15933,3723],{"class":379},[200,15935,10180],{"class":210},[200,15937,510],{"class":214},[200,15939,211],{"class":210},[200,15941,215],{"class":214},[200,15943,15944],{"class":223},"createElement",[200,15946,227],{"class":235},[200,15948,249],{"class":214},[200,15950,1171],{"class":245},[200,15952,249],{"class":214},[200,15954,416],{"class":235},[200,15956,15957],{"class":202,"line":371},[200,15958,529],{"emptyLinePlaceholder":20},[200,15960,15961,15963,15965,15968,15970,15972,15974],{"class":202,"line":398},[200,15962,15812],{"class":210},[200,15964,215],{"class":214},[200,15966,15967],{"class":210},"innerHTML",[200,15969,510],{"class":214},[200,15971,15832],{"class":210},[200,15973,215],{"class":214},[200,15975,15976],{"class":210},"value\n",[200,15978,15979,15981,15983,15986,15988,15990,15993,15995,15997,15999],{"class":202,"line":405},[200,15980,15812],{"class":210},[200,15982,215],{"class":214},[200,15984,15985],{"class":223},"setAttribute",[200,15987,227],{"class":235},[200,15989,249],{"class":214},[200,15991,15992],{"class":245},"data-nuxt-ui-colors",[200,15994,249],{"class":214},[200,15996,550],{"class":214},[200,15998,3894],{"class":214},[200,16000,416],{"class":235},[200,16002,16003,16006,16008,16010,16012,16015,16017,16019],{"class":202,"line":410},[200,16004,16005],{"class":210},"  document",[200,16007,215],{"class":214},[200,16009,14574],{"class":210},[200,16011,215],{"class":214},[200,16013,16014],{"class":223},"appendChild",[200,16016,227],{"class":235},[200,16018,1171],{"class":210},[200,16020,416],{"class":235},[200,16022,16023],{"class":202,"line":861},[200,16024,529],{"emptyLinePlaceholder":20},[200,16026,16027,16030,16032,16034,16036,16038],{"class":202,"line":890},[200,16028,16029],{"class":210},"  headData",[200,16031,215],{"class":214},[200,16033,14892],{"class":210},[200,16035,510],{"class":214},[200,16037,350],{"class":235},[200,16039,230],{"class":214},[200,16041,16042,16044,16046,16048,16051,16054,16057,16059,16061],{"class":202,"line":900},[200,16043,15823],{"class":235},[200,16045,239],{"class":214},[200,16047,242],{"class":214},[200,16049,16050],{"class":245},"document.head.removeChild(document.querySelector(",[200,16052,16053],{"class":210},"\\'",[200,16055,16056],{"class":245},"[data-nuxt-ui-colors]",[200,16058,16053],{"class":210},[200,16060,6119],{"class":245},[200,16062,992],{"class":214},[200,16064,16065,16067],{"class":202,"line":905},[200,16066,5080],{"class":214},[200,16068,4496],{"class":235},[200,16070,16071],{"class":202,"line":931},[200,16072,1130],{"class":214},[200,16074,16075],{"class":202,"line":3640},[200,16076,529],{"emptyLinePlaceholder":20},[200,16078,16079,16082],{"class":202,"line":3645},[200,16080,16081],{"class":223},"useHead",[200,16083,16084],{"class":210},"(headData)\n",[35,16086,16087],{},"There are multiple issues at play here.",[35,16089,16090],{},"The first seven lines, along with the last, are used to inject the Nuxt UI CSS variables into the head, at both build time and runtime. By disabling the plugin, I removed this behavior, so the FOUC inevitably occurs.",[35,16092,419,16093,16095],{},[156,16094,15446],{}," block creates a style element and must only be executed on the client side, during hydration, if the content is not server-rendered. My app is using Vite SSG to be server-rendered, so this should not happen.",[35,16097,16098,16099,16101],{},"So, what is this ",[156,16100,15917],{},"? Let's continue to investigate.",[35,16103,16104,16105,16109,16110,16113],{},"At first, Nuxt UI is made for Nuxt, but thanks to the work of ",[39,16106,16108],{"href":1877,"rel":16107},[77],"Daniel Roe"," and a lot of stubs, Nuxt UI is also usable outside of Nuxt. One of the stubs is the ",[156,16111,16112],{},"useNuxtApp"," composable:",[192,16115,16118],{"className":1370,"code":16116,"filename":16117,"language":1373,"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,16119,16120,16133,16139,16150,16169,16176,16191,16195],{"__ignoreMap":13},[200,16121,16122,16124,16126,16129,16131],{"class":202,"line":203},[200,16123,1380],{"class":206},[200,16125,4987],{"class":379},[200,16127,16128],{"class":223}," useNuxtApp",[200,16130,1532],{"class":214},[200,16132,280],{"class":214},[200,16134,16135,16137],{"class":202,"line":14},[200,16136,3743],{"class":206},[200,16138,280],{"class":214},[200,16140,16141,16144,16146,16148],{"class":202,"line":255},[200,16142,16143],{"class":235},"    isHydrating",[200,16145,239],{"class":214},[200,16147,2424],{"class":2423},[200,16149,252],{"class":214},[200,16151,16152,16155,16157,16159,16162,16164,16167],{"class":202,"line":272},[200,16153,16154],{"class":235},"    payload",[200,16156,239],{"class":214},[200,16158,318],{"class":214},[200,16160,16161],{"class":235}," serverRendered",[200,16163,239],{"class":214},[200,16165,16166],{"class":2423}," false",[200,16168,333],{"class":214},[200,16170,16171,16174],{"class":202,"line":283},[200,16172,16173],{"class":210},"    hooks",[200,16175,252],{"class":214},[200,16177,16178,16181,16183,16186,16188],{"class":202,"line":300},[200,16179,16180],{"class":235},"    hook",[200,16182,239],{"class":214},[200,16184,16185],{"class":210}," hooks",[200,16187,215],{"class":214},[200,16189,16190],{"class":210},"hook\n",[200,16192,16193],{"class":202,"line":310},[200,16194,4490],{"class":214},[200,16196,16197],{"class":202,"line":336},[200,16198,1130],{"class":214},[35,16200,16201,16202,16205],{},"Here it is. The ",[156,16203,16204],{},"serverRendered"," property is set to false, always. So even when Vite SSG is used, the plugin thinks it's in a browser and tries to create the style element. Obviously, this doesn't work.",[35,16207,16208,16209,16212],{},"The patch is really simple. Instead of always returning false, we need to return ",[156,16210,16211],{},"import.meta.env.SSR || false"," to properly detect server-side rendering.",[2895,16214,16216,16217,16219,16220,16222,16228,16232,16235,16238,16241,16244,16266,16275,16278],{"href":16215},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fpull\u002F5347","\n  fix(vue): check ",[156,16218,14377],{}," to support ",[156,16221,14272],{},[35,16223,16224,16225],{},"Then, I re-enabled the Nuxt UI plugin, the build passed, and the FOUC was gone! ",[1217,16226,16227],{},"Love it. A small boolean change made a big difference.",[51,16229,16231],{"id":16230},"making-a-better-head","Making a better head",[35,16233,16234],{},"The build was passing and the FOUC was gone. I continued to check the head of the generated file to ensure everything was in order.",[35,16236,16237],{},"It wasn't the case.",[35,16239,16240],{},"I could stop there, but I was on a roll of pull requests, plunged in Unhead, Nuxt UI and Vite SSG.",[35,16242,16243],{},"I discovered two things.",[68,16245,16246,16257],{},[71,16247,16248,16249,16252,16253,16256],{},"Nuxt UI sets a ",[156,16250,16251],{},"tagPriority"," of ",[156,16254,16255],{},"-2"," to the style element when injecting it. This means it will be one of the first elements in the head instead of being automatically placed.",[71,16258,16259,16260,1638,16263,16265],{},"Unhead was incorrectly handling the order of ",[156,16261,16262],{},"inlined",[156,16264,15093],{}," scripts.",[35,16267,16268,16269,16274],{},"For the first, I only ",[39,16270,16273],{"href":16271,"rel":16272},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fpull\u002F5347#issuecomment-3472594042",[77],"wrote a comment"," to understand why it was set. It could be a requirement for the Nuxt integration.",[35,16276,16277],{},"However, for the second point, knowing it was a bug, I opened two pull requests.",[2895,16279,16282],{"title":16280,"href":16281},"fix: inline scripts ordering","https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F596",[16283,16284,16287,16290],"pullrequest",{"title":16285,"href":16286},"fix: module scripts","https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F600",[35,16288,16289],{},"While doing these pull requests, I discovered that the CI wasn't working, some tests were failing, and the typecheck was broken, so I created 3 PRs to fix all of them.",[2895,16291,16294],{"title":16292,"href":16293},"test: update snapshots","https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F598",[16283,16295,16298],{"title":16296,"href":16297},"test: fix tsc","https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F597",[16283,16299,16302,16305,16308,16313,16317,16320,16326,16329,16332,16335,16337,16343,16346,16352],{"title":16300,"href":16301},"ci: fix","https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F599",[35,16303,16304],{},"I was just fixing a simple bug in the way Unhead handled script ordering and I finished fixing the tests and the integration.",[35,16306,16307],{},"When you care about the details, that's often how it ends up, and by making clean and working pull requests, you increase your chances of being accepted.",[35,16309,16310],{},[1217,16311,16312],{},"I didn't even have time to write a blog post about them before they merged.",[51,16314,16316],{"id":16315},"open-source-not-by-choice","Open-source not by choice",[35,16318,16319],{},"I just wanted to build my website to document my infrastructure. I ended up fixing bugs in three tools, and opening 7 pull requests.",[35,16321,16322,16323],{},"Honestly, I would love to never have to fix them, and I would love to never have to spend my time on fixing these bugs. But that's not the open-source implicit contract, nor its mindset. ",[1217,16324,16325],{},"It's how I see it. Not everyone does.",[35,16327,16328],{},"People offer you, for free, and often as is, their code for your own usage. It saves you time and money and allows you to build faster and better. Yes, I spend time fixing bugs, but how many times have I saved by using open-source software? How often have I been able to contribute to open-source software to build the experience I wanted within my projects? It's not even worth starting to count. Giving back, a little of your knowledge and time, seems to be a fair deal.",[35,16330,16331],{},"And that's normal that software has bugs, missing features or simply unplanned use cases. It's inherent to software and neither closed-source software nor open-source software is immune to this.",[35,16333,16334],{},"However, with open-source, you can change the situation by having this freedom to contribute to the software you're using.",[1159,16336],{},[35,16338,16339,16340,215],{},"Just build stuff, anything you want, but bring your dreams to life, on internet, don't stop to ",[156,16341,16342],{},"localhost",[35,16344,16345],{},"Along the journey, you'll encounter bugs, missing features, or simply things that could be improved. When you do, don't hesitate to dive into your stack, to understand it, and try to fix it.",[35,16347,16348,16351],{},[6850,16349,16350],{},"That's how you contribute to open-source",". Not the other way around. That's also one of the best ways to learn.",[35,16353,16354],{},"Cultivate the open-source mindset and opportunities will naturally arise.",[1171,16356,16357],{},"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":16359},[16360,16361,16362,16363],{"id":14211,"depth":14,"text":14212},{"id":15337,"depth":14,"text":15338},{"id":15765,"depth":14,"text":15766},{"id":16230,"depth":14,"text":16231},"2025-11-03","I'm often asked on how to contribute to open-source and I rarely find a satisfying answer. So I took some time to reflect on it. Here's my take.",{},"---\ncontentId: 93da817a-f293-412c-8696-befcb4f95a8f\ntitle: Want to Contribute to Open Source? You're Doing It Wrong\ndescription: I'm often asked on how to contribute to open-source and I rarely find a satisfying answer. So I took some time to reflect on it. Here's my take.\ndate: 2025-11-03\n---\n\nI have been actively contributing to open-source since 2020. Since then, I've spoken with many developers who want to contribute but don't know where to start, often asking for an issue to work on.\n\nI'm deeply convinced that open-source is more than that and, more importantly, doesn't work like that.\n\nTo me, open-source, at its core, is a mindset. It's about the freedom to explore, understand, modify, and improve the source code of the software you want. Of course, this model encourages collaboration and sharing by allowing anyone to contribute, but it doesn't require you to.\n\nOpen-source shouldn't, and should never, be a goal in itself. It's part of the journey, but it's not the destination. Otherwise, you'll end up feeling frustrated and unfulfilled by never being able to contribute.\n\nLet me tell you a story to illustrate this point.\n\n---\n\nI'm building a new website called [infra.soubiran.dev](https:\u002F\u002Finfra.soubiran.dev) to document my own infrastructure. It's a personal wiki but open to everyone. _I won't go into details; a dedicated post will come later._\n\nTo build it, I needed to find the right tools. Until now, I was using [VitePress with a custom theme for my personal blog](.\u002Fvitepress-for-a-personal-website-is-that-possible.md), the one you're reading right now. It works great, but I've nearly reached its limits, and with all the ideas I have, I know it could be time to move on.\n\nSo, I started looking into alternatives. Honestly, there are two options:\n\n1. Using [Nuxt](https:\u002F\u002Fnuxt.com) with [Nuxt Content](https:\u002F\u002Fcontent.nuxt.com)\n2. Using Vite with Vue and some [unplugins](https:\u002F\u002Funplugin.unjs.io\u002F) to handle markdown files.\n\nI went for the second option. Funny enough, before moving to VitePress, I had the same dilemma: Nuxt Content, VitePress, or Vite with Vue. At that time, I chose VitePress.\n\nAnyway, I started building the website.\n\nFor the past couple of months, there's one tech I can't live without: [Nuxt UI](https:\u002F\u002Fui.nuxt.com). It's an incredible component library made by the Nuxt team. All my projects that use Vue or Nuxt use Nuxt UI, and I regularly push it to its limits. _This is also a reason why I decided to move away from VitePress, which is not compatible with Nuxt UI._\n\n## Building with open-source software\n\nThe website I'm building is content-driven. This means that the content is primarily text-based and, in my case, all the pages are written in Markdown. To update the website, I edit the Markdown files and then rebuild the site.\n\nThe pages are generated and pre-rendered at build time. There is no need to generate them at runtime, as every user will receive the same pre-rendered content. To make it work, I use [Vite SSG](https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-ssg).\n\nIt's a drop-in replacement for Vite when building the website. It automatically builds the client and the server, then uses the server to pre-render each page, as if a user were requesting it.\n\nAfter installing it, everything worked as expected.\n\nSo I continued to build the website. I installed Nuxt UI and started configuring it. To use Nuxt UI within a Vue application, you have to use a 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\nAnd then, everything broke. During the pre-rendering, something tried to access `document` to create a style: `const style = document.createElement('style')`. Really strange.\n\nWhen pre-rendering, Vite SSG injects `import.meta.env.SSR` to detect if it's running in SSR mode. It also allows the bundler to tree-shake unused code effectively. So, to fix the bug, I wrapped the plugin usage in a conditional check for SSR mode.\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\nNow, pre-rendering worked as expected, or so I thought.\n\nI wrote some pages, created components, and pushed everything to production. It built successfully, so I opened the website and saw the following:\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>Loading of my new website\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nCan you see it?\n\nBefore going further, and if you noticed it, would you have fixed it? You know it will take time, and you won't be able to continue writing content. That's a real dilemma. Taking time for something you might consider a detail, or continuing to work on your content.\n\n**Stunning websites are made of details**. For me, details are very important, and it's essential to pay attention to them. They can transform the user experience, improve user comfort, and increase loyalty. Never underestimate them.\n\nFor those who don't see it, that's called a [FOUC](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FFlash_of_unstyled_content), a flash of unstyled content. The content appears before all the style is available. It usually happens when the script needs to add a `dark` class to the HTML element. The website loads with the default theme (light mode), appears, and then the JavaScript is parsed and executed. The script adds the `dark` class and the theme switches to dark mode. The light mode lasts a few hundred milliseconds, but it's enough to be noticed. This is all related to the way a browser renders a page.\n\nIt can be very tricky to fix. Usually, it happens when tags in your `head` aren't correctly ordered or when the script is not executed at the right time. So, I jumped into the head of the generated page:\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\nAnd I tried to understand what was happening.\n\nTo know if the head is correctly sorted, you can use [capo.js](https:\u002F\u002Frviscomi.github.io\u002Fcapo.js\u002F). After a quick inspection, I had the following result:\n\n![capo.js result showing head issues](\u002Fposts\u002Fwhant-to-contribute-to-open-source-youre-doing-it-wrong\u002Fcapo-js-result.webp)\n\nIn the Vite SSG documentation, it says to use [Unhead](https:\u002F\u002Funhead.unjs.io), so I read all the documentation about it. I discovered that Unhead automatically [sorts the head](https:\u002F\u002Funhead.unjs.io\u002Fdocs\u002Fvue\u002Fhead\u002Fguides\u002Fcore-concepts\u002Fpositions#sort-order) elements, but if it's already used, why isn't the head correctly sorted?\n\n## Everything didn't go as planned\n\nIf Unhead is supposed to sort the head elements, could the problem be in Vite SSG?\n\nOnly one way to find out: debugging.\n\nAfter reading the whole source code of Vite SSG, I found an interesting part:\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\nThis part is responsible for generating the HTML before being saved. The interesting line is `await renderDOMHead(head, { document: jsdom.window.document })`. This is the only part where the head is used and rendered inside the HTML.\n\nMore interestingly, the function comes from `@unhead\u002Fdom`. Strange, because we're using SSR, so having a front-end function on the server looks suspicious.\n\nAnd I was right. This function doesn't optimize the head. It's supposed to change the head elements once the page is loaded, on the client. Bringing the whole sorting logic to the client side is completely pointless.\n\nWhile reading the Unhead documentation, I discovered the recommended way to use it with 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\nThe function `transformHtmlTemplate` extracts the current head elements from the template and injects them into the head before re-rendering the HTML and injecting it into the page. This ensures that the head is correctly sorted before sending it to the client.\n\nIt looked like a good solution to fix the head sorting issue. This led me to create a PR to Vite SSG to improve the integration with Unhead.\n\n[refactor: uses unhead\u002Fserver to optimize head](https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-ssg\u002Fpull\u002F464)\n\nIn order to quickly use this fix in my project, I patched Vite SSG locally. This allowed me to continue developing my project without waiting for my PR to be merged and then for a release.\n\nBut not everything went as planned.\n\nThe head was, nearly, well-ordered, but after that fix, the FOUC was still there. _Sad._\n\nIt means that the problem isn't related to the head sorting.\n\n## Deeper investigation\n\nThe Vite SSG patch isn't useless, as it improves the head sorting. However, it didn't solve the FOUC issue. I had to dig deeper.\n\nI continued to look for the root cause. I tried to analyze the application performance to identify any bottlenecks, race conditions, or other issues that could be causing the FOUC. I didn't find anything.\n\nI inspected the CSS and found that the styles from the prose were correctly applied, but not the ones from Nuxt UI. If you look closely at the video, you'll see the title color change from black to white, but the text underneath is already white. The CSS variables for Nuxt UI aren't available on the first render but are applied when the JavaScript loads.\n\nTo confirm this suspicion, I checked the Nuxt UI source code where I found a Vue plugin named `colors.ts`. The same that caused the build issue at the beginning.\n\nEverything was leading to this plugin, so I took the time to understand it, and I found the root cause of both the FOUC and the build issue:\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\nThere are multiple issues at play here.\n\nThe first seven lines, along with the last, are used to inject the Nuxt UI CSS variables into the head, at both build time and runtime. By disabling the plugin, I removed this behavior, so the FOUC inevitably occurs.\n\nThe `if` block creates a style element and must only be executed on the client side, during hydration, if the content is not server-rendered. My app is using Vite SSG to be server-rendered, so this should not happen.\n\nSo, what is this `nuxtApp`? Let's continue to investigate.\n\nAt first, Nuxt UI is made for Nuxt, but thanks to the work of [Daniel Roe](https:\u002F\u002Fgithub.com\u002Fdanielroe) and a lot of stubs, Nuxt UI is also usable outside of Nuxt. One of the stubs is the `useNuxtApp` composable:\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\nHere it is. The `serverRendered` property is set to false, always. So even when Vite SSG is used, the plugin thinks it's in a browser and tries to create the style element. Obviously, this doesn't work.\n\nThe patch is really simple. Instead of always returning false, we need to return `import.meta.env.SSR || false` to properly detect server-side rendering.\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\nThen, I re-enabled the Nuxt UI plugin, the build passed, and the FOUC was gone! _Love it. A small boolean change made a big difference._\n\n## Making a better head\n\nThe build was passing and the FOUC was gone. I continued to check the head of the generated file to ensure everything was in order.\n\nIt wasn't the case.\n\nI could stop there, but I was on a roll of pull requests, plunged in Unhead, Nuxt UI and Vite SSG.\n\nI discovered two things.\n\n1. Nuxt UI sets a `tagPriority` of `-2` to the style element when injecting it. This means it will be one of the first elements in the head instead of being automatically placed.\n2. Unhead was incorrectly handling the order of `inlined` and `module` scripts.\n\nFor the first, I only [wrote a comment](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fpull\u002F5347#issuecomment-3472594042) to understand why it was set. It could be a requirement for the Nuxt integration.\n\nHowever, for the second point, knowing it was a bug, I opened two 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\nWhile doing these pull requests, I discovered that the CI wasn't working, some tests were failing, and the typecheck was broken, so I created 3 PRs to fix all of them.\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\nI was just fixing a simple bug in the way Unhead handled script ordering and I finished fixing the tests and the integration.\n\nWhen you care about the details, that's often how it ends up, and by making clean and working pull requests, you increase your chances of being accepted.\n\n_I didn't even have time to write a blog post about them before they merged._\n\n## Open-source not by choice\n\nI just wanted to build my website to document my infrastructure. I ended up fixing bugs in three tools, and opening 7 pull requests.\n\nHonestly, I would love to never have to fix them, and I would love to never have to spend my time on fixing these bugs. But that's not the open-source implicit contract, nor its mindset. _It's how I see it. Not everyone does._\n\nPeople offer you, for free, and often as is, their code for your own usage. It saves you time and money and allows you to build faster and better. Yes, I spend time fixing bugs, but how many times have I saved by using open-source software? How often have I been able to contribute to open-source software to build the experience I wanted within my projects? It's not even worth starting to count. Giving back, a little of your knowledge and time, seems to be a fair deal.\n\nAnd that's normal that software has bugs, missing features or simply unplanned use cases. It's inherent to software and neither closed-source software nor open-source software is immune to this.\n\nHowever, with open-source, you can change the situation by having this freedom to contribute to the software you're using.\n\n---\n\nJust build stuff, anything you want, but bring your dreams to life, on internet, don't stop to `localhost`.\n\nAlong the journey, you'll encounter bugs, missing features, or simply things that could be improved. When you do, don't hesitate to dive into your stack, to understand it, and try to fix it.\n\n**That's how you contribute to open-source**. Not the other way around. That's also one of the best ways to learn.\n\nCultivate the open-source mindset and opportunities will naturally arise.\n",{"title":14127,"description":16365},{"loc":2971,"images":16370},[16371],{"loc":15319},"posts\u002Fwant-to-contribute-to-open-source-youre-doing-it-wrong","tyIAbOiKNyiSnvygkr2QCXnMymNL2L3HZl1rIwq4PU4",{"id":16375,"title":16376,"body":16377,"contentId":9301,"date":16846,"description":16847,"extension":18,"meta":16848,"navigation":20,"path":16849,"rawbody":16850,"robots":23,"schemaOrg":23,"seo":16851,"sitemap":16852,"stem":16866,"__hash__":16867},"posts\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest.md","My Adventure to Nantes for Europe's Largest DevFest",{"type":7,"value":16378,"toc":16841},[16379,16382,16385,16399,16403,16408,16411,16420,16425,16431,16437,16440,16446,16461,16470,16476,16482,16491,16497,16500,16503,16506,16670,16673,16677,16680,16734,16737,16739,16745,16751,16757,16760,16769,16775,16783,16789,16792,16794,16800,16806,16808,16813,16820,16823,16826,16832],[35,16380,16381],{},"DevFest is a community-led tech conference series hosted by Google Developer Groups (GDGs) that happens around the world. Each event is organized by local GDGs and features a mix of talks, workshops, and networking opportunities for developers of all skill levels.",[35,16383,16384],{},"I discovered them while trying to find events where I could submit my talk proposals. Once you have a talk you put a lot of work into and that people appreciate, you really want to share it with a wider audience.",[35,16386,16387,16388,16392,16393,1638,16396,16398],{},"So after ",[39,16389,16391],{"href":16390},"\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special.md","Devoxx 2025",", I submitted my two talks, ",[6850,16394,16395],{},"La réactivité et les signaux: démystifions la magie du frontend",[6850,16397,13959],{},", to many meetups and conferences. On June 24, I received an email from Conference Hall, a platform hosting CFPs, telling me that one of my talks had been accepted for DevFest Nantes 2025. I was so happy to explore a new conference!",[51,16400,16402],{"id":16401},"devfest-nantes-2025","DevFest Nantes 2025",[35,16404,16405],{},[1217,16406,16407],{},"Life is currently driving me more than I'm driving my life. I barely read any of the emails from the organizers. I had no idea what to expect.",[35,16409,16410],{},"DevFest Nantes is the largest DevFest in Europe: more than 3,500 attendees, 2 days, 4 tracks, 71 talks and 90 speakers. That's huge!",[35,16412,16413,16414,16419],{},"The conference started with the speakers' dinner on Wednesday, October 15. This took place in the ",[39,16415,16418],{"href":16416,"rel":16417},"https:\u002F\u002Fwww.lesmachines-nantes.fr\u002Fdecouvrir\u002Fle-carrousel-des-mondes-marins\u002F",[77],"Carrousel of the Marine Worlds"," where we had the privilege of visiting it and taking a ride.",[35,16421,16422],{},[1690,16423],{"alt":16418,"src":16424},"\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fcarrousel-of-the-marine-worlds.webp",[35,16426,16427],{},[1690,16428],{"alt":16429,"src":16430},"Me, piloting my own marine creature","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fme-piloting-my-own-marine-creature.webp",[35,16432,16433,16434],{},"Then we had a networking event to meet and connect with other speakers. It's always a pleasure to see familiar faces and discover new speakers. This is even more true when the food is good. ",[1217,16435,16436],{},"Your social battery has to be full before going to a speakers' dinner!",[35,16438,16439],{},"The event started on Thursday, October 16, 2025. It took place in the Nantes Congress Center. The building is really impressive, with very large rooms. One thing really impressed me: each sponsor's stand was beautifully decorated, on theme, creating a magical atmosphere that was very pleasant. Even the activities to win goodies and attract people were created just for the event. The food was amazing, with plenty to eat and no waiting time. There was even a quiet room for speakers to relax.",[35,16441,16442],{},[1690,16443],{"alt":16444,"src":16445},"Nantes Congress Center","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fnantes-congress-center.webp",[35,16447,16448,16449,16454,16455,16460],{},"While waiting for a talk to start, I was scrolling on LinkedIn and I saw ",[39,16450,16453],{"href":16451,"rel":16452},"https:\u002F\u002Fwww.linkedin.com\u002Fposts\u002Fxavier-pestel_hey-super-moment-heureux-de-rencontrer-activity-7384490805656387584-psjo",[77],"a post by Xavier"," saying that he was at DevFest Nantes. Maybe you don't know him. He has a YouTube channel named ",[39,16456,16459],{"href":16457,"rel":16458},"https:\u002F\u002Fwww.youtube.com\u002F@xavki",[77],"xavki"," where you can learn everything about cloud and infrastructure. It's a gold mine! A friend of mine got the job he has today thanks to him. So, I went to say hi to him.",[35,16462,16463,16464,16469],{},"Honestly, I was lost for words for a couple of seconds. I was like a child in front of their favorite hero. While we were talking, Stéphane joined us. Unexpected! Stéphane has a blog named ",[39,16465,16468],{"href":16466,"rel":16467},"https:\u002F\u002Fblog.stephane-robert.info\u002F",[77],"DevSecOps"," containing so much valuable information. Mind-blowing moment.",[35,16471,16472],{},[1690,16473],{"alt":16474,"src":16475},"Stéphane, Xavier and me during DevFest Nantes","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fstephane-xavier-me.webp",[35,16477,16478,16479],{},"DevFest Nantes is also a party between the first and second day. You can meet people, a lot of people, eat good food, dance, and simply enjoy the moment. You won't have any photos because what happens at DevFest Nantes stays at DevFest Nantes. ",[1217,16480,16481],{},"With a glass of wine in my left hand and a crêpe in my right hand, it's hard to take pictures.",[35,16483,16484,16485,16490],{},"The last scheduled slot on Friday was a show from ",[39,16486,16489],{"href":16487,"rel":16488},"https:\u002F\u002Flafaltazi.com",[77],"La Faltazi",". Forty-five minutes of pure laughter. What a great idea to end the conference with improvisational theater! An amazing performance and a perfect ending.",[35,16492,16493],{},[1690,16494],{"alt":16495,"src":16496},"La Faltazi performing at DevFest Nantes 2025","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fla-faltazi-performing-at-devfest-nantes-2025.webp",[35,16498,16499],{},"This was the conference in a few words but what about the talks?",[35,16501,16502],{},"There are things that won't change. Until the last minute, I hadn't read the program, so I mostly followed my intuition when choosing which events to attend. For example, I'm writing a talk about AI so I attended some of the talks related to this topic, to see how they approached it, and to try to bring something new. I was not really interested in the content.",[35,16504,16505],{},"The talks I attended:",[1576,16507,16508,16580],{},[71,16509,16510,16513],{},[6850,16511,16512],{},"Thursday, October 16, 2025",[1576,16514,16515,16525,16535,16550,16560,16570],{},[71,16516,16517,6853,16520],{},[6850,16518,16519],{},"Advice Well Taken",[39,16521,16524],{"href":16522,"rel":16523},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdasha-ilina-633005132\u002F",[77],"Dasha Ilina",[71,16526,16527,6853,16530],{},[6850,16528,16529],{},"Ressusciter vos présentations en articles de blog avec AWS Amplify Gen 2 et Amazon Bedrock",[39,16531,16534],{"href":16532,"rel":16533},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fadrien-lebret\u002F",[77],"Adrien Lebret",[71,16536,16537,6853,16540,1638,16545],{},[6850,16538,16539],{},"Révolution TanStack Query : Enfin une bonne gestion d'état",[39,16541,16544],{"href":16542,"rel":16543},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flucas-audart\u002F",[77],"Lucas Audart",[39,16546,16549],{"href":16547,"rel":16548},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmickaelalves\u002F",[77],"Mickael Alves",[71,16551,16552,6853,16555],{},[6850,16553,16554],{},"The future of routing with the Navigation API",[39,16556,16559],{"href":16557,"rel":16558},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fposva\u002F",[77],"Eduardo San Martin Morote",[71,16561,16562,6853,16565],{},[6850,16563,16564],{},"From RAG to riches : les secrets des LLM augmentés par la donnée !",[39,16566,16569],{"href":16567,"rel":16568},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurent-mangin\u002F",[77],"Laurent Mangin",[71,16571,16572,6853,16575],{},[6850,16573,16574],{},"État des Lieux de la Souveraineté du Cloud : Des Clouds Publics aux Clouds Confidentiels",[39,16576,16579],{"href":16577,"rel":16578},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fseifeddinmansri\u002F",[77],"Seifeddin Mansri",[71,16581,16582,16585],{},[6850,16583,16584],{},"Friday, October 17, 2025",[1576,16586,16587,16598,16612,16627,16637,16652,16662],{},[71,16588,16589,6853,16592,16597],{},[6850,16590,16591],{},"Karpenter * Keda : Le duo gagnant du FinOps",[39,16593,16596],{"href":16594,"rel":16595},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgmembre\u002F",[77],"Guillaume Membré"," and Sébastien Fourreau",[71,16599,16600,6853,16603,1638,16607],{},[6850,16601,16602],{},"45 min pour mettre son application à genoux : le guide complet du test de charge",[39,16604,16606],{"href":9582,"rel":16605},[77],"Loïc Ortola",[39,16608,16611],{"href":16609,"rel":16610},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmathilde-lorrain\u002F",[77],"Mathilde Lorrain",[71,16613,16614,6853,16617,1638,16622],{},[6850,16615,16616],{},"Limits, Requests, QoS, PriorityClasses, on balaie ce que vous pensiez savoir sur le scheduling dans Kubernetes",[39,16618,16621],{"href":16619,"rel":16620},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdenis-germain\u002F",[77],"Denis Germain",[39,16623,16626],{"href":16624,"rel":16625},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fquentin-joly-it\u002F",[77],"Quentin Joly",[71,16628,16629,6853,16632],{},[6850,16630,16631],{},"Agents intelligents, la nouvelle frontière des LLMs",[39,16633,16636],{"href":16634,"rel":16635},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fglaforge\u002F",[77],"Guillaume Laforge",[71,16638,16639,6853,16642,1638,16647],{},[6850,16640,16641],{},"Optimiser des UIs Web legacy dans un moteur de jeu AAA",[39,16643,16646],{"href":16644,"rel":16645},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcedriccf\u002F",[77],"Cédric Chariere Fiedler",[39,16648,16651],{"href":16649,"rel":16650},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fclementgregoire\u002F",[77],"Clément Grégoire",[71,16653,16654,6853,16657],{},[6850,16655,16656],{},"L'aspirant chevalier LynxJS peut-il détrôner monseigneur React Native et le duc de Flutter ?",[39,16658,16661],{"href":16659,"rel":16660},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsimon-bernardin-271633b2\u002F",[77],"Simon Bernardin",[71,16663,16664,6853,16666,16669],{},[6850,16665,13959],{},[39,16667,7033],{"href":7241,"rel":16668},[77]," (it's me!)",[35,16671,16672],{},"And I already know I'll watch the recordings of some talks I missed.",[51,16674,16676],{"id":16675},"talk-highlights","Talk highlights",[35,16678,16679],{},"At every conference, I really love to highlight a few talks I really enjoyed. This is based on the impact they had on me, the quality of the content, and the speakers' ability to engage the audience. This is also very personal and subjective.",[1576,16681,16682,16712],{},[71,16683,16684,6853,16686,16689,16691,16692,1638,16697,16702,16703,16705,16706,16708],{},[6850,16685,16554],{},[39,16687,16559],{"href":16557,"rel":16688},[77],[7162,16690],{},"This talk is really deep because there's little chance we'll have to play with the Navigation API directly. It's more an API for package authors than for end users, like Eduardo, who is the creator of ",[39,16693,16696],{"href":16694,"rel":16695},"https:\u002F\u002Frouter.vuejs.org\u002F",[77],"Vue Router",[39,16698,16701],{"href":16699,"rel":16700},"https:\u002F\u002Fpinia.vuejs.org\u002F",[77],"Pinia",". But I found it really important to understand why this API exists, the drawbacks of the current one, and the potential it has for the future.",[7162,16704],{},"His slides are full of smart animations, making them really easy to follow.",[7162,16707],{},[1690,16709],{"alt":16710,"src":16711},"Eduardo San Martin Morote presenting at DevFest Nantes 2025","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fthe-future-of-routing-with-the-navigation-api.webp",[71,16713,16714,6853,16716,1638,16719,16722,16724,16725,16727,16728,16730],{},[6850,16715,16602],{},[39,16717,16606],{"href":9582,"rel":16718},[77],[39,16720,16611],{"href":16609,"rel":16721},[77],[7162,16723],{},"This talk was already in my highlights of ",[39,16726,16391],{"href":8973},". I'm including it again because I attended it at DevFest Nantes and really appreciated it. It's really impressive. They could talk for far more than 45 minutes. Time flies during their presentation; it's well-written and very accessible.",[7162,16729],{},[1690,16731],{"alt":16732,"src":16733},"Loïc Ortola and Mathilde Lorrain presenting at 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,16735,16736],{},"I highly recommend watching these talks.",[51,16738,7206],{"id":7205},[35,16740,16741,16742,16744],{},"My talk was ",[6850,16743,9139],{},". My slot was the last of the day, on Friday, October 17 at 16:50. I had the opportunity to play it multiple times, so stress wasn't a problem. However, after two long days, I was really tired. I rested for an hour just before my talk.",[35,16746,16747],{},[1690,16748],{"alt":16749,"src":16750},"Me and my coffee.","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fme-and-my-coffee.webp",[35,16752,16753,16754],{},"When I woke up, I drank an espresso and I was up and running again. ",[1217,16755,16756],{},"And I still don't like coffee.",[35,16758,16759],{},"It's now time to go on stage.",[35,16761,16762,16763,16768],{},"Before I went on stage, ",[39,16764,16767],{"href":16765,"rel":16766},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpierre-tibulle-68674812b\u002F",[77],"Pierre"," introduced me. This is the first time I experienced this and it's really fun. He also made sure that everything was good with the setup. Thank you, Pierre!",[35,16770,16771],{},[1690,16772],{"alt":16773,"src":16774},"Pierre introducing me at DevFest Nantes 2025","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fpierre-introducing-me.webp",[35,16776,16777,16778,16782],{},"Then, I went on stage and delivered my talk. A quick selfie with the audience and let's explain ",[39,16779,16781],{"href":14025,"rel":16780},[77],"e18e","! The amphitheater was really impressive: 800 seats! That's a lot and, by far, the biggest room I've ever spoken in.",[35,16784,16785],{},[1690,16786],{"alt":16787,"src":16788},"My selfie with the audience","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fmy-selfie-with-the-audience.webp",[35,16790,16791],{},"Everything went well and people enjoyed it! Fifteen minutes is so short; it's over just when you start to feel good, but that's the way it is.",[1159,16793],{},[35,16795,16796,16797,11837],{},"I really appreciated DevFest Nantes, certainly ",[6850,16798,16799],{},"the best conference of the year",[35,16801,16802],{},[1690,16803],{"alt":16804,"src":16805},"The GDG Nantes team at DevFest Nantes 2025","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fgdg-nantes-team.webp",[1159,16807],{},[35,16809,16810],{},[1217,16811,16812],{},"Technical part, you can skip it if you're not interested.",[35,16814,16815,16816,16819],{},"For this conference, and like the previous ones, I used ",[39,16817,7881],{"href":7879,"rel":16818},[77],", the SaaS I'm building to make presentations more interactive. It works well,which seems normal, yet I'm always amazed to have been able to build a platform, by myself, that just works.",[35,16821,16822],{},"In less than fifteen minutes, more than 17,000 requests (25,000 in total, but 8,000 were cached by Cloudflare) hit my small €4 VPS (2 vCPU and 4 GB of RAM). No lag, no bugs.",[35,16824,16825],{},"For the most curious among you, here is the duration breakdown of the route used to send an emoji on my slide. At the peak, more than 100 people were trying to access the app at the same time.",[35,16827,16828],{},[1690,16829],{"alt":16830,"src":16831},"Duration breakdown of the route used to send live reactions","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fduration-breakdown.webp",[35,16833,16834,16835,16840],{},"Behind the scenes, the VPS is also running Plausible, which consumes a lot of RAM. I'm testing lighter alternatives. On the VPS monitoring, the CPU usage peaked at 87%, but the load average was still under control (0.73, 0.68, 0.61). After some tests using ",[39,16836,16839],{"href":16837,"rel":16838},"https:\u002F\u002Fgithub.com\u002Fmcollina\u002Fautocannon",[77],"autocannon",", the VPS was able to handle 41 req\u002Fs with 100 concurrent connections over 60 seconds when accessing an overview page. After some tweaks, I was able to increase the performance to 47 req\u002Fs, and once Plausible is shut down, I expect to increase it to 53 req\u002Fs. So yes, the VPS is small, but it's enough for now.",{"title":13,"searchDepth":14,"depth":14,"links":16842},[16843,16844,16845],{"id":16401,"depth":14,"text":16402},{"id":16675,"depth":14,"text":16676},{"id":7205,"depth":14,"text":7206},"2025-10-29","After Devoxx, MiXiT, and BreizhCamp, I had the opportunity to attend DevFest Nantes. Two days of talks, networking, and connecting with fellow developers.",{},"\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest","---\ncontentId: 71b437c6-68f6-42e8-bf6e-b657a5b89948\ntitle: My Adventure to Nantes for Europe's Largest DevFest\ndescription: After Devoxx, MiXiT, and BreizhCamp, I had the opportunity to attend DevFest Nantes. Two days of talks, networking, and connecting with fellow developers.\ndate: 2025-10-29\n---\n\nDevFest is a community-led tech conference series hosted by Google Developer Groups (GDGs) that happens around the world. Each event is organized by local GDGs and features a mix of talks, workshops, and networking opportunities for developers of all skill levels.\n\nI discovered them while trying to find events where I could submit my talk proposals. Once you have a talk you put a lot of work into and that people appreciate, you really want to share it with a wider audience.\n\nSo after [Devoxx 2025](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special.md), I submitted my two talks, **La réactivité et les signaux: démystifions la magie du frontend** and **En JavaScript, il y a un paquet pour tout... et souvent pour rien.**, to many meetups and conferences. On June 24, I received an email from Conference Hall, a platform hosting CFPs, telling me that one of my talks had been accepted for DevFest Nantes 2025. I was so happy to explore a new conference!\n\n## DevFest Nantes 2025\n\n_Life is currently driving me more than I'm driving my life. I barely read any of the emails from the organizers. I had no idea what to expect._\n\nDevFest Nantes is the largest DevFest in Europe: more than 3,500 attendees, 2 days, 4 tracks, 71 talks and 90 speakers. That's huge!\n\nThe conference started with the speakers' dinner on Wednesday, October 15. This took place in the [Carrousel of the Marine Worlds](https:\u002F\u002Fwww.lesmachines-nantes.fr\u002Fdecouvrir\u002Fle-carrousel-des-mondes-marins\u002F) where we had the privilege of visiting it and taking a ride.\n\n![Carrousel of the Marine Worlds](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fcarrousel-of-the-marine-worlds.webp)\n\n![Me, piloting my own marine creature](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fme-piloting-my-own-marine-creature.webp)\n\nThen we had a networking event to meet and connect with other speakers. It's always a pleasure to see familiar faces and discover new speakers. This is even more true when the food is good. _Your social battery has to be full before going to a speakers' dinner!_\n\nThe event started on Thursday, October 16, 2025. It took place in the Nantes Congress Center. The building is really impressive, with very large rooms. One thing really impressed me: each sponsor's stand was beautifully decorated, on theme, creating a magical atmosphere that was very pleasant. Even the activities to win goodies and attract people were created just for the event. The food was amazing, with plenty to eat and no waiting time. There was even a quiet room for speakers to relax.\n\n![Nantes Congress Center](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fnantes-congress-center.webp)\n\nWhile waiting for a talk to start, I was scrolling on LinkedIn and I saw [a post by Xavier](https:\u002F\u002Fwww.linkedin.com\u002Fposts\u002Fxavier-pestel_hey-super-moment-heureux-de-rencontrer-activity-7384490805656387584-psjo) saying that he was at DevFest Nantes. Maybe you don't know him. He has a YouTube channel named [xavki](https:\u002F\u002Fwww.youtube.com\u002F@xavki) where you can learn everything about cloud and infrastructure. It's a gold mine! A friend of mine got the job he has today thanks to him. So, I went to say hi to him.\n\nHonestly, I was lost for words for a couple of seconds. I was like a child in front of their favorite hero. While we were talking, Stéphane joined us. Unexpected! Stéphane has a blog named [DevSecOps](https:\u002F\u002Fblog.stephane-robert.info\u002F) containing so much valuable information. Mind-blowing moment.\n\n![Stéphane, Xavier and me during DevFest Nantes](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fstephane-xavier-me.webp)\n\nDevFest Nantes is also a party between the first and second day. You can meet people, a lot of people, eat good food, dance, and simply enjoy the moment. You won't have any photos because what happens at DevFest Nantes stays at DevFest Nantes. _With a glass of wine in my left hand and a crêpe in my right hand, it's hard to take pictures._\n\nThe last scheduled slot on Friday was a show from [La Faltazi](https:\u002F\u002Flafaltazi.com). Forty-five minutes of pure laughter. What a great idea to end the conference with improvisational theater! An amazing performance and a perfect ending.\n\n![La Faltazi performing at DevFest Nantes 2025](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fla-faltazi-performing-at-devfest-nantes-2025.webp)\n\nThis was the conference in a few words but what about the talks?\n\nThere are things that won't change. Until the last minute, I hadn't read the program, so I mostly followed my intuition when choosing which events to attend. For example, I'm writing a talk about AI so I attended some of the talks related to this topic, to see how they approached it, and to try to bring something new. I was not really interested in the content.\n\nThe talks I attended:\n\n- **Thursday, October 16, 2025**\n  - **Advice Well Taken** by [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** by [Adrien Lebret](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fadrien-lebret\u002F)\n  - **Révolution TanStack Query : Enfin une bonne gestion d'état** by [Lucas Audart](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flucas-audart\u002F) and [Mickael Alves](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmickaelalves\u002F)\n  - **The future of routing with the Navigation API** by [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 !** by [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** by [Seifeddin Mansri](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fseifeddinmansri\u002F)\n- **Friday, October 17, 2025**\n  - **Karpenter * Keda : Le duo gagnant du FinOps** by [Guillaume Membré](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgmembre\u002F) and Sébastien Fourreau\n  - **45 min pour mettre son application à genoux : le guide complet du test de charge** by [Loïc Ortola](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floicortola\u002F) and [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** by [Denis Germain](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdenis-germain\u002F) and [Quentin Joly](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fquentin-joly-it\u002F)\n  - **Agents intelligents, la nouvelle frontière des LLMs** by [Guillaume Laforge](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fglaforge\u002F)\n  - **Optimiser des UIs Web legacy dans un moteur de jeu AAA** by [Cédric Chariere Fiedler](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcedriccf\u002F) and [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 ?** by [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.** by [Estéban Soubiran](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25) (it's me!)\n\nAnd I already know I'll watch the recordings of some talks I missed.\n\n## Talk highlights\n\nAt every conference, I really love to highlight a few talks I really enjoyed. This is based on the impact they had on me, the quality of the content, and the speakers' ability to engage the audience. This is also very personal and subjective.\n\n- **The future of routing with the Navigation API** by [Eduardo San Martin Morote](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fposva\u002F)\n\n  This talk is really deep because there's little chance we'll have to play with the Navigation API directly. It's more an API for package authors than for end users, like Eduardo, who is the creator of [Vue Router](https:\u002F\u002Frouter.vuejs.org\u002F) and [Pinia](https:\u002F\u002Fpinia.vuejs.org\u002F). But I found it really important to understand why this API exists, the drawbacks of the current one, and the potential it has for the future.\n\n  His slides are full of smart animations, making them really easy to follow.\n\n  ![Eduardo San Martin Morote presenting at 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** by [Loïc Ortola](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floicortola\u002F) and [Mathilde Lorrain](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmathilde-lorrain\u002F)\n\n  This talk was already in my highlights of [Devoxx 2025](.\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special.md). I'm including it again because I attended it at DevFest Nantes and really appreciated it. It's really impressive. They could talk for far more than 45 minutes. Time flies during their presentation; it's well-written and very accessible.\n\n  ![Loïc Ortola and Mathilde Lorrain presenting at 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\nI highly recommend watching these talks.\n\n## My talk\n\nMy talk was **En JavaScript, il y a un paquet pour tout... et souvent pour rien**. My slot was the last of the day, on Friday, October 17 at 16:50. I had the opportunity to play it multiple times, so stress wasn't a problem. However, after two long days, I was really tired. I rested for an hour just before my talk.\n\n![Me and my coffee.](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fme-and-my-coffee.webp)\n\nWhen I woke up, I drank an espresso and I was up and running again. _And I still don't like coffee._\n\nIt's now time to go on stage.\n\nBefore I went on stage, [Pierre](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpierre-tibulle-68674812b\u002F) introduced me. This is the first time I experienced this and it's really fun. He also made sure that everything was good with the setup. Thank you, Pierre!\n\n![Pierre introducing me at DevFest Nantes 2025](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fpierre-introducing-me.webp)\n\nThen, I went on stage and delivered my talk. A quick selfie with the audience and let's explain [e18e](https:\u002F\u002Fe18e.dev\u002F)! The amphitheater was really impressive: 800 seats! That's a lot and, by far, the biggest room I've ever spoken in.\n\n![My selfie with the audience](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fmy-selfie-with-the-audience.webp)\n\nEverything went well and people enjoyed it! Fifteen minutes is so short; it's over just when you start to feel good, but that's the way it is.\n\n---\n\nI really appreciated DevFest Nantes, certainly **the best conference of the year**!\n\n![The GDG Nantes team at DevFest Nantes 2025](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fgdg-nantes-team.webp)\n\n---\n\n_Technical part, you can skip it if you're not interested._\n\nFor this conference, and like the previous ones, I used [Inalia](https:\u002F\u002Finalia.app), the SaaS I'm building to make presentations more interactive. It works well,which seems normal, yet I'm always amazed to have been able to build a platform, by myself, that just works.\n\nIn less than fifteen minutes, more than 17,000 requests (25,000 in total, but 8,000 were cached by Cloudflare) hit my small €4 VPS (2 vCPU and 4 GB of RAM). No lag, no bugs.\n\nFor the most curious among you, here is the duration breakdown of the route used to send an emoji on my slide. At the peak, more than 100 people were trying to access the app at the same time.\n\n![Duration breakdown of the route used to send live reactions](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fduration-breakdown.webp)\n\nBehind the scenes, the VPS is also running Plausible, which consumes a lot of RAM. I'm testing lighter alternatives. On the VPS monitoring, the CPU usage peaked at 87%, but the load average was still under control (0.73, 0.68, 0.61). After some tests using [autocannon](https:\u002F\u002Fgithub.com\u002Fmcollina\u002Fautocannon), the VPS was able to handle 41 req\u002Fs with 100 concurrent connections over 60 seconds when accessing an overview page. After some tweaks, I was able to increase the performance to 47 req\u002Fs, and once Plausible is shut down, I expect to increase it to 53 req\u002Fs. So yes, the VPS is small, but it's enough for now.\n",{"title":16376,"description":16847},{"loc":16849,"images":16853},[16854,16855,16856,16857,16858,16859,16860,16861,16862,16863,16864,16865],{"loc":16424},{"loc":16430},{"loc":16445},{"loc":16475},{"loc":16496},{"loc":16711},{"loc":16733},{"loc":16750},{"loc":16774},{"loc":16788},{"loc":16805},{"loc":16831},"posts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest","O6y5Shg-pjGe0d7UVNfypGM9psaoOh2MydweqRyFz18",{"id":16869,"title":42,"body":16870,"contentId":3031,"date":17215,"description":17216,"extension":18,"meta":17217,"navigation":20,"path":41,"rawbody":17218,"robots":23,"schemaOrg":23,"seo":17219,"sitemap":17220,"stem":17221,"__hash__":17222},"posts\u002Fposts\u002Fai-agents-will-deeply-transform-our-experience-with-the-web.md",{"type":7,"value":16871,"toc":17207},[16872,16875,16901,16907,16910,16915,16918,16921,16927,16931,16943,16952,16960,16963,16967,16970,16973,16981,16984,16988,16991,17000,17009,17021,17029,17032,17035,17041,17053,17057,17063,17112,17115,17119,17128,17134,17148,17151,17154,17157,17167,17172,17176,17182,17185,17188,17191,17195,17197],[35,16873,16874],{},"The rise of AI Agents is crazy. Every software application, even documentation, is shipping an AI Agent or AI-powered feature.",[35,16876,16877,16878,3076,16883,16888,16889,16894,16895,16900],{},"Notion, with its ",[39,16879,16882],{"href":16880,"rel":16881},"https:\u002F\u002Fwww.notion.com\u002Freleases\u002F2025-09-18",[77],"3.0 release",[39,16884,16887],{"href":16885,"rel":16886},"https:\u002F\u002Fcursor.com\u002Fdocs",[77],"Cursor documentation",", IDEs like VS Code with ",[39,16890,16893],{"href":16891,"rel":16892},"https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2025\u002F04\u002F07\u002FagentMode",[77],"Copilot Agent"," and all its forks, even n8n, with ",[39,16896,16899],{"href":16897,"rel":16898},"https:\u002F\u002Fn8n.io\u002Fai-agents\u002F",[77],"workflows automation with AI Agents",", are shipping Agents.",[35,16902,16903,16904],{},"Agents, Agents, Agents. Everywhere, every time, for everything. ",[1217,16905,16906],{},"From my point of view because the filter bubble from social media heavily distorts the perception of reality.",[35,16908,16909],{},"We no longer browse the web. We ask ChatGPT or Perplexity to do it for us, and it radically changes our relationship with software, services, and information.",[35,16911,16912],{},[6850,16913,16914],{},"We no longer do actions. We now ask for them.",[35,16916,16917],{},"This is a huge shift and the user experience is in the front line. We're not talking about changing some details on how web apps are designed; we're talking about imagining something completely new, from scratch, with no precedent.",[35,16919,16920],{},"We're talking about a complete change in usage patterns.",[35,16922,16923,16924],{},"And this is massive. ",[1217,16925,16926],{},"Maybe I'm totally wrong and we all laugh about it in a few years. But if this is the future, then we need to be prepared.",[51,16928,16930],{"id":16929},"function-calling-changed-ai","Function calling changed AI",[35,16932,16933,16934,16939,16940],{},"In June 2023, more than two years ago, ",[39,16935,16938],{"href":16936,"rel":16937},"https:\u002F\u002Fopenai.com\u002Findex\u002Ffunction-calling-and-other-api-updates\u002F",[77],"OpenAI introduced function calling"," capabilities in their API. This enabled AI to connect and interact with the real world. ",[1217,16941,16942],{},"At that time, context was 16k for high-end models.",[35,16944,16945,16946,16951],{},"This was mainly used by coding editors to enhance their capabilities. AI could now search for information, create files, run commands, and more. This was the beginning of AI Agents. In February 2025, ",[39,16947,16950],{"href":16948,"rel":16949},"https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2025\u002F02\u002F24\u002Fintroducing-copilot-agent-mode",[77],"VS Code announced its GitHub Copilot agent mode",", but software like Cursor or Windsurf had already been leveraging these capabilities for months.",[35,16953,16954,16959],{},[39,16955,16958],{"href":16956,"rel":16957},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fdocs\u002Fgetting-started\u002Fintro",[77],"MCP",", which stands for Model Context Protocol, appeared in November 2024. MCP isn't directly related to AI. It's a connector between an AI application and another service to augment the AI capabilities. The AI application connects to the MCP server, lists its tools, and injects them to the AI using the function calling capabilities introduced earlier.",[35,16961,16962],{},"This is smart. It makes plugins for AI capabilities possible allowing any software to enhance any AI application.",[51,16964,16966],{"id":16965},"its-all-about-the-idea","It's all about the idea",[35,16968,16969],{},"So we have AI with capabilities. How is it supposed to change the world?",[35,16971,16972],{},"Honestly, I can't answer, and I won't even try. We're talking about things that do not exist yet. Everything has to be imagined, and we're in that innovative phase where ideas have to be defined, tested, and analyzed.",[35,16974,16975,16976,215],{},"The chance of failure is high, higher than the chance of success. But that's how innovation works. We are early in the ",[39,16977,16980],{"href":16978,"rel":16979},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTechnology_adoption_life_cycle",[77],"technology adoption lifecycle",[35,16982,16983],{},"We know it's possible, but \"it\" remains to be defined.",[51,16985,16987],{"id":16986},"chatting-with-content","Chatting with content",[35,16989,16990],{},"Two things made me realize that something had changed.",[35,16992,16993,16994,16999],{},"In March 2023, ",[39,16995,16998],{"href":16996,"rel":16997},"https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2023\u002F03\u002F30\u002Fvscode-copilot#_embracing-the-chat-view",[77],"VS Code introduced a Chat view",", but it wasn't working well and it wasn't able to edit files.",[35,17001,17002,17003,17008],{},"So in October 2023, I built a VS Code extension called ",[39,17004,17007],{"href":17005,"rel":17006},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Futils-ai",[77],"Utils AI",", allowing me to quickly run AI on my files. It was a way to quickly fix typos and improve SEO for my articles or documentation pages. It worked great.",[35,17010,17011,17012,17017,17018],{},"It wasn't until November 2024 that ",[39,17013,17016],{"href":17014,"rel":17015},"https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2024\u002F11\u002F12\u002Fintroducing-copilot-edits",[77],"VS Code introduced its edit mode with Copilot Edits",", allowing the AI to modify files directly. You ask, it modifies intelligently. ",[1217,17019,17020],{},"At least it tries.",[35,17022,7737,17023,17028],{},[39,17024,17027],{"href":17025,"rel":17026},"https:\u002F\u002Fblog.google\u002Ftechnology\u002Fai\u002Fnotebooklm-google-ai\u002F",[77],"Google introduced NotebookLM",", a new tool to interact with content, text, PDFs, videos, using natural language.",[35,17030,17031],{},"Since then, I discovered something new. You can literally chat with your content, having a conversation. Mind blowing.",[35,17033,17034],{},"Within VS Code, I asked Copilot to fix typos, then to suggest title improvements, add to bold important text, and tell me if I missed something or if a part was unclear. Clearly, my Utils AI extension became completely obsolete, but something even more powerful emerged.",[35,17036,17037,17038],{},"Since then, I have an obsession in mind. How can I make something similar for my own website soubiran.dev? ",[6850,17039,17040],{},"How can I chat to my content using natural language?",[35,17042,17043,17044,17049,17050],{},"Recently, ",[39,17045,17048],{"href":17046,"rel":17047},"https:\u002F\u002Fx.com\u002Fadamwathan",[77],"this tweet from Adam"," about Notion 3.0 made me realize the potential of a chat bar to interact with a website could go far beyond chatting. ",[1217,17051,17052],{},"Keep it in mind; it will be part of another article.",[51,17054,17056],{"id":17055},"embracing-failure","Embracing failure",[35,17058,17059,17060],{},"I lost count of how many times I tried. However, I can confidently say that each was a failure, as you still can't chat to your content using natural language. ",[1217,17061,17062],{},"But that's from failure that we learn the most.",[68,17064,17065,17074,17077,17103],{},[71,17066,17067,17068,17073],{},"In 2024, I tried to build ",[39,17069,17072],{"href":17070,"rel":17071},"https:\u002F\u002Fpython.langchain.com\u002Fdocs\u002Ftutorials\u002Frag\u002F",[77],"a RAG using LangChain",". I never created more than a small POC on my computer.",[71,17075,17076],{},"In February 2025, I built a search bar for my website. It's fully functional and works fine, but it's far from the experience I'm looking for.",[71,17078,17079,17080,17085,17086,1638,17091,17096,17097,17102],{},"Later, I tried to use a search engine like ",[39,17081,17084],{"href":17082,"rel":17083},"https:\u002F\u002Ftypesense.org\u002F",[77],"TypeSense",". I also set up ",[39,17087,17090],{"href":17088,"rel":17089},"https:\u002F\u002Ftypesense.org\u002Fdocs\u002Fguide\u002Fsemantic-search.html",[77],"semantic search",[39,17092,17095],{"href":17093,"rel":17094},"https:\u002F\u002Ftypesense.org\u002Fdocs\u002Fguide\u002Fnatural-language-search.html",[77],"natural language search",". I even tried ",[39,17098,17101],{"href":17099,"rel":17100},"https:\u002F\u002Fgithub.com\u002Falgolia\u002Fdocsearch",[77],"DocSearch"," to automate the process. All these solutions were good, but they were just search. Even the natural language search was just a search. Nothing was near a chat experience.",[71,17104,17105,17106,17111],{},"In April 2025, ",[39,17107,17110],{"href":17108,"rel":17109},"https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-autorag-on-cloudflare\u002F",[77],"Cloudflare introduced AutoRAG",": a fully managed RAG solution, from indexing to querying. I tried it; the overall system worked well, but it was dumb as a rock.",[35,17113,17114],{},"I failed again and again.",[51,17116,17118],{"id":17117},"the-birth-of-an-idea","The birth of an idea",[35,17120,17121,17122,17127],{},"On October 1st, 2025, ",[39,17123,17126],{"href":17124,"rel":17125},"https:\u002F\u002Fx.com\u002Fbenjamincanac\u002Fstatus\u002F1973338105391587339",[77],"Benjamin tweeted about a new AI search"," for Nuxt UI. I instantly tried it, and it was exactly what I envisioned for my own website. Impressive step forward!",[35,17129,17130,17131],{},"As soon as I get home, I started studying the code to understand how does he achieve such a result. I tried it again and again, I opened to Network tab of the Devtools to understand the request, and I deeply read the code to understand each word. ",[1217,17132,17133],{},"Thank you the Nuxt team for making it open-source!",[35,17135,17136,17137,17141,17142,17147],{},"Something that instantly jumped to my mind was this idea of using their own ",[39,17138,16958],{"href":17139,"rel":17140},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fblob\u002F6c083ef76fd209e5202b5b649944c8c454e639b7\u002Fdocs\u002Fserver\u002Fapi\u002Fsearch.ts#L8-L14",[77]," with an ",[39,17143,17146],{"href":17144,"rel":17145},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fblob\u002F6c083ef76fd209e5202b5b649944c8c454e639b7\u002Fdocs\u002Fserver\u002Fapi\u002Fsearch.ts#L42",[77],"AI Agent"," to answer user queries. You could say it's overkill, I would answered that it's insanely powerful.",[35,17149,17150],{},"It was around 21h when I had an idea. What if I could create a similar system for my own website?",[35,17152,17153],{},"I started thinking about the solution, imagining problems, and trying to solve them all within my mind. Twenty minutes later, I had all the pieces needed to build something. I powered on my computer, opened OBS, launched a stream, and seven hours later the POC was working.",[35,17155,17156],{},"What a joy! I have no words to describe the feeling when you first see the AI using its tools and then perfectly answering user queries.",[130,17158,132,17159,132,17164],{},[134,17160,136,17161,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,17162],{"src":17163,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fai-agents-will-deeply-transform-our-experience-with-the-web\u002Fchat-with-soubiran-dev.mp4",[143,17165,17166],{},"My AI Agent in action.",[35,17168,17169],{},[1217,17170,17171],{},"To be fair, the failures I encountered along the way were invaluable to achieving this so quickly.",[51,17173,17175],{"id":17174},"an-opened-frame-to-the-future","An opened frame to the future",[35,17177,17178,17179],{},"It took me more than a year, nearly two, a lot of failure, to build a working prototype of my vision. Finally, it's here. ",[1217,17180,17181],{},"Patience matters.",[35,17183,17184],{},"It's impressive and astonishing to see that level of precision in the answers. The AI is able to understand the subtext perfectly and provide relevant information.",[35,17186,17187],{},"The craziest part? It's just the beginning and I have so many ideas to explore to make the system even more powerful.",[35,17189,17190],{},"For now, it enables me to chat with my website, and this opens a new paradigm for the web. I call it:",[35,17192,17193],{},[6850,17194,54],{},[1159,17196],{},[35,17198,17199,17200],{},"This is a really thrilling concept. You can expect articles, technical deep dives, live streams, and even talks. ",[1217,17201,17202,17203,17206],{},"You're a meetup organizer looking for speakers? ",[39,17204,17205],{"href":9163},"Reach out!"," as it could be a talk.",{"title":13,"searchDepth":14,"depth":14,"links":17208},[17209,17210,17211,17212,17213,17214],{"id":16929,"depth":14,"text":16930},{"id":16965,"depth":14,"text":16966},{"id":16986,"depth":14,"text":16987},{"id":17055,"depth":14,"text":17056},{"id":17117,"depth":14,"text":17118},{"id":17174,"depth":14,"text":17175},"2025-10-06","The web is evolving in the recent years. The rise of AI and AI Agents recently could completely change how we interact with information and services online.",{},"---\ncontentId: e258d6e6-2f2f-400d-9b6f-02801a7284db\ntitle: \"AI Agents Will Deeply Transform Our Experience With the Web\"\ndescription: The web is evolving in the recent years. The rise of AI and AI Agents recently could completely change how we interact with information and services online.\ndate: 2025-10-06\n---\n\nThe rise of AI Agents is crazy. Every software application, even documentation, is shipping an AI Agent or AI-powered feature.\n\nNotion, with its [3.0 release](https:\u002F\u002Fwww.notion.com\u002Freleases\u002F2025-09-18), [Cursor documentation](https:\u002F\u002Fcursor.com\u002Fdocs), IDEs like VS Code with [Copilot Agent](https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2025\u002F04\u002F07\u002FagentMode) and all its forks, even n8n, with [workflows automation with AI Agents](https:\u002F\u002Fn8n.io\u002Fai-agents\u002F), are shipping Agents.\n\nAgents, Agents, Agents. Everywhere, every time, for everything. _From my point of view because the filter bubble from social media heavily distorts the perception of reality._\n\nWe no longer browse the web. We ask ChatGPT or Perplexity to do it for us, and it radically changes our relationship with software, services, and information.\n\n**We no longer do actions. We now ask for them.**\n\nThis is a huge shift and the user experience is in the front line. We're not talking about changing some details on how web apps are designed; we're talking about imagining something completely new, from scratch, with no precedent.\n\nWe're talking about a complete change in usage patterns.\n\nAnd this is massive. _Maybe I'm totally wrong and we all laugh about it in a few years. But if this is the future, then we need to be prepared._\n\n## Function calling changed AI\n\nIn June 2023, more than two years ago, [OpenAI introduced function calling](https:\u002F\u002Fopenai.com\u002Findex\u002Ffunction-calling-and-other-api-updates\u002F) capabilities in their API. This enabled AI to connect and interact with the real world. _At that time, context was 16k for high-end models._\n\nThis was mainly used by coding editors to enhance their capabilities. AI could now search for information, create files, run commands, and more. This was the beginning of AI Agents. In February 2025, [VS Code announced its GitHub Copilot agent mode](https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2025\u002F02\u002F24\u002Fintroducing-copilot-agent-mode), but software like Cursor or Windsurf had already been leveraging these capabilities for months.\n\n[MCP](https:\u002F\u002Fmodelcontextprotocol.io\u002Fdocs\u002Fgetting-started\u002Fintro), which stands for Model Context Protocol, appeared in November 2024. MCP isn't directly related to AI. It's a connector between an AI application and another service to augment the AI capabilities. The AI application connects to the MCP server, lists its tools, and injects them to the AI using the function calling capabilities introduced earlier.\n\nThis is smart. It makes plugins for AI capabilities possible allowing any software to enhance any AI application.\n\n## It's all about the idea\n\nSo we have AI with capabilities. How is it supposed to change the world?\n\nHonestly, I can't answer, and I won't even try. We're talking about things that do not exist yet. Everything has to be imagined, and we're in that innovative phase where ideas have to be defined, tested, and analyzed.\n\nThe chance of failure is high, higher than the chance of success. But that's how innovation works. We are early in the [technology adoption lifecycle](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTechnology_adoption_life_cycle).\n\nWe know it's possible, but \"it\" remains to be defined.\n\n## Chatting with content\n\nTwo things made me realize that something had changed.\n\nIn March 2023, [VS Code introduced a Chat view](https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2023\u002F03\u002F30\u002Fvscode-copilot#_embracing-the-chat-view), but it wasn't working well and it wasn't able to edit files.\n\nSo in October 2023, I built a VS Code extension called [Utils AI](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Futils-ai), allowing me to quickly run AI on my files. It was a way to quickly fix typos and improve SEO for my articles or documentation pages. It worked great.\n\nIt wasn't until November 2024 that [VS Code introduced its edit mode with Copilot Edits](https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2024\u002F11\u002F12\u002Fintroducing-copilot-edits), allowing the AI to modify files directly. You ask, it modifies intelligently. _At least it tries._\n\nAt the same time, [Google introduced NotebookLM](https:\u002F\u002Fblog.google\u002Ftechnology\u002Fai\u002Fnotebooklm-google-ai\u002F), a new tool to interact with content, text, PDFs, videos, using natural language.\n\nSince then, I discovered something new. You can literally chat with your content, having a conversation. Mind blowing.\n\nWithin VS Code, I asked Copilot to fix typos, then to suggest title improvements, add to bold important text, and tell me if I missed something or if a part was unclear. Clearly, my Utils AI extension became completely obsolete, but something even more powerful emerged.\n\nSince then, I have an obsession in mind. How can I make something similar for my own website soubiran.dev? **How can I chat to my content using natural language?**\n\nRecently, [this tweet from Adam](https:\u002F\u002Fx.com\u002Fadamwathan) about Notion 3.0 made me realize the potential of a chat bar to interact with a website could go far beyond chatting. _Keep it in mind; it will be part of another article._\n\n## Embracing failure\n\nI lost count of how many times I tried. However, I can confidently say that each was a failure, as you still can't chat to your content using natural language. _But that's from failure that we learn the most._\n\n1. In 2024, I tried to build [a RAG using LangChain](https:\u002F\u002Fpython.langchain.com\u002Fdocs\u002Ftutorials\u002Frag\u002F). I never created more than a small POC on my computer.\n2. In February 2025, I built a search bar for my website. It's fully functional and works fine, but it's far from the experience I'm looking for.\n3. Later, I tried to use a search engine like [TypeSense](https:\u002F\u002Ftypesense.org\u002F). I also set up [semantic search](https:\u002F\u002Ftypesense.org\u002Fdocs\u002Fguide\u002Fsemantic-search.html) and [natural language search](https:\u002F\u002Ftypesense.org\u002Fdocs\u002Fguide\u002Fnatural-language-search.html). I even tried [DocSearch](https:\u002F\u002Fgithub.com\u002Falgolia\u002Fdocsearch) to automate the process. All these solutions were good, but they were just search. Even the natural language search was just a search. Nothing was near a chat experience.\n4. In April 2025, [Cloudflare introduced AutoRAG](https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-autorag-on-cloudflare\u002F): a fully managed RAG solution, from indexing to querying. I tried it; the overall system worked well, but it was dumb as a rock.\n\nI failed again and again.\n\n## The birth of an idea\n\nOn October 1st, 2025, [Benjamin tweeted about a new AI search](https:\u002F\u002Fx.com\u002Fbenjamincanac\u002Fstatus\u002F1973338105391587339) for Nuxt UI. I instantly tried it, and it was exactly what I envisioned for my own website. Impressive step forward!\n\nAs soon as I get home, I started studying the code to understand how does he achieve such a result. I tried it again and again, I opened to Network tab of the Devtools to understand the request, and I deeply read the code to understand each word. _Thank you the Nuxt team for making it open-source!_\n\nSomething that instantly jumped to my mind was this idea of using their own [MCP](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fblob\u002F6c083ef76fd209e5202b5b649944c8c454e639b7\u002Fdocs\u002Fserver\u002Fapi\u002Fsearch.ts#L8-L14) with an [AI Agent](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fblob\u002F6c083ef76fd209e5202b5b649944c8c454e639b7\u002Fdocs\u002Fserver\u002Fapi\u002Fsearch.ts#L42) to answer user queries. You could say it's overkill, I would answered that it's insanely powerful.\n\nIt was around 21h when I had an idea. What if I could create a similar system for my own website?\n\nI started thinking about the solution, imagining problems, and trying to solve them all within my mind. Twenty minutes later, I had all the pieces needed to build something. I powered on my computer, opened OBS, launched a stream, and seven hours later the POC was working.\n\nWhat a joy! I have no words to describe the feeling when you first see the AI using its tools and then perfectly answering user queries.\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>My AI Agent in action.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n_To be fair, the failures I encountered along the way were invaluable to achieving this so quickly._\n\n## An opened frame to the future\n\nIt took me more than a year, nearly two, a lot of failure, to build a working prototype of my vision. Finally, it's here. _Patience matters._\n\nIt's impressive and astonishing to see that level of precision in the answers. The AI is able to understand the subtext perfectly and provide relevant information.\n\nThe craziest part? It's just the beginning and I have so many ideas to explore to make the system even more powerful.\n\nFor now, it enables me to chat with my website, and this opens a new paradigm for the web. I call it:\n\n**Website as a Search Bar**\n\n---\n\nThis is a really thrilling concept. You can expect articles, technical deep dives, live streams, and even talks. _You're a meetup organizer looking for speakers? [Reach out!](..\u002Ftalks.md) as it could be a talk._\n",{"title":42,"description":17216},{"loc":41},"posts\u002Fai-agents-will-deeply-transform-our-experience-with-the-web","_4Koyl-MTXGnN2Wlt2hU3i0uy38DGL-VqwjKLTMr3W0",{"id":17224,"title":17225,"body":17226,"contentId":9321,"date":17537,"description":17538,"extension":18,"meta":17539,"navigation":20,"path":17540,"rawbody":17541,"robots":23,"schemaOrg":23,"seo":17542,"sitemap":17543,"stem":17544,"__hash__":17545},"posts\u002Fposts\u002Fsoftware-engineering-a-human-journey-before-anything-else.md","Software Engineering: A Human Journey Before Anything Else",{"type":7,"value":17227,"toc":17531},[17228,17236,17241,17244,17247,17249,17256,17259,17261,17269,17274,17277,17280,17283,17287,17290,17299,17302,17311,17320,17323,17334,17337,17341,17355,17358,17361,17364,17367,17370,17373,17376,17379,17382,17414,17417,17425,17429,17432,17435,17438,17441,17444,17453,17457,17466,17469,17472,17475,17478,17481,17499,17501,17516,17518,17526,17528],[35,17229,17230,17231,215],{},"Nuxt UI just released its ",[39,17232,17235],{"href":17233,"rel":17234},"https:\u002F\u002Fnuxt.com\u002Fblog\u002Fnuxt-ui-v4",[77],"version 4",[35,17237,17238],{},[1217,17239,17240],{},"This article isn't about the tech. It's a human story. You can continue to read.",[35,17242,17243],{},"You might wonder why this matters enough to write about it. For me, it marks the end of a project that kept me busy for months and gave me the chance to collaborate with people around the world.",[35,17245,17246],{},"Today I want to share that journey because I tweeted a lot about it, but apart from the people I collaborated with, I haven't shared much.",[1159,17248],{},[35,17250,17251,17252,17255],{},"Some weeks ago I ",[39,17253,17254],{"href":7934},"published an article about my shift"," from writing code for the sake of writing code to using my skills to create moments. That change drastically affected how I view software development, my job, and my goals.",[35,17257,17258],{},"This release is part of that journey.",[1159,17260],{},[35,17262,17263,17264,239],{},"So when did this journey start? On Dec 7, 2024 I ",[39,17265,17268],{"href":17266,"rel":17267},"https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1865402012629610871",[77],"tweeted",[939,17270,17271],{},[35,17272,17273],{},"Imagine, Nuxt UI but like Shadcn Vue 🫣",[35,17275,17276],{},"At first it was just an idea. I tweet often to test interest.",[35,17278,17279],{},"That tweet has been viewed more than 18k times and liked 105 times. The signal was clear: people were interested. Funny enough, apart from that tweet I didn't have a concrete plan or a clear vision of what I wanted to build.",[35,17281,17282],{},"So why did I tweet it?",[51,17284,17286],{"id":17285},"the-beginning","The beginning",[35,17288,17289],{},"Let's go back a few years to give some context.",[35,17291,17292,17293,17298],{},"From day one, I'm a huge fan of Nuxt UI. ",[39,17294,17297],{"href":17295,"rel":17296},"https:\u002F\u002Fx.com\u002Fnuxtlabs\u002Fstatus\u002F1658488718230163459",[77],"Day one was May 16, 2023",". Strangely, I clearly remember what I was doing when I saw the announcement on Twitter: I was leaving my office, on the bridge crossing the Seine, about to take the tram. That could be ordinary, but I never take the tram to go home, I always take the bus.",[35,17300,17301],{},"The announcement didn't leave me indifferent. I felt a spark of excitement. Nuxt was becoming more than just a framework. Nuxt has always been about details and delightful developer experience. If they could bring that same level to the component library, they could win over developers.",[35,17303,17304,17305,17310],{},"At first it wasn't Nuxt UI but NuxtLabs UI. The components had been extracted from Volta by the Volta team. We had to wait for ",[39,17306,17309],{"href":17307,"rel":17308},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Freleases\u002Ftag\u002Fv2.8.0",[77],"version 2.8.0"," before NuxtLabs UI was rebranded to Nuxt UI.",[35,17312,17313,17314,17319],{},"A few months later, on November 2, 2023, ",[39,17315,17318],{"href":17316,"rel":17317},"https:\u002F\u002Fx.com\u002Fnuxt_js\u002Fstatus\u002F1720123517008556120",[77],"they announced Nuxt UI Pro",". Amazing, but it cost $199. At the time I was still a student and, honestly, with all the love I have for Nuxt, that was a lot of money for something I used only as a hobby.",[35,17321,17322],{},"In the FAQ they mentioned that students could get a discount. After extensively trying the Pro components, and without being able to deploy them, I reached out to the team for that discount on November 5, 2023.",[35,17324,17325,17326,17330,17331],{},"A day later, ",[39,17327,13867],{"href":17328,"rel":17329},"https:\u002F\u002Fx.com\u002FAtinux",[77]," replied with a 100% discount code to thank me for my contributions to Nuxt Content, UnJS and Nuxt Studio. Nuxt UI Pro, for free. I couldn't believe it. ",[1217,17332,17333],{},"Thank you, Sébastien! Just thinking about it makes me smile.",[35,17335,17336],{},"Now, let's move to the end of 2024.",[51,17338,17340],{"id":17339},"the-need","The need",[35,17342,17343,17344,17349,17350,17354],{},"For the past few months I have been ",[39,17345,17348],{"href":17346,"rel":17347},"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],"learning Laravel",". With Laravel I discovered Inertia and I completely ",[39,17351,17353],{"href":9115,"rel":17352},[77],"fell in love",". This tool changed the way I build applications.",[35,17356,17357],{},"At the same time I started a job where I had the freedom to choose my tech stack. I chose Laravel, Inertia and Vue. So for both my personal and professional projects I embraced this stack.",[35,17359,17360],{},"Nothing is perfect, and this stack had a downside.",[35,17362,17363],{},"Nuxt UI was available only for Nuxt and not as customizable as I wanted. That was frustrating because I had grown to love the components and the developer experience they provided. There was nothing close to matching it for Vue.",[35,17365,17366],{},"But aren't we makers? If it doesn't exist, why not create it?",[35,17368,17369],{},"This led me to an idea. Shadcn was becoming the de facto library for building React applications. It comes with a beautiful design system and a smart distribution system. Projects own their components, which makes customization easy at the project level. However, the developer experience when using these components is far from the Nuxt UI experience.",[35,17371,17372],{},"What about creating a Shadcn-like library for Vue? A Nuxt UI clone, distributed like Shadcn, but for Vue and compatible with Inertia. The Nuxt UI developer experience available to every Vue developer, it gave me chills to imagine it.",[35,17374,17375],{},"This is how the tweet was born.",[35,17377,17378],{},"With that idea in mind and a strong interest in building this library, I had to get started. Building and maintaining a library is huge work, so I asked on Twitter if anyone was interested in joining me.",[35,17380,17381],{},"Five people answered the call, much more than I expected!",[35,17383,17384,17385,3076,17390,3076,17395,3076,17400,3080,17405,17410,17411],{},"Shout out to ",[39,17386,17389],{"href":17387,"rel":17388},"https:\u002F\u002Fx.com\u002Fabei557832",[77],"a bei",[39,17391,17394],{"href":17392,"rel":17393},"https:\u002F\u002Fx.com\u002FUnans___",[77],"David Pena",[39,17396,17399],{"href":17397,"rel":17398},"https:\u002F\u002Fx.com\u002Fenzonotario_",[77],"Enzo Notario",[39,17401,17404],{"href":17402,"rel":17403},"https:\u002F\u002Fx.com\u002F_aussieboi",[77],"aussieboi",[39,17406,17409],{"href":17407,"rel":17408},"https:\u002F\u002Fx.com\u002FLyashenkoG",[77],"Grygoriy Lyashenko"," for joining me on this adventure! ",[1217,17412,17413],{},"If one day you come to Paris, please reach out!",[35,17415,17416],{},"I had a plan, a team, and we were ready to build.",[35,17418,17419,17420,17424],{},"At the same time, Nuxt UI was progressing toward version 3. Two weeks before my tweet, a pull request to make Nuxt UI compatible with Vue 3, created by ",[39,17421,17423],{"href":1877,"rel":17422},[77],"Daniel",", was merged. But it still wasn't as customizable as I wanted, and there was no support for Inertia.",[51,17426,17428],{"id":17427},"the-build","The build",[35,17430,17431],{},"At first, I wasn't sure how to approach this. I knew I wanted to create a library that matched the developer experience of Nuxt UI, but I didn't want to simply copy it.",[35,17433,17434],{},"This wasn't a direct fork of Nuxt UI. Regularly I copied components from Nuxt UI, adapted them to make them Nuxt-agnostic, and sometimes changed their behavior to match my preferences.",[35,17436,17437],{},"The team did that for every component, every composable, every utility function, and even every theme. We also built a documentation site to let users copy and paste code snippets easily, with automatic code modifications if you were using Inertia or simply Vite, and embedded demos for each component.",[35,17439,17440],{},"We even built a bash script to automate the setup process and a way to install only the needed components along with their dependencies.",[35,17442,17443],{},"This was absolutely amazing to build and so satisfying to have something working and used in projects.",[35,17445,17446,17447,17452],{},"For example, ",[39,17448,17451],{"href":17449,"rel":17450},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcode.soubiran.dev\u002Ftree\u002F53b1811948b1879c8f9d354d76aa144883baaa4d\u002Fsrc\u002Fui",[77],"code.soubiran.dev"," was built with this library. Even my own website, the one you're currently on, also uses it.",[51,17454,17456],{"id":17455},"the-truth","The truth",[35,17458,17459,17460,17465],{},"On April 14, 2025, ",[39,17461,17464],{"href":17462,"rel":17463},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fpull\u002F3845",[77],"a pull request to add support for Inertia"," was merged.",[35,17467,17468],{},"I had to face the facts: Nuxt UI was finally becoming what I wanted to build. The customization was not as good as owning the components, but it was open source. Why not submit some contributions? I know each component inside out and had already made some improvements in my fork. I just had to port them back.",[35,17470,17471],{},"So I did. Since December 1, 2024, I contributed nineteen times.",[35,17473,17474],{},"Maintaining my fork was honestly a lot of work and far more than the value I was able to add. Sometimes it's better to let go and focus on what truly matters.",[35,17476,17477],{},"I'm grateful for the experience. It gave me the chance to learn deeply from the Nuxt UI source code and connect with the team through Twitter messages and GitHub issues. One fun aspect of working with a global team: you could go to sleep after sending a message and wake up to a new pull request!",[35,17479,17480],{},"Now, it's time to admit: Vue UI is officially over.",[1576,17482,17483,17491],{},[71,17484,17485,17486],{},"Website: ",[39,17487,17490],{"href":17488,"rel":17489},"https:\u002F\u002Fvue-ui.barbapapazes.dev",[77],"vue-ui.barbapapazes.dev",[71,17492,17493,17494],{},"Code: ",[39,17495,17498],{"href":17496,"rel":17497},"https:\u002F\u002Fgithub.com\u002Fvue-ui-labs\u002Fvue-ui",[77],"Vue UI Labs",[1159,17500],{},[35,17502,17503,17504,3076,17507,3080,17511,17515],{},"Thanks to the entire Nuxt team, especially ",[39,17505,13867],{"href":17328,"rel":17506},[77],[39,17508,13872],{"href":17509,"rel":17510},"https:\u002F\u002Fx.com\u002Fbenjamincanac",[77],[39,17512,9353],{"href":17513,"rel":17514},"https:\u002F\u002Fx.com\u002FHugoRCD__",[77]," for your wonderful work on Nuxt UI. 🫶",[1159,17517],{},[35,17519,17520,17521,215],{},"A picture is worth a thousand words, but sometimes a thousand words express more than anything else. I hope this inspires you to embark on your own journey. Remember: ",[39,17522,17525],{"href":17523,"rel":17524},"https:\u002F\u002Fsoubiran.dev\u002Fposts\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things",[77],"You Don't Need to Be Chosen, You Can Just Do Things",[1159,17527],{},[35,17529,17530],{},"From time to time I love to look back at my previous articles. I like to take time to reflect on the journey I've taken. This helps me appreciate each new day and each new person I meet along the way.",{"title":13,"searchDepth":14,"depth":14,"links":17532},[17533,17534,17535,17536],{"id":17285,"depth":14,"text":17286},{"id":17339,"depth":14,"text":17340},{"id":17427,"depth":14,"text":17428},{"id":17455,"depth":14,"text":17456},"2025-09-26","Merge the distribution system of Shadcn with the developer experience of Nuxt UI. An idea that leads to an amazing collaboration experience.",{},"\u002Fposts\u002Fsoftware-engineering-a-human-journey-before-anything-else","---\ncontentId: 03ce2599-d5a8-45f6-92a6-dd95ce2fc4e6\ntitle: \"Software Engineering: A Human Journey Before Anything Else\"\ndescription: Merge the distribution system of Shadcn with the developer experience of Nuxt UI. An idea that leads to an amazing collaboration experience.\ndate: 2025-09-26\n---\n\nNuxt UI just released its [version 4](https:\u002F\u002Fnuxt.com\u002Fblog\u002Fnuxt-ui-v4).\n\n_This article isn't about the tech. It's a human story. You can continue to read._\n\nYou might wonder why this matters enough to write about it. For me, it marks the end of a project that kept me busy for months and gave me the chance to collaborate with people around the world.\n\nToday I want to share that journey because I tweeted a lot about it, but apart from the people I collaborated with, I haven't shared much.\n\n---\n\nSome weeks ago I [published an article about my shift](.\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things.md) from writing code for the sake of writing code to using my skills to create moments. That change drastically affected how I view software development, my job, and my goals.\n\nThis release is part of that journey.\n\n---\n\nSo when did this journey start? On Dec 7, 2024 I [tweeted](https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1865402012629610871):\n\n> Imagine, Nuxt UI but like Shadcn Vue 🫣\n\nAt first it was just an idea. I tweet often to test interest.\n\nThat tweet has been viewed more than 18k times and liked 105 times. The signal was clear: people were interested. Funny enough, apart from that tweet I didn't have a concrete plan or a clear vision of what I wanted to build.\n\nSo why did I tweet it?\n\n## The beginning\n\nLet's go back a few years to give some context.\n\nFrom day one, I'm a huge fan of Nuxt UI. [Day one was May 16, 2023](https:\u002F\u002Fx.com\u002Fnuxtlabs\u002Fstatus\u002F1658488718230163459). Strangely, I clearly remember what I was doing when I saw the announcement on Twitter: I was leaving my office, on the bridge crossing the Seine, about to take the tram. That could be ordinary, but I never take the tram to go home, I always take the bus.\n\nThe announcement didn't leave me indifferent. I felt a spark of excitement. Nuxt was becoming more than just a framework. Nuxt has always been about details and delightful developer experience. If they could bring that same level to the component library, they could win over developers.\n\nAt first it wasn't Nuxt UI but NuxtLabs UI. The components had been extracted from Volta by the Volta team. We had to wait for [version 2.8.0](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Freleases\u002Ftag\u002Fv2.8.0) before NuxtLabs UI was rebranded to Nuxt UI.\n\nA few months later, on November 2, 2023, [they announced Nuxt UI Pro](https:\u002F\u002Fx.com\u002Fnuxt_js\u002Fstatus\u002F1720123517008556120). Amazing, but it cost $199. At the time I was still a student and, honestly, with all the love I have for Nuxt, that was a lot of money for something I used only as a hobby.\n\nIn the FAQ they mentioned that students could get a discount. After extensively trying the Pro components, and without being able to deploy them, I reached out to the team for that discount on November 5, 2023.\n\nA day later, [Sébastien](https:\u002F\u002Fx.com\u002FAtinux) replied with a 100% discount code to thank me for my contributions to Nuxt Content, UnJS and Nuxt Studio. Nuxt UI Pro, for free. I couldn't believe it. _Thank you, Sébastien! Just thinking about it makes me smile._\n\nNow, let's move to the end of 2024.\n\n## The need\n\nFor the past few months I have been [learning 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). With Laravel I discovered Inertia and I completely [fell in love](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-09-17\u002Fnodejs-paris\u002F1). This tool changed the way I build applications.\n\nAt the same time I started a job where I had the freedom to choose my tech stack. I chose Laravel, Inertia and Vue. So for both my personal and professional projects I embraced this stack.\n\nNothing is perfect, and this stack had a downside.\n\nNuxt UI was available only for Nuxt and not as customizable as I wanted. That was frustrating because I had grown to love the components and the developer experience they provided. There was nothing close to matching it for Vue.\n\nBut aren't we makers? If it doesn't exist, why not create it?\n\nThis led me to an idea. Shadcn was becoming the de facto library for building React applications. It comes with a beautiful design system and a smart distribution system. Projects own their components, which makes customization easy at the project level. However, the developer experience when using these components is far from the Nuxt UI experience.\n\nWhat about creating a Shadcn-like library for Vue? A Nuxt UI clone, distributed like Shadcn, but for Vue and compatible with Inertia. The Nuxt UI developer experience available to every Vue developer, it gave me chills to imagine it.\n\nThis is how the tweet was born.\n\nWith that idea in mind and a strong interest in building this library, I had to get started. Building and maintaining a library is huge work, so I asked on Twitter if anyone was interested in joining me.\n\nFive people answered the call, much more than I expected!\n\nShout out to [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), and [Grygoriy Lyashenko](https:\u002F\u002Fx.com\u002FLyashenkoG) for joining me on this adventure! _If one day you come to Paris, please reach out!_\n\nI had a plan, a team, and we were ready to build.\n\nAt the same time, Nuxt UI was progressing toward version 3. Two weeks before my tweet, a pull request to make Nuxt UI compatible with Vue 3, created by [Daniel](https:\u002F\u002Fgithub.com\u002Fdanielroe), was merged. But it still wasn't as customizable as I wanted, and there was no support for Inertia.\n\n## The build\n\nAt first, I wasn't sure how to approach this. I knew I wanted to create a library that matched the developer experience of Nuxt UI, but I didn't want to simply copy it.\n\nThis wasn't a direct fork of Nuxt UI. Regularly I copied components from Nuxt UI, adapted them to make them Nuxt-agnostic, and sometimes changed their behavior to match my preferences.\n\nThe team did that for every component, every composable, every utility function, and even every theme. We also built a documentation site to let users copy and paste code snippets easily, with automatic code modifications if you were using Inertia or simply Vite, and embedded demos for each component.\n\nWe even built a bash script to automate the setup process and a way to install only the needed components along with their dependencies.\n\nThis was absolutely amazing to build and so satisfying to have something working and used in projects.\n\nFor example, [code.soubiran.dev](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcode.soubiran.dev\u002Ftree\u002F53b1811948b1879c8f9d354d76aa144883baaa4d\u002Fsrc\u002Fui) was built with this library. Even my own website, the one you're currently on, also uses it.\n\n## The truth\n\nOn April 14, 2025, [a pull request to add support for Inertia](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fpull\u002F3845) was merged.\n\nI had to face the facts: Nuxt UI was finally becoming what I wanted to build. The customization was not as good as owning the components, but it was open source. Why not submit some contributions? I know each component inside out and had already made some improvements in my fork. I just had to port them back.\n\nSo I did. Since December 1, 2024, I contributed nineteen times.\n\nMaintaining my fork was honestly a lot of work and far more than the value I was able to add. Sometimes it's better to let go and focus on what truly matters.\n\nI'm grateful for the experience. It gave me the chance to learn deeply from the Nuxt UI source code and connect with the team through Twitter messages and GitHub issues. One fun aspect of working with a global team: you could go to sleep after sending a message and wake up to a new pull request!\n\nNow, it's time to admit: Vue UI is officially over.\n\n- Website: [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\nThanks to the entire Nuxt team, especially [Sébastien](https:\u002F\u002Fx.com\u002FAtinux), [Benjamin](https:\u002F\u002Fx.com\u002Fbenjamincanac), and [Hugo](https:\u002F\u002Fx.com\u002FHugoRCD__) for your wonderful work on Nuxt UI. 🫶\n\n---\n\nA picture is worth a thousand words, but sometimes a thousand words express more than anything else. I hope this inspires you to embark on your own journey. Remember: [You Don't Need to Be Chosen, You Can Just Do Things](https:\u002F\u002Fsoubiran.dev\u002Fposts\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things).\n\n---\n\nFrom time to time I love to look back at my previous articles. I like to take time to reflect on the journey I've taken. This helps me appreciate each new day and each new person I meet along the way.\n",{"title":17225,"description":17538},{"loc":17540},"posts\u002Fsoftware-engineering-a-human-journey-before-anything-else","2NL2HGUPFibQ5uGf8PlaP1L92qo_ceHIMBgCg9afQeY",{"id":17547,"title":17548,"body":17549,"contentId":9491,"date":17735,"description":17736,"extension":18,"meta":17737,"navigation":20,"path":17738,"rawbody":17739,"robots":23,"schemaOrg":23,"seo":17740,"sitemap":17741,"stem":17742,"__hash__":17743},"posts\u002Fposts\u002Fyoure-not-a-software-engineer-sorry-but-thats-okay.md","You're not a software engineer, sorry, but that's okay",{"type":7,"value":17550,"toc":17733},[17551,17556,17559,17561,17564,17567,17569,17572,17575,17578,17581,17584,17587,17590,17593,17600,17605,17612,17615,17618,17621,17624,17626,17629,17635,17638,17641,17644,17653,17656,17659,17662,17664,17667,17670,17673,17676,17679,17682,17685,17688,17690,17695,17698,17705,17708,17716,17719,17722],[35,17552,17553],{},[1217,17554,17555],{},"You're not a software engineer, sorry, but that's okay.",[35,17557,17558],{},"I know, it can be hard to hear. But I'm sure you have other qualities.",[1159,17560],{},[35,17562,17563],{},"If you found this article by yourself, you may not be the target. On the other hand, if I sent it to you personally, you should probably read it and take a moment to think about it.",[35,17565,17566],{},"Also, this article is focused on web development and related fields. I won't talk about other areas of software engineering like embedded systems or data science.",[1159,17568],{},[35,17570,17571],{},"Before going further, how do we get there and what are we discussing?",[35,17573,17574],{},"I'm facing a real challenge here. As code becomes more and more accessible, people tend to think that they can just jump in and start building complex systems without any knowledge, that software developers are just a financial hole without any real value. I'm just tired of explaining my job, again and again. I have the freedom to write my thoughts once and for all in a clear way, and share them with anyone who thinks they can do my job. So here we are.",[35,17576,17577],{},"In today's world, more and more people are writing code. The whole society is becoming digitalized. This means that there's never been a greater need to write software.",[35,17579,17580],{},"Some people saw an opportunity to make money. What about telling people that they can become rich easily, from a chair in an office, just by learning for 2 to 6 months by selling a training? Great idea! Bootcamps were born.",[35,17582,17583],{},"This was a scam.",[35,17585,17586],{},"Learning code is much harder and takes way more time than 2 to 6 months. Also, only good developers can make a lot of money. You won't become rich. And even worse, you won't have a job because the market has been saturated with inexperienced junior developers, hoping to find a well-paid job.",[35,17588,17589],{},"But the story doesn't end here.",[35,17591,17592],{},"This movement came before LLMs. With AI assistants like ChatGPT or Claude Code, and AI builders like Loveable, V0 or Bolt.new, coding has never been more accessible than it is today, and that's terrible.",[35,17594,17595,17596,17599],{},"Absolutely everyone can now write code. ",[1217,17597,17598],{},"The exact sentence should be \"Absolutely everyone can ask a LLM to generate code for them.\""," The barrier to entry was already low with languages like Python. Now, with AI, barriers are gone.",[35,17601,17602],{},[1217,17603,17604],{},"Don't get me wrong. If this can create a vocation, or allow individuals to express themselves through code without having to deal with all the complexity, it's a good thing.",[35,17606,17607,17608,17611],{},"The truth is, ",[6850,17609,17610],{},"writing code is the easiest part of the job",". If you find it difficult, it's probably because you're on the early days of your journey. If you think you're a software engineer by having followed a bootcamp or by using AI tools, you should take a step back, and continue reading. I'm writing this article for you.",[35,17613,17614],{},"The real world, the adult world, is much more complex than a frontend todo list written with HTML and CSS.",[35,17616,17617],{},"Companies have specific business requirements. Most of the time, they find it difficult to express them clearly. People who have a need aren't from this domain; they don't understand what we're doing. That's ok, but as a software engineer, your role is to bridge the gap.",[35,17619,17620],{},"One of your challenges is to extract the need, to understand it, to challenge it, and build a clear and coherent vision from it. Then, you have to design an appropriate solution. This is part of the engineering job implied by the title \"software engineer.\"",[35,17622,17623],{},"AI assistants and AI builders won't magically do that alone. Of course, they can help you, but they won't understand the whole business because there is a lot of implicit knowledge and context that only a human can grasp.",[1159,17625],{},[35,17627,17628],{},"Now we all have the history in mind. Let's have a closer look at the \"software engineer\" title.",[35,17630,17631,17632,215],{},"From the Wikipedia definition, ",[1217,17633,17634],{},"software engineering is a branch of both computer science and engineering focused on designing, developing, testing, and maintaining software applications. It involves applying engineering principles and computer programming expertise to develop software systems that meet user needs. The terms programmer and coder overlap with software engineer, but they imply only the construction aspect of a typical software engineer workload",[35,17636,17637],{},"I really love that definition because it says absolutely everything about the job.",[35,17639,17640],{},"Building software involves science, both theoretical and practical. It's not just about chance or intuition. It's about understanding, analyzing, and solving problems. Also, four tasks are highlighted: designing, developing, testing, and maintaining. When I said earlier that writing code is the easiest part of the job, it's because it's only one of the four tasks, despite being the most visible one. And compared to the other tasks, writing code is a no-brainer.",[35,17642,17643],{},"And don't forget that all of this must be done to solve a real user need. Otherwise, you're just writing code for the sake of writing code, which is useless. By using AI tools to write most of your code, and by only focusing on the code, there is a high probability that you're more of a programmer or a coder than a software engineer.",[35,17645,17646,17647,17652],{},"A while ago, ",[39,17648,17651],{"href":17649,"rel":17650},"https:\u002F\u002Fsoubiran.dev\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests",[77],"I wrote about testing",". You should read it to have a first appreciation of the topic, because it's not AI builders who will ensure the quality of your code and testing is truly difficult.",[35,17654,17655],{},"I won't go deeper into the designing and maintaining aspects, but if you're trying to sell me a NoSQL database in a microservices architecture, I hope you have a solid understanding of the trade-offs involved, and you're not just trying to follow a trend or reciting to me some buzzwords that you've heard on a merchant of complexity blog.",[35,17657,17658],{},"Making wrong assumptions and poor understanding of the ecosystem leads to poor design decisions. This creates what we call technical debt. And like every debt, you'll have to pay it back one day, and the more you accumulate, the harder it becomes to manage. By trying to build software without the required knowledge, you become a generator of technical debt. Nothing more.",[35,17660,17661],{},"In a personal context, that's not a problem. It's your sandbox, play with it. You can throw away your code or rewrite it. In a business context, the stakes are much higher. Poor design decisions can lead to project failures, wasted resources, and lost opportunities. It's crucial to take the time to make informed decisions.",[1159,17663],{},[35,17665,17666],{},"If you're still reading, I hope convinced you how more complex the role of a software developer is. If that's not the case, let me show you something.",[35,17668,17669],{},"Modern applications are not simple from an external point of view and depending on the scale you're working, the complexity can quickly escalate. For this part, let's imagine a pretty standard application with manageable traffic. (i.e. not Facebook, Uber or Twitter scale)",[35,17671,17672],{},"Your application has a frontend. With this frontend, written using HTML, CSS, certainly with a framework like Tailwind CSS or Panda CSS, and JavaScript. For the JavaScript, you can use a library like Alpine, Unpoly, HTMX, Hotwire or Livewire if you're using Laravel. But you may decide to use a more robust framework like React, Vue.js, Angular, Svelte, etc. You may also prefer to use a meta-framework like Next, React Router, Nuxt, SvelteKit. But we're far from being done. The frontend provides a user interface to deliver a user experience to a user, a real person that has a real need. So you may need to consider accessibility, performance, responsiveness. This requires you to spend some time thinking about the user interface (UI) and the user experience (UX). Honestly, it's a lot of work so you may consider picking a component library, or building your own based on the design system of your company. If you're using Vue, you may choose Nuxt UI, PrimeVue, Quasar, Vuetify, Reka UI, Shadcn-vue, Native UI, Volt UI, Daisy UI, Flowbite Vue, Element Plus, Ant Design Vue, Ark UI or Vuestic UI. Maybe I should also mention that depending on the architecture, you may need a data fetching layer like TanStack Query, and if you're building a lot of tables, TanStack Tables. I'll spare you store management libraries and animation libraries, you could be overwhelmed.",[35,17674,17675],{},"That's just the frontend, and I haven't mentioned Vite. Yes, that's already a lot.",[35,17677,17678],{},"Now, imagine the backend, with all the different languages and frameworks available, the database systems with SQL, NoSQL, TimeSeries, Graph databases and each flavor for each, the queue systems like RabbitMQ, Kafka, or a simple database table, caching systems like Redis or Memcached, all the managed versions provided by the cloud providers, the different techniques used to test, build, and deploy your application, using a CI\u002FCD pipeline, Docker, Compose, Swarm, or Kubernetes, with a staging environment, and all the repository management. There is so much to consider, so much to learn, and so much new stuff every day that if you don't keep up, you're instantly behind.",[35,17680,17681],{},"Have I mentioned security, observability, high-availability, scalability, elasticity or deployment? Because it's also part of the job.",[35,17683,17684],{},"Honestly, you don't have to know how to use everything I mentioned. But if 90% of the words sound unfamiliar, then trying to pretend that you know what you're doing is a terrible idea. For you, for your team, and for the whole company you're working for.",[35,17686,17687],{},"Technical debt is real. Saying \"I don't know\" is an honest and comprehensive answer.",[1159,17689],{},[939,17691,17692],{},[35,17693,17694],{},"Computing has become infrastructure, but it doesn't work like infrastructure.",[35,17696,17697],{},"This article is also worth reading:",[35,17699,17700],{},[39,17701,17704],{"href":17702,"rel":17703},"https:\u002F\u002Fwww.theatlantic.com\u002Ftechnology\u002Farchive\u002F2015\u002F11\u002Fprogrammers-should-not-call-themselves-engineers\u002F414271\u002F",[77],"Programmers: Stop Calling Yourselves Engineers",[35,17706,17707],{},"And this video worth watching:",[35,17709,17710,17715],{},[39,17711,17714],{"href":17712,"rel":17713},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=Zki4d6sHhy4",[77],"Building High-Trust Environments"," by John Drexler",[35,17717,17718],{},"If you want to start a career as a software engineer or a software developer, I must inform you that it requires sacrifice and dedication. Like a lot of other professions, you won't become good just by reading or watching a video or by listening to a professor for a couple of hours. You won't become the best without immersing yourself completely in the field.",[35,17720,17721],{},"Maybe, by seeing a software developer at work, everything may seem simple. It's probably because it knows its work without having to sell you complexity to justify its existence.",[939,17723,17724],{},[35,17725,17726,17727,17732],{},"It's hard to sell simple, because simple looks easy, and who wants to pay for that?\nFrom ",[39,17728,17731],{"href":17729,"rel":17730},"https:\u002F\u002Fworld.hey.com\u002Fdhh\u002Fmerchants-of-complexity-4851301b",[77],"Merchants of Complexity"," by David Heinemeier Hansson",{"title":13,"searchDepth":14,"depth":14,"links":17734},[],"2025-09-04","My personal take on the software engineering landscape. You may not agree with me, and I would love to hear your thoughts.",{},"\u002Fposts\u002Fyoure-not-a-software-engineer-sorry-but-thats-okay","---\ncontentId: aefb6e0d-6566-43ca-a39d-e26ae877ce56\ntitle: You're not a software engineer, sorry, but that's okay\ndescription: My personal take on the software engineering landscape. You may not agree with me, and I would love to hear your thoughts.\ndate: 2025-09-04\n---\n\n_You're not a software engineer, sorry, but that's okay._\n\nI know, it can be hard to hear. But I'm sure you have other qualities.\n\n---\n\nIf you found this article by yourself, you may not be the target. On the other hand, if I sent it to you personally, you should probably read it and take a moment to think about it.\n\nAlso, this article is focused on web development and related fields. I won't talk about other areas of software engineering like embedded systems or data science.\n\n---\n\nBefore going further, how do we get there and what are we discussing?\n\nI'm facing a real challenge here. As code becomes more and more accessible, people tend to think that they can just jump in and start building complex systems without any knowledge, that software developers are just a financial hole without any real value. I'm just tired of explaining my job, again and again. I have the freedom to write my thoughts once and for all in a clear way, and share them with anyone who thinks they can do my job. So here we are.\n\nIn today's world, more and more people are writing code. The whole society is becoming digitalized. This means that there's never been a greater need to write software.\n\nSome people saw an opportunity to make money. What about telling people that they can become rich easily, from a chair in an office, just by learning for 2 to 6 months by selling a training? Great idea! Bootcamps were born.\n\nThis was a scam.\n\nLearning code is much harder and takes way more time than 2 to 6 months. Also, only good developers can make a lot of money. You won't become rich. And even worse, you won't have a job because the market has been saturated with inexperienced junior developers, hoping to find a well-paid job.\n\nBut the story doesn't end here.\n\nThis movement came before LLMs. With AI assistants like ChatGPT or Claude Code, and AI builders like Loveable, V0 or Bolt.new, coding has never been more accessible than it is today, and that's terrible.\n\nAbsolutely everyone can now write code. _The exact sentence should be \"Absolutely everyone can ask a LLM to generate code for them.\"_ The barrier to entry was already low with languages like Python. Now, with AI, barriers are gone.\n\n_Don't get me wrong. If this can create a vocation, or allow individuals to express themselves through code without having to deal with all the complexity, it's a good thing._\n\nThe truth is, **writing code is the easiest part of the job**. If you find it difficult, it's probably because you're on the early days of your journey. If you think you're a software engineer by having followed a bootcamp or by using AI tools, you should take a step back, and continue reading. I'm writing this article for you.\n\nThe real world, the adult world, is much more complex than a frontend todo list written with HTML and CSS.\n\nCompanies have specific business requirements. Most of the time, they find it difficult to express them clearly. People who have a need aren't from this domain; they don't understand what we're doing. That's ok, but as a software engineer, your role is to bridge the gap.\n\nOne of your challenges is to extract the need, to understand it, to challenge it, and build a clear and coherent vision from it. Then, you have to design an appropriate solution. This is part of the engineering job implied by the title \"software engineer.\"\n\nAI assistants and AI builders won't magically do that alone. Of course, they can help you, but they won't understand the whole business because there is a lot of implicit knowledge and context that only a human can grasp.\n\n---\n\nNow we all have the history in mind. Let's have a closer look at the \"software engineer\" title.\n\nFrom the Wikipedia definition, _software engineering is a branch of both computer science and engineering focused on designing, developing, testing, and maintaining software applications. It involves applying engineering principles and computer programming expertise to develop software systems that meet user needs. The terms programmer and coder overlap with software engineer, but they imply only the construction aspect of a typical software engineer workload_.\n\nI really love that definition because it says absolutely everything about the job.\n\nBuilding software involves science, both theoretical and practical. It's not just about chance or intuition. It's about understanding, analyzing, and solving problems. Also, four tasks are highlighted: designing, developing, testing, and maintaining. When I said earlier that writing code is the easiest part of the job, it's because it's only one of the four tasks, despite being the most visible one. And compared to the other tasks, writing code is a no-brainer.\n\nAnd don't forget that all of this must be done to solve a real user need. Otherwise, you're just writing code for the sake of writing code, which is useless. By using AI tools to write most of your code, and by only focusing on the code, there is a high probability that you're more of a programmer or a coder than a software engineer.\n\nA while ago, [I wrote about testing](https:\u002F\u002Fsoubiran.dev\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests). You should read it to have a first appreciation of the topic, because it's not AI builders who will ensure the quality of your code and testing is truly difficult.\n\nI won't go deeper into the designing and maintaining aspects, but if you're trying to sell me a NoSQL database in a microservices architecture, I hope you have a solid understanding of the trade-offs involved, and you're not just trying to follow a trend or reciting to me some buzzwords that you've heard on a merchant of complexity blog.\n\nMaking wrong assumptions and poor understanding of the ecosystem leads to poor design decisions. This creates what we call technical debt. And like every debt, you'll have to pay it back one day, and the more you accumulate, the harder it becomes to manage. By trying to build software without the required knowledge, you become a generator of technical debt. Nothing more.\n\nIn a personal context, that's not a problem. It's your sandbox, play with it. You can throw away your code or rewrite it. In a business context, the stakes are much higher. Poor design decisions can lead to project failures, wasted resources, and lost opportunities. It's crucial to take the time to make informed decisions.\n\n---\n\nIf you're still reading, I hope convinced you how more complex the role of a software developer is. If that's not the case, let me show you something.\n\nModern applications are not simple from an external point of view and depending on the scale you're working, the complexity can quickly escalate. For this part, let's imagine a pretty standard application with manageable traffic. (i.e. not Facebook, Uber or Twitter scale)\n\nYour application has a frontend. With this frontend, written using HTML, CSS, certainly with a framework like Tailwind CSS or Panda CSS, and JavaScript. For the JavaScript, you can use a library like Alpine, Unpoly, HTMX, Hotwire or Livewire if you're using Laravel. But you may decide to use a more robust framework like React, Vue.js, Angular, Svelte, etc. You may also prefer to use a meta-framework like Next, React Router, Nuxt, SvelteKit. But we're far from being done. The frontend provides a user interface to deliver a user experience to a user, a real person that has a real need. So you may need to consider accessibility, performance, responsiveness. This requires you to spend some time thinking about the user interface (UI) and the user experience (UX). Honestly, it's a lot of work so you may consider picking a component library, or building your own based on the design system of your company. If you're using Vue, you may choose Nuxt UI, PrimeVue, Quasar, Vuetify, Reka UI, Shadcn-vue, Native UI, Volt UI, Daisy UI, Flowbite Vue, Element Plus, Ant Design Vue, Ark UI or Vuestic UI. Maybe I should also mention that depending on the architecture, you may need a data fetching layer like TanStack Query, and if you're building a lot of tables, TanStack Tables. I'll spare you store management libraries and animation libraries, you could be overwhelmed.\n\nThat's just the frontend, and I haven't mentioned Vite. Yes, that's already a lot.\n\nNow, imagine the backend, with all the different languages and frameworks available, the database systems with SQL, NoSQL, TimeSeries, Graph databases and each flavor for each, the queue systems like RabbitMQ, Kafka, or a simple database table, caching systems like Redis or Memcached, all the managed versions provided by the cloud providers, the different techniques used to test, build, and deploy your application, using a CI\u002FCD pipeline, Docker, Compose, Swarm, or Kubernetes, with a staging environment, and all the repository management. There is so much to consider, so much to learn, and so much new stuff every day that if you don't keep up, you're instantly behind.\n\nHave I mentioned security, observability, high-availability, scalability, elasticity or deployment? Because it's also part of the job.\n\nHonestly, you don't have to know how to use everything I mentioned. But if 90% of the words sound unfamiliar, then trying to pretend that you know what you're doing is a terrible idea. For you, for your team, and for the whole company you're working for.\n\nTechnical debt is real. Saying \"I don't know\" is an honest and comprehensive answer.\n\n---\n\n> Computing has become infrastructure, but it doesn't work like infrastructure.\n\nThis article is also worth reading:\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\nAnd this video worth watching:\n\n[Building High-Trust Environments](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=Zki4d6sHhy4) by John Drexler\n\nIf you want to start a career as a software engineer or a software developer, I must inform you that it requires sacrifice and dedication. Like a lot of other professions, you won't become good just by reading or watching a video or by listening to a professor for a couple of hours. You won't become the best without immersing yourself completely in the field.\n\nMaybe, by seeing a software developer at work, everything may seem simple. It's probably because it knows its work without having to sell you complexity to justify its existence.\n\n> It's hard to sell simple, because simple looks easy, and who wants to pay for that?\n> From [Merchants of Complexity](https:\u002F\u002Fworld.hey.com\u002Fdhh\u002Fmerchants-of-complexity-4851301b) by David Heinemeier Hansson\n",{"title":17548,"description":17736},{"loc":17738},"posts\u002Fyoure-not-a-software-engineer-sorry-but-thats-okay","fWWQzaA6ZXenTzxDXb3HNJgge7xDXa19Rtd8kTY1uzc",{"id":17745,"title":17746,"body":17747,"contentId":9316,"date":17839,"description":17840,"extension":18,"meta":17841,"navigation":20,"path":17842,"rawbody":17843,"robots":23,"schemaOrg":23,"seo":17844,"sitemap":17845,"stem":17846,"__hash__":17847},"posts\u002Fposts\u002Fbeing-lost-an-essay-to-find-myself-within-uncertainty.md","Being lost. An essay to find myself within uncertainty.",{"type":7,"value":17748,"toc":17837},[17749,17754,17757,17760,17766,17769,17772,17775,17778,17781,17784,17787,17790,17793,17796,17799,17802,17805,17810,17812,17815,17818,17820,17823,17826,17829,17831,17834],[35,17750,17751],{},[1217,17752,17753],{},"Being lost in your own life. What a strange feeling.",[35,17755,17756],{},"Honestly, I do not know where this post will lead us. Maybe you will never read these lines and if you do, there is a chance you won't understand. This is a deep reflection on my journey during a moment of uncertainty.",[35,17758,17759],{},"Since weeks, I completely lost the pleasure of coding at work. Where I used to lack time in my day, now there is far too much of it, waiting for the end of the day.",[35,17761,17762,17763],{},"That's a problem, a real one. Not only because I'm completely unproductive, but also because this is leaking into other areas of my life. This means that even my personal projects are suffering and worse, getting off my couch is becoming a real effort. Knowing that I will share a moment with some friends, for a chilling or a party, is currently the only thing pushing me to get out. ",[1217,17764,17765],{},"Going to CrossFit is also a good reason to move.",[35,17767,17768],{},"Scrolling through my feeds is so much simpler, even if I know that it is just a temporary escape, an illusion which, when it fades away, leaves me in a worse state than before.",[35,17770,17771],{},"The worst part is that I have two conferences to build and to train, a SaaS to finish, and some live streams to do.",[35,17773,17774],{},"You will certainly say that I should take a break. You could be right. But if you take me away from my personal projects, the things that have driven me for years, even if it's hard for now, what will be left?",[35,17776,17777],{},"Also, taking a break does not solve the root cause of the problem, only the symptoms.",[35,17779,17780],{},"But how did I get there?",[35,17782,17783],{},"It's a multi-faceted problem. I've been thinking and writing some notes about it for two weeks, and gosh, this could be one of the hardest problems I've ever encountered. So many things are intertwined.",[35,17785,17786],{},"At work, every little action requires me to move mountains. Something that I can do at home within an hour or two now takes weeks. Not days, weeks. The most exhausting part is not really having to ask for permission, or anything else; it's more about the mental load that comes with it. When you have to constantly justify, ask, and wait, the project inevitably stagnates. This stagnation is incredibly frustrating and demotivating.",[35,17788,17789],{},"I strongly believe in the mindset of shipping early and often, even with the risk of failure. It's better to learn from small mistakes than to wait for the perfect moment that may never come. Currently, my velocity is zero.",[35,17791,17792],{},"At the same time, during a live stream where I discovered Java with a professional, I realized that another world exists, and that, in the end, I do not know a lot about web development. It might sound amazing, but it also makes me realize how much I have to learn. The only way for me to learn all that is to practice within a specialized company. With that in mind, and knowing that everything is way harder than it should be at work, I feel the need to change something.",[35,17794,17795],{},"The thing is, my current work is an incredible opportunity. There is literally nothing else like it in France. I'm building a space launcher. So yeah, I'm making a pros and cons list. Is there anything more overwhelming than having choices?",[35,17797,17798],{},"Now, add the overthinking, thinking about all the possibilities, analyzing every detail, and trying to predict every outcome that will never happen.",[35,17800,17801],{},"The perfect cocktail for being completely lost.",[35,17803,17804],{},"Welcome to my mind.",[35,17806,17807],{},[1217,17808,17809],{},"And, on top of all that, there's this quiet distraction. Someone whose presence lingers in my thoughts. It shouldn't complicate things, but it does. I'm on a rollercoaster.",[1159,17811],{},[35,17813,17814],{},"Is life just ironic sometimes, or is she really trying to send a message? I wonder. Recently, during a conversation in the subway, someone said that it must be great to turn a passion into a job. I have to admit that when all the stars align, it can be incredibly fulfilling. Everyone always talks about that alignment. I rarely hear about the misalignment, the struggles, and the doubts. It's like a house of cards, a fragile balance that can quickly collapse. When it does, your whole world, everything you built for years, can come crashing down.",[35,17816,17817],{},"As with everything, it's a trade-off. Unfortunately, few talk about the downsides.",[1159,17819],{},[35,17821,17822],{},"Maybe all of this is part of the process. I hope so. I really hope so.",[35,17824,17825],{},"This loss of control, for someone who used to try to control as much as possible, is quite unsettling. I'm overthinking everything to try to catch up, but maybe the only way out is to let go of control.",[35,17827,17828],{},"I'll see. Let time take its course.",[1159,17830],{},[35,17832,17833],{},"I'm writing despite being within this chaos, unable to find the right words and without seeing the light at the end of the tunnel. But I have two hopes. The first is that doing the exercises of introspection and trying to put words to my feelings helps me reduce the fog. The second is to have a snapshot of my current feelings and thoughts, that I can use later to understand how I got here and how I move forward.",[35,17835,17836],{},"Maybe you're connecting with this feeling too. At least, I hope you understand it.",{"title":13,"searchDepth":14,"depth":14,"links":17838},[],"2025-09-02","Sometimes, everything goes well. But when everything falls apart, it becomes really hard to find your way back.",{},"\u002Fposts\u002Fbeing-lost-an-essay-to-find-myself-within-uncertainty","---\ncontentId: a0605bb8-6058-496d-920a-561d1f5f2ce6\ntitle: Being lost. An essay to find myself within uncertainty.\ndescription: Sometimes, everything goes well. But when everything falls apart, it becomes really hard to find your way back.\ndate: 2025-09-02\n---\n\n_Being lost in your own life. What a strange feeling._\n\nHonestly, I do not know where this post will lead us. Maybe you will never read these lines and if you do, there is a chance you won't understand. This is a deep reflection on my journey during a moment of uncertainty.\n\nSince weeks, I completely lost the pleasure of coding at work. Where I used to lack time in my day, now there is far too much of it, waiting for the end of the day.\n\nThat's a problem, a real one. Not only because I'm completely unproductive, but also because this is leaking into other areas of my life. This means that even my personal projects are suffering and worse, getting off my couch is becoming a real effort. Knowing that I will share a moment with some friends, for a chilling or a party, is currently the only thing pushing me to get out. _Going to CrossFit is also a good reason to move._\n\nScrolling through my feeds is so much simpler, even if I know that it is just a temporary escape, an illusion which, when it fades away, leaves me in a worse state than before.\n\nThe worst part is that I have two conferences to build and to train, a SaaS to finish, and some live streams to do.\n\nYou will certainly say that I should take a break. You could be right. But if you take me away from my personal projects, the things that have driven me for years, even if it's hard for now, what will be left?\n\nAlso, taking a break does not solve the root cause of the problem, only the symptoms.\n\nBut how did I get there?\n\nIt's a multi-faceted problem. I've been thinking and writing some notes about it for two weeks, and gosh, this could be one of the hardest problems I've ever encountered. So many things are intertwined.\n\nAt work, every little action requires me to move mountains. Something that I can do at home within an hour or two now takes weeks. Not days, weeks. The most exhausting part is not really having to ask for permission, or anything else; it's more about the mental load that comes with it. When you have to constantly justify, ask, and wait, the project inevitably stagnates. This stagnation is incredibly frustrating and demotivating.\n\nI strongly believe in the mindset of shipping early and often, even with the risk of failure. It's better to learn from small mistakes than to wait for the perfect moment that may never come. Currently, my velocity is zero.\n\nAt the same time, during a live stream where I discovered Java with a professional, I realized that another world exists, and that, in the end, I do not know a lot about web development. It might sound amazing, but it also makes me realize how much I have to learn. The only way for me to learn all that is to practice within a specialized company. With that in mind, and knowing that everything is way harder than it should be at work, I feel the need to change something.\n\nThe thing is, my current work is an incredible opportunity. There is literally nothing else like it in France. I'm building a space launcher. So yeah, I'm making a pros and cons list. Is there anything more overwhelming than having choices?\n\nNow, add the overthinking, thinking about all the possibilities, analyzing every detail, and trying to predict every outcome that will never happen.\n\nThe perfect cocktail for being completely lost.\n\nWelcome to my mind.\n\n_And, on top of all that, there's this quiet distraction. Someone whose presence lingers in my thoughts. It shouldn't complicate things, but it does. I'm on a rollercoaster._\n\n---\n\nIs life just ironic sometimes, or is she really trying to send a message? I wonder. Recently, during a conversation in the subway, someone said that it must be great to turn a passion into a job. I have to admit that when all the stars align, it can be incredibly fulfilling. Everyone always talks about that alignment. I rarely hear about the misalignment, the struggles, and the doubts. It's like a house of cards, a fragile balance that can quickly collapse. When it does, your whole world, everything you built for years, can come crashing down.\n\nAs with everything, it's a trade-off. Unfortunately, few talk about the downsides.\n\n---\n\nMaybe all of this is part of the process. I hope so. I really hope so.\n\nThis loss of control, for someone who used to try to control as much as possible, is quite unsettling. I'm overthinking everything to try to catch up, but maybe the only way out is to let go of control.\n\nI'll see. Let time take its course.\n\n---\n\nI'm writing despite being within this chaos, unable to find the right words and without seeing the light at the end of the tunnel. But I have two hopes. The first is that doing the exercises of introspection and trying to put words to my feelings helps me reduce the fog. The second is to have a snapshot of my current feelings and thoughts, that I can use later to understand how I got here and how I move forward.\n\nMaybe you're connecting with this feeling too. At least, I hope you understand it.\n",{"title":17746,"description":17840},{"loc":17842},"posts\u002Fbeing-lost-an-essay-to-find-myself-within-uncertainty","2JLtncvHQPQmaXOjaAu_EUFC07pa5oL4a2EKM1TsmIs",{"id":17849,"title":17525,"body":17850,"contentId":5658,"date":18173,"description":18174,"extension":18,"meta":18175,"navigation":20,"path":18176,"rawbody":18177,"robots":23,"schemaOrg":23,"seo":18178,"sitemap":18179,"stem":18180,"__hash__":18181},"posts\u002Fposts\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things.md",{"type":7,"value":17851,"toc":18168},[17852,17857,17871,17895,17898,17900,17907,17916,17920,17926,17929,17932,17935,17946,17949,17952,17963,17966,17970,17973,17976,17979,17999,18005,18020,18026,18032,18053,18056,18059,18063,18069,18072,18079,18082,18085,18088,18091,18099,18102,18105,18111,18117,18120,18123,18137,18140,18143,18146,18149,18154,18157,18161,18163],[939,17853,17854],{},[35,17855,17856],{},"You don't need permission, you don't need to be chosen, you can just start, and you can just do things.",[35,17858,17859,17860,17865,17866,215],{},"This isn't from me. I would love to claim it. This quote is from ",[39,17861,17864],{"href":17862,"rel":17863},"https:\u002F\u002Fx.com\u002Faarondfrancis",[77],"Aaron Francis"," at ",[39,17867,17870],{"href":17868,"rel":17869},"https:\u002F\u002Fyoutu.be\u002Fdgr-WAUgELw",[77],"LaraconUS 2025",[35,17872,17873,17874,17879,17880,17885,17886,17879,17891,17894],{},"Sometimes, inexplicably, some phrases, whether from a friend, a book, a movie, or a talk, just hit you. This one hit me hard, as the continuation of the ",[39,17875,17878],{"href":17876,"rel":17877},"https:\u002F\u002Fyoutu.be\u002FNTMDzGLs8pw?t=3732",[77],"\"we must ship\""," from ",[39,17881,17884],{"href":17882,"rel":17883},"https:\u002F\u002Fx.com\u002Ftaylorotwell",[77],"Taylor Otwell",", the ",[39,17887,17890],{"href":17888,"rel":17889},"https:\u002F\u002Faaronfrancis.com\u002F2024\u002Fwhat-if-you-tried-hard-dac139a5",[77],"\"What if you tried hard?\"",[39,17892,17864],{"href":17862,"rel":17893},[77],", and years of experimenting with my own projects.",[35,17896,17897],{},"Behind this quote lies pragmatism, the pragmatism of building things and shipping them to real users, not just yourself. To transform ideas into reality, not to wait to be ready, but to take action and do it.",[1159,17899],{},[939,17901,17902],{},[35,17903,17904,17906],{},[200,17905,945],{},"\nThis is my personal point of view that I wanted to share. Mainly for my future self, but also for you, if you find it useful.",[35,17908,17909,17910,17915],{},"For years, ",[39,17911,17914],{"href":17912,"rel":17913},"https:\u002F\u002Fgithub.com\u002FBarbapapazes",[77],"my tagline on GitHub"," is \"I create user experiences on the web and in real life!\" There is nothing about code, and nothing about being a developer. It's completely intentional and I find it deeply linked to this quote and the overall mindset.",[51,17917,17919],{"id":17918},"am-i-a-developer","Am I a developer?",[35,17921,17922,17923],{},"Over time, I find it difficult to define myself as a developer. Reducing myself, and everything I create, to just \"someone who writes code\" really hurts me. ",[1217,17924,17925],{},"I could seriously dedicate an entire article to this topic.",[35,17927,17928],{},"You may wonder why, especially if you aren't from the industry.",[35,17930,17931],{},"Writing code is fun, and I personally love the technical challenges we meet along the way. I love solving highly technical challenges as well as watching highly technical conferences, and sharing my own technical challenges with my peers. But at the end of the day, most people don't care about the technical challenges you faced; I'm not sure they can even understand them.",[35,17933,17934],{},"This could be a detail, but it makes me sad. Because you have nothing to talk about with anyone. This is especially true when your friends or family aren't developers, and when you're the only software engineer, web-focused, in your company.",[1576,17936,17937,17940,17943],{},[71,17938,17939],{},"What have you done today? asks one of my colleagues.",[71,17941,17942],{},"Oh you know, I just revamped the whole internal business logic of the application using DDD to centralize the business rules, applying object calisthenics rules, inversion of control, and the tell-don't-ask principle, to make it more readable and maintainable, and thanks to TDD, everything should work as expected, I reply.",[71,17944,17945],{},"Nice, seems good, he says.",[35,17947,17948],{},"I know that he doesn't really understand what I just said. I don't blame him, that's not his job. And I have to admit that this is fuc*ing boring. Even for me. Even if this is the reality. I love all this stuff, but this is not what non-specialized people want to hear.",[35,17950,17951],{},"The truth is that, even today, I don't know how to explain what I do without reducing it to \"I write code\". At the same time, writing code isn't the part that matters most to me. Without very long explanations, it's nearly impossible to get the message across. And I don't want to bore people with this. Dilemma.",[35,17953,17954,17955],{},"This is at a point where one of my biggest fears is, when meeting someone new, having to answer the question \"What do you do for a living?\" I'm still looking for the right way to phrase it. ",[1217,17956,17957,17962],{},[39,17958,17961],{"href":17959,"rel":17960},"https:\u002F\u002Fsoubiran.dev\u002F#:~:text=currently%20working%20for%20a%20French%20space%20company%2C%20%C2%A0MaiaSpace%2C%20as%20an%20aerospace%20engineer",[77],"\"I'm building a launch vehicle.\""," Not really true, not really false, but at least, easily understandable.",[35,17964,17965],{},"So? Where does this lead us?",[51,17967,17969],{"id":17968},"share-your-passions","Share your passions",[35,17971,17972],{},"For a long time, I wrote code for the sake of writing code. No real purpose, no real goal, just writing code. I was happy with it and the number of lines written became a metric of my happiness. I became addicted to it, far too much. I can't recommend reaching this level to anyone. All my focus was on the code to the point that I was completely out of my own life.",[35,17974,17975],{},"But recently, my focus has shifted. I've completely moved away from this. I know for sure that the references I presented above helped me realize this.",[35,17977,17978],{},"Before telling you more about this, I want to share a little personal story because this change was a journey, not a single event that clicked in my mind.",[35,17980,17981,17982,1638,17987,17992,17993,17998],{},"Five years ago, I was a student in an engineering school. I was deeply involved in student life and an opportunity came to me. I was asked to continue the tutoring platform that aimed to help students find tutors. With two friends, we started to work on it. A lot of work to create an API, a web application using ",[39,17983,17986],{"href":17984,"rel":17985},"https:\u002F\u002Fgithub.com\u002FCampus-INSA-CVL\u002Ftutorinsa-client-vue",[77],"Vue",[39,17988,17991],{"href":17989,"rel":17990},"https:\u002F\u002Fgithub.com\u002FCampus-INSA-CVL\u002Ftutorinsa-client-react",[77],"React"," (don't ask me why both), and ",[39,17994,17997],{"href":17995,"rel":17996},"https:\u002F\u002Fgithub.com\u002FCampus-INSA-CVL\u002Ftutorinsa-mobile",[77],"a mobile application"," using React Native.",[35,18000,18001,18002],{},"We worked so hard on it, from May to October 2020, we put everything we had into it but we never reached the end users. We never shipped it. No one ever saw it. We learned a lot during the process and failure is part of it. We knew it. But it still hurts. ",[1217,18003,18004],{},"Trust the process.",[35,18006,18007,18008,18013,18014,18019],{},"A year after the end of the tutoring project, I started a new one called ",[39,18009,18012],{"href":18010,"rel":18011},"https:\u002F\u002Fgithub.com\u002Finsamee-app",[77],"Insamee"," with another friend. It was the continuation of the previous one, but this time, we wanted to build a whole ecosystem around it. Four platforms, ",[39,18015,18018],{"href":18016,"rel":18017},"https:\u002F\u002Fgithub.com\u002Finsamee-app\u002Fteam",[77],"TEAM",", to facilitate student life to grow. Ambitious.",[35,18021,18022,18023],{},"Seven months later, in April 2022, we had something that was working. However, you may guess how it ended. We never reached the end users. We never shipped it. Despite that, I had the ability to work on it full-time thanks to a school arrangement during two months. Amazing experience, would recommend it. ",[1217,18024,18025],{},"\"Try hard for a while. You might be surprised by what happens.\", Aaron Francis. In that case, I had the opportunity to skip classes.",[35,18027,18028,18029],{},"For both of these projects, we were way too focused on the code than on shipping it. ",[1217,18030,18031],{},"\"We must ship.\", Taylor Otwell. And celebrate.",[35,18033,18034,18035,18040,18041,18046,18047,18052],{},"A couple of months later, I built ",[39,18036,18039],{"href":18037,"rel":18038},"https:\u002F\u002Fgithub.com\u002FClassement-des-Associations\u002Fvote.le-classement.fr",[77],"a voting platform"," for an event named ",[39,18042,18045],{"href":18043,"rel":18044},"https:\u002F\u002Fle-classement.fr\u002F",[77],"Le Classement des Associations",". This time, I was alone, and I had a clear goal: to ship it. No room to fail, we had announced it publicly. In September 2022, ",[39,18048,18051],{"href":18049,"rel":18050},"https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Fla-plateforme-de-vote-du-classement-des-associations",[77],"I shipped it"," and for a month, more than 25k votes were cast. It was finally a success, after years of failures.",[35,18054,18055],{},"For the first time, I created a real moment for people. They were talking about it on social media, they were sharing it with their schoolmates, and they were using it. It was all digital but without any code, it would have never been possible.",[35,18057,18058],{},"At this point, something started to change but I wasn't aware of it yet.",[51,18060,18062],{"id":18061},"empower-moments","Empower moments",[35,18064,18065,18068],{},[6850,18066,18067],{},"Creating and sharing moments"," with people I really appreciate became my top priority. This creates much more meaning in my life.",[35,18070,18071],{},"This makes me wonder how I can use my passion for code to facilitate this and to empower these moments or to create new ones.",[35,18073,18074,18075,18078],{},"Before diving into this, I must acknowledge that always using code doesn't even make sense. ",[6850,18076,18077],{},"Sometimes, asking is better than anything else."," But occasionally, building something can be the thing to create more than a moment, an experience.",[35,18080,18081],{},"This is exactly what happened recently.",[35,18083,18084],{},"There is something I never shared before, but I love cooking, especially pastries. While the process of creating a pastry is already a moment, that can be shared to become even more intense, sharing the creation with friends around a table is, for me, the best part. It is a natural moment of connection and conviviality.",[35,18086,18087],{},"I recently talked about it with two friends, and I realized that it was difficult to show them what I was able to do. Of course, I have some photos, but among all my gallery, it takes time to find all of them. And I wanted to share more than just photos; I wanted to share the title of the recipe and a description. I wanted everything to be in one place, easily accessible to all my friends, without having to send them a bunch of photos each time I wanted to share something.",[35,18089,18090],{},"So I decided to build a centralized platform to share my creations. One of the requirements was to ensure that only my friends could access it. It turns out that I have a friend who owns and maintains his own Identity Provider for accessing his own applications. By using it, I was not only able to ensure that only my friends could access it, but I worked on a real-world use case with a very close friend. This is so much fun to work with friends on a project.",[35,18092,18093,18094,18098],{},"This was a quick project that gave me the opportunity to use ",[39,18095,18097],{"href":17082,"rel":18096},[77],"Typesense"," for the search. As you type, a request is sent to the server, and the results are displayed in less than 100ms. Wahoo effect is guaranteed.",[35,18100,18101],{},"Some days later, thinking back to some discussions I had with a friend, another idea came to my mind.",[35,18103,18104],{},"Cooking is great, but then, you have to eat the cake. Each weekend, I bake a cake and to be honest, eating a whole cake alone takes multiple days and then, another cake is ready. At first it was fun, but now, it's getting too much.",[35,18106,18107,18108],{},"During this conversation I had with her, she asked me why I didn't share it with my colleagues. I selfishly replied. But after all, she was right. Pastries can be used to create moments, in the same way I use code to create moments. Of course, they are different, but both are moments, and at the end, you share them with people you appreciate. ",[1217,18109,18110],{},"All the environment can be used to think out of the box.",[35,18112,18113,18114],{},"So I decided to extend this platform intelligently to my colleagues. Intelligently because I cannot use the same Identity Provider, and I can't use the one from my company. Also, I don't want to have to manage my creations on two different platforms but I want to keep the two platforms separate. Different codebases with shared data. ",[1217,18115,18116],{},"If the technical solution interests you, please let me know in the comments.",[35,18118,18119],{},"Perfect timing. I had 15 days of vacation. So I had to build it in 15 days, and to ship it at the end, ready or not. I shared a countdown with my team, just before leaving, to make it official and public. The countdown was set to stop on August 11th 2025 at 9:00 AM (UTC+2).",[35,18121,18122],{},"I had to iterate on the experience I wanted to deliver multiple times to ensure it was both useful, usable, and enjoyable. I stopped my reflection on the following points:",[1576,18124,18125,18128,18131,18134],{},[71,18126,18127],{},"Allow users to view my creations,",[71,18129,18130],{},"Allow users to suggest new recipes, based on new ideas or existing recipes,",[71,18132,18133],{},"Allow users to give feedback on my creations,",[71,18135,18136],{},"Allow users to know when I will bring the next cake to the office.",[35,18138,18139],{},"Only 15 days to build it. That's challenging because I had to balance time between building the platform and enjoying my vacation. It was not easy, sleep was short, but remember: \"Try hard for a while. You might be surprised by what happens.\"",[35,18141,18142],{},"I split my day into a (small) session of code, 40km biking, and a wing foil or a catamaran course. I repeated this cycle every day. Sometimes, a sunrise, sometimes the discovery of an island.",[35,18144,18145],{},"The last day, after 32 hours of thinking and coding, everything was ready to be shipped.",[35,18147,18148],{},"I'm writing these lines on Sunday, August 10th, the day before the launch. Everything is ready to go live tomorrow morning for my colleagues, as planned. I can't say if it will work, I don't know if they will like it, but I put everything I had into it.",[35,18150,18151],{},[6850,18152,18153],{},"I try hard and I ship it.",[35,18155,18156],{},"And whatever happens now, I will continue to create moments for people I care about. Sometimes with a message, sometimes with code, but always with passion.",[35,18158,18159],{},[1217,18160,18004],{},[1159,18162],{},[35,18164,18165],{},[1217,18166,18167],{},"For Julie and Jordan, without whom this project would never have seen the light of day. We shipped it!",{"title":13,"searchDepth":14,"depth":14,"links":18169},[18170,18171,18172],{"id":17918,"depth":14,"text":17919},{"id":17968,"depth":14,"text":17969},{"id":18061,"depth":14,"text":18062},"2025-08-09","I recently shifted. Writing code is no longer the end, but just a means to create and share moments with people I care about.",{},"\u002Fposts\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things","---\ncontentId: 47cda943-2e43-45d7-9257-0f98eaf651e6\ntitle: You Don't Need to Be Chosen, You Can Just Do Things\ndescription: I recently shifted. Writing code is no longer the end, but just a means to create and share moments with people I care about.\ndate: 2025-08-09\n---\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\nThis isn't from me. I would love to claim it. This quote is from [Aaron Francis](https:\u002F\u002Fx.com\u002Faarondfrancis) at [LaraconUS 2025](https:\u002F\u002Fyoutu.be\u002Fdgr-WAUgELw).\n\nSometimes, inexplicably, some phrases, whether from a friend, a book, a movie, or a talk, just hit you. This one hit me hard, as the continuation of the [\"we must ship\"](https:\u002F\u002Fyoutu.be\u002FNTMDzGLs8pw?t=3732) from [Taylor Otwell](https:\u002F\u002Fx.com\u002Ftaylorotwell), the [\"What if you tried hard?\"](https:\u002F\u002Faaronfrancis.com\u002F2024\u002Fwhat-if-you-tried-hard-dac139a5) from [Aaron Francis](https:\u002F\u002Fx.com\u002Faarondfrancis), and years of experimenting with my own projects.\n\nBehind this quote lies pragmatism, the pragmatism of building things and shipping them to real users, not just yourself. To transform ideas into reality, not to wait to be ready, but to take action and do it.\n\n---\n\n> [!NOTE]\n> This is my personal point of view that I wanted to share. Mainly for my future self, but also for you, if you find it useful.\n\nFor years, [my tagline on GitHub](https:\u002F\u002Fgithub.com\u002FBarbapapazes) is \"I create user experiences on the web and in real life!\" There is nothing about code, and nothing about being a developer. It's completely intentional and I find it deeply linked to this quote and the overall mindset.\n\n## Am I a developer?\n\nOver time, I find it difficult to define myself as a developer. Reducing myself, and everything I create, to just \"someone who writes code\" really hurts me. _I could seriously dedicate an entire article to this topic._\n\nYou may wonder why, especially if you aren't from the industry.\n\nWriting code is fun, and I personally love the technical challenges we meet along the way. I love solving highly technical challenges as well as watching highly technical conferences, and sharing my own technical challenges with my peers. But at the end of the day, most people don't care about the technical challenges you faced; I'm not sure they can even understand them.\n\nThis could be a detail, but it makes me sad. Because you have nothing to talk about with anyone. This is especially true when your friends or family aren't developers, and when you're the only software engineer, web-focused, in your company.\n\n- What have you done today? asks one of my colleagues.\n- Oh you know, I just revamped the whole internal business logic of the application using DDD to centralize the business rules, applying object calisthenics rules, inversion of control, and the tell-don't-ask principle, to make it more readable and maintainable, and thanks to TDD, everything should work as expected, I reply.\n- Nice, seems good, he says.\n\nI know that he doesn't really understand what I just said. I don't blame him, that's not his job. And I have to admit that this is fuc*ing boring. Even for me. Even if this is the reality. I love all this stuff, but this is not what non-specialized people want to hear.\n\nThe truth is that, even today, I don't know how to explain what I do without reducing it to \"I write code\". At the same time, writing code isn't the part that matters most to me. Without very long explanations, it's nearly impossible to get the message across. And I don't want to bore people with this. Dilemma.\n\nThis is at a point where one of my biggest fears is, when meeting someone new, having to answer the question \"What do you do for a living?\" I'm still looking for the right way to phrase it. _[\"I'm building a launch vehicle.\"](https:\u002F\u002Fsoubiran.dev\u002F#:~:text=currently%20working%20for%20a%20French%20space%20company%2C%20%C2%A0MaiaSpace%2C%20as%20an%20aerospace%20engineer) Not really true, not really false, but at least, easily understandable._\n\nSo? Where does this lead us?\n\n## Share your passions\n\nFor a long time, I wrote code for the sake of writing code. No real purpose, no real goal, just writing code. I was happy with it and the number of lines written became a metric of my happiness. I became addicted to it, far too much. I can't recommend reaching this level to anyone. All my focus was on the code to the point that I was completely out of my own life.\n\nBut recently, my focus has shifted. I've completely moved away from this. I know for sure that the references I presented above helped me realize this.\n\nBefore telling you more about this, I want to share a little personal story because this change was a journey, not a single event that clicked in my mind.\n\nFive years ago, I was a student in an engineering school. I was deeply involved in student life and an opportunity came to me. I was asked to continue the tutoring platform that aimed to help students find tutors. With two friends, we started to work on it. A lot of work to create an API, a web application using [Vue](https:\u002F\u002Fgithub.com\u002FCampus-INSA-CVL\u002Ftutorinsa-client-vue) and [React](https:\u002F\u002Fgithub.com\u002FCampus-INSA-CVL\u002Ftutorinsa-client-react) (don't ask me why both), and [a mobile application](https:\u002F\u002Fgithub.com\u002FCampus-INSA-CVL\u002Ftutorinsa-mobile) using React Native.\n\nWe worked so hard on it, from May to October 2020, we put everything we had into it but we never reached the end users. We never shipped it. No one ever saw it. We learned a lot during the process and failure is part of it. We knew it. But it still hurts. _Trust the process._\n\nA year after the end of the tutoring project, I started a new one called [Insamee](https:\u002F\u002Fgithub.com\u002Finsamee-app) with another friend. It was the continuation of the previous one, but this time, we wanted to build a whole ecosystem around it. Four platforms, [TEAM](https:\u002F\u002Fgithub.com\u002Finsamee-app\u002Fteam), to facilitate student life to grow. Ambitious.\n\nSeven months later, in April 2022, we had something that was working. However, you may guess how it ended. We never reached the end users. We never shipped it. Despite that, I had the ability to work on it full-time thanks to a school arrangement during two months. Amazing experience, would recommend it. _\"Try hard for a while. You might be surprised by what happens.\", Aaron Francis. In that case, I had the opportunity to skip classes._\n\nFor both of these projects, we were way too focused on the code than on shipping it. _\"We must ship.\", Taylor Otwell. And celebrate._\n\nA couple of months later, I built [a voting platform](https:\u002F\u002Fgithub.com\u002FClassement-des-Associations\u002Fvote.le-classement.fr) for an event named [Le Classement des Associations](https:\u002F\u002Fle-classement.fr\u002F). This time, I was alone, and I had a clear goal: to ship it. No room to fail, we had announced it publicly. In September 2022, [I shipped it](https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Fla-plateforme-de-vote-du-classement-des-associations) and for a month, more than 25k votes were cast. It was finally a success, after years of failures.\n\nFor the first time, I created a real moment for people. They were talking about it on social media, they were sharing it with their schoolmates, and they were using it. It was all digital but without any code, it would have never been possible.\n\nAt this point, something started to change but I wasn't aware of it yet.\n\n## Empower moments\n\n**Creating and sharing moments** with people I really appreciate became my top priority. This creates much more meaning in my life.\n\nThis makes me wonder how I can use my passion for code to facilitate this and to empower these moments or to create new ones.\n\nBefore diving into this, I must acknowledge that always using code doesn't even make sense. **Sometimes, asking is better than anything else.** But occasionally, building something can be the thing to create more than a moment, an experience.\n\nThis is exactly what happened recently.\n\nThere is something I never shared before, but I love cooking, especially pastries. While the process of creating a pastry is already a moment, that can be shared to become even more intense, sharing the creation with friends around a table is, for me, the best part. It is a natural moment of connection and conviviality.\n\nI recently talked about it with two friends, and I realized that it was difficult to show them what I was able to do. Of course, I have some photos, but among all my gallery, it takes time to find all of them. And I wanted to share more than just photos; I wanted to share the title of the recipe and a description. I wanted everything to be in one place, easily accessible to all my friends, without having to send them a bunch of photos each time I wanted to share something.\n\nSo I decided to build a centralized platform to share my creations. One of the requirements was to ensure that only my friends could access it. It turns out that I have a friend who owns and maintains his own Identity Provider for accessing his own applications. By using it, I was not only able to ensure that only my friends could access it, but I worked on a real-world use case with a very close friend. This is so much fun to work with friends on a project.\n\nThis was a quick project that gave me the opportunity to use [Typesense](https:\u002F\u002Ftypesense.org\u002F) for the search. As you type, a request is sent to the server, and the results are displayed in less than 100ms. Wahoo effect is guaranteed.\n\nSome days later, thinking back to some discussions I had with a friend, another idea came to my mind.\n\nCooking is great, but then, you have to eat the cake. Each weekend, I bake a cake and to be honest, eating a whole cake alone takes multiple days and then, another cake is ready. At first it was fun, but now, it's getting too much.\n\nDuring this conversation I had with her, she asked me why I didn't share it with my colleagues. I selfishly replied. But after all, she was right. Pastries can be used to create moments, in the same way I use code to create moments. Of course, they are different, but both are moments, and at the end, you share them with people you appreciate. _All the environment can be used to think out of the box._\n\nSo I decided to extend this platform intelligently to my colleagues. Intelligently because I cannot use the same Identity Provider, and I can't use the one from my company. Also, I don't want to have to manage my creations on two different platforms but I want to keep the two platforms separate. Different codebases with shared data. _If the technical solution interests you, please let me know in the comments._\n\nPerfect timing. I had 15 days of vacation. So I had to build it in 15 days, and to ship it at the end, ready or not. I shared a countdown with my team, just before leaving, to make it official and public. The countdown was set to stop on August 11th 2025 at 9:00 AM (UTC+2).\n\nI had to iterate on the experience I wanted to deliver multiple times to ensure it was both useful, usable, and enjoyable. I stopped my reflection on the following points:\n\n- Allow users to view my creations,\n- Allow users to suggest new recipes, based on new ideas or existing recipes,\n- Allow users to give feedback on my creations,\n- Allow users to know when I will bring the next cake to the office.\n\nOnly 15 days to build it. That's challenging because I had to balance time between building the platform and enjoying my vacation. It was not easy, sleep was short, but remember: \"Try hard for a while. You might be surprised by what happens.\"\n\nI split my day into a (small) session of code, 40km biking, and a wing foil or a catamaran course. I repeated this cycle every day. Sometimes, a sunrise, sometimes the discovery of an island.\n\nThe last day, after 32 hours of thinking and coding, everything was ready to be shipped.\n\nI'm writing these lines on Sunday, August 10th, the day before the launch. Everything is ready to go live tomorrow morning for my colleagues, as planned. I can't say if it will work, I don't know if they will like it, but I put everything I had into it.\n\n**I try hard and I ship it.**\n\nAnd whatever happens now, I will continue to create moments for people I care about. Sometimes with a message, sometimes with code, but always with passion.\n\n_Trust the process._\n\n---\n\n_For Julie and Jordan, without whom this project would never have seen the light of day. We shipped it!_\n",{"title":17525,"description":18174},{"loc":18176},"posts\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things","jRS07LnTtR3Yk5Ci-vb5w0zAEFrXHFZVCYasOjqgygM",{"id":18183,"title":18184,"body":18185,"contentId":9296,"date":18581,"description":18582,"extension":18,"meta":18583,"navigation":20,"path":18584,"rawbody":18585,"robots":23,"schemaOrg":23,"seo":18586,"sitemap":18587,"stem":18593,"__hash__":18594},"posts\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker.md","BreizhCamp 2025: My First Time in Brittany as a Speaker",{"type":7,"value":18186,"toc":18575},[18187,18196,18204,18207,18212,18218,18221,18224,18227,18436],[35,18188,18189,18190,18195],{},"This year, continuing my speaking journey, I had the pleasure of attending ",[39,18191,18194],{"href":18192,"rel":18193},"https:\u002F\u002Fwww.breizhcamp.org\u002F",[77],"BreizhCamp 2025"," in Brittany, France. It was a delight to present outside of Paris, engaging with a new audience and networking with unknown faces.",[35,18197,18198,18199,13731,18201,18203],{},"BreizhCamp is a smaller conference compared to ",[39,18200,7142],{"href":8973},[39,18202,7484],{"href":7487},", hosting around 700 attendees daily, with five tracks of talks and 96 speakers.",[35,18205,18206],{},"Personally, I favor conferences of a more intimate scale. They provide the opportunity to connect with others and engage in meaningful discussions.",[35,18208,18209],{},[1217,18210,18211],{},"If someone from the BreizhCamp team is reading this, kudos for the fantastic organization! 🫶",[35,18213,18214],{},[1690,18215],{"alt":18216,"src":18217},"The place for breakfast, coffee breaks, and meals","\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker\u002Fbreizhcamp-2025.jpeg",[51,18219,18194],{"id":18220},"breizhcamp-2025",[35,18222,18223],{},"If you've followed my blog, you know I never review the schedule before the morning of the conference, just minutes before it starts. This time was no exception. You might ask why, and the reason is simple: it minimizes cognitive load. Having just a few minutes to choose forces decisive action without overthinking.",[35,18225,18226],{},"Here are the talks I attended:",[1576,18228,18229,18291,18384],{},[71,18230,18231,239,18234],{},[6850,18232,18233],{},"Wednesday, June 25, 2025",[1576,18235,18236,18251,18256,18271],{},[71,18237,18238,6853,18241,1638,18246],{},[6850,18239,18240],{},"Enter the Daggerverse !",[39,18242,18245],{"href":18243,"rel":18244},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-haumont-834a5427\u002F",[77],"Julien Haumont",[39,18247,18250],{"href":18248,"rel":18249},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Femeric-martineau\u002F",[77],"Emeric Martineau",[71,18252,18253,18255],{},[6850,18254,9124],{}," by Estéban Soubiran (that's me!)",[71,18257,18258,6853,18261,1638,18266],{},[6850,18259,18260],{},"Redécouvrir la coopération : Atelier de Mob Programming",[39,18262,18265],{"href":18263,"rel":18264},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmanon-carbonnel\u002F",[77],"Manon Carbonnel",[39,18267,18270],{"href":18268,"rel":18269},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthomas-ployon-8bb9a2176\u002F",[77],"Thomas Ployon",[71,18272,18273,6853,18276,3076,18281,3080,18286],{},[6850,18274,18275],{},"Démystifions nos clean architectures",[39,18277,18280],{"href":18278,"rel":18279},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fyann-schepens-a279871a\u002F",[77],"Yann Schepens",[39,18282,18285],{"href":18283,"rel":18284},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolas-giraud-17a44383\u002F",[77],"Nicolas Giraud",[39,18287,18290],{"href":18288,"rel":18289},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ftblaisot\u002F",[77],"Thomas Blaisot",[71,18292,18293,239,18296],{},[6850,18294,18295],{},"Thursday, June 26, 2025",[1576,18297,18298,18306,18316,18329,18334,18344,18354,18364,18374],{},[71,18299,18300,6853,18303],{},[6850,18301,18302],{},"Comment nous avons transformé les Restos du Coeur en Cloud Provider",[39,18304,6943],{"href":6941,"rel":18305},[77],[71,18307,18308,6853,18311],{},[6850,18309,18310],{},"HTTP\u002F3 : 36 ans d'histoire du protocole HTTP",[39,18312,18315],{"href":18313,"rel":18314},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fchrisnavas\u002F",[77],"Chris Navas",[71,18317,18318,6853,18321,1638,18324],{},[6850,18319,18320],{},"“Make the change easy”: Apprivoiser le legacy avec les Object Calisthenics",[39,18322,18265],{"href":18263,"rel":18323},[77],[39,18325,18328],{"href":18326,"rel":18327},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgael-salaun\u002F",[77],"Gaël Salaün",[71,18330,18331,18333],{},[6850,18332,13959],{}," by Estéban Soubiran (that's me again!)",[71,18335,18336,6853,18339],{},[6850,18337,18338],{},"Amusons-nous avec jj, une alternative à git !",[39,18340,18343],{"href":18341,"rel":18342},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fharold-favereau-32809a255\u002F",[77],"Harold Favereau",[71,18345,18346,6853,18349],{},[6850,18347,18348],{},"Savez-vous vraiment ce qu'est la couleur ? Découvrez la science derrière les pixels 🎨",[39,18350,18353],{"href":18351,"rel":18352},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-sulpis\u002F",[77],"Julien Sulpis",[71,18355,18356,6853,18359],{},[6850,18357,18358],{},"🧑‍🎤🎸 La preuve de programme vous fera apprécier les tests",[39,18360,18363],{"href":18361,"rel":18362},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpunkstarman\u002F",[77],"William Bartlett",[71,18365,18366,6853,18369],{},[6850,18367,18368],{},"Apprend les langues à ton IDE avec LSP",[39,18370,18373],{"href":18371,"rel":18372},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmaxime-reynier\u002F",[77],"Maxime Reynier",[71,18375,18376,6853,18379],{},[6850,18377,18378],{},"Gaming et Industrie : La Double Vie d'Unreal Engine",[39,18380,18383],{"href":18381,"rel":18382},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fenora-landurein-b51368172\u002F",[77],"Enora Landurein",[71,18385,18386,239,18389],{},[6850,18387,18388],{},"Friday, June 27, 2025",[1576,18390,18391,18401,18411,18421],{},[71,18392,18393,6853,18396],{},[6850,18394,18395],{},"Rendre son back générique grâce au pattern composite",[39,18397,18400],{"href":18398,"rel":18399},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falexandre-baron-44b256156\u002F",[77],"Alexandre Baron",[71,18402,18403,6853,18406],{},[6850,18404,18405],{},"Modular monolith : la dernière croisade des architectures ?",[39,18407,18410],{"href":18408,"rel":18409},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpiscitellit\u002F",[77],"Thomas Piscitelli",[71,18412,18413,6853,18416],{},[6850,18414,18415],{},"Comment sont implémentés les containers sous Linux ?",[39,18417,18420],{"href":18418,"rel":18419},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fchristophe-quintard\u002F",[77],"Christophe Quintard",[71,18422,18423,6853,18426,1638,18431],{},[6850,18424,18425],{},"Le pattern Hive : une stratégie de modularisation pour votre monolithe modulaire ou vos microservices",[39,18427,18430],{"href":18428,"rel":18429},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-top%C3%A7u\u002F",[77],"Julien Topçu",[39,18432,18435],{"href":18433,"rel":18434},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthomas-p-0664769\u002F",[77],"Thomas Pierrain",[7139,18437,18439,18442,18444,18447,18450,18499,18502,18506,18509,18519,18529,18535,18541,18545,18548,18553,18556,18559,18562,18570],{"icon":7141,"text":9007,"to":18438,"target":7144},"https:\u002F\u002Fwww.youtube.com\u002F@BreizhCamp",[35,18440,18441],{},"This is the complete list of talks I attended. I highly recommend checking out the conference's YouTube channel for all available presentations.",[51,18443,16676],{"id":16675},[35,18445,18446],{},"When recapping a conference, I love highlighting the talks that really impressed me, and BreizhCamp 2025 was no exception, with a lineup of both inspiring and technical presentations.",[35,18448,18449],{},"Two particular talks amazed me:",[1576,18451,18452,18480],{},[71,18453,18454,6853,18456,18459,18461,18462,18467,18468,18473,18474,18476],{},[6850,18455,18302],{},[39,18457,6943],{"href":6941,"rel":18458},[77],[7162,18460],{},"This wasn't my first time hearing Julien speak about the ",[39,18463,18466],{"href":18464,"rel":18465},"https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fcloud-du-coeur\u002Fposts",[77],"Cloud du Coeur",", and it's always a pleasure. He manages to deliver talks that are amusing, informative, and technical simultaneously. The work he does for the ",[39,18469,18472],{"href":18470,"rel":18471},"https:\u002F\u002Fwww.restosducoeur.org\u002F",[77],"Restos du Coeur"," is significant and impactful. Watching his talk is highly recommended as it inspires and exemplifies how technology can serve the greater good.",[7162,18475],{},[1690,18477],{"alt":18478,"src":18479},"Julien Briault presenting at 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,18481,18482,6853,18484,1638,18487,18490,18492,18493,18495],{},[6850,18483,18320],{},[39,18485,18265],{"href":18263,"rel":18486},[77],[39,18488,18328],{"href":18326,"rel":18489},[77],[7162,18491],{},"This talk was a perfect blend of technical insight, practical advice, and humor. The examples were exceptionally well-chosen, simple enough to understand yet realistic enough for real-world application. I learned something valuable that I intend to apply in my work. The 45 minutes flew by, and I wished it could have lasted longer.",[7162,18494],{},[1690,18496],{"alt":18497,"src":18498},"Manon Carbonnel and Gaël Salaün presenting at 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,18500,18501],{},"I highly recommend watching these talks. Enjoy!",[51,18503,18505],{"id":18504},"my-talks","My talks",[35,18507,18508],{},"At BreizhCamp 2025, I was thrilled to present two talks:",[68,18510,18511,18515],{},[71,18512,18513],{},[6850,18514,9124],{},[71,18516,18517],{},[6850,18518,13959],{},[35,18520,18521,18522,1638,18525,18528],{},"After presenting them at ",[39,18523,18524],{"href":8973},"Devoxx France 2025",[39,18526,18527],{"href":7487},"MiXiT 2025",", it feels great to deliver them again. I'm proud of these talks, and presenting them now is always enjoyable. With familiarity, I can truly enjoy the moment.",[35,18530,18531,18532],{},"To add a twist, I challenged myself: I caught the train to Rennes on Wednesday morning at 6:30 AM, with my talk scheduled for 11:00 AM. The night before, I attended a Takima meetup, resulting in only three hours of sleep. I gave my all during the talk, but the afternoon session was challenging. ",[1217,18533,18534],{},"Don't attempt this, this is memorable, but not advisable!",[35,18536,18537],{},[1690,18538],{"alt":18539,"src":18540},"Me, presenting at 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,18542,18544],{"id":18543},"the-end-of-a-season","The end of a season",[35,18546,18547],{},"There's a saying:",[939,18549,18550],{},[35,18551,18552],{},"La Bretagne, ça vous gagne !",[35,18554,18555],{},"which means: Brittany will win you over! Indeed, BreizhCamp 2025 won me over. I thoroughly enjoyed the conference, meeting new people, and the overall ambiance. I will return next year and hope to see you there!",[35,18557,18558],{},"Speaking of next year, this was the final conference for the 2024\u002F2025 season for me. It was an incredible season, with 13 talks over 10 months. Truly amazing!",[35,18560,18561],{},"The new season starts in September 2025, and exciting plans are already on the horizon. I can confirm a speaking engagement at an upcoming meetup in Paris, a conference in Angers, and some fresh talks to announce in the coming months.",[35,18563,18564,18565,215],{},"I'll continue streaming on Twitch, doing live coding sessions, slide creation, and surprises. Stay tuned and follow me on ",[39,18566,18569],{"href":18567,"rel":18568},"https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes",[77],"Twitch",[35,18571,18572],{},[1217,18573,18574],{},"The end of one thing is the beginning of another. Enjoy the journey!",{"title":13,"searchDepth":14,"depth":14,"links":18576},[18577,18578,18579,18580],{"id":18220,"depth":14,"text":18194},{"id":16675,"depth":14,"text":16676},{"id":18504,"depth":14,"text":18505},{"id":18543,"depth":14,"text":18544},"2025-06-29","Always a pleasure to present outside of Paris, BreizhCamp 2025 was a great opportunity to engage with a new audience and network.",{},"\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker","---\ncontentId: d1269015-72b0-45ae-86d8-f898a5fd35ee\ntitle: \"BreizhCamp 2025: My First Time in Brittany as a Speaker\"\ndescription: \"Always a pleasure to present outside of Paris, BreizhCamp 2025 was a great opportunity to engage with a new audience and network.\"\ndate: 2025-06-29\n---\n\nThis year, continuing my speaking journey, I had the pleasure of attending [BreizhCamp 2025](https:\u002F\u002Fwww.breizhcamp.org\u002F) in Brittany, France. It was a delight to present outside of Paris, engaging with a new audience and networking with unknown faces.\n\nBreizhCamp is a smaller conference compared to [Devoxx France](.\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special.md) or [MiXiT](.\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025.md), hosting around 700 attendees daily, with five tracks of talks and 96 speakers.\n\nPersonally, I favor conferences of a more intimate scale. They provide the opportunity to connect with others and engage in meaningful discussions.\n\n_If someone from the BreizhCamp team is reading this, kudos for the fantastic organization! 🫶_\n\n![The place for breakfast, coffee breaks, and meals](\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker\u002Fbreizhcamp-2025.jpeg)\n\n## BreizhCamp 2025\n\nIf you've followed my blog, you know I never review the schedule before the morning of the conference, just minutes before it starts. This time was no exception. You might ask why, and the reason is simple: it minimizes cognitive load. Having just a few minutes to choose forces decisive action without overthinking.\n\nHere are the talks I attended:\n\n- **Wednesday, June 25, 2025**:\n  - **Enter the Daggerverse !** by [Julien Haumont](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-haumont-834a5427\u002F) and [Emeric Martineau](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Femeric-martineau\u002F)\n  - **La réactivité et les signaux : démystifions la magie du frontend** by Estéban Soubiran (that's me!)\n  - **Redécouvrir la coopération : Atelier de Mob Programming** by [Manon Carbonnel](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmanon-carbonnel\u002F) and [Thomas Ployon](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthomas-ployon-8bb9a2176\u002F)\n  - **Démystifions nos clean architectures** by [Yann Schepens](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fyann-schepens-a279871a\u002F), [Nicolas Giraud](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolas-giraud-17a44383\u002F), and [Thomas Blaisot](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ftblaisot\u002F)\n\n- **Thursday, June 26, 2025**:\n  - **Comment nous avons transformé les Restos du Coeur en Cloud Provider** by [Julien Briault](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-briault-441539137\u002F)\n  - **HTTP\u002F3 : 36 ans d'histoire du protocole HTTP** by [Chris Navas](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fchrisnavas\u002F)\n  - **“Make the change easy”: Apprivoiser le legacy avec les Object Calisthenics** by [Manon Carbonnel](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmanon-carbonnel\u002F) and [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.** by Estéban Soubiran (that's me again!)\n  - **Amusons-nous avec jj, une alternative à git !** by [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 🎨** by [Julien Sulpis](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-sulpis\u002F)\n  - **🧑‍🎤🎸 La preuve de programme vous fera apprécier les tests** by [William Bartlett](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpunkstarman\u002F)\n  - **Apprend les langues à ton IDE avec LSP** by [Maxime Reynier](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmaxime-reynier\u002F)\n  - **Gaming et Industrie : La Double Vie d'Unreal Engine** by [Enora Landurein](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fenora-landurein-b51368172\u002F)\n\n- **Friday, June 27, 2025**:\n  - **Rendre son back générique grâce au pattern composite** by [Alexandre Baron](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falexandre-baron-44b256156\u002F)\n  - **Modular monolith : la dernière croisade des architectures ?** by [Thomas Piscitelli](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpiscitellit\u002F)\n  - **Comment sont implémentés les containers sous Linux ?** by [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** by [Julien Topçu](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-top%C3%A7u\u002F) and [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\nThis is the complete list of talks I attended. I highly recommend checking out the conference's YouTube channel for all available presentations.\n\n## Talk highlights\n\nWhen recapping a conference, I love highlighting the talks that really impressed me, and BreizhCamp 2025 was no exception, with a lineup of both inspiring and technical presentations.\n\nTwo particular talks amazed me:\n\n- **Comment nous avons transformé les Restos du Coeur en Cloud Provider** by [Julien Briault](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-briault-441539137\u002F)\n\n  This wasn't my first time hearing Julien speak about the [Cloud du Coeur](https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fcloud-du-coeur\u002Fposts), and it's always a pleasure. He manages to deliver talks that are amusing, informative, and technical simultaneously. The work he does for the [Restos du Coeur](https:\u002F\u002Fwww.restosducoeur.org\u002F) is significant and impactful. Watching his talk is highly recommended as it inspires and exemplifies how technology can serve the greater good.\n\n  ![Julien Briault presenting at 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** by [Manon Carbonnel](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmanon-carbonnel\u002F) and [Gaël Salaün](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgael-salaun\u002F)\n\n  This talk was a perfect blend of technical insight, practical advice, and humor. The examples were exceptionally well-chosen, simple enough to understand yet realistic enough for real-world application. I learned something valuable that I intend to apply in my work. The 45 minutes flew by, and I wished it could have lasted longer.\n\n  ![Manon Carbonnel and Gaël Salaün presenting at 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\nI highly recommend watching these talks. Enjoy!\n\n## My talks\n\nAt BreizhCamp 2025, I was thrilled to present two talks:\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\nAfter presenting them at [Devoxx France 2025](.\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special.md) and [MiXiT 2025](.\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025.md), it feels great to deliver them again. I'm proud of these talks, and presenting them now is always enjoyable. With familiarity, I can truly enjoy the moment.\n\nTo add a twist, I challenged myself: I caught the train to Rennes on Wednesday morning at 6:30 AM, with my talk scheduled for 11:00 AM. The night before, I attended a Takima meetup, resulting in only three hours of sleep. I gave my all during the talk, but the afternoon session was challenging. _Don't attempt this, this is memorable, but not advisable!_\n\n![Me, presenting at 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## The end of a season\n\nThere's a saying:\n\n> La Bretagne, ça vous gagne !\n\nwhich means: Brittany will win you over! Indeed, BreizhCamp 2025 won me over. I thoroughly enjoyed the conference, meeting new people, and the overall ambiance. I will return next year and hope to see you there!\n\nSpeaking of next year, this was the final conference for the 2024\u002F2025 season for me. It was an incredible season, with 13 talks over 10 months. Truly amazing!\n\nThe new season starts in September 2025, and exciting plans are already on the horizon. I can confirm a speaking engagement at an upcoming meetup in Paris, a conference in Angers, and some fresh talks to announce in the coming months.\n\nI'll continue streaming on Twitch, doing live coding sessions, slide creation, and surprises. Stay tuned and follow me on [Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes).\n\n_The end of one thing is the beginning of another. Enjoy the journey!_\n",{"title":18184,"description":18582},{"loc":18584,"images":18588},[18589,18590,18591,18592],{"loc":18217},{"loc":18479},{"loc":18498},{"loc":18540},"posts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker","O02l9zOj-MM7ZSaSl5g6iaF17OTp-q_KRk5LqnbJl1s",{"id":18596,"title":18597,"body":18598,"contentId":9266,"date":19366,"description":19367,"extension":18,"meta":19368,"navigation":20,"path":19369,"rawbody":19370,"robots":23,"schemaOrg":23,"seo":19371,"sitemap":19372,"stem":19373,"__hash__":19374},"posts\u002Fposts\u002Fusing-pinia-colada-in-modals-without-spoiling-the-ux.md","Using Pinia Colada in Modals Without Spoiling the UX",{"type":7,"value":18599,"toc":19360},[18600,18611,18626,18632,18636,18639,18649,18652,18655,18661,18664,18669,18900,18906,18928,18933,19005,19014,19023,19027,19030,19044,19050,19054,19068,19073,19076,19079,19309,19321,19331,19335,19338,19346,19352,19357],[35,18601,18602,18603,18610],{},"Since months, I've been using ",[39,18604,18607],{"href":18605,"rel":18606},"https:\u002F\u002Fpinia-colada.esm.dev\u002F",[77],[6850,18608,18609],{},"Pinia Colada",". I discovered it while searching for an elegant solution for data fetching on my personal website (the one you're reading right now). Since then, I've used it in all my projects that require data from an API. It's easy to use, functions seamlessly, and enables me to create better experiences with its built-in cache, stale-while-revalidate feature, and the ease of implementing optimistic updates.",[35,18612,18613,18615,18616,18621,18622,18625],{},[6850,18614,18609],{}," is a data fetching layer for ",[39,18617,18619],{"href":16699,"rel":18618},[77],[6850,18620,16701],{},", the intuitive store for ",[6850,18623,18624],{},"Vue.js",", making asynchronous state management a breeze. You should definitely give it a try. I can't start a new project without it anymore.",[35,18627,18628,18629,18631],{},"However, I recently faced a challenge using ",[6850,18630,18609],{}," in a modal. Let me explain the problem and how I solved it.",[51,18633,18635],{"id":18634},"the-problem","The problem",[35,18637,18638],{},"Before explaining, take a look at the following video:",[130,18640,132,18641,132,18646],{},[134,18642,136,18643,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,18644],{"src":18645,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fusing-pinia-colada-in-modals-without-spoiling-the-ux\u002Fthe-problem.mp4",[143,18647,18648],{},"The modal closes before the data is updated.",[35,18650,18651],{},"Can you spot the problem?",[35,18653,18654],{},"When I submit the form in the modal, the \"Submit\" button is disabled, and a loading spinner appears. So far, so good. But then, the modal closes before the data updates on the page. The user has to wait, yet no feedback is provided, resulting in a terrible user experience. The user remains unaware whether the action was successful.",[35,18656,18657,18658],{},"You can find the code for this video on ",[1582,18659,215],{"repo":18660},"barbapapazes\u002Fpinia-colada-await-invalidate-queries",[35,18662,18663],{},"With this high-level overview, let's delve into the technical details of the problem and its causes.",[35,18665,419,18666,18668],{},[6850,18667,18609],{}," code I wrote to mutate the data is as follows:",[192,18670,18672],{"className":1370,"code":18671,"language":1373,"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,18673,18674,18693,18728,18755,18771,18785,18793,18806,18841,18845,18858,18876,18890,18894],{"__ignoreMap":13},[200,18675,18676,18678,18681,18683,18686,18688,18691],{"class":202,"line":203},[200,18677,443],{"class":379},[200,18679,18680],{"class":210}," open ",[200,18682,449],{"class":214},[200,18684,18685],{"class":223}," ref",[200,18687,227],{"class":210},[200,18689,18690],{"class":2423},"false",[200,18692,416],{"class":210},[200,18694,18695,18697,18699,18702,18704,18707,18709,18712,18714,18717,18719,18721,18724,18726],{"class":202,"line":14},[200,18696,443],{"class":379},[200,18698,318],{"class":214},[200,18700,18701],{"class":235}," mutate",[200,18703,239],{"class":214},[200,18705,18706],{"class":210}," createComment",[200,18708,550],{"class":214},[200,18710,18711],{"class":235}," isLoading",[200,18713,239],{"class":214},[200,18715,18716],{"class":210}," isCreatingComment ",[200,18718,413],{"class":214},[200,18720,510],{"class":214},[200,18722,18723],{"class":223}," useMutation",[200,18725,227],{"class":210},[200,18727,230],{"class":214},[200,18729,18730,18733,18735,18737,18739,18742,18744,18746,18749,18751,18753],{"class":202,"line":255},[200,18731,18732],{"class":223},"  mutation",[200,18734,239],{"class":214},[200,18736,3714],{"class":214},[200,18738,393],{"class":379},[200,18740,18741],{"class":223}," $fetch",[200,18743,227],{"class":210},[200,18745,249],{"class":214},[200,18747,18748],{"class":245},"\u002Fapi\u002Fcomments",[200,18750,249],{"class":214},[200,18752,550],{"class":214},[200,18754,280],{"class":214},[200,18756,18757,18760,18762,18764,18767,18769],{"class":202,"line":272},[200,18758,18759],{"class":235},"    method",[200,18761,239],{"class":214},[200,18763,242],{"class":214},[200,18765,18766],{"class":245},"POST",[200,18768,249],{"class":214},[200,18770,252],{"class":214},[200,18772,18773,18776,18778,18781,18783],{"class":202,"line":283},[200,18774,18775],{"class":235},"    body",[200,18777,239],{"class":214},[200,18779,18780],{"class":210}," comment",[200,18782,215],{"class":214},[200,18784,15976],{"class":210},[200,18786,18787,18789,18791],{"class":202,"line":300},[200,18788,5080],{"class":214},[200,18790,2089],{"class":210},[200,18792,252],{"class":214},[200,18794,18795,18798,18800,18802,18804],{"class":202,"line":310},[200,18796,18797],{"class":223},"  onSettled",[200,18799,239],{"class":214},[200,18801,3714],{"class":214},[200,18803,393],{"class":379},[200,18805,280],{"class":214},[200,18807,18808,18811,18813,18816,18818,18820,18823,18825,18827,18829,18832,18834,18837,18839],{"class":202,"line":336},[200,18809,18810],{"class":210},"    queryCache",[200,18812,215],{"class":214},[200,18814,18815],{"class":223},"invalidateQueries",[200,18817,227],{"class":235},[200,18819,2752],{"class":214},[200,18821,18822],{"class":235}," key",[200,18824,239],{"class":214},[200,18826,350],{"class":235},[200,18828,249],{"class":214},[200,18830,18831],{"class":245},"comments",[200,18833,249],{"class":214},[200,18835,18836],{"class":235},"] ",[200,18838,413],{"class":214},[200,18840,416],{"class":235},[200,18842,18843],{"class":202,"line":342},[200,18844,368],{"class":214},[200,18846,18847,18850,18852,18854,18856],{"class":202,"line":365},[200,18848,18849],{"class":223},"  onSuccess",[200,18851,239],{"class":214},[200,18853,3714],{"class":214},[200,18855,393],{"class":379},[200,18857,280],{"class":214},[200,18859,18860,18863,18865,18867,18869,18871,18873],{"class":202,"line":371},[200,18861,18862],{"class":210},"    comment",[200,18864,215],{"class":214},[200,18866,15837],{"class":210},[200,18868,215],{"class":214},[200,18870,698],{"class":210},[200,18872,510],{"class":214},[200,18874,18875],{"class":214}," ''\n",[200,18877,18878,18881,18883,18885,18887],{"class":202,"line":398},[200,18879,18880],{"class":210},"    open",[200,18882,215],{"class":214},[200,18884,15837],{"class":210},[200,18886,510],{"class":214},[200,18888,18889],{"class":2423}," false\n",[200,18891,18892],{"class":202,"line":405},[200,18893,4490],{"class":214},[200,18895,18896,18898],{"class":202,"line":410},[200,18897,413],{"class":214},[200,18899,416],{"class":210},[35,18901,419,18902,18905],{},[156,18903,18904],{},"open"," variable controls the modal's visibility.",[35,18907,18908,18909,18912,18913,18916,18917,18920,18921,18924,18925,18927],{},"The user opens the modal, fills the form, and clicks the \"Submit\" button. The ",[156,18910,18911],{},"createComment"," mutation is then called, sending data to the API via the ",[156,18914,18915],{},"mutate"," function. Upon success, the ",[156,18918,18919],{},"onSuccess"," callback resets the form and closes the modal. Finally, the ",[156,18922,18923],{},"onSettled"," callback invalidates the ",[156,18926,18831],{}," query to refetch data from the API.",[35,18929,419,18930,18932],{},[6850,18931,18609],{}," query looks like this:",[192,18934,18936],{"className":1370,"code":18935,"language":1373,"meta":13,"style":13},"const { state } = useQuery({\n  key: ['comments'],\n  query: () => $fetch('\u002Fapi\u002Fcomments')\n})\n",[156,18937,18938,18958,18977,18999],{"__ignoreMap":13},[200,18939,18940,18942,18944,18947,18949,18951,18954,18956],{"class":202,"line":203},[200,18941,443],{"class":379},[200,18943,318],{"class":214},[200,18945,18946],{"class":210}," state ",[200,18948,413],{"class":214},[200,18950,510],{"class":214},[200,18952,18953],{"class":223}," useQuery",[200,18955,227],{"class":210},[200,18957,230],{"class":214},[200,18959,18960,18963,18965,18967,18969,18971,18973,18975],{"class":202,"line":14},[200,18961,18962],{"class":235},"  key",[200,18964,239],{"class":214},[200,18966,350],{"class":210},[200,18968,249],{"class":214},[200,18970,18831],{"class":245},[200,18972,249],{"class":214},[200,18974,360],{"class":210},[200,18976,252],{"class":214},[200,18978,18979,18981,18983,18985,18987,18989,18991,18993,18995,18997],{"class":202,"line":255},[200,18980,6707],{"class":223},[200,18982,239],{"class":214},[200,18984,3714],{"class":214},[200,18986,393],{"class":379},[200,18988,18741],{"class":223},[200,18990,227],{"class":210},[200,18992,249],{"class":214},[200,18994,18748],{"class":245},[200,18996,249],{"class":214},[200,18998,416],{"class":210},[200,19000,19001,19003],{"class":202,"line":272},[200,19002,413],{"class":214},[200,19004,416],{"class":210},[35,19006,19007,19008,19010,19011,19013],{},"The issue is that ",[156,19009,18923],{},", which invalidates the query and re-fetches the data, is called after ",[156,19012,18919],{},", which closes the modal. Consequently, the user can see the modal closing before the data appears on the page, causing confusion. \"Where is my comment? Did it work?\"",[35,19015,19016,19017,5557,19020,19022],{},"Could we simply move ",[156,19018,19019],{},"open.value = false",[156,19021,18923],{},", after the query invalidation? Unfortunately, no.",[51,19024,19026],{"id":19025},"multiple-solutions","Multiple solutions",[35,19028,19029],{},"To solve the problem, two solutions came to mind:",[1576,19031,19032,19038],{},[71,19033,19034,19037],{},[6850,19035,19036],{},"Optimistic Updates",": This is the best solution for user experience, immediately updating the UI, but results in more complex code. It's not always feasible, especially if the API returns data different from what was sent. For example, with a comment system allowing markdown, the API returns formatted markdown, but the user sends raw markdown. In such cases, optimistic updates aren't viable, and a loading state is the only solution.",[71,19039,19040,19043],{},[6850,19041,19042],{},"Loading State",": This is the simplest solution. Disable the \"Submit\" button, display a loading spinner, and wait for the API response. It's the most common and easiest to implement. However, in a modal, it requires adjustments compared to an inline form.",[35,19045,19046,19047,19049],{},"For my project, optimistic updates weren't an option, so I had to use a loading state. Yet while implementing it, I faced this problem. No matter how many times I read the ",[6850,19048,18609],{}," documentation, I couldn't come up with a solution. At some point, I came up with an idea. I jumped to the documentation to check if my understanding could be a viable solution.",[51,19051,19053],{"id":19052},"awaiting-for-invalidated-queries","Awaiting for invalidated queries",[35,19055,19056,19057,19067],{},"The documentation includes a section about ",[39,19058,19061,19062,19064,19065,611],{"href":19059,"rel":19060},"https:\u002F\u002Fpinia-colada.esm.dev\u002Fguide\u002Fquery-invalidation.html#To-await-or-not-to-await",[77],"\"To ",[156,19063,207],{}," or not to ",[156,19066,207],{}," that states:",[939,19069,19070],{},[35,19071,19072],{},"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,19074,19075],{},"This was the key. I had never understood the case for awaiting invalidated queries, but it became clear with this problem. By awaiting the invalidated queries, I could delay closing the modal until the data was re-fetched and available on the page.",[35,19077,19078],{},"Here's the code to implement this solution:",[192,19080,19082],{"className":1370,"code":19081,"language":1373,"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,19083,19084,19100,19130,19154,19168,19180,19188,19212,19246,19250,19265,19282,19295,19299,19303],{"__ignoreMap":13},[200,19085,19086,19088,19090,19092,19094,19096,19098],{"class":202,"line":203},[200,19087,443],{"class":379},[200,19089,18680],{"class":210},[200,19091,449],{"class":214},[200,19093,18685],{"class":223},[200,19095,227],{"class":210},[200,19097,18690],{"class":2423},[200,19099,416],{"class":210},[200,19101,19102,19104,19106,19108,19110,19112,19114,19116,19118,19120,19122,19124,19126,19128],{"class":202,"line":14},[200,19103,443],{"class":379},[200,19105,318],{"class":214},[200,19107,18701],{"class":235},[200,19109,239],{"class":214},[200,19111,18706],{"class":210},[200,19113,550],{"class":214},[200,19115,18711],{"class":235},[200,19117,239],{"class":214},[200,19119,18716],{"class":210},[200,19121,413],{"class":214},[200,19123,510],{"class":214},[200,19125,18723],{"class":223},[200,19127,227],{"class":210},[200,19129,230],{"class":214},[200,19131,19132,19134,19136,19138,19140,19142,19144,19146,19148,19150,19152],{"class":202,"line":255},[200,19133,18732],{"class":223},[200,19135,239],{"class":214},[200,19137,3714],{"class":214},[200,19139,393],{"class":379},[200,19141,18741],{"class":223},[200,19143,227],{"class":210},[200,19145,249],{"class":214},[200,19147,18748],{"class":245},[200,19149,249],{"class":214},[200,19151,550],{"class":214},[200,19153,280],{"class":214},[200,19155,19156,19158,19160,19162,19164,19166],{"class":202,"line":272},[200,19157,18759],{"class":235},[200,19159,239],{"class":214},[200,19161,242],{"class":214},[200,19163,18766],{"class":245},[200,19165,249],{"class":214},[200,19167,252],{"class":214},[200,19169,19170,19172,19174,19176,19178],{"class":202,"line":283},[200,19171,18775],{"class":235},[200,19173,239],{"class":214},[200,19175,18780],{"class":210},[200,19177,215],{"class":214},[200,19179,15976],{"class":210},[200,19181,19182,19184,19186],{"class":202,"line":300},[200,19183,5080],{"class":214},[200,19185,2089],{"class":210},[200,19187,252],{"class":214},[200,19189,19190,19192,19194,19196,19198,19201,19203,19206,19208,19210],{"class":202,"line":310},[200,19191,18797],{"class":223},[200,19193,239],{"class":214},[200,19195,380],{"class":379},[200,19197,1095],{"class":214},[200,19199,19200],{"class":386},"_",[200,19202,550],{"class":214},[200,19204,19205],{"class":386}," error",[200,19207,2089],{"class":214},[200,19209,393],{"class":379},[200,19211,280],{"class":214},[200,19213,19214,19217,19220,19222,19224,19226,19228,19230,19232,19234,19236,19238,19240,19242,19244],{"class":202,"line":336},[200,19215,19216],{"class":206},"    await",[200,19218,19219],{"class":210}," queryCache",[200,19221,215],{"class":214},[200,19223,18815],{"class":223},[200,19225,227],{"class":235},[200,19227,2752],{"class":214},[200,19229,18822],{"class":235},[200,19231,239],{"class":214},[200,19233,350],{"class":235},[200,19235,249],{"class":214},[200,19237,18831],{"class":245},[200,19239,249],{"class":214},[200,19241,18836],{"class":235},[200,19243,413],{"class":214},[200,19245,416],{"class":235},[200,19247,19248],{"class":202,"line":342},[200,19249,529],{"emptyLinePlaceholder":20},[200,19251,19252,19254,19256,19258,19261,19263],{"class":202,"line":365},[200,19253,5252],{"class":206},[200,19255,1095],{"class":235},[200,19257,11837],{"class":214},[200,19259,19260],{"class":210},"error",[200,19262,5267],{"class":235},[200,19264,230],{"class":214},[200,19266,19267,19270,19272,19274,19276,19278,19280],{"class":202,"line":371},[200,19268,19269],{"class":210},"      comment",[200,19271,215],{"class":214},[200,19273,15837],{"class":210},[200,19275,215],{"class":214},[200,19277,698],{"class":210},[200,19279,510],{"class":214},[200,19281,18875],{"class":214},[200,19283,19284,19287,19289,19291,19293],{"class":202,"line":398},[200,19285,19286],{"class":210},"      open",[200,19288,215],{"class":214},[200,19290,15837],{"class":210},[200,19292,510],{"class":214},[200,19294,18889],{"class":2423},[200,19296,19297],{"class":202,"line":405},[200,19298,4962],{"class":214},[200,19300,19301],{"class":202,"line":410},[200,19302,368],{"class":214},[200,19304,19305,19307],{"class":202,"line":861},[200,19306,413],{"class":214},[200,19308,416],{"class":210},[35,19310,19311,19312,19314,19315,19317,19318,19320],{},"Rather than using ",[156,19313,18919],{}," to close the modal, I moved it to ",[156,19316,18923],{},", adding an ",[156,19319,207],{}," before the cache invalidation. Thus, the modal closes only after the data is re-fetched and displayed on the page. Simple, elegant, and effective.",[130,19322,132,19323,132,19328],{},[134,19324,136,19325,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,19326],{"src":19327,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fusing-pinia-colada-in-modals-without-spoiling-the-ux\u002Fthe-solution.mp4",[143,19329,19330],{},"The modal closes after the data is updated.",[51,19332,19334],{"id":19333},"finally","Finally",[35,19336,19337],{},"This small issue underscores two significant points:",[1576,19339,19340,19343],{},[71,19341,19342],{},"Every detail impacts user experience. Small nuances can create substantial differences.",[71,19344,19345],{},"Read the documentation, again and again, even if it can seem irrelevant initially, can help solve problems as I did here.",[35,19347,19348,19349,19351],{},"I hope this article clarifies this usage of ",[6850,19350,18609],{}," in modals without degrading user experience. If you have questions or suggestions, feel free to leave a comment below.",[35,19353,19354,19355],{},"To see the code in action, you can find it on ",[1582,19356,215],{"repo":18660},[1171,19358,19359],{},"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":19361},[19362,19363,19364,19365],{"id":18634,"depth":14,"text":18635},{"id":19025,"depth":14,"text":19026},{"id":19052,"depth":14,"text":19053},{"id":19333,"depth":14,"text":19334},"2025-06-07","Far from being straightforward but essential to provide a good user experience, you'll understand when to await for invalidated queries in Pinia Colada.",{},"\u002Fposts\u002Fusing-pinia-colada-in-modals-without-spoiling-the-ux","---\ncontentId: 9eb990da-ce9f-42ea-9836-5bb1fca28691\ntitle: Using Pinia Colada in Modals Without Spoiling the UX\ndescription: Far from being straightforward but essential to provide a good user experience, you'll understand when to await for invalidated queries in Pinia Colada.\ndate: 2025-06-07\n---\n\nSince months, I've been using [**Pinia Colada**](https:\u002F\u002Fpinia-colada.esm.dev\u002F). I discovered it while searching for an elegant solution for data fetching on my personal website (the one you're reading right now). Since then, I've used it in all my projects that require data from an API. It's easy to use, functions seamlessly, and enables me to create better experiences with its built-in cache, stale-while-revalidate feature, and the ease of implementing optimistic updates.\n\n**Pinia Colada** is a data fetching layer for [**Pinia**](https:\u002F\u002Fpinia.vuejs.org\u002F), the intuitive store for **Vue.js**, making asynchronous state management a breeze. You should definitely give it a try. I can't start a new project without it anymore.\n\nHowever, I recently faced a challenge using **Pinia Colada** in a modal. Let me explain the problem and how I solved it.\n\n## The problem\n\nBefore explaining, take a look at the following video:\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>The modal closes before the data is updated.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nCan you spot the problem?\n\nWhen I submit the form in the modal, the \"Submit\" button is disabled, and a loading spinner appears. So far, so good. But then, the modal closes before the data updates on the page. The user has to wait, yet no feedback is provided, resulting in a terrible user experience. The user remains unaware whether the action was successful.\n\nYou can find the code for this video on \u003CGitHubLink repo=\"barbapapazes\u002Fpinia-colada-await-invalidate-queries\" \u002F>.\n\nWith this high-level overview, let's delve into the technical details of the problem and its causes.\n\nThe **Pinia Colada** code I wrote to mutate the data is as follows:\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\nThe `open` variable controls the modal's visibility.\n\nThe user opens the modal, fills the form, and clicks the \"Submit\" button. The `createComment` mutation is then called, sending data to the API via the `mutate` function. Upon success, the `onSuccess` callback resets the form and closes the modal. Finally, the `onSettled` callback invalidates the `comments` query to refetch data from the API.\n\nThe **Pinia Colada** query looks like this:\n\n```ts\nconst { state } = useQuery({\n  key: ['comments'],\n  query: () => $fetch('\u002Fapi\u002Fcomments')\n})\n```\n\nThe issue is that `onSettled`, which invalidates the query and re-fetches the data, is called after `onSuccess`, which closes the modal. Consequently, the user can see the modal closing before the data appears on the page, causing confusion. \"Where is my comment? Did it work?\"\n\nCould we simply move `open.value = false` to `onSettled`, after the query invalidation? Unfortunately, no.\n\n## Multiple solutions\n\nTo solve the problem, two solutions came to mind:\n\n- **Optimistic Updates**: This is the best solution for user experience, immediately updating the UI, but results in more complex code. It's not always feasible, especially if the API returns data different from what was sent. For example, with a comment system allowing markdown, the API returns formatted markdown, but the user sends raw markdown. In such cases, optimistic updates aren't viable, and a loading state is the only solution.\n- **Loading State**: This is the simplest solution. Disable the \"Submit\" button, display a loading spinner, and wait for the API response. It's the most common and easiest to implement. However, in a modal, it requires adjustments compared to an inline form.\n\nFor my project, optimistic updates weren't an option, so I had to use a loading state. Yet while implementing it, I faced this problem. No matter how many times I read the **Pinia Colada** documentation, I couldn't come up with a solution. At some point, I came up with an idea. I jumped to the documentation to check if my understanding could be a viable solution.\n\n## Awaiting for invalidated queries\n\nThe documentation includes a section about [\"To `await` or not to `await`\"](https:\u002F\u002Fpinia-colada.esm.dev\u002Fguide\u002Fquery-invalidation.html#To-await-or-not-to-await) that states:\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\nThis was the key. I had never understood the case for awaiting invalidated queries, but it became clear with this problem. By awaiting the invalidated queries, I could delay closing the modal until the data was re-fetched and available on the page.\n\nHere's the code to implement this 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\nRather than using `onSuccess` to close the modal, I moved it to `onSettled`, adding an `await` before the cache invalidation. Thus, the modal closes only after the data is re-fetched and displayed on the page. Simple, elegant, and effective.\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>The modal closes after the data is updated.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n## Finally\n\nThis small issue underscores two significant points:\n\n- Every detail impacts user experience. Small nuances can create substantial differences.\n- Read the documentation, again and again, even if it can seem irrelevant initially, can help solve problems as I did here.\n\nI hope this article clarifies this usage of **Pinia Colada** in modals without degrading user experience. If you have questions or suggestions, feel free to leave a comment below.\n\nTo see the code in action, you can find it on \u003CGitHubLink repo=\"barbapapazes\u002Fpinia-colada-await-invalidate-queries\" \u002F>.\n",{"title":18597,"description":19367},{"loc":19369},"posts\u002Fusing-pinia-colada-in-modals-without-spoiling-the-ux","4csWvRXbYWPSJl-UnWmyX1rvgcKe-wwjF7B_dHCeERA",{"id":19376,"title":19377,"body":19378,"contentId":4263,"date":20403,"description":20404,"extension":18,"meta":20405,"navigation":20,"path":20406,"rawbody":20407,"robots":23,"schemaOrg":23,"seo":20408,"sitemap":20409,"stem":20410,"__hash__":20411},"posts\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website.md","A Model Context Protocol (MCP) Server for My Website",{"type":7,"value":19379,"toc":20397},[19380,19386,19401,19413,19417,19420,19449,19452,19482,19488,19494,19503,19517,19522,19526,19529,19535,19567,19661,19664,19668,19679,20324,20327,20343,20349,20352,20363,20367,20370,20380,20387,20394],[35,19381,19382,19385],{},[6850,19383,19384],{},"Large Language Models"," (LLMs) are ubiquitous these days. The pace of advancements is staggering, and it's challenging to keep up with the constant updates, it's absolutely insane!",[35,19387,19388,19389,19392,19393,19400],{},"Amidst the overwhelming information, I found the concept of an ",[6850,19390,19391],{},"agent",", especially with the ",[39,19394,19397],{"href":19395,"rel":19396},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fintroduction",[77],[6850,19398,19399],{},"Model Context Protocol"," (MCP), particularly intriguing and promising. So, I decided to experiment with it and see how it could enhance my workflow.",[939,19402,19403],{},[35,19404,19405,19407,19408,215],{},[200,19406,945],{},"\nPrefer watching a video? Check out the video version of this article on ",[39,19409,19412],{"href":19410,"rel":19411},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NnLIYresumQ",[77],"YouTube",[51,19414,19416],{"id":19415},"the-challenge-with-identifiers","The challenge with identifiers",[35,19418,19419],{},"Currently, my website consists of two key components:",[1576,19421,19422,19435],{},[71,19423,419,19424,19427,19428,19434],{},[6850,19425,19426],{},"frontend",", built with ",[39,19429,19432],{"href":19430,"rel":19431},"https:\u002F\u002Fvitepress.dev",[77],[6850,19433,2913],{},", where each page is a Markdown file.",[71,19436,419,19437,19440,19441,19448],{},[6850,19438,19439],{},"backend",", a ",[39,19442,19445],{"href":19443,"rel":19444},"https:\u002F\u002Flaravel.com",[77],[6850,19446,19447],{},"Laravel"," application that adds additional features and functionality.",[35,19450,19451],{},"To ensure the correct content is served and stored for each frontend page, I need to share an identifier between these components. Each Markdown file has a unique identifier in its frontmatter.",[192,19453,19456],{"className":5891,"code":19454,"filename":19455,"language":18,"meta":13,"style":13},"---\nid: 1\ntitle:\ndescription:\n---\n","src\u002Fposts\u002Fa-simple-markdown-file.md",[156,19457,19458,19463,19468,19473,19478],{"__ignoreMap":13},[200,19459,19460],{"class":202,"line":203},[200,19461,19462],{"class":210},"---\n",[200,19464,19465],{"class":202,"line":14},[200,19466,19467],{"class":210},"id: 1\n",[200,19469,19470],{"class":202,"line":255},[200,19471,19472],{"class":210},"title:\n",[200,19474,19475],{"class":202,"line":272},[200,19476,19477],{"class":210},"description:\n",[200,19479,19480],{"class":202,"line":283},[200,19481,19462],{"class":214},[35,19483,19484,19485,19487],{},"This identifier fetches the corresponding content from the backend when loading the page. For everything to work seamlessly, the backend must also store this identifier in the database. Without it, maintaining a functional relational database is impossible, as my database includes a ",[156,19486,26],{}," table.",[35,19489,19490,19491,19493],{},"To obtain these identifiers, the backend routinely fetches a JSON file generated at build time by ",[6850,19492,2913],{},". This file contains all the Markdown files and their frontmatter. This approach works well, but there's a catch.",[35,19495,19496,19497,19499,19500,19502],{},"When writing a new Markdown file, I must access the backend administration page, sort the ",[156,19498,26],{}," table by the ",[156,19501,3280],{}," column, and find the last used identifier. Then, I write this identifier, incremented by one, into the file I'm working on. It's a tedious task that adds no real value and consumes time, a process I thoroughly dislike.",[35,19504,19505,19506,19509,19510,19513,19514],{},"Simultaneously, I frequently leverage ",[6850,19507,19508],{},"LLMs"," to enhance my articles' technical content, including ",[6850,19511,19512],{},"SEO",", grammar, and spelling. This led me to wonder: ",[6850,19515,19516],{},"Can I integrate an agent within the current LLM process to ensure that the identifier is always correct?",[35,19518,19519],{},[1217,19520,19521],{},"Short answer: yes!",[51,19523,19525],{"id":19524},"implementing-a-new-backend-route","Implementing a new backend route",[35,19527,19528],{},"MCP servers aren't mystical; they provide a means for LLMs to interact with data sources. Hence, I need to create a backend API route to return the next post identifier. This route will be accessed by the MCP server via the tools capability.",[35,19530,19531,19532,19534],{},"Creating this API route in my ",[6850,19533,19447],{}," application and setting up an invokable controller is fairly straightforward:",[192,19536,19541],{"className":19537,"code":19538,"filename":19539,"language":19540,"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,19542,19543,19548,19553,19557,19562],{"__ignoreMap":13},[200,19544,19545],{"class":202,"line":203},[200,19546,19547],{},"use App\\Http\\Controllers\\NextPostIdController;\n",[200,19549,19550],{"class":202,"line":14},[200,19551,19552],{},"use Illuminate\\Support\\Facades\\Route;\n",[200,19554,19555],{"class":202,"line":255},[200,19556,529],{"emptyLinePlaceholder":20},[200,19558,19559],{"class":202,"line":272},[200,19560,19561],{},"Route::get('posts\u002Fnext-id', NextPostIdController::class)\n",[200,19563,19564],{"class":202,"line":283},[200,19565,19566],{},"    ->name('posts.next-id');\n",[192,19568,19571],{"className":19537,"code":19569,"filename":19570,"language":19540,"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,19572,19573,19578,19582,19587,19591,19596,19601,19605,19610,19614,19619,19624,19629,19634,19639,19644,19648,19653,19657],{"__ignoreMap":13},[200,19574,19575],{"class":202,"line":203},[200,19576,19577],{},"\u003C?php\n",[200,19579,19580],{"class":202,"line":14},[200,19581,529],{"emptyLinePlaceholder":20},[200,19583,19584],{"class":202,"line":255},[200,19585,19586],{},"namespace App\\Http\\Controllers;\n",[200,19588,19589],{"class":202,"line":272},[200,19590,529],{"emptyLinePlaceholder":20},[200,19592,19593],{"class":202,"line":283},[200,19594,19595],{},"use App\\Models\\Post;\n",[200,19597,19598],{"class":202,"line":300},[200,19599,19600],{},"use Illuminate\\Http\\JsonResponse;\n",[200,19602,19603],{"class":202,"line":310},[200,19604,529],{"emptyLinePlaceholder":20},[200,19606,19607],{"class":202,"line":336},[200,19608,19609],{},"class NextPostIdController extends Controller\n",[200,19611,19612],{"class":202,"line":342},[200,19613,230],{},[200,19615,19616],{"class":202,"line":365},[200,19617,19618],{},"    \u002F**\n",[200,19620,19621],{"class":202,"line":371},[200,19622,19623],{},"     * Handle the incoming request.\n",[200,19625,19626],{"class":202,"line":398},[200,19627,19628],{},"     *\u002F\n",[200,19630,19631],{"class":202,"line":405},[200,19632,19633],{},"    public function __invoke(): JsonResponse\n",[200,19635,19636],{"class":202,"line":410},[200,19637,19638],{},"    {\n",[200,19640,19641],{"class":202,"line":861},[200,19642,19643],{},"        $nextPostId = Post::max('id') + 1;\n",[200,19645,19646],{"class":202,"line":890},[200,19647,529],{"emptyLinePlaceholder":20},[200,19649,19650],{"class":202,"line":900},[200,19651,19652],{},"        return response()->json(['next_post_id' => $nextPostId]);\n",[200,19654,19655],{"class":202,"line":905},[200,19656,4962],{},[200,19658,19659],{"class":202,"line":931},[200,19660,1130],{},[35,19662,19663],{},"This setup, although simple, is crucial for an effective MCP server.",[51,19665,19667],{"id":19666},"configuring-the-mcp-server","Configuring the MCP server",[35,19669,19670,19671,19678],{},"For the MCP server, I'm utilizing the official ",[39,19672,19675],{"href":19673,"rel":19674},"https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Ftypescript-sdk",[77],[6850,19676,19677],{},"TypeScript SDK",". It's straightforward and highly efficient.",[192,19680,19682],{"className":1370,"code":19681,"filename":10012,"language":1373,"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: 'Get the next post ID for a post on 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('Error fetching next post ID')\n    }\n  }\n\n  throw new Error('Unknown tool')\n})\n\nconst transport = new StdioServerTransport()\nawait server.connect(transport)\n",[156,19683,19684,19704,19724,19730,19737,19742,19755,19774,19793,19813,19817,19832,19836,19851,19865,19869,19873,19882,19892,19896,19900,19904,19908,19930,19934,19956,19962,19971,19975,19990,20006,20024,20028,20032,20036,20042,20046,20071,20102,20109,20147,20151,20157,20168,20182,20195,20201,20205,20209,20222,20244,20249,20254,20259,20280,20287,20292,20308],{"__ignoreMap":13},[200,19685,19686,19688,19690,19693,19695,19697,19699,19702],{"class":202,"line":203},[200,19687,973],{"class":206},[200,19689,318],{"class":214},[200,19691,19692],{"class":210}," Server",[200,19694,981],{"class":214},[200,19696,984],{"class":206},[200,19698,242],{"class":214},[200,19700,19701],{"class":245},"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Findex.js",[200,19703,992],{"class":214},[200,19705,19706,19708,19710,19713,19715,19717,19719,19722],{"class":202,"line":14},[200,19707,973],{"class":206},[200,19709,318],{"class":214},[200,19711,19712],{"class":210}," StdioServerTransport",[200,19714,981],{"class":214},[200,19716,984],{"class":206},[200,19718,242],{"class":214},[200,19720,19721],{"class":245},"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fstdio.js",[200,19723,992],{"class":214},[200,19725,19726,19728],{"class":202,"line":255},[200,19727,973],{"class":206},[200,19729,280],{"class":214},[200,19731,19732,19735],{"class":202,"line":272},[200,19733,19734],{"class":210},"  CallToolRequestSchema",[200,19736,252],{"class":214},[200,19738,19739],{"class":202,"line":283},[200,19740,19741],{"class":210},"  ListToolsRequestSchema\n",[200,19743,19744,19746,19748,19750,19753],{"class":202,"line":300},[200,19745,413],{"class":214},[200,19747,984],{"class":206},[200,19749,242],{"class":214},[200,19751,19752],{"class":245},"@modelcontextprotocol\u002Fsdk\u002Ftypes.js",[200,19754,992],{"class":214},[200,19756,19757,19759,19761,19763,19765,19767,19769,19772],{"class":202,"line":310},[200,19758,973],{"class":206},[200,19760,318],{"class":214},[200,19762,4843],{"class":210},[200,19764,981],{"class":214},[200,19766,984],{"class":206},[200,19768,242],{"class":214},[200,19770,19771],{"class":245},"ofetch",[200,19773,992],{"class":214},[200,19775,19776,19778,19780,19782,19784,19786,19788,19791],{"class":202,"line":336},[200,19777,973],{"class":206},[200,19779,318],{"class":214},[200,19781,3165],{"class":210},[200,19783,981],{"class":214},[200,19785,984],{"class":206},[200,19787,242],{"class":214},[200,19789,19790],{"class":245},"zod",[200,19792,992],{"class":214},[200,19794,19795,19797,19799,19802,19804,19806,19808,19811],{"class":202,"line":342},[200,19796,973],{"class":206},[200,19798,318],{"class":214},[200,19800,19801],{"class":210}," zodToJsonSchema",[200,19803,981],{"class":214},[200,19805,984],{"class":206},[200,19807,242],{"class":214},[200,19809,19810],{"class":245},"zod-to-json-schema",[200,19812,992],{"class":214},[200,19814,19815],{"class":202,"line":365},[200,19816,529],{"emptyLinePlaceholder":20},[200,19818,19819,19821,19824,19826,19828,19830],{"class":202,"line":371},[200,19820,443],{"class":379},[200,19822,19823],{"class":210}," server ",[200,19825,449],{"class":214},[200,19827,12997],{"class":214},[200,19829,19692],{"class":223},[200,19831,3115],{"class":210},[200,19833,19834],{"class":202,"line":398},[200,19835,3131],{"class":214},[200,19837,19838,19840,19842,19844,19847,19849],{"class":202,"line":405},[200,19839,11797],{"class":235},[200,19841,239],{"class":214},[200,19843,242],{"class":214},[200,19845,19846],{"class":245},"mcp.soubiran.dev",[200,19848,249],{"class":214},[200,19850,252],{"class":214},[200,19852,19853,19856,19858,19860,19863],{"class":202,"line":410},[200,19854,19855],{"class":235},"    version",[200,19857,239],{"class":214},[200,19859,242],{"class":214},[200,19861,19862],{"class":245},"0.0.0",[200,19864,992],{"class":214},[200,19866,19867],{"class":202,"line":861},[200,19868,368],{"class":214},[200,19870,19871],{"class":202,"line":890},[200,19872,3131],{"class":214},[200,19874,19875,19878,19880],{"class":202,"line":900},[200,19876,19877],{"class":235},"    capabilities",[200,19879,239],{"class":214},[200,19881,280],{"class":214},[200,19883,19884,19887,19889],{"class":202,"line":905},[200,19885,19886],{"class":235},"      tools",[200,19888,239],{"class":214},[200,19890,19891],{"class":214}," {}\n",[200,19893,19894],{"class":202,"line":931},[200,19895,4962],{"class":214},[200,19897,19898],{"class":202,"line":3640},[200,19899,4490],{"class":214},[200,19901,19902],{"class":202,"line":3645},[200,19903,416],{"class":210},[200,19905,19906],{"class":202,"line":4020},[200,19907,529],{"emptyLinePlaceholder":20},[200,19909,19910,19912,19915,19917,19919,19921,19923,19925,19928],{"class":202,"line":4475},[200,19911,443],{"class":379},[200,19913,19914],{"class":210}," GetNextId ",[200,19916,449],{"class":214},[200,19918,3165],{"class":210},[200,19920,215],{"class":214},[200,19922,293],{"class":223},[200,19924,227],{"class":210},[200,19926,19927],{"class":214},"{}",[200,19929,416],{"class":210},[200,19931,19932],{"class":202,"line":4481},[200,19933,529],{"emptyLinePlaceholder":20},[200,19935,19936,19938,19940,19943,19946,19948,19950,19952,19954],{"class":202,"line":4487},[200,19937,3108],{"class":210},[200,19939,215],{"class":214},[200,19941,19942],{"class":223},"setRequestHandler",[200,19944,19945],{"class":210},"(ListToolsRequestSchema",[200,19947,550],{"class":214},[200,19949,380],{"class":379},[200,19951,3714],{"class":214},[200,19953,393],{"class":379},[200,19955,280],{"class":214},[200,19957,19958,19960],{"class":202,"line":4493},[200,19959,3743],{"class":206},[200,19961,280],{"class":214},[200,19963,19964,19967,19969],{"class":202,"line":5454},[200,19965,19966],{"class":235},"    tools",[200,19968,239],{"class":214},[200,19970,1400],{"class":235},[200,19972,19973],{"class":202,"line":5459},[200,19974,4442],{"class":214},[200,19976,19977,19979,19981,19983,19986,19988],{"class":202,"line":13227},[200,19978,11722],{"class":235},[200,19980,239],{"class":214},[200,19982,242],{"class":214},[200,19984,19985],{"class":245},"next_post_id",[200,19987,249],{"class":214},[200,19989,252],{"class":214},[200,19991,19992,19995,19997,19999,20002,20004],{"class":202,"line":13253},[200,19993,19994],{"class":235},"        description",[200,19996,239],{"class":214},[200,19998,242],{"class":214},[200,20000,20001],{"class":245},"Get the next post ID for a post on soubiran.dev",[200,20003,249],{"class":214},[200,20005,252],{"class":214},[200,20007,20008,20011,20013,20015,20017,20020,20022],{"class":202,"line":13269},[200,20009,20010],{"class":235},"        inputSchema",[200,20012,239],{"class":214},[200,20014,19801],{"class":223},[200,20016,227],{"class":235},[200,20018,20019],{"class":210},"GetNextId",[200,20021,2089],{"class":235},[200,20023,252],{"class":214},[200,20025,20026],{"class":202,"line":13313},[200,20027,12083],{"class":214},[200,20029,20030],{"class":202,"line":13319},[200,20031,4484],{"class":235},[200,20033,20034],{"class":202,"line":13324},[200,20035,4490],{"class":214},[200,20037,20038,20040],{"class":202,"line":13350},[200,20039,413],{"class":214},[200,20041,416],{"class":210},[200,20043,20044],{"class":202,"line":13355},[200,20045,529],{"emptyLinePlaceholder":20},[200,20047,20048,20050,20052,20054,20057,20059,20061,20063,20065,20067,20069],{"class":202,"line":13375},[200,20049,3108],{"class":210},[200,20051,215],{"class":214},[200,20053,19942],{"class":223},[200,20055,20056],{"class":210},"(CallToolRequestSchema",[200,20058,550],{"class":214},[200,20060,380],{"class":379},[200,20062,1095],{"class":214},[200,20064,6663],{"class":386},[200,20066,2089],{"class":214},[200,20068,393],{"class":379},[200,20070,280],{"class":214},[200,20072,20073,20076,20078,20080,20082,20085,20087,20090,20092,20094,20096,20098,20100],{"class":202,"line":13395},[200,20074,20075],{"class":206},"  if",[200,20077,1095],{"class":235},[200,20079,6663],{"class":210},[200,20081,215],{"class":214},[200,20083,20084],{"class":210},"params",[200,20086,215],{"class":214},[200,20088,20089],{"class":210},"name",[200,20091,3283],{"class":214},[200,20093,242],{"class":214},[200,20095,19985],{"class":245},[200,20097,249],{"class":214},[200,20099,5267],{"class":235},[200,20101,230],{"class":214},[200,20103,20104,20107],{"class":202,"line":13400},[200,20105,20106],{"class":206},"    try",[200,20108,280],{"class":214},[200,20110,20111,20114,20116,20118,20120,20122,20125,20128,20130,20133,20136,20138,20140,20143,20145],{"class":202,"line":13408},[200,20112,20113],{"class":379},"      const",[200,20115,5041],{"class":210},[200,20117,510],{"class":214},[200,20119,452],{"class":206},[200,20121,4843],{"class":223},[200,20123,20124],{"class":214},"\u003C{",[200,20126,20127],{"class":235}," next_post_id",[200,20129,239],{"class":214},[200,20131,20132],{"class":1978}," number",[200,20134,20135],{"class":214}," }>",[200,20137,227],{"class":235},[200,20139,249],{"class":214},[200,20141,20142],{"class":245},"http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fposts\u002Fnext-id",[200,20144,249],{"class":214},[200,20146,416],{"class":235},[200,20148,20149],{"class":202,"line":13426},[200,20150,529],{"emptyLinePlaceholder":20},[200,20152,20153,20155],{"class":202,"line":13456},[200,20154,11715],{"class":206},[200,20156,280],{"class":214},[200,20158,20159,20162,20164,20166],{"class":202,"line":13462},[200,20160,20161],{"class":235},"        content",[200,20163,239],{"class":214},[200,20165,350],{"class":235},[200,20167,230],{"class":214},[200,20169,20170,20172,20174,20176,20178,20180],{"class":202,"line":13467},[200,20171,4914],{"class":235},[200,20173,239],{"class":214},[200,20175,242],{"class":214},[200,20177,698],{"class":245},[200,20179,249],{"class":214},[200,20181,252],{"class":214},[200,20183,20184,20186,20188,20190,20192],{"class":202,"line":13472},[200,20185,4929],{"class":235},[200,20187,239],{"class":214},[200,20189,5041],{"class":210},[200,20191,215],{"class":214},[200,20193,20194],{"class":210},"next_post_id\n",[200,20196,20197,20199],{"class":202,"line":13487},[200,20198,13378],{"class":214},[200,20200,4496],{"class":235},[200,20202,20203],{"class":202,"line":13498},[200,20204,4478],{"class":214},[200,20206,20207],{"class":202,"line":13503},[200,20208,4962],{"class":214},[200,20210,20211,20214,20216,20218,20220],{"class":202,"line":13508},[200,20212,20213],{"class":206},"    catch",[200,20215,1095],{"class":235},[200,20217,19260],{"class":210},[200,20219,5267],{"class":235},[200,20221,230],{"class":214},[200,20223,20225,20228,20230,20233,20235,20237,20240,20242],{"class":202,"line":20224},50,[200,20226,20227],{"class":206},"      throw",[200,20229,12997],{"class":214},[200,20231,20232],{"class":223}," Error",[200,20234,227],{"class":235},[200,20236,249],{"class":214},[200,20238,20239],{"class":245},"Error fetching next post ID",[200,20241,249],{"class":214},[200,20243,416],{"class":235},[200,20245,20247],{"class":202,"line":20246},51,[200,20248,4962],{"class":214},[200,20250,20252],{"class":202,"line":20251},52,[200,20253,4490],{"class":214},[200,20255,20257],{"class":202,"line":20256},53,[200,20258,529],{"emptyLinePlaceholder":20},[200,20260,20262,20265,20267,20269,20271,20273,20276,20278],{"class":202,"line":20261},54,[200,20263,20264],{"class":206},"  throw",[200,20266,12997],{"class":214},[200,20268,20232],{"class":223},[200,20270,227],{"class":235},[200,20272,249],{"class":214},[200,20274,20275],{"class":245},"Unknown tool",[200,20277,249],{"class":214},[200,20279,416],{"class":235},[200,20281,20283,20285],{"class":202,"line":20282},55,[200,20284,413],{"class":214},[200,20286,416],{"class":210},[200,20288,20290],{"class":202,"line":20289},56,[200,20291,529],{"emptyLinePlaceholder":20},[200,20293,20295,20297,20300,20302,20304,20306],{"class":202,"line":20294},57,[200,20296,443],{"class":379},[200,20298,20299],{"class":210}," transport ",[200,20301,449],{"class":214},[200,20303,12997],{"class":214},[200,20305,19712],{"class":223},[200,20307,466],{"class":210},[200,20309,20311,20313,20316,20318,20321],{"class":202,"line":20310},58,[200,20312,207],{"class":206},[200,20314,20315],{"class":210}," server",[200,20317,215],{"class":214},[200,20319,20320],{"class":223},"connect",[200,20322,20323],{"class":210},"(transport)\n",[35,20325,20326],{},"This code establishes a STDIO MCP server with two key request handlers:",[68,20328,20329,20337],{},[71,20330,419,20331,20334,20335,215],{},[156,20332,20333],{},"ListToolsRequest"," handler returns the available tools to the agent. Currently, there is one tool: ",[156,20336,19985],{},[71,20338,419,20339,20342],{},[156,20340,20341],{},"CallToolRequest"," handler is activated when the agent uses the tool. It calls the backend API route to retrieve the next post ID.",[35,20344,20345,20346,20348],{},"The tool call process is simple; it fetches the next post ID from the backend API route I've defined, using the ",[156,20347,19771],{}," library for ease. The post ID is then returned as a number.",[35,20350,20351],{},"Consequently, any agent invoking this tool will receive the next post ID as a response within its context. This allows me to draft Markdown files without worrying about post IDs, the agent handles it perfectly!",[939,20353,20354],{},[35,20355,20356,20358,20359,215],{},[200,20357,945],{},"\nFor the complete code, watch the ",[39,20360,20362],{"href":19410,"rel":20361},[77],"YouTube video",[51,20364,20366],{"id":20365},"does-it-work","Does it work?",[35,20368,20369],{},"Yes, it works seamlessly! See it in action:",[130,20371,132,20372,132,20377],{},[134,20373,136,20374,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,20375],{"src":20376,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website\u002Fworking-mcp-soubiran-dev.mp4",[143,20378,20379],{},"Requesting the agent to enhance a file and insert the correct ID automatically using the next post ID from the MCP server.",[35,20381,20382,20383,20386],{},"With this ",[6850,20384,20385],{},"MCP server"," operational, I can now interact with my Markdown files while supplying context to the agent. This allows me to focus on content rather than technical details. My ultimate aim is to hasten my writing process, making it more efficient and enjoyable, and this software is a step towards that goal.",[35,20388,20389,20390,20393],{},"I'm thoroughly impressed by the MCP SDK's user-friendliness and integration with ",[6850,20391,20392],{},"VS Code",". The future of agents is promising, and I highly recommend experimenting with them!",[1171,20395,20396],{},"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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .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 .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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":13,"searchDepth":14,"depth":14,"links":20398},[20399,20400,20401,20402],{"id":19415,"depth":14,"text":19416},{"id":19524,"depth":14,"text":19525},{"id":19666,"depth":14,"text":19667},{"id":20365,"depth":14,"text":20366},"2025-05-10","Exploring the potential of the Model Context Protocol (MCP) by integrating it into my workflow and optimizing my website's backend operations.",{},"\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website","---\ncontentId: 4ff51ee2-926d-4924-86f5-97cd9e378fa7\ntitle: \"A Model Context Protocol (MCP) Server for My Website\"\ndescription: \"Exploring the potential of the Model Context Protocol (MCP) by integrating it into my workflow and optimizing my website's backend operations.\"\ndate: 2025-05-10\n---\n\n**Large Language Models** (LLMs) are ubiquitous these days. The pace of advancements is staggering, and it's challenging to keep up with the constant updates, it's absolutely insane!\n\nAmidst the overwhelming information, I found the concept of an **agent**, especially with the [**Model Context Protocol**](https:\u002F\u002Fmodelcontextprotocol.io\u002Fintroduction) (MCP), particularly intriguing and promising. So, I decided to experiment with it and see how it could enhance my workflow.\n\n> [!NOTE]\n> Prefer watching a video? Check out the video version of this article on [YouTube](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NnLIYresumQ).\n\n## The challenge with identifiers\n\nCurrently, my website consists of two key components:\n\n- The **frontend**, built with [**VitePress**](https:\u002F\u002Fvitepress.dev), where each page is a Markdown file.\n- The **backend**, a [**Laravel**](https:\u002F\u002Flaravel.com) application that adds additional features and functionality.\n\nTo ensure the correct content is served and stored for each frontend page, I need to share an identifier between these components. Each Markdown file has a unique identifier in its frontmatter.\n\n```md [src\u002Fposts\u002Fa-simple-markdown-file.md]\n---\nid: 1\ntitle:\ndescription:\n---\n```\n\nThis identifier fetches the corresponding content from the backend when loading the page. For everything to work seamlessly, the backend must also store this identifier in the database. Without it, maintaining a functional relational database is impossible, as my database includes a `posts` table.\n\nTo obtain these identifiers, the backend routinely fetches a JSON file generated at build time by **VitePress**. This file contains all the Markdown files and their frontmatter. This approach works well, but there's a catch.\n\nWhen writing a new Markdown file, I must access the backend administration page, sort the `posts` table by the `id` column, and find the last used identifier. Then, I write this identifier, incremented by one, into the file I'm working on. It's a tedious task that adds no real value and consumes time, a process I thoroughly dislike.\n\nSimultaneously, I frequently leverage **LLMs** to enhance my articles' technical content, including **SEO**, grammar, and spelling. This led me to wonder: **Can I integrate an agent within the current LLM process to ensure that the identifier is always correct?**\n\n_Short answer: yes!_\n\n## Implementing a new backend route\n\nMCP servers aren't mystical; they provide a means for LLMs to interact with data sources. Hence, I need to create a backend API route to return the next post identifier. This route will be accessed by the MCP server via the tools capability.\n\nCreating this API route in my **Laravel** application and setting up an invokable controller is fairly straightforward:\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\nThis setup, although simple, is crucial for an effective MCP server.\n\n## Configuring the MCP server\n\nFor the MCP server, I'm utilizing the official [**TypeScript SDK**](https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Ftypescript-sdk). It's straightforward and highly efficient.\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: 'Get the next post ID for a post on 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('Error fetching next post ID')\n    }\n  }\n\n  throw new Error('Unknown tool')\n})\n\nconst transport = new StdioServerTransport()\nawait server.connect(transport)\n```\n\nThis code establishes a STDIO MCP server with two key request handlers:\n\n1. The `ListToolsRequest` handler returns the available tools to the agent. Currently, there is one tool: `next_post_id`.\n2. The `CallToolRequest` handler is activated when the agent uses the tool. It calls the backend API route to retrieve the next post ID.\n\nThe tool call process is simple; it fetches the next post ID from the backend API route I've defined, using the `ofetch` library for ease. The post ID is then returned as a number.\n\nConsequently, any agent invoking this tool will receive the next post ID as a response within its context. This allows me to draft Markdown files without worrying about post IDs, the agent handles it perfectly!\n\n> [!NOTE]\n> For the complete code, watch the [YouTube video](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NnLIYresumQ).\n\n## Does it work?\n\nYes, it works seamlessly! See it in 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>Requesting the agent to enhance a file and insert the correct ID automatically using the next post ID from the MCP server.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nWith this **MCP server** operational, I can now interact with my Markdown files while supplying context to the agent. This allows me to focus on content rather than technical details. My ultimate aim is to hasten my writing process, making it more efficient and enjoyable, and this software is a step towards that goal.\n\nI'm thoroughly impressed by the MCP SDK's user-friendliness and integration with **VS Code**. The future of agents is promising, and I highly recommend experimenting with them!\n",{"title":19377,"description":20404},{"loc":20406},"posts\u002Fa-model-context-protocol-mcp-server-for-my-website","YNWY8DOJmhb_q5sIL8EKVe4RHVZ5VSL9bqOf1tiZV3M",{"id":20413,"title":20414,"body":20415,"contentId":9291,"date":20793,"description":20794,"extension":18,"meta":20795,"navigation":20,"path":20796,"rawbody":20797,"robots":23,"schemaOrg":23,"seo":20798,"sitemap":20799,"stem":20811,"__hash__":20812},"posts\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025.md","The Conference for Ethics and Diversity in Tech: MiXiT 2025",{"type":7,"value":20416,"toc":20787},[20417,20420,20426,20432,20438,20443,20446,20449,20452,20594],[35,20418,20419],{},"This year, things are evolving. My first conference experience was Devoxx France in 2023, and until now, I had never participated in a conference outside of Paris. However, traveling, meeting new people, discovering their passions, and sharing mine has always been something I wanted to pursue.",[35,20421,20422,20423,20425],{},"When the opportunity to speak at ",[6850,20424,7484],{}," arose, I promptly submitted several proposals. A few weeks later, I received an exciting email: I was selected to speak at the 2025 edition, delivering not one but two talks. I was thrilled to attend this conference and, for the first time, to connect with a new community.",[35,20427,20428],{},[1690,20429],{"alt":20430,"src":20431},"Me in front of the Parc des Hauteurs in Lyon.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fme-in-front-of-parc-des-hauteurs.webp",[35,20433,20434],{},[1690,20435],{"alt":20436,"src":20437},"Lyon view from the Parc des Hauteurs.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Flyon-from-parc-des-hauteurs.webp",[35,20439,20440],{},[1217,20441,20442],{},"Thank you, MiXiT, and everyone for this amazing conference! 🫶",[51,20444,18527],{"id":20445},"mixit-2025",[35,20447,20448],{},"I must confess that I didn't read or prepare anything for this conference until each morning. I chose to savor the moments without stressing, resulting in no real continuity in everything I watched, it was all based on intuition.",[35,20450,20451],{},"Here is the list of the talks I attended:",[1576,20453,20454,20539],{},[71,20455,20456,20459],{},[6850,20457,20458],{},"Tuesday, April 29, 2025",[1576,20460,20461,20471,20479,20487,20497,20507,20522,20529],{},[71,20462,20463,6853,20466],{},[6850,20464,20465],{},"Robustesse : une nouvelle culture Tech ?",[39,20467,20470],{"href":20468,"rel":20469},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Folivier-hamant-a55149153\u002F",[77],"Olivier Hamant",[71,20472,20473,6853,20476],{},[6850,20474,20475],{},"LLMs, toutes ces questions que vous n'avez jamais osé poser à leur sujet",[39,20477,16636],{"href":16634,"rel":20478},[77],[71,20480,20481,6853,20484],{},[6850,20482,20483],{},"Stimuler l'Innovation de Votre Entreprise Grâce à une Plateforme Basée sur l'IA Générative",[39,20485,7018],{"href":7016,"rel":20486},[77],[71,20488,20489,6853,20492],{},[6850,20490,20491],{},"Nos manières de compter, périls économiques, démocratiques et écologiques.",[39,20493,20496],{"href":20494,"rel":20495},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fval%C3%A9rie-charolles-88779594\u002F",[77],"Valérie Charolles",[71,20498,20499,6853,20502],{},[6850,20500,20501],{},"Quand le Terminal dévore la UI : TUI pour tout le monde !",[39,20503,20506],{"href":20504,"rel":20505},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthierrychantier\u002F",[77],"Thierry Chantier",[71,20508,20509,6853,20512,1638,20517],{},[6850,20510,20511],{},"Rendre transparents les changements des CGUs avec Open Terms Archive",[39,20513,20516],{"href":20514,"rel":20515},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmattisg\u002F",[77],"Matti Schneider",[39,20518,20521],{"href":20519,"rel":20520},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fclementbiron\u002F",[77],"Clément Biron",[71,20523,20524,6853,20526],{},[6850,20525,13959],{},[39,20527,7033],{"href":7031,"rel":20528},[77],[71,20530,20531,6853,20534],{},[6850,20532,20533],{},"Design : sabotage, hacking & politique.",[39,20535,20538],{"href":20536,"rel":20537},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgeoffreydorne\u002F",[77],"Geoffrey Dorne",[71,20540,20541,20544],{},[6850,20542,20543],{},"Wednesday, April 30, 2025",[1576,20545,20546,20556,20563,20569,20579],{},[71,20547,20548,6853,20551],{},[6850,20549,20550],{},"Faut-il changer d'ère numérique pour préserver la démocratie ?",[39,20552,20555],{"href":20553,"rel":20554},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdavid-chavalarias\u002F",[77],"David Chavalarias",[71,20557,20558,6853,20560],{},[6850,20559,9124],{},[39,20561,7033],{"href":7031,"rel":20562},[77],[71,20564,20565,20568],{},[6850,20566,20567],{},"Mapping the critical infrastructure sustaining our understanding of the Earth"," by Codrina Maria Illie",[71,20570,20571,6853,20574],{},[6850,20572,20573],{},"L'aventure d'une requête HTTP - ou le chemin de la vie des devs",[39,20575,20578],{"href":20576,"rel":20577},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmartinpascal\u002F",[77],"Pascal Martin",[71,20580,20581,6853,20584,1638,20589],{},[6850,20582,20583],{},"Les super-pouvoirs méconnus de Docker Desktop",[39,20585,20588],{"href":20586,"rel":20587},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fguillaumelours\u002F",[77],"Guillaume Lours",[39,20590,20593],{"href":20591,"rel":20592},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fphcharriere\u002F",[77],"Philippe Charrière",[7139,20595,20597,20600,20603,20609,20615,20621,20623,20626,20685,20687,20691,20699,20706,20716,20722,20724],{"icon":7141,"text":7484,"to":20596,"target":7144},"https:\u002F\u002Fwww.youtube.com\u002F@mixitconf",[35,20598,20599],{},"There were truly fascinating topics at the conference, and honestly, some of them blew my mind.",[35,20601,20602],{},"Attending a conference is also about networking, and with the speakers' dinner on Monday night and the cocktail party on Tuesday night, I had the chance to converse with many people about a wide range of topics, exactly what I love doing.",[35,20604,20605],{},[1690,20606],{"alt":20607,"src":20608},"My glass of wine during the speakers' dinner. This is the only photo I took. Oops!","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fspeakers-dinner.webp",[35,20610,20611],{},[1690,20612],{"alt":20613,"src":20614},"The town hall of Lyon, where the cocktail party took place.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Ftown-hall.webp",[35,20616,20617],{},[1690,20618],{"alt":20619,"src":20620},"The cocktail party at the town hall of Lyon.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fcocktail-party.webp",[51,20622,16676],{"id":16675},[35,20624,20625],{},"Some talks particularly grabbed my attention or resonated with me. Here are some highlights:",[68,20627,20628,20644,20669],{},[71,20629,20630,6853,20632,20635,20637,20638,20640],{},[6850,20631,20483],{},[39,20633,7018],{"href":7016,"rel":20634},[77],[7162,20636],{},"Integrating AI into your company is now absolutely essential. It's adapt or perish. During his 45-minute talk, Philippe Laumay outlined how his R&D department established a laboratory to allow every employee to experiment with generative AI. The aims were to educate the company about this inevitable technology and to enable them to uncover its potential for integrating it into workflows. It was genuinely impressive, showcasing years of work with numbers to support the effectiveness of this approach. If you plan on integrating AI into your business, this talk is a must-watch.",[7162,20639],{},[1690,20641],{"alt":20642,"src":20643},"Photo of Philippe Laumay during his talk.","\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,20645,20646,6853,20648,1638,20651,20654,20656,20657,20662,20663,20665],{},[6850,20647,20511],{},[39,20649,20516],{"href":20514,"rel":20650},[77],[39,20652,20521],{"href":20519,"rel":20653},[77],[7162,20655],{},"We've all received emails notifying us of changed terms of service. But who reads them? How do we verify actual changes? This talk covered an open-source project called ",[39,20658,20661],{"href":20659,"rel":20660},"https:\u002F\u002Fopentermsarchive.org\u002Fen\u002F",[77],"Open Terms Archive",", aimed at making big tech companies' terms of service transparent by publicly recording every version for democratic oversight. They showcased changes from Meta's latest terms, especially regarding free speech, and I was genuinely taken aback by their findings.",[7162,20664],{},[1690,20666],{"alt":20667,"src":20668},"Photo of Matti Schneider and Clément Biron during their talk.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Frendre-transparents-les-changements-des-cgus-avec-open-terms-archive.webp",[71,20670,20671,6853,20673,20676,20678,20679,20681],{},[6850,20672,20573],{},[39,20674,20578],{"href":20576,"rel":20675},[77],[7162,20677],{},"During this time slot, I was uncertain about which talk to attend. On a whim, I chose this one, not knowing the speaker and having briefly glanced at the abstract. It turned out to be an excellent choice. Pascal Martin opened with a question: \"What happens when you press enter in your browser?\" This led to a riveting 45-minute explanation of the intricacies of web development. It was enlightening, and I plan to share the video with anyone who thinks the web is merely HTML, CSS, and JavaScript.",[7162,20680],{},[1690,20682],{"alt":20683,"src":20684},"Photo of Pascal Martin during his talk.","\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,20686,18501],{},[51,20688,20690],{"id":20689},"talking-about-alien-signals-in-a-podcast","Talking about Alien Signals in a podcast",[35,20692,20693,20694,20698],{},"This year, MiXiT launched a podcast to interview speakers about their talks. From now until the 2026 edition, they'll release a new 30-minute episode every three weeks. It's a clever strategy to keep the community engaged and to continue learning from the conference. The podcast is called ",[39,20695,8027],{"href":20696,"rel":20697},"https:\u002F\u002Fopen.spotify.com\u002Fshow\u002F1VjQM9KdqFVRRFzkgPf7b3",[77],", and I highly recommend subscribing.",[35,20700,20701,20702,20705],{},"I had the honor of participating, discussing ",[39,20703,14039],{"href":14037,"rel":20704},[77],". It was a new yet wonderful experience; time flew by as I delved into a subject I love.",[35,20707,20708],{},[1217,20709,20710,20711,20715],{},"Initially, I was set to discuss ",[39,20712,20714],{"href":14025,"rel":20713},[77],"Ecosystem Performance",", but after the talk on reactivity and signals in the frontend, they asked me to cover that instead. I was delighted to do so.",[35,20717,20718],{},[1690,20719],{"alt":20720,"src":20721},"With the podcast team after recording.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fpodcast.webp",[51,20723,18505],{"id":18504},[7139,20725,20729,20732,20760,20766],{"icon":20726,"text":20727,"href":20728},"i-ph-presentation-duotone","I'm open to invitations if you want me to talk at your conference!","mailto:esteban@soubiran.dev",[35,20730,20731],{},"I was chosen to present two talks:",[68,20733,20734,20745],{},[71,20735,20736,20738,20739,20741,20742,20744],{},[6850,20737,9124],{}," (French, \"The Reactivity and Signals: Demystifying the Magic of Frontend\" in English)",[7162,20740],{},"This is my flagship talk, one that I've spent weeks preparing. Luckily, it was already prepared as I presented it recently. The aim was simple: explore and thoroughly understand the ",[6850,20743,14039],{}," library through real code explanations. This talk is tailored for those seeking in-depth knowledge of JavaScript reactivity. The room was packed, which always amazes me given the technical depth of the subject.",[71,20746,20747,20749,20750,20752,20753,20756,20757,20759],{},[6850,20748,13959],{}," (French, \"In JavaScript, there is a package for everything... and often for nothing\" in English)",[7162,20751],{},"This talk focuses on ",[39,20754,20714],{"href":14025,"rel":20755},[77],", an initiative to clean, speed up, and improve the JavaScript ecosystem, especially packages. I presented the project and tools to help anyone understand their ",[156,20758,14029],{}," and make enhancements. Preparing and delivering this 15-minute talk was a blast, and feedback indicates that attendees gained new insights.",[35,20761,20762],{},[1690,20763],{"alt":20764,"src":20765},"Me, in front of the audience","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fme-in-front-of-audience.webp",[7139,20767,20769,20773,20776,20779,20784],{"icon":20726,"text":20768,"href":1816,"target":7144},"Discover the slides of my talks",[51,20770,20772],{"id":20771},"lets-continue","Let's continue",[35,20774,20775],{},"I love this lifestyle, traveling across France, meeting people, discovering their passions, and sharing mine. So, I've decided to submit proposals to six more conferences in 2025. It seems like a lot, but it energizes me and enriches my perspective. Every return from a conference fills my mind with new ideas, although there's rarely enough time to explore them all. 🫠",[35,20777,20778],{},"You might think that submitting my proposals will leave me with even less time due to preparation needs. However, I did it strategically. I proposed talks already prepared; when you spend months perfecting them, you want to present them more than once!",[35,20780,20781],{},[1217,20782,20783],{},"The first time is always the hardest, but everything becomes smoother afterward.",[35,20785,20786],{},"With this newfound time, perhaps I could resume streaming on Twitch (in French). Would you tune in? 🫣",{"title":13,"searchDepth":14,"depth":14,"links":20788},[20789,20790,20791,20792],{"id":20445,"depth":14,"text":18527},{"id":16675,"depth":14,"text":16676},{"id":20689,"depth":14,"text":20690},{"id":18504,"depth":14,"text":18505},"2025-04-30","Exploring insights and experiences from MiXiT 2025, where I delivered two talks and engaged with a vibrant new community.",{},"\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025","---\ncontentId: 1e785fe3-5952-4ba4-9406-c1c3c073c825\ntitle: \"The Conference for Ethics and Diversity in Tech: MiXiT 2025\"\ndescription: \"Exploring insights and experiences from MiXiT 2025, where I delivered two talks and engaged with a vibrant new community.\"\ndate: 2025-04-30\n---\n\nThis year, things are evolving. My first conference experience was Devoxx France in 2023, and until now, I had never participated in a conference outside of Paris. However, traveling, meeting new people, discovering their passions, and sharing mine has always been something I wanted to pursue.\n\nWhen the opportunity to speak at **MiXiT** arose, I promptly submitted several proposals. A few weeks later, I received an exciting email: I was selected to speak at the 2025 edition, delivering not one but two talks. I was thrilled to attend this conference and, for the first time, to connect with a new community.\n\n![Me in front of the Parc des Hauteurs in Lyon.](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fme-in-front-of-parc-des-hauteurs.webp)\n\n![Lyon view from the Parc des Hauteurs.](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Flyon-from-parc-des-hauteurs.webp)\n\n_Thank you, MiXiT, and everyone for this amazing conference! 🫶_\n\n## MiXiT 2025\n\nI must confess that I didn't read or prepare anything for this conference until each morning. I chose to savor the moments without stressing, resulting in no real continuity in everything I watched, it was all based on intuition.\n\nHere is the list of the talks I attended:\n\n- **Tuesday, April 29, 2025**\n  - **Robustesse : une nouvelle culture Tech ?** by [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** by [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** by [Philippe Laumay](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaumay\u002F)\n  - **Nos manières de compter, périls économiques, démocratiques et écologiques.** by [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 !** by [Thierry Chantier](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthierrychantier\u002F)\n  - **Rendre transparents les changements des CGUs avec Open Terms Archive** by [Matti Schneider](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmattisg\u002F) and [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.** by [Estéban Soubiran](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F)\n  - **Design : sabotage, hacking & politique.** by [Geoffrey Dorne](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgeoffreydorne\u002F)\n\n- **Wednesday, April 30, 2025**\n  - **Faut-il changer d'ère numérique pour préserver la démocratie ?** by [David Chavalarias](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdavid-chavalarias\u002F)\n  - **La réactivité et les signaux : démystifions la magie du frontend** by [Estéban Soubiran](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F)\n  - **Mapping the critical infrastructure sustaining our understanding of the Earth** by Codrina Maria Illie\n  - **L'aventure d'une requête HTTP - ou le chemin de la vie des devs** by [Pascal Martin](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmartinpascal\u002F)\n  - **Les super-pouvoirs méconnus de Docker Desktop** by [Guillaume Lours](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fguillaumelours\u002F) and [Philippe Charrière](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fphcharriere\u002F)\n\n\u003CCallout icon=\"i-simple-icons-youtube\" text=\"MiXiT\" to=\"https:\u002F\u002Fwww.youtube.com\u002F@mixitconf\" target=\"_blank\" \u002F>\n\nThere were truly fascinating topics at the conference, and honestly, some of them blew my mind.\n\nAttending a conference is also about networking, and with the speakers' dinner on Monday night and the cocktail party on Tuesday night, I had the chance to converse with many people about a wide range of topics, exactly what I love doing.\n\n![My glass of wine during the speakers' dinner. This is the only photo I took. Oops!](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fspeakers-dinner.webp)\n\n![The town hall of Lyon, where the cocktail party took place.](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Ftown-hall.webp)\n\n![The cocktail party at the town hall of Lyon.](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fcocktail-party.webp)\n\n## Talk highlights\n\nSome talks particularly grabbed my attention or resonated with me. Here are some highlights:\n\n1. **Stimuler l'Innovation de Votre Entreprise Grâce à une Plateforme Basée sur l'IA Générative** by [Philippe Laumay](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaumay\u002F)\n\n   Integrating AI into your company is now absolutely essential. It's adapt or perish. During his 45-minute talk, Philippe Laumay outlined how his R&D department established a laboratory to allow every employee to experiment with generative AI. The aims were to educate the company about this inevitable technology and to enable them to uncover its potential for integrating it into workflows. It was genuinely impressive, showcasing years of work with numbers to support the effectiveness of this approach. If you plan on integrating AI into your business, this talk is a must-watch.\n\n   ![Photo of Philippe Laumay during his talk.](\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** by [Matti Schneider](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmattisg\u002F) and [Clément Biron](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fclementbiron\u002F)\n\n   We've all received emails notifying us of changed terms of service. But who reads them? How do we verify actual changes? This talk covered an open-source project called [Open Terms Archive](https:\u002F\u002Fopentermsarchive.org\u002Fen\u002F), aimed at making big tech companies' terms of service transparent by publicly recording every version for democratic oversight. They showcased changes from Meta's latest terms, especially regarding free speech, and I was genuinely taken aback by their findings.\n\n   ![Photo of Matti Schneider and Clément Biron during their talk.](\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** by [Pascal Martin](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmartinpascal\u002F)\n\n   During this time slot, I was uncertain about which talk to attend. On a whim, I chose this one, not knowing the speaker and having briefly glanced at the abstract. It turned out to be an excellent choice. Pascal Martin opened with a question: \"What happens when you press enter in your browser?\" This led to a riveting 45-minute explanation of the intricacies of web development. It was enlightening, and I plan to share the video with anyone who thinks the web is merely HTML, CSS, and JavaScript.\n\n   ![Photo of Pascal Martin during his talk.](\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\nI highly recommend watching these talks. Enjoy!\n\n## Talking about Alien Signals in a podcast\n\nThis year, MiXiT launched a podcast to interview speakers about their talks. From now until the 2026 edition, they'll release a new 30-minute episode every three weeks. It's a clever strategy to keep the community engaged and to continue learning from the conference. The podcast is called [MiXiT On Air](https:\u002F\u002Fopen.spotify.com\u002Fshow\u002F1VjQM9KdqFVRRFzkgPf7b3), and I highly recommend subscribing.\n\nI had the honor of participating, discussing [Alien Signals](https:\u002F\u002Fgithub.com\u002Fstackblitz\u002Falien-signals). It was a new yet wonderful experience; time flew by as I delved into a subject I love.\n\n_Initially, I was set to discuss [Ecosystem Performance](https:\u002F\u002Fe18e.dev\u002F), but after the talk on reactivity and signals in the frontend, they asked me to cover that instead. I was delighted to do so._\n\n![With the podcast team after recording.](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fpodcast.webp)\n\n## My talks\n\n\u003CCallout icon=\"i-ph-presentation-duotone\" text=\"I'm open to invitations if you want me to talk at your conference!\" href=\"mailto:esteban@soubiran.dev\" \u002F>\n\nI was chosen to present two talks:\n\n1. **La réactivité et les signaux : démystifions la magie du frontend** (French, \"The Reactivity and Signals: Demystifying the Magic of Frontend\" in English)\n\n   This is my flagship talk, one that I've spent weeks preparing. Luckily, it was already prepared as I presented it recently. The aim was simple: explore and thoroughly understand the **Alien Signals** library through real code explanations. This talk is tailored for those seeking in-depth knowledge of JavaScript reactivity. The room was packed, which always amazes me given the technical depth of the subject.\n\n2. **En JavaScript, il y a un paquet pour tout... et souvent pour rien.** (French, \"In JavaScript, there is a package for everything... and often for nothing\" in English)\n\n   This talk focuses on [Ecosystem Performance](https:\u002F\u002Fe18e.dev\u002F), an initiative to clean, speed up, and improve the JavaScript ecosystem, especially packages. I presented the project and tools to help anyone understand their `node_modules` and make enhancements. Preparing and delivering this 15-minute talk was a blast, and feedback indicates that attendees gained new insights.\n\n![Me, in front of the audience](\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=\"Discover the slides of my talks\" href=\"\u002Ftalks\" target=\"_blank\" \u002F>\n\n## Let's continue\n\nI love this lifestyle, traveling across France, meeting people, discovering their passions, and sharing mine. So, I've decided to submit proposals to six more conferences in 2025. It seems like a lot, but it energizes me and enriches my perspective. Every return from a conference fills my mind with new ideas, although there's rarely enough time to explore them all. 🫠\n\nYou might think that submitting my proposals will leave me with even less time due to preparation needs. However, I did it strategically. I proposed talks already prepared; when you spend months perfecting them, you want to present them more than once!\n\n_The first time is always the hardest, but everything becomes smoother afterward._\n\nWith this newfound time, perhaps I could resume streaming on Twitch (in French). Would you tune in? 🫣\n",{"title":20414,"description":20794},{"loc":20796,"images":20800},[20801,20802,20803,20804,20805,20806,20807,20808,20809,20810],{"loc":20431},{"loc":20437},{"loc":20608},{"loc":20614},{"loc":20620},{"loc":20643},{"loc":20668},{"loc":20684},{"loc":20721},{"loc":20765},"posts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025","XbxlAT7Vn58fO5BKZMQe7yJaWmEERP7euna6BjH_2uc",{"id":20814,"title":20815,"body":20816,"contentId":9286,"date":21315,"description":21316,"extension":18,"meta":21317,"navigation":20,"path":21318,"rawbody":21319,"robots":23,"schemaOrg":23,"seo":21320,"sitemap":21321,"stem":21336,"__hash__":21337},"posts\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special.md","Beyond the Tech: People Who Made Devoxx France 2025 Special",{"type":7,"value":20817,"toc":21312},[20818,20824],[35,20819,20820,20821,20823],{},"This year, I had the pleasure of attending ",[6850,20822,7142],{}," as a speaker for the third time.",[7905,20825,20827,20830,20835,20841,20843,20846,20849,20861,20864,20867,20993,20999],{"id":20826},"08852918-9829-4658-97b3-1aabede8e9fe",[35,20828,20829],{},"However, this year was different from the previous two. I felt more at ease, without any pressure from giving two talks or the need to attend all sessions. I met amazing individuals who made this event truly special and memorable.",[35,20831,20832],{},[1217,20833,20834],{},"Devoxx France is a conference held in Paris, France, featuring three days of talks, 70 exhibitors, over 300 speakers, and 4,500 attendees.",[35,20836,20837],{},[1690,20838],{"alt":20839,"src":20840},"Devoxx France opening.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fdevoxx-france-opening.webp",[51,20842,6822],{"id":6821},[35,20844,20845],{},"As always, I prepared my schedule each morning while waiting for the first session. Arriving 45 minutes early to be first in line gave me the time to plan my day!",[35,20847,20848],{},"You might wonder why I arrive so early. The reason is simple: each day starts with two keynotes, but the main amphitheater only has 800 seats. Although they are broadcast in other rooms, I prefer being in the main room to see the speakers in person and enjoy the atmosphere. To secure the best seat, I have to arrive early since the line is already long 40 minutes before the first session begins.",[35,20850,20851,20852,3076,20855,3080,20857,20860],{},"This year, many sessions focused on ",[6850,20853,20854],{},"AI",[6850,20856,19508],{},[6850,20858,20859],{},"Agents",". It's fascinating to see AI's evolution reflected in session titles. In 2023, it was about \"AI\" and \"LLM\"; in 2024, \"AI\" and \"RAG\" (Retrieval-Augmented Generation); and in 2025, \"AI\" and \"Agents.\" It's incredible how fast the landscape is changing!",[35,20862,20863],{},"Yet, I aimed to avoid sessions on AI and LLMs, as this topic is already pervasive. I sought out sessions with less conventional topics.",[35,20865,20866],{},"Here was my schedule for the three days of Devoxx France 2025:",[68,20868,20869,20903,20952],{},[71,20870,20871,20872],{},"Wednesday, April 16, 2025\n",[1576,20873,20874,20880,20885,20891,20897],{},[71,20875,20876,20879],{},[6850,20877,20878],{},"L'Intelligence Artificielle n'existe pas"," by Luc Julia",[71,20881,20882,20879],{},[6850,20883,20884],{},"Luc Julia sans filtre : l'IA, la tech et les idées reçues",[71,20886,20887,20890],{},[6850,20888,20889],{},"Arcane: Quand la technologie n'est pas le produit: une série d'animation"," by Philippe Llerena",[71,20892,20893,20896],{},[6850,20894,20895],{},"Comment nous avons transformé les Restos du Cœur en Cloud Provider"," by Julien Briault and Stéphane Trognon",[71,20898,20899,20902],{},[6850,20900,20901],{},"\"Ça marche dans mon .devcontainer\""," by Benoit Moussaud",[71,20904,20905,20906],{},"Thursday, April 17, 2025\n",[1576,20907,20908,20914,20920,20926,20932,20938,20944],{},[71,20909,20910,20913],{},[6850,20911,20912],{},"Language, IA et propagande : la guerre des récits a déjà commencé"," by Elodie Mielczareck",[71,20915,20916,20919],{},[6850,20917,20918],{},"La territorialisation des infrastructures comme levier de pouvoir"," by Ophélie Coelho",[71,20921,20922,20925],{},[6850,20923,20924],{},"Les clés de l'architecture pour les devs"," by Christian Sperandio and Cyrille Martraire",[71,20927,20928,20931],{},[6850,20929,20930],{},"Design System : Du Chaos à la Cohérence avec style !"," by Claire Gourcy",[71,20933,20934,20937],{},[6850,20935,20936],{},"On met en place une stack React props et complète"," by Olivier Abdelnour",[71,20939,20940,20943],{},[6850,20941,20942],{},"Load-testons M6+ pour préparer l'Euro 2024"," by Paul-Henri Pillet and Benoit Colin",[71,20945,20946,20948,20949,2089],{},[6850,20947,9124],{}," by Estéban Soubiran (",[1217,20950,20951],{},"it's me!",[71,20953,20954,20955],{},"Friday, April 18, 2025\n",[1576,20956,20957,20963,20969,20974,20981,20987],{},[71,20958,20959,20962],{},[6850,20960,20961],{},"Plongez dans l'Ère Quantique : décryptez et anticipez la révolution à venir"," by Fanny Bouton",[71,20964,20965,20968],{},[6850,20966,20967],{},"Les LLM rêvent-ils de cavaliers électriques ?"," by Thibaut Giraud",[71,20970,20971,20973],{},[6850,20972,16602],{}," by Loïc Ortola and Mathilde Lorrain",[71,20975,20976,20948,20978,2089],{},[6850,20977,13959],{},[1217,20979,20980],{},"it's me, again!",[71,20982,20983,20986],{},[6850,20984,20985],{},"Construire une IA agentique avec les Structured Outputs, Function Calling et MCP"," by Julien Dubois",[71,20988,20989,20992],{},[6850,20990,20991],{},"Optimisation des requêtes PostgreSQL : Parlons Performance !"," by Laetitia Avrot",[35,20994,20995,20996,215],{},"These were just the sessions I attended. I had to make choices and possibly missed some great talks. Fortunately, the sessions are recorded and available on the ",[6850,20997,20998],{},"Devoxx France YouTube channel",[7139,21000,21001,21028,21033,21035,21038,21161,21163,21165,21168,21235],{"w-full":13,"icon":7141,"text":7142,"to":7143,"target":7144},[35,21002,21003,21004,21009,21010,21015,21016,21021,21022,21027],{},"This year, I attended fewer sessions than in previous years, opting to spend more time connecting with people. One highlight was visiting the Clever Cloud stand, where I had an engaging conversation with ",[39,21005,21008],{"href":21006,"rel":21007},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcnivolle\u002F",[77],"Clément Nivolle",", co-founder of Clever Cloud. We discussed topics ranging from the French cloud ecosystem to the influence of hyperscalers on politics. At the Microsoft stand, I talked with ",[39,21011,21014],{"href":21012,"rel":21013},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbaptisteohanes\u002F",[77],"Baptiste Ohanes"," about Microsoft, the cloud, and AI. I also reconnected with ",[39,21017,21020],{"href":21018,"rel":21019},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ftugdualgrall\u002F",[77],"Tugdual Grall",", whom I had met at ",[39,21023,21026],{"href":21024,"rel":21025},"https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Frecap-2023#github-galaxy",[77],"GitHub Galaxy 2023",". He showcased new features of GitHub Copilot, like the Agent mode. Watching his demo, it was clear that by not using AI, you're missing out on many advancements. Thank you all for the wonderful discussions!",[35,21029,21030],{},[1217,21031,21032],{},"If you attend conferences, don't neglect the value of networking, it can be more rewarding than the sessions themselves.",[51,21034,7148],{"id":7147},[35,21036,21037],{},"I had the privilege to attend many exceptional talks, and here are some of my favorites (in no particular order):",[68,21039,21040,21069,21095,21130],{},[71,21041,21042,6853,21044,21049,21051,21056,21057,21062,21063,21065],{},[6850,21043,20889],{},[39,21045,21048],{"href":21046,"rel":21047},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fphilippellerena\u002F",[77],"Philippe Llerena",[7162,21050],{},[39,21052,21055],{"href":21053,"rel":21054},"https:\u002F\u002Fwww.arcane.com\u002Fen-us\u002F",[77],"Arcane"," is a phenomenon, a true masterpiece and work of art. It was a brilliant decision to present a talk about Arcane. Philippe, the CTO of ",[39,21058,21061],{"href":21059,"rel":21060},"https:\u002F\u002Fforticheprod.com\u002F",[77],"Fortiche",", the studio behind Arcane, explained the crucial role technology plays in its production. It was refreshing to see how developers can empower artists and not only deliver another application.",[7162,21064],{},[1690,21066],{"alt":21067,"src":21068},"Photo of Philippe Llerena during his talk.","\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,21070,21071,6853,21073,1638,21076,21081,419,21083,21088,21089,21091],{},[6850,21072,20895],{},[39,21074,6943],{"href":6941,"rel":21075},[77],[39,21077,21080],{"href":21078,"rel":21079},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fstephanetrognon\u002F",[77],"Stéphane Trognon",[7162,21082],{},[39,21084,21087],{"href":21085,"rel":21086},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRestaurants_du_C%C5%93ur",[77],"Restos du Cœur"," is a French charity providing food aid. In this three-hour talk, Julien and Stéphane shared the story behind transforming their IT system. The connection between a charity and a cloud provider might be unclear, but with over 35,000 people served daily, and every euro equating to a meal, an expensive provider wasn't an option. This journey led to creating an in-house cloud provider. It exemplifies using technology for humanitarian solutions. Their work is truly inspiring, and I hope the project continues to grow.",[7162,21090],{},[1690,21092],{"alt":21093,"src":21094},"Photo of Julien Briault and Stéphane Trognon during their talk.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fcomment-nous-avons-transforme-les-restos-du-coeur-en-cloud-provider.webp",[71,21096,21097,6853,21099,21104,21106,21107,21112,21113,21118,21119,215,21124,21126],{},[6850,21098,20930],{},[39,21100,21103],{"href":21101,"rel":21102},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fclaire-gourcy-3197861a\u002F",[77],"Claire Gourcy",[7162,21105],{},"Claire, a UX-UI designer at ",[39,21108,21111],{"href":21109,"rel":21110},"https:\u002F\u002Fwww.michelin.com",[77],"Michelin",", shared the journey behind creating Michelin's design system, which unifies the experience across all products, services, and tools. It reminded me of the challenge ",[39,21114,21117],{"href":21115,"rel":21116},"https:\u002F\u002Fwww.infomaniak.com\u002F",[77],"Infomaniak"," faces in unifying its web platforms and services. It was impressive to see the project's evolution from a best-effort initiative into the fully-implemented ",[39,21120,21123],{"href":21121,"rel":21122},"https:\u002F\u002Fdesignsystem.michelin.com\u002F",[77],"Motion Design System",[7162,21125],{},[1690,21127],{"alt":21128,"src":21129},"Photo of Claire Gourcy and Bibendum during her talk.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fdesign-system-du-chaos-a-la-coherence-avec-style.webp",[71,21131,21132,6853,21134,1638,21137,21140,21142,21143,21148,21149,21151,21155,21157],{},[6850,21133,16602],{},[39,21135,16606],{"href":9582,"rel":21136},[77],[39,21138,16611],{"href":16609,"rel":21139},[77],[7162,21141],{},"This talk was a masterclass. The content was incredibly insightful, I learned a lot, and the presentation by Loïc and Mathilde was impressive. Their collaboration, humor with the ",[39,21144,21147],{"href":21145,"rel":21146},"https:\u002F\u002Fwww.youtube.com\u002Fuser\u002Fbref",[77],"Bref"," moment, and use of techniques like slides, live coding, refactoring, and real-world examples made it engaging. The practical tips showcased the power of load testing.",[7162,21150],{},[1690,21152],{"alt":21153,"src":21154},"Photo of Loïc Ortola and Mathilde Lorrain during their talk.","\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",[7162,21156],{},[1690,21158],{"alt":21159,"src":21160},"Photo of Loïc Ortola and Mathilde taking a selfie with the audience. (On LinkedIn, you can find the selfie with the audience.)","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Floic-ortola-mathilde-lorrain-selfie.webp",[35,21162,18501],{},[51,21164,18505],{"id":18504},[35,21166,21167],{},"This year, I had the pleasure of delivering two talks, which involved a lot of fun and hard work! 🫠",[68,21169,21170,21214],{},[71,21171,21172,21174,21175,21177,21178,21180,21181,21183,21187,21189,21190,21194,21195,21197,21198,21200,21204,21206,21207,21209,21210,21213],{},[6850,21173,9124],{}," (in French, \"The Reactivity and Signals: Demystifying the Magic of Frontend\" in English)",[7162,21176],{},"This was my flagship talk, and I spent weeks preparing it (and I'm continually improving it for future presentations). The goal was simple: exploring and deeply understanding the library ",[6850,21179,14039],{}," through real code explanations. This talk targets those seeking a deep understanding of reactivity in JavaScript. The full room indicated strong interest, with more than 200 attendees. When informed there were no free seats five minutes before my talk, my stress level rose. But once I started, I entered my zone, and the stress vanished.",[7162,21182],{},[1690,21184],{"alt":21185,"src":21186},"Photo of me before my talk.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fla-reactivite-et-les-signaux-demystifions-la-magie-du-frontend.webp",[7162,21188],{},"After a year of work, it was the real first use of ",[39,21191,7881],{"href":21192,"rel":21193},"https:\u002F\u002Finalia.app\u002F",[77],", the SaaS I'm developing to enhance talks with real-time interactions. One feature enables the audience to respond with emoji reactions, sharing their real-time thoughts and feelings. Seeing 🍆, 🍑, and 👅 on screen was amusing, at times requiring me to focus elsewhere than on my slides! 😆",[7162,21196],{},"More than 20,000 emoji reactions were sent during the talk! 🤯",[7162,21199],{},[1690,21201],{"alt":21202,"src":21203},"Photo of my talk covered in emoji.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fla-reactivite-et-les-signaux-demystifions-la-magie-du-frontend-emoji.webp",[7162,21205],{},"When the audience uses reactions effectively, it becomes transformative. I can sense when they're lost, bored, or genuinely interested.",[7162,21208],{},"Understanding ",[6850,21211,21212],{},"Align Signals"," and building this platform was arduous but worthwhile. Feedback has been overwhelmingly positive, despite the deep, technical content. Thank you all for your feedback and enthusiasm.",[71,21215,21216,21218,21219,21221,21222,21225,21226,21228,21229,21231],{},[6850,21217,13959],{}," (in French, \"In JavaScript, there is a package for everything... and often for nothing\" in English)",[7162,21220],{},"This talk centers on ",[39,21223,20714],{"href":14025,"rel":21224},[77],", an initiative to clean up, speed up, and enhance the JavaScript ecosystem, especially packages. I presented the project and tools to help anyone understand their ",[156,21227,14029],{}," and make improvements. Preparing and presenting this 15-minute talk was fun, and feedback shows attendees learned something new.",[7162,21230],{},[1690,21232],{"alt":21233,"src":21234},"Photo of the audience during my talk.","\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",[7139,21236,21237,21241,21244,21247,21254,21264,21270,21276,21282,21288,21293,21297,21309],{"icon":20726,"text":20768,"href":1816,"target":7144},[51,21238,21240],{"id":21239},"special-thanks-to-takima","Special thanks to Takima",[35,21242,21243],{},"Sometimes, one encounter can completely transform your experience.",[35,21245,21246],{},"I'm not the most outgoing person, and I've always found it challenging to strike up conversations with new faces, even approaching exhibitors can be difficult. Over the years, I've attended meetups, conferences, and other events on my own, as my friends aren't web developers. While I enjoy going to these gatherings, it can feel a bit exhausting to go it alone, especially during a three-day event like Devoxx France where networking plays a big part, or during the meet-and-greet party where people are at the heart of the moment.",[35,21248,21249,21250,21253],{},"This all changed when I met the incredible team from ",[39,21251,9575],{"href":9573,"rel":21252},[77]," during their TakiMeet events. They attended my talk, and their stand felt like a welcoming headquarters, a place where I always had someone to chat with or even a spot to sit and relax, even with empty social batteries. This change was a game-changer for me and this year's Devoxx was an absolute blast!",[35,21255,21256,21257,21259,21260,21263],{},"Thank you to the amazing team at ",[6850,21258,9575],{},", with special thanks to ",[1217,21261,21262],{},"Mathilde, Camille, and Cyrielle",", for your genuine hospitality and the conversations we shared. I truly appreciated our time together and I hope to reconnect soon. 🫶",[35,21265,21266],{},[1690,21267],{"alt":21268,"src":21269},"Photo of the Takima stand at Devoxx France 2025.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-stand.webp",[35,21271,21272],{},[1690,21273],{"alt":21274,"src":21275},"A part of the Takima team watching a talk of one of their colleague.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-team.webp",[35,21277,21278],{},[1690,21279],{"alt":21280,"src":21281},"Takima stand during the game draw to win headphones, speakers, and trainings.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-game.webp",[35,21283,21284],{},[1690,21285],{"alt":21286,"src":21287},"Bidendum, a part of the Takima team, and me!","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-bidendum.webp",[35,21289,21290],{},[1217,21291,21292],{},"It's all about the people!",[51,21294,21296],{"id":21295},"to-be-continued","To be continued",[35,21298,21299,21300,21304,21305,21308],{},"Devoxx France was a blast, but the adventure doesn't end here. At the end of April, I'll be attending ",[39,21301,7484],{"href":21302,"rel":21303},"https:\u002F\u002Fmixitconf.org\u002Ffr\u002F",[77]," to deliver the same talk on reactivity and signals. The journey continues in June with ",[39,21306,9007],{"href":18192,"rel":21307},[77],". If you're in the area, don't hesitate to come and say hi!",[35,21310,21311],{},"Happy coding! 👋",{"title":13,"searchDepth":14,"depth":14,"links":21313},[21314],{"id":6821,"depth":14,"text":6822},"2025-04-18","A recap of my experience at Devoxx France 2025, focusing on the people and connections that made the event memorable.",{},"\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special","---\ncontentId: 38a49fbc-ac6d-4307-ae17-e662a855d897\ntitle: \"Beyond the Tech: People Who Made Devoxx France 2025 Special\"\ndescription: \"A recap of my experience at Devoxx France 2025, focusing on the people and connections that made the event memorable.\"\ndate: 2025-04-18\n---\n\nThis year, I had the pleasure of attending **Devoxx France** as a speaker for the third time.\n\n\u003CCalloutReadMore id=\"08852918-9829-4658-97b3-1aabede8e9fe\" \u002F>\n\nHowever, this year was different from the previous two. I felt more at ease, without any pressure from giving two talks or the need to attend all sessions. I met amazing individuals who made this event truly special and memorable.\n\n_Devoxx France is a conference held in Paris, France, featuring three days of talks, 70 exhibitors, over 300 speakers, and 4,500 attendees._\n\n![Devoxx France opening.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fdevoxx-france-opening.webp)\n\n## My schedule\n\nAs always, I prepared my schedule each morning while waiting for the first session. Arriving 45 minutes early to be first in line gave me the time to plan my day!\n\nYou might wonder why I arrive so early. The reason is simple: each day starts with two keynotes, but the main amphitheater only has 800 seats. Although they are broadcast in other rooms, I prefer being in the main room to see the speakers in person and enjoy the atmosphere. To secure the best seat, I have to arrive early since the line is already long 40 minutes before the first session begins.\n\nThis year, many sessions focused on **AI**, **LLMs**, and **Agents**. It's fascinating to see AI's evolution reflected in session titles. In 2023, it was about \"AI\" and \"LLM\"; in 2024, \"AI\" and \"RAG\" (Retrieval-Augmented Generation); and in 2025, \"AI\" and \"Agents.\" It's incredible how fast the landscape is changing!\n\nYet, I aimed to avoid sessions on AI and LLMs, as this topic is already pervasive. I sought out sessions with less conventional topics.\n\nHere was my schedule for the three days of Devoxx France 2025:\n\n1. Wednesday, April 16, 2025\n   - **L'Intelligence Artificielle n'existe pas** by Luc Julia\n   - **Luc Julia sans filtre : l'IA, la tech et les idées reçues** by Luc Julia\n   - **Arcane: Quand la technologie n'est pas le produit: une série d'animation** by Philippe Llerena\n   - **Comment nous avons transformé les Restos du Cœur en Cloud Provider** by Julien Briault and Stéphane Trognon\n   - **\"Ça marche dans mon .devcontainer\"** by Benoit Moussaud\n2. Thursday, April 17, 2025\n   - **Language, IA et propagande : la guerre des récits a déjà commencé** by Elodie Mielczareck\n   - **La territorialisation des infrastructures comme levier de pouvoir** by Ophélie Coelho\n   - **Les clés de l'architecture pour les devs** by Christian Sperandio and Cyrille Martraire\n   - **Design System : Du Chaos à la Cohérence avec style !** by Claire Gourcy\n   - **On met en place une stack React props et complète** by Olivier Abdelnour\n   - **Load-testons M6+ pour préparer l'Euro 2024** by Paul-Henri Pillet and Benoit Colin\n   - **La réactivité et les signaux : démystifions la magie du frontend** by Estéban Soubiran (_it's me!_)\n3. Friday, April 18, 2025\n   - **Plongez dans l'Ère Quantique : décryptez et anticipez la révolution à venir** by Fanny Bouton\n   - **Les LLM rêvent-ils de cavaliers électriques ?** by Thibaut Giraud\n   - **45 min pour mettre son application à genoux : le guide complet du test de charge** by Loïc Ortola and Mathilde Lorrain\n   - **En JavaScript, il y a un paquet pour tout... et souvent pour rien.** by Estéban Soubiran (_it's me, again!_)\n   - **Construire une IA agentique avec les Structured Outputs, Function Calling et MCP** by Julien Dubois\n   - **Optimisation des requêtes PostgreSQL : Parlons Performance !** by Laetitia Avrot\n\nThese were just the sessions I attended. I had to make choices and possibly missed some great talks. Fortunately, the sessions are recorded and available on the **Devoxx France YouTube channel**.\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\nThis year, I attended fewer sessions than in previous years, opting to spend more time connecting with people. One highlight was visiting the Clever Cloud stand, where I had an engaging conversation with [Clément Nivolle](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcnivolle\u002F), co-founder of Clever Cloud. We discussed topics ranging from the French cloud ecosystem to the influence of hyperscalers on politics. At the Microsoft stand, I talked with [Baptiste Ohanes](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbaptisteohanes\u002F) about Microsoft, the cloud, and AI. I also reconnected with [Tugdual Grall](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ftugdualgrall\u002F), whom I had met at [GitHub Galaxy 2023](https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Frecap-2023#github-galaxy). He showcased new features of GitHub Copilot, like the Agent mode. Watching his demo, it was clear that by not using AI, you're missing out on many advancements. Thank you all for the wonderful discussions!\n\n_If you attend conferences, don't neglect the value of networking, it can be more rewarding than the sessions themselves._\n\n## Talks I loved\n\nI had the privilege to attend many exceptional talks, and here are some of my favorites (in no particular order):\n\n1. **Arcane: Quand la technologie n'est pas le produit: une série d'animation** by [Philippe Llerena](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fphilippellerena\u002F)\n\n   [Arcane](https:\u002F\u002Fwww.arcane.com\u002Fen-us\u002F) is a phenomenon, a true masterpiece and work of art. It was a brilliant decision to present a talk about Arcane. Philippe, the CTO of [Fortiche](https:\u002F\u002Fforticheprod.com\u002F), the studio behind Arcane, explained the crucial role technology plays in its production. It was refreshing to see how developers can empower artists and not only deliver another application.\n\n   ![Photo of Philippe Llerena during his talk.](\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** by [Julien Briault](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-briault-441539137\u002F) and [Stéphane Trognon](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fstephanetrognon\u002F)\n\n   The [Restos du Cœur](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRestaurants_du_C%C5%93ur) is a French charity providing food aid. In this three-hour talk, Julien and Stéphane shared the story behind transforming their IT system. The connection between a charity and a cloud provider might be unclear, but with over 35,000 people served daily, and every euro equating to a meal, an expensive provider wasn't an option. This journey led to creating an in-house cloud provider. It exemplifies using technology for humanitarian solutions. Their work is truly inspiring, and I hope the project continues to grow.\n\n   ![Photo of Julien Briault and Stéphane Trognon during their talk.](\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 !** by [Claire Gourcy](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fclaire-gourcy-3197861a\u002F)\n\n   Claire, a UX-UI designer at [Michelin](https:\u002F\u002Fwww.michelin.com), shared the journey behind creating Michelin's design system, which unifies the experience across all products, services, and tools. It reminded me of the challenge [Infomaniak](https:\u002F\u002Fwww.infomaniak.com\u002F) faces in unifying its web platforms and services. It was impressive to see the project's evolution from a best-effort initiative into the fully-implemented [Motion Design System](https:\u002F\u002Fdesignsystem.michelin.com\u002F).\n\n   ![Photo of Claire Gourcy and Bibendum during her talk.](\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** by [Loïc Ortola](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floicortola\u002F) and [Mathilde Lorrain](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmathilde-lorrain\u002F)\n\n   This talk was a masterclass. The content was incredibly insightful, I learned a lot, and the presentation by Loïc and Mathilde was impressive. Their collaboration, humor with the [Bref](https:\u002F\u002Fwww.youtube.com\u002Fuser\u002Fbref) moment, and use of techniques like slides, live coding, refactoring, and real-world examples made it engaging. The practical tips showcased the power of load testing.\n\n   ![Photo of Loïc Ortola and Mathilde Lorrain during their talk.](\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 of Loïc Ortola and Mathilde taking a selfie with the audience. (On LinkedIn, you can find the selfie with the audience.)](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Floic-ortola-mathilde-lorrain-selfie.webp)\n\nI highly recommend watching these talks. Enjoy!\n\n## My talks\n\nThis year, I had the pleasure of delivering two talks, which involved a lot of fun and hard work! 🫠\n\n1. **La réactivité et les signaux : démystifions la magie du frontend** (in French, \"The Reactivity and Signals: Demystifying the Magic of Frontend\" in English)\n\n   This was my flagship talk, and I spent weeks preparing it (and I'm continually improving it for future presentations). The goal was simple: exploring and deeply understanding the library **Alien Signals** through real code explanations. This talk targets those seeking a deep understanding of reactivity in JavaScript. The full room indicated strong interest, with more than 200 attendees. When informed there were no free seats five minutes before my talk, my stress level rose. But once I started, I entered my zone, and the stress vanished.\n\n   ![Photo of me before my talk.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fla-reactivite-et-les-signaux-demystifions-la-magie-du-frontend.webp)\n\n   After a year of work, it was the real first use of [Inalia](https:\u002F\u002Finalia.app\u002F), the SaaS I'm developing to enhance talks with real-time interactions. One feature enables the audience to respond with emoji reactions, sharing their real-time thoughts and feelings. Seeing 🍆, 🍑, and 👅 on screen was amusing, at times requiring me to focus elsewhere than on my slides! 😆\n\n   More than 20,000 emoji reactions were sent during the talk! 🤯\n\n   ![Photo of my talk covered in emoji.](\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   When the audience uses reactions effectively, it becomes transformative. I can sense when they're lost, bored, or genuinely interested.\n\n   Understanding **Align Signals** and building this platform was arduous but worthwhile. Feedback has been overwhelmingly positive, despite the deep, technical content. Thank you all for your feedback and enthusiasm.\n\n2. **En JavaScript, il y a un paquet pour tout... et souvent pour rien.** (in French, \"In JavaScript, there is a package for everything... and often for nothing\" in English)\n\n   This talk centers on [Ecosystem Performance](https:\u002F\u002Fe18e.dev\u002F), an initiative to clean up, speed up, and enhance the JavaScript ecosystem, especially packages. I presented the project and tools to help anyone understand their `node_modules` and make improvements. Preparing and presenting this 15-minute talk was fun, and feedback shows attendees learned something new.\n\n   ![Photo of the audience during my talk.](\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=\"Discover the slides of my talks\" href=\"\u002Ftalks\" target=\"_blank\" \u002F>\n\n## Special thanks to Takima\n\nSometimes, one encounter can completely transform your experience.\n\nI'm not the most outgoing person, and I've always found it challenging to strike up conversations with new faces, even approaching exhibitors can be difficult. Over the years, I've attended meetups, conferences, and other events on my own, as my friends aren't web developers. While I enjoy going to these gatherings, it can feel a bit exhausting to go it alone, especially during a three-day event like Devoxx France where networking plays a big part, or during the meet-and-greet party where people are at the heart of the moment.\n\nThis all changed when I met the incredible team from [Takima](https:\u002F\u002Fwww.takima.fr\u002F) during their TakiMeet events. They attended my talk, and their stand felt like a welcoming headquarters, a place where I always had someone to chat with or even a spot to sit and relax, even with empty social batteries. This change was a game-changer for me and this year's Devoxx was an absolute blast!\n\nThank you to the amazing team at **Takima**, with special thanks to _Mathilde, Camille, and Cyrielle_, for your genuine hospitality and the conversations we shared. I truly appreciated our time together and I hope to reconnect soon. 🫶\n\n![Photo of the Takima stand at Devoxx France 2025.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-stand.webp)\n\n![A part of the Takima team watching a talk of one of their colleague.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-team.webp)\n\n![Takima stand during the game draw to win headphones, speakers, and trainings.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-game.webp)\n\n![Bidendum, a part of the Takima team, and me!](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-bidendum.webp)\n\n_It's all about the people!_\n\n## To be continued\n\nDevoxx France was a blast, but the adventure doesn't end here. At the end of April, I'll be attending [MiXiT](https:\u002F\u002Fmixitconf.org\u002Ffr\u002F) to deliver the same talk on reactivity and signals. The journey continues in June with [BreizhCamp](https:\u002F\u002Fwww.breizhcamp.org\u002F). If you're in the area, don't hesitate to come and say hi!\n\nHappy coding! 👋\n",{"title":20815,"description":21316},{"loc":21318,"images":21322},[21323,21324,21325,21326,21327,21328,21329,21330,21331,21332,21333,21334,21335],{"loc":20840},{"loc":21068},{"loc":21094},{"loc":21129},{"loc":21154},{"loc":21160},{"loc":21186},{"loc":21203},{"loc":21234},{"loc":21269},{"loc":21275},{"loc":21281},{"loc":21287},"posts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special","ac5KJhl0luyI6S5lwmu8RzMHR5DL6ChDYpHw9Vly1VE",{"id":21339,"title":21340,"body":21341,"contentId":9257,"date":24160,"description":24161,"extension":18,"meta":24162,"navigation":20,"path":24163,"rawbody":24164,"robots":23,"schemaOrg":23,"seo":24165,"sitemap":24166,"stem":24167,"__hash__":24168},"posts\u002Fposts\u002Fa-journey-to-craft-interactive-ui-experiences-with-dialogs.md","A Journey to Craft Interactive UI Experiences with Dialogs",{"type":7,"value":21342,"toc":24152},[21343,21350,24149],[35,21344,21345,21346,21349],{},"As far as I remember, this challenge has been the most persistent one since I began my journey in ",[6850,21347,21348],{},"web development",". I recently uncovered a method to build a component library, which was a long endeavor. However, solving the intricacies of dialog and modal management has been even more extensive, complex, and crucial due to its ubiquitous presence in every application.",[7905,21351,21353,21360,21364,21377,21380,21395,21398,21409,21412,21415,21505,21588,21591,21594,21597,21639,21642,21645,21655,21659,21666,21746,21749,21757,21975,21978,21982,21985,21991,22101,22104,22267,22270,22395,22586,22589,22840,22862,22866,22873,22880,23036,23039,23056,23151,23154,23173,23330,23337,23340,23344,23347,23349,23594,23938,24072,24074,24086,24099,24103,24106,24123,24146],{"id":21352},"f9754148-40bd-4df2-a144-f686e894b200",[35,21354,21355,21356,21359],{},"Today, I'm thrilled to share the solution I found, ",[1217,21357,21358],{},"I didn't develop it",", for managing dialogs and modals effectively within your applications. This resolution stems from years of experience, and I hope it benefits you as it has benefited me.",[51,21361,21363],{"id":21362},"understanding-the-issue","Understanding the issue",[35,21365,21366,21367,3076,21370,3080,21373,21376],{},"Before delving into the problem, let's align on what we're discussing. This article focuses on ",[6850,21368,21369],{},"dialogs",[6850,21371,21372],{},"modals",[6850,21374,21375],{},"slideovers",". Each serves to display information or solicit confirmation, differing mainly in presentation and usage.",[35,21378,21379],{},"Try to open the dialog by clicking the button below.",[11386,21381,132,21385],{"className":21382},[21383,21384],"flex","justify-center",[21386,21387,136,21391],"modal",{"title":21388,"description":21389,":ui":21390},"Dialog","This is a dialog","{ content: 'min-w-[280px]' }",[910,21392,21394],{"label":21393},"Open Dialog","\n  \n",[35,21396,21397],{},"Similarly, you can open a slideover by clicking the button.",[11386,21399,132,21401],{"className":21400},[21383,21384],[21402,21403,136,21406],"slideover",{"title":21404,"description":21405,":ui":21390},"Slideover","This is a slideover",[910,21407,21394],{"label":21408},"Open Slideover",[35,21410,21411],{},"The design and dialog types might seem secondary; the important aspect lies in the underlying code. Let's examine it:",[35,21413,21414],{},"::: code-group",[192,21416,21420],{"className":21417,"code":21418,"filename":21419,"language":12060,"meta":13,"style":13},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Ctemplate>\n  \u003CModal\n    title=\"Dialog\"\n    description=\"This is a dialog\"\n  >\n    \u003CButton label=\"Open Dialog\" \u002F>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n","src\u002Fcomponents\u002FModal.vue",[156,21421,21422,21431,21438,21451,21463,21467,21488,21497],{"__ignoreMap":13},[200,21423,21424,21426,21429],{"class":202,"line":203},[200,21425,600],{"class":214},[200,21427,21428],{"class":235},"template",[200,21430,648],{"class":214},[200,21432,21433,21435],{"class":202,"line":14},[200,21434,657],{"class":214},[200,21436,21437],{"class":235},"Modal\n",[200,21439,21440,21443,21445,21447,21449],{"class":202,"line":255},[200,21441,21442],{"class":379},"    title",[200,21444,449],{"class":214},[200,21446,611],{"class":214},[200,21448,21388],{"class":245},[200,21450,617],{"class":214},[200,21452,21453,21455,21457,21459,21461],{"class":202,"line":272},[200,21454,3136],{"class":379},[200,21456,449],{"class":214},[200,21458,611],{"class":214},[200,21460,21389],{"class":245},[200,21462,617],{"class":214},[200,21464,21465],{"class":202,"line":283},[200,21466,11536],{"class":214},[200,21468,21469,21471,21474,21477,21479,21481,21483,21485],{"class":202,"line":300},[200,21470,804],{"class":214},[200,21472,21473],{"class":235},"Button",[200,21475,21476],{"class":379}," label",[200,21478,449],{"class":214},[200,21480,611],{"class":214},[200,21482,21393],{"class":245},[200,21484,611],{"class":214},[200,21486,21487],{"class":214}," \u002F>\n",[200,21489,21490,21492,21495],{"class":202,"line":310},[200,21491,893],{"class":214},[200,21493,21494],{"class":235},"Modal",[200,21496,648],{"class":214},[200,21498,21499,21501,21503],{"class":202,"line":336},[200,21500,681],{"class":214},[200,21502,21428],{"class":235},[200,21504,648],{"class":214},[192,21506,21509],{"className":21417,"code":21507,"filename":21508,"language":12060,"meta":13,"style":13},"\u003Ctemplate>\n  \u003CSlideover\n    title=\"Slideover\"\n    description=\"This is a slideover\"\n  >\n    \u003CButton label=\"Open Slideover\" \u002F>\n  \u003C\u002FSlideover>\n\u003C\u002Ftemplate>\n","src\u002Fcomponents\u002FSlideover.vue",[156,21510,21511,21519,21526,21538,21550,21554,21572,21580],{"__ignoreMap":13},[200,21512,21513,21515,21517],{"class":202,"line":203},[200,21514,600],{"class":214},[200,21516,21428],{"class":235},[200,21518,648],{"class":214},[200,21520,21521,21523],{"class":202,"line":14},[200,21522,657],{"class":214},[200,21524,21525],{"class":235},"Slideover\n",[200,21527,21528,21530,21532,21534,21536],{"class":202,"line":255},[200,21529,21442],{"class":379},[200,21531,449],{"class":214},[200,21533,611],{"class":214},[200,21535,21404],{"class":245},[200,21537,617],{"class":214},[200,21539,21540,21542,21544,21546,21548],{"class":202,"line":272},[200,21541,3136],{"class":379},[200,21543,449],{"class":214},[200,21545,611],{"class":214},[200,21547,21405],{"class":245},[200,21549,617],{"class":214},[200,21551,21552],{"class":202,"line":283},[200,21553,11536],{"class":214},[200,21555,21556,21558,21560,21562,21564,21566,21568,21570],{"class":202,"line":300},[200,21557,804],{"class":214},[200,21559,21473],{"class":235},[200,21561,21476],{"class":379},[200,21563,449],{"class":214},[200,21565,611],{"class":214},[200,21567,21408],{"class":245},[200,21569,611],{"class":214},[200,21571,21487],{"class":214},[200,21573,21574,21576,21578],{"class":202,"line":310},[200,21575,893],{"class":214},[200,21577,21404],{"class":235},[200,21579,648],{"class":214},[200,21581,21582,21584,21586],{"class":202,"line":336},[200,21583,681],{"class":214},[200,21585,21428],{"class":235},[200,21587,648],{"class":214},[35,21589,21590],{},":::",[35,21592,21593],{},"They are quite similar, relying on the same component beneath the surface. The true intrigue is in how this code is utilized. These examples illustrate that dialogs integrate seamlessly as any HTML element, which is the crux of the problem.",[35,21595,21596],{},"Dialogs are a special type of component in an application:",[68,21598,21599,21602,21616],{},[71,21600,21601],{},"They can be invoked from anywhere within the application.",[71,21603,21604,21605],{},"They open through various methods, such as:",[1576,21606,21607,21610,21613],{},[71,21608,21609],{},"Button clicks",[71,21611,21612],{},"Key presses",[71,21614,21615],{},"Route navigations",[71,21617,21618,21619,21636,21638],{},"The ubiquitous nature makes dialog-free applications nearly impractical without sacrificing user experience:",[1576,21620,21621,21624,21627,21630,21633],{},[71,21622,21623],{},"Creating items",[71,21625,21626],{},"Editing items",[71,21628,21629],{},"Action confirmations",[71,21631,21632],{},"Item visualizations",[71,21634,21635],{},"Etc.",[7162,21637],{},"Each action might lead to immediate consequences or await a server response, complicating dialog usage possibilities.",[35,21640,21641],{},"Due to these factors, dialogs demand meticulous care in development and usage; otherwise, they and their logic can quickly become chaotic.",[35,21643,21644],{},"Additionally, it has always been a personal preference of mine to enable dialog stacking with smooth animations, like this:",[130,21646,132,21647,132,21652],{},[134,21648,136,21649,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,21650],{"src":21651,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fa-journey-to-craft-interactive-ui-experiences-with-dialogs\u002Fmodal-stacking.mp4",[143,21653,21654],{},"Stacking dialogs with a sleek animation.",[51,21656,21658],{"id":21657},"inline-dialogs","Inline dialogs",[35,21660,21661,21662,21665],{},"An initial attempt to address this challenge is via ",[6850,21663,21664],{},"inline modals",". It's a prevalent approach and suits simple cases. The strategy involves incorporating a modal component directly within the template that requires it, as demonstrated earlier.",[192,21667,21668],{"className":21417,"code":21418,"filename":21419,"language":12060,"meta":13,"style":13},[156,21669,21670,21678,21684,21696,21708,21712,21730,21738],{"__ignoreMap":13},[200,21671,21672,21674,21676],{"class":202,"line":203},[200,21673,600],{"class":214},[200,21675,21428],{"class":235},[200,21677,648],{"class":214},[200,21679,21680,21682],{"class":202,"line":14},[200,21681,657],{"class":214},[200,21683,21437],{"class":235},[200,21685,21686,21688,21690,21692,21694],{"class":202,"line":255},[200,21687,21442],{"class":379},[200,21689,449],{"class":214},[200,21691,611],{"class":214},[200,21693,21388],{"class":245},[200,21695,617],{"class":214},[200,21697,21698,21700,21702,21704,21706],{"class":202,"line":272},[200,21699,3136],{"class":379},[200,21701,449],{"class":214},[200,21703,611],{"class":214},[200,21705,21389],{"class":245},[200,21707,617],{"class":214},[200,21709,21710],{"class":202,"line":283},[200,21711,11536],{"class":214},[200,21713,21714,21716,21718,21720,21722,21724,21726,21728],{"class":202,"line":300},[200,21715,804],{"class":214},[200,21717,21473],{"class":235},[200,21719,21476],{"class":379},[200,21721,449],{"class":214},[200,21723,611],{"class":214},[200,21725,21393],{"class":245},[200,21727,611],{"class":214},[200,21729,21487],{"class":214},[200,21731,21732,21734,21736],{"class":202,"line":310},[200,21733,893],{"class":214},[200,21735,21494],{"class":235},[200,21737,648],{"class":214},[200,21739,21740,21742,21744],{"class":202,"line":336},[200,21741,681],{"class":214},[200,21743,21428],{"class":235},[200,21745,648],{"class":214},[35,21747,21748],{},"Despite the straightforward implementation, this approach presents at least two significant drawbacks:",[68,21750,21751,21754],{},[71,21752,21753],{},"Dialog components become tightly coupled with the utilizing component, complicating comprehension and refactoring.",[71,21755,21756],{},"Overhead arises from boilerplate code managing dialog states, especially when opening dialogs without buttons or decoupling templates from dialogs. An example of opening a dialog using a key press follows:",[192,21758,21761],{"className":21417,"code":21759,"filename":21760,"language":12060,"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=\"Dialog\"\n      description=\"This is a dialog\"\n    \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","src\u002Fcomponents\u002FInlineModal.vue",[156,21762,21763,21784,21802,21806,21823,21827,21836,21849,21869,21873,21879,21887,21891,21899,21907,21913,21930,21942,21954,21959,21967],{"__ignoreMap":13},[200,21764,21765,21767,21769,21771,21773,21775,21777,21779,21782],{"class":202,"line":203},[200,21766,600],{"class":214},[200,21768,14892],{"class":235},[200,21770,14590],{"class":379},[200,21772,449],{"class":214},[200,21774,611],{"class":214},[200,21776,1373],{"class":245},[200,21778,611],{"class":214},[200,21780,21781],{"class":379}," setup",[200,21783,648],{"class":214},[200,21785,21786,21788,21790,21792,21794,21796,21798,21800],{"class":202,"line":14},[200,21787,973],{"class":206},[200,21789,318],{"class":214},[200,21791,18685],{"class":210},[200,21793,981],{"class":214},[200,21795,984],{"class":206},[200,21797,242],{"class":214},[200,21799,12060],{"class":245},[200,21801,992],{"class":214},[200,21803,21804],{"class":202,"line":255},[200,21805,529],{"emptyLinePlaceholder":20},[200,21807,21808,21810,21813,21815,21817,21819,21821],{"class":202,"line":272},[200,21809,443],{"class":379},[200,21811,21812],{"class":210}," isDialogOpen ",[200,21814,449],{"class":214},[200,21816,18685],{"class":223},[200,21818,227],{"class":210},[200,21820,18690],{"class":2423},[200,21822,416],{"class":210},[200,21824,21825],{"class":202,"line":283},[200,21826,529],{"emptyLinePlaceholder":20},[200,21828,21829,21832,21834],{"class":202,"line":300},[200,21830,21831],{"class":223},"defineShortcuts",[200,21833,227],{"class":210},[200,21835,230],{"class":214},[200,21837,21838,21841,21843,21845,21847],{"class":202,"line":310},[200,21839,21840],{"class":223},"  meta_k",[200,21842,239],{"class":214},[200,21844,3714],{"class":214},[200,21846,393],{"class":379},[200,21848,280],{"class":214},[200,21850,21851,21854,21856,21858,21860,21862,21865,21867],{"class":202,"line":336},[200,21852,21853],{"class":210},"    isDialogOpen",[200,21855,215],{"class":214},[200,21857,15837],{"class":210},[200,21859,510],{"class":214},[200,21861,15914],{"class":214},[200,21863,21864],{"class":210},"isDialogOpen",[200,21866,215],{"class":214},[200,21868,15976],{"class":210},[200,21870,21871],{"class":202,"line":342},[200,21872,4490],{"class":214},[200,21874,21875,21877],{"class":202,"line":365},[200,21876,413],{"class":214},[200,21878,416],{"class":210},[200,21880,21881,21883,21885],{"class":202,"line":371},[200,21882,681],{"class":214},[200,21884,14892],{"class":235},[200,21886,648],{"class":214},[200,21888,21889],{"class":202,"line":398},[200,21890,529],{"emptyLinePlaceholder":20},[200,21892,21893,21895,21897],{"class":202,"line":405},[200,21894,600],{"class":214},[200,21896,21428],{"class":235},[200,21898,648],{"class":214},[200,21900,21901,21903,21905],{"class":202,"line":410},[200,21902,657],{"class":214},[200,21904,11386],{"class":235},[200,21906,648],{"class":214},[200,21908,21909,21911],{"class":202,"line":861},[200,21910,804],{"class":214},[200,21912,21437],{"class":235},[200,21914,21915,21918,21920,21922,21924,21926,21928],{"class":202,"line":890},[200,21916,21917],{"class":379},"      v-model",[200,21919,239],{"class":214},[200,21921,18904],{"class":379},[200,21923,449],{"class":214},[200,21925,611],{"class":214},[200,21927,21864],{"class":210},[200,21929,617],{"class":214},[200,21931,21932,21934,21936,21938,21940],{"class":202,"line":900},[200,21933,3557],{"class":379},[200,21935,449],{"class":214},[200,21937,611],{"class":214},[200,21939,21388],{"class":245},[200,21941,617],{"class":214},[200,21943,21944,21946,21948,21950,21952],{"class":202,"line":905},[200,21945,3573],{"class":379},[200,21947,449],{"class":214},[200,21949,611],{"class":214},[200,21951,21389],{"class":245},[200,21953,617],{"class":214},[200,21955,21956],{"class":202,"line":931},[200,21957,21958],{"class":214},"    \u002F>\n",[200,21960,21961,21963,21965],{"class":202,"line":3640},[200,21962,893],{"class":214},[200,21964,11386],{"class":235},[200,21966,648],{"class":214},[200,21968,21969,21971,21973],{"class":202,"line":3645},[200,21970,681],{"class":214},[200,21972,21428],{"class":235},[200,21974,648],{"class":214},[35,21976,21977],{},"This method quickly becomes cumbersome and tough to maintain over time. Multiple dialogs intensify the boilerplate, requiring repeated additions in each component utilizing the dialog.",[51,21979,21981],{"id":21980},"global-dialogs","Global dialogs",[35,21983,21984],{},"Enhancing dialog management involves programmatically opening dialogs universally, decoupling their invocation from any direct component relationship. This pattern facilitates code refactoring by separating dialogs physically from components, enhancing reusability across multiple components without redundant incorporations.",[35,21986,21987,21988,21990],{},"An ",[156,21989,13707],{}," file might look like this:",[192,21992,21996],{"className":21993,"code":21994,"filename":21995,"language":12060,"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!-- And so on... -->\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","src\u002FApp.vue",[156,21997,21998,22006,22014,22023,22033,22041,22045,22056,22066,22076,22080,22085,22093],{"__ignoreMap":13},[200,21999,22000,22002,22004],{"class":202,"line":203},[200,22001,600],{"class":214},[200,22003,21428],{"class":235},[200,22005,648],{"class":214},[200,22007,22008,22010,22012],{"class":202,"line":14},[200,22009,657],{"class":214},[200,22011,11386],{"class":235},[200,22013,648],{"class":214},[200,22015,22016,22018,22021],{"class":202,"line":255},[200,22017,804],{"class":214},[200,22019,22020],{"class":235},"Layout",[200,22022,648],{"class":214},[200,22024,22025,22028,22031],{"class":202,"line":272},[200,22026,22027],{"class":214},"      \u003C",[200,22029,22030],{"class":235},"RouterView",[200,22032,21487],{"class":214},[200,22034,22035,22037,22039],{"class":202,"line":283},[200,22036,15074],{"class":214},[200,22038,22020],{"class":235},[200,22040,648],{"class":214},[200,22042,22043],{"class":202,"line":300},[200,22044,529],{"emptyLinePlaceholder":20},[200,22046,22048,22050,22053],{"class":22047,"line":310},[202,9871],[200,22049,804],{"class":214},[200,22051,22052],{"class":235},"ModalFeature1",[200,22054,22055],{"class":214}," \u002F>",[200,22057,22059,22061,22064],{"class":22058,"line":336},[202,9871],[200,22060,804],{"class":214},[200,22062,22063],{"class":235},"ModalFeature2",[200,22065,22055],{"class":214},[200,22067,22069,22071,22074],{"class":22068,"line":342},[202,9871],[200,22070,804],{"class":214},[200,22072,22073],{"class":235},"ModalFeature3",[200,22075,22055],{"class":214},[200,22077,22078],{"class":202,"line":365},[200,22079,529],{"emptyLinePlaceholder":20},[200,22081,22082],{"class":202,"line":371},[200,22083,22084],{"class":401},"    \u003C!-- And so on... -->\n",[200,22086,22087,22089,22091],{"class":202,"line":398},[200,22088,893],{"class":214},[200,22090,11386],{"class":235},[200,22092,648],{"class":214},[200,22094,22095,22097,22099],{"class":202,"line":405},[200,22096,681],{"class":214},[200,22098,21428],{"class":235},[200,22100,648],{"class":214},[35,22102,22103],{},"Achieving this setup mandates sharing a variable between the dialog-opening component and the dialog itself. In Vue, a straightforward--though not ideal--approach is to create a composable externalizing its state.",[192,22105,22108],{"className":9741,"code":22106,"filename":22107,"language":1373,"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,22109,22110,22128,22132,22150,22154,22167,22179,22193,22197,22201,22212,22224,22228,22232,22238,22244,22248,22254,22259,22263],{"__ignoreMap":13},[200,22111,22112,22114,22116,22118,22120,22122,22124,22126],{"class":202,"line":203},[200,22113,973],{"class":206},[200,22115,318],{"class":214},[200,22117,18685],{"class":210},[200,22119,981],{"class":214},[200,22121,984],{"class":206},[200,22123,242],{"class":214},[200,22125,12060],{"class":245},[200,22127,992],{"class":214},[200,22129,22130],{"class":202,"line":14},[200,22131,529],{"emptyLinePlaceholder":20},[200,22133,22135,22137,22140,22142,22144,22146,22148],{"class":22134,"line":255},[202,9871],[200,22136,443],{"class":379},[200,22138,22139],{"class":210}," isOpen ",[200,22141,449],{"class":214},[200,22143,18685],{"class":223},[200,22145,227],{"class":210},[200,22147,18690],{"class":2423},[200,22149,5267],{"class":210},[200,22151,22152],{"class":202,"line":272},[200,22153,529],{"emptyLinePlaceholder":20},[200,22155,22156,22158,22160,22163,22165],{"class":202,"line":283},[200,22157,1380],{"class":206},[200,22159,4987],{"class":379},[200,22161,22162],{"class":223}," useDialog",[200,22164,1532],{"class":214},[200,22166,280],{"class":214},[200,22168,22169,22172,22175,22177],{"class":202,"line":300},[200,22170,22171],{"class":379},"  function",[200,22173,22174],{"class":223}," open",[200,22176,1532],{"class":214},[200,22178,280],{"class":214},[200,22180,22181,22184,22186,22188,22190],{"class":202,"line":310},[200,22182,22183],{"class":210},"    isOpen",[200,22185,215],{"class":214},[200,22187,15837],{"class":210},[200,22189,510],{"class":214},[200,22191,22192],{"class":2423}," true\n",[200,22194,22195],{"class":202,"line":336},[200,22196,4490],{"class":214},[200,22198,22199],{"class":202,"line":342},[200,22200,529],{"emptyLinePlaceholder":20},[200,22202,22203,22205,22208,22210],{"class":202,"line":365},[200,22204,22171],{"class":379},[200,22206,22207],{"class":223}," close",[200,22209,1532],{"class":214},[200,22211,280],{"class":214},[200,22213,22214,22216,22218,22220,22222],{"class":202,"line":371},[200,22215,22183],{"class":210},[200,22217,215],{"class":214},[200,22219,15837],{"class":210},[200,22221,510],{"class":214},[200,22223,18889],{"class":2423},[200,22225,22226],{"class":202,"line":398},[200,22227,4490],{"class":214},[200,22229,22230],{"class":202,"line":405},[200,22231,529],{"emptyLinePlaceholder":20},[200,22233,22234,22236],{"class":202,"line":410},[200,22235,3743],{"class":206},[200,22237,280],{"class":214},[200,22239,22240,22242],{"class":202,"line":861},[200,22241,22183],{"class":210},[200,22243,252],{"class":214},[200,22245,22246],{"class":202,"line":890},[200,22247,529],{"emptyLinePlaceholder":20},[200,22249,22250,22252],{"class":202,"line":900},[200,22251,18880],{"class":210},[200,22253,252],{"class":214},[200,22255,22256],{"class":202,"line":905},[200,22257,22258],{"class":210},"    close\n",[200,22260,22261],{"class":202,"line":931},[200,22262,4490],{"class":214},[200,22264,22265],{"class":202,"line":3640},[200,22266,1130],{"class":214},[35,22268,22269],{},"Utilize this composable for both the dialog-opening component and the dialog itself.",[192,22271,22273],{"className":21993,"code":22272,"filename":21995,"language":12060,"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=\"Open Dialog\" @click=\"open\" \u002F>\n\u003C\u002Ftemplate>\n",[156,22274,22275,22295,22314,22318,22335,22343,22347,22355,22387],{"__ignoreMap":13},[200,22276,22277,22279,22281,22283,22285,22287,22289,22291,22293],{"class":202,"line":203},[200,22278,600],{"class":214},[200,22280,14892],{"class":235},[200,22282,14590],{"class":379},[200,22284,449],{"class":214},[200,22286,611],{"class":214},[200,22288,1373],{"class":245},[200,22290,611],{"class":214},[200,22292,21781],{"class":379},[200,22294,648],{"class":214},[200,22296,22297,22299,22301,22303,22305,22307,22309,22312],{"class":202,"line":14},[200,22298,973],{"class":206},[200,22300,318],{"class":214},[200,22302,22162],{"class":210},[200,22304,981],{"class":214},[200,22306,984],{"class":206},[200,22308,242],{"class":214},[200,22310,22311],{"class":245},"@\u002Fcomposables\u002FuseDialog",[200,22313,992],{"class":214},[200,22315,22316],{"class":202,"line":255},[200,22317,529],{"emptyLinePlaceholder":20},[200,22319,22321,22323,22325,22327,22329,22331,22333],{"class":22320,"line":272},[202,9871],[200,22322,443],{"class":379},[200,22324,318],{"class":214},[200,22326,18680],{"class":210},[200,22328,413],{"class":214},[200,22330,510],{"class":214},[200,22332,22162],{"class":223},[200,22334,6320],{"class":210},[200,22336,22337,22339,22341],{"class":202,"line":283},[200,22338,681],{"class":214},[200,22340,14892],{"class":235},[200,22342,648],{"class":214},[200,22344,22345],{"class":202,"line":300},[200,22346,529],{"emptyLinePlaceholder":20},[200,22348,22349,22351,22353],{"class":202,"line":310},[200,22350,600],{"class":214},[200,22352,21428],{"class":235},[200,22354,648],{"class":214},[200,22356,22357,22359,22361,22363,22365,22367,22369,22371,22374,22377,22379,22381,22383,22385],{"class":202,"line":336},[200,22358,657],{"class":214},[200,22360,21473],{"class":235},[200,22362,21476],{"class":379},[200,22364,449],{"class":214},[200,22366,611],{"class":214},[200,22368,21393],{"class":245},[200,22370,611],{"class":214},[200,22372,22373],{"class":214}," @",[200,22375,22376],{"class":379},"click",[200,22378,449],{"class":214},[200,22380,611],{"class":214},[200,22382,18904],{"class":210},[200,22384,611],{"class":214},[200,22386,21487],{"class":214},[200,22388,22389,22391,22393],{"class":202,"line":342},[200,22390,681],{"class":214},[200,22392,21428],{"class":235},[200,22394,648],{"class":214},[192,22396,22399],{"className":21993,"code":22397,"filename":22398,"language":12060,"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=\"Dialog\"\n    description=\"This is a dialog\"\n  >\n    \u003CButton label=\"Close\" @click=\"close\" \u002F>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n","src\u002Fcomponents\u002FModalFeature1.vue",[156,22400,22401,22421,22439,22443,22466,22474,22478,22486,22492,22510,22522,22534,22538,22570,22578],{"__ignoreMap":13},[200,22402,22403,22405,22407,22409,22411,22413,22415,22417,22419],{"class":202,"line":203},[200,22404,600],{"class":214},[200,22406,14892],{"class":235},[200,22408,14590],{"class":379},[200,22410,449],{"class":214},[200,22412,611],{"class":214},[200,22414,1373],{"class":245},[200,22416,611],{"class":214},[200,22418,21781],{"class":379},[200,22420,648],{"class":214},[200,22422,22423,22425,22427,22429,22431,22433,22435,22437],{"class":202,"line":14},[200,22424,973],{"class":206},[200,22426,318],{"class":214},[200,22428,22162],{"class":210},[200,22430,981],{"class":214},[200,22432,984],{"class":206},[200,22434,242],{"class":214},[200,22436,22311],{"class":245},[200,22438,992],{"class":214},[200,22440,22441],{"class":202,"line":255},[200,22442,529],{"emptyLinePlaceholder":20},[200,22444,22446,22448,22450,22453,22455,22458,22460,22462,22464],{"class":22445,"line":272},[202,9871],[200,22447,443],{"class":379},[200,22449,318],{"class":214},[200,22451,22452],{"class":210}," isOpen",[200,22454,550],{"class":214},[200,22456,22457],{"class":210}," close ",[200,22459,413],{"class":214},[200,22461,510],{"class":214},[200,22463,22162],{"class":223},[200,22465,6320],{"class":210},[200,22467,22468,22470,22472],{"class":202,"line":283},[200,22469,681],{"class":214},[200,22471,14892],{"class":235},[200,22473,648],{"class":214},[200,22475,22476],{"class":202,"line":300},[200,22477,529],{"emptyLinePlaceholder":20},[200,22479,22480,22482,22484],{"class":202,"line":310},[200,22481,600],{"class":214},[200,22483,21428],{"class":235},[200,22485,648],{"class":214},[200,22487,22488,22490],{"class":202,"line":336},[200,22489,657],{"class":214},[200,22491,21437],{"class":235},[200,22493,22494,22497,22499,22501,22503,22505,22508],{"class":202,"line":342},[200,22495,22496],{"class":379},"    v-model",[200,22498,239],{"class":214},[200,22500,18904],{"class":379},[200,22502,449],{"class":214},[200,22504,611],{"class":214},[200,22506,22507],{"class":210},"isOpen",[200,22509,617],{"class":214},[200,22511,22512,22514,22516,22518,22520],{"class":202,"line":365},[200,22513,21442],{"class":379},[200,22515,449],{"class":214},[200,22517,611],{"class":214},[200,22519,21388],{"class":245},[200,22521,617],{"class":214},[200,22523,22524,22526,22528,22530,22532],{"class":202,"line":371},[200,22525,3136],{"class":379},[200,22527,449],{"class":214},[200,22529,611],{"class":214},[200,22531,21389],{"class":245},[200,22533,617],{"class":214},[200,22535,22536],{"class":202,"line":398},[200,22537,11536],{"class":214},[200,22539,22540,22542,22544,22546,22548,22550,22553,22555,22557,22559,22561,22563,22566,22568],{"class":202,"line":405},[200,22541,804],{"class":214},[200,22543,21473],{"class":235},[200,22545,21476],{"class":379},[200,22547,449],{"class":214},[200,22549,611],{"class":214},[200,22551,22552],{"class":245},"Close",[200,22554,611],{"class":214},[200,22556,22373],{"class":214},[200,22558,22376],{"class":379},[200,22560,449],{"class":214},[200,22562,611],{"class":214},[200,22564,22565],{"class":210},"close",[200,22567,611],{"class":214},[200,22569,21487],{"class":214},[200,22571,22572,22574,22576],{"class":202,"line":410},[200,22573,893],{"class":214},[200,22575,21494],{"class":235},[200,22577,648],{"class":214},[200,22579,22580,22582,22584],{"class":202,"line":861},[200,22581,681],{"class":214},[200,22583,21428],{"class":235},[200,22585,648],{"class":214},[35,22587,22588],{},"This approach is certainly superior to the inline method but has drawbacks:",[68,22590,22591,22594],{},[71,22592,22593],{},"Inclusion of modals at the app (or route) top-level creates potential code redundancy.",[71,22595,22596,22597],{},"Dialog management becomes elaborate, particularly with numerous dialogs or props, each requiring passage through the composable.",[192,22598,22600],{"className":9741,"code":22599,"language":1373,"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,22601,22602,22620,22624,22640,22659,22677,22681,22693,22722,22735,22748,22760,22764,22768,22778,22790,22794,22798,22804,22810,22816,22822,22828,22832,22836],{"__ignoreMap":13},[200,22603,22604,22606,22608,22610,22612,22614,22616,22618],{"class":202,"line":203},[200,22605,973],{"class":206},[200,22607,318],{"class":214},[200,22609,18685],{"class":210},[200,22611,981],{"class":214},[200,22613,984],{"class":206},[200,22615,242],{"class":214},[200,22617,12060],{"class":245},[200,22619,992],{"class":214},[200,22621,22622],{"class":202,"line":14},[200,22623,529],{"emptyLinePlaceholder":20},[200,22625,22626,22628,22630,22632,22634,22636,22638],{"class":202,"line":255},[200,22627,443],{"class":379},[200,22629,22139],{"class":210},[200,22631,449],{"class":214},[200,22633,18685],{"class":223},[200,22635,227],{"class":210},[200,22637,18690],{"class":2423},[200,22639,416],{"class":210},[200,22641,22643,22645,22648,22650,22652,22654,22657],{"class":22642,"line":272},[202,9871],[200,22644,443],{"class":379},[200,22646,22647],{"class":210}," title ",[200,22649,449],{"class":214},[200,22651,18685],{"class":223},[200,22653,227],{"class":210},[200,22655,22656],{"class":214},"''",[200,22658,5267],{"class":210},[200,22660,22662,22664,22667,22669,22671,22673,22675],{"class":22661,"line":283},[202,9871],[200,22663,443],{"class":379},[200,22665,22666],{"class":210}," description ",[200,22668,449],{"class":214},[200,22670,18685],{"class":223},[200,22672,227],{"class":210},[200,22674,22656],{"class":214},[200,22676,5267],{"class":210},[200,22678,22679],{"class":202,"line":300},[200,22680,529],{"emptyLinePlaceholder":20},[200,22682,22683,22685,22687,22689,22691],{"class":202,"line":310},[200,22684,1380],{"class":206},[200,22686,4987],{"class":379},[200,22688,22162],{"class":223},[200,22690,1532],{"class":214},[200,22692,280],{"class":214},[200,22694,22695,22697,22699,22701,22704,22706,22709,22711,22714,22716,22718,22720],{"class":202,"line":336},[200,22696,22171],{"class":379},[200,22698,22174],{"class":223},[200,22700,227],{"class":214},[200,22702,22703],{"class":386},"newTitle",[200,22705,239],{"class":214},[200,22707,22708],{"class":1978}," string",[200,22710,550],{"class":214},[200,22712,22713],{"class":386}," newDescription",[200,22715,239],{"class":214},[200,22717,22708],{"class":1978},[200,22719,2089],{"class":214},[200,22721,280],{"class":214},[200,22723,22724,22726,22728,22730,22732],{"class":202,"line":342},[200,22725,21442],{"class":210},[200,22727,215],{"class":214},[200,22729,15837],{"class":210},[200,22731,510],{"class":214},[200,22733,22734],{"class":210}," newTitle\n",[200,22736,22737,22739,22741,22743,22745],{"class":202,"line":365},[200,22738,3136],{"class":210},[200,22740,215],{"class":214},[200,22742,15837],{"class":210},[200,22744,510],{"class":214},[200,22746,22747],{"class":210}," newDescription\n",[200,22749,22750,22752,22754,22756,22758],{"class":202,"line":371},[200,22751,22183],{"class":210},[200,22753,215],{"class":214},[200,22755,15837],{"class":210},[200,22757,510],{"class":214},[200,22759,22192],{"class":2423},[200,22761,22762],{"class":202,"line":398},[200,22763,4490],{"class":214},[200,22765,22766],{"class":202,"line":405},[200,22767,529],{"emptyLinePlaceholder":20},[200,22769,22770,22772,22774,22776],{"class":202,"line":410},[200,22771,22171],{"class":379},[200,22773,22207],{"class":223},[200,22775,1532],{"class":214},[200,22777,280],{"class":214},[200,22779,22780,22782,22784,22786,22788],{"class":202,"line":861},[200,22781,22183],{"class":210},[200,22783,215],{"class":214},[200,22785,15837],{"class":210},[200,22787,510],{"class":214},[200,22789,18889],{"class":2423},[200,22791,22792],{"class":202,"line":890},[200,22793,4490],{"class":214},[200,22795,22796],{"class":202,"line":900},[200,22797,529],{"emptyLinePlaceholder":20},[200,22799,22800,22802],{"class":202,"line":905},[200,22801,3743],{"class":206},[200,22803,280],{"class":214},[200,22805,22806,22808],{"class":202,"line":931},[200,22807,22183],{"class":210},[200,22809,252],{"class":214},[200,22811,22812,22814],{"class":202,"line":3640},[200,22813,21442],{"class":210},[200,22815,252],{"class":214},[200,22817,22818,22820],{"class":202,"line":3645},[200,22819,3136],{"class":210},[200,22821,252],{"class":214},[200,22823,22824,22826],{"class":202,"line":4020},[200,22825,18880],{"class":210},[200,22827,252],{"class":214},[200,22829,22830],{"class":202,"line":4475},[200,22831,22258],{"class":210},[200,22833,22834],{"class":202,"line":4481},[200,22835,4490],{"class":214},[200,22837,22838],{"class":202,"line":4487},[200,22839,1130],{"class":214},[939,22841,22842],{},[35,22843,22844,22846,22847,17879,22854,22861],{},[200,22845,945],{},"\nSimplify composable writing with ",[39,22848,22851],{"href":22849,"rel":22850},"https:\u002F\u002Fvueuse.org\u002Fshared\u002FcreateSharedComposable\u002F",[77],[156,22852,22853],{},"createSharedComposable",[39,22855,22858],{"href":22856,"rel":22857},"https:\u002F\u002Fvueuse.org\u002F",[77],[156,22859,22860],{},"@vueuse\u002Fcore"," to craft a singleton composable, automatically sharing the same state between all utilizing components.",[51,22863,22865],{"id":22864},"programmatic-dialogs","Programmatic dialogs",[35,22867,22868,22869,22872],{},"The latest, profoundly effective approach I discovered involves ",[6850,22870,22871],{},"programmatically managing dialogs",". This method resolves the difficulties mentioned earlier by allowing complete dialog control through code rather than templates.",[35,22874,22875,22876,22879],{},"Having used programmatic dialogs in ",[6850,22877,22878],{},"Angular"," with Angular Material, I appreciated its capacity for code simplification.",[192,22881,22884],{"className":1370,"code":22882,"filename":22883,"language":1373,"meta":13,"style":13},"const dialogRef = dialog.open(UserProfileComponent, {\n  height: '400px',\n  width: '600px',\n})\n\ndialogRef.afterClosed().subscribe((result) => {\n  console.log(`Dialog result: ${result}`) \u002F\u002F Pizza!\n})\n\ndialogRef.close('Pizza!')\n","Angular Material Dialog",[156,22885,22886,22909,22925,22941,22947,22951,22980,23007,23013,23017],{"__ignoreMap":13},[200,22887,22888,22890,22893,22895,22898,22900,22902,22905,22907],{"class":202,"line":203},[200,22889,443],{"class":379},[200,22891,22892],{"class":210}," dialogRef ",[200,22894,449],{"class":214},[200,22896,22897],{"class":210}," dialog",[200,22899,215],{"class":214},[200,22901,18904],{"class":223},[200,22903,22904],{"class":210},"(UserProfileComponent",[200,22906,550],{"class":214},[200,22908,280],{"class":214},[200,22910,22911,22914,22916,22918,22921,22923],{"class":202,"line":14},[200,22912,22913],{"class":235},"  height",[200,22915,239],{"class":214},[200,22917,242],{"class":214},[200,22919,22920],{"class":245},"400px",[200,22922,249],{"class":214},[200,22924,252],{"class":214},[200,22926,22927,22930,22932,22934,22937,22939],{"class":202,"line":255},[200,22928,22929],{"class":235},"  width",[200,22931,239],{"class":214},[200,22933,242],{"class":214},[200,22935,22936],{"class":245},"600px",[200,22938,249],{"class":214},[200,22940,252],{"class":214},[200,22942,22943,22945],{"class":202,"line":272},[200,22944,413],{"class":214},[200,22946,416],{"class":210},[200,22948,22949],{"class":202,"line":283},[200,22950,529],{"emptyLinePlaceholder":20},[200,22952,22953,22956,22958,22961,22963,22965,22968,22970,22972,22974,22976,22978],{"class":202,"line":300},[200,22954,22955],{"class":210},"dialogRef",[200,22957,215],{"class":214},[200,22959,22960],{"class":223},"afterClosed",[200,22962,1532],{"class":210},[200,22964,215],{"class":214},[200,22966,22967],{"class":223},"subscribe",[200,22969,227],{"class":210},[200,22971,227],{"class":214},[200,22973,3531],{"class":386},[200,22975,2089],{"class":214},[200,22977,393],{"class":379},[200,22979,280],{"class":214},[200,22981,22982,22984,22986,22988,22990,22992,22995,22997,22999,23002,23004],{"class":202,"line":310},[200,22983,1113],{"class":210},[200,22985,215],{"class":214},[200,22987,1118],{"class":223},[200,22989,227],{"class":235},[200,22991,3318],{"class":214},[200,22993,22994],{"class":245},"Dialog result: ",[200,22996,4853],{"class":214},[200,22998,3531],{"class":210},[200,23000,23001],{"class":214},"}`",[200,23003,5267],{"class":235},[200,23005,23006],{"class":401},"\u002F\u002F Pizza!\n",[200,23008,23009,23011],{"class":202,"line":336},[200,23010,413],{"class":214},[200,23012,416],{"class":210},[200,23014,23015],{"class":202,"line":342},[200,23016,529],{"emptyLinePlaceholder":20},[200,23018,23019,23021,23023,23025,23027,23029,23032,23034],{"class":202,"line":365},[200,23020,22955],{"class":210},[200,23022,215],{"class":214},[200,23024,22565],{"class":223},[200,23026,227],{"class":210},[200,23028,249],{"class":214},[200,23030,23031],{"class":245},"Pizza!",[200,23033,249],{"class":214},[200,23035,416],{"class":210},[35,23037,23038],{},"For a long time, I missed such a feature in Vue.",[35,23040,23041,23042,23046,23047,23051,23052,23055],{},"Until recently, when ",[39,23043,2918],{"href":23044,"rel":23045},"https:\u002F\u002Fnuxt.com\u002F",[77]," released version 3 of ",[39,23048,9190],{"href":23049,"rel":23050},"https:\u002F\u002Fui.nuxt.com\u002F",[77]," with the ",[156,23053,23054],{},"useOverlay"," composable. This shared application composable enables dialog opening from any location, mirroring Angular Material functionality.",[192,23057,23059],{"className":21417,"code":23058,"filename":21995,"language":12060,"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,23060,23061,23081,23095,23099,23112,23117,23131,23139,23143],{"__ignoreMap":13},[200,23062,23063,23065,23067,23069,23071,23073,23075,23077,23079],{"class":202,"line":203},[200,23064,600],{"class":214},[200,23066,14892],{"class":235},[200,23068,21781],{"class":379},[200,23070,14590],{"class":379},[200,23072,449],{"class":214},[200,23074,611],{"class":214},[200,23076,1373],{"class":245},[200,23078,611],{"class":214},[200,23080,648],{"class":214},[200,23082,23083,23085,23088,23090,23093],{"class":202,"line":14},[200,23084,443],{"class":379},[200,23086,23087],{"class":210}," overlay ",[200,23089,449],{"class":214},[200,23091,23092],{"class":223}," useOverlay",[200,23094,466],{"class":210},[200,23096,23097],{"class":202,"line":255},[200,23098,529],{"emptyLinePlaceholder":20},[200,23100,23101,23103,23105,23108,23110],{"class":202,"line":272},[200,23102,3711],{"class":379},[200,23104,4987],{"class":379},[200,23106,23107],{"class":223}," openModal",[200,23109,1532],{"class":214},[200,23111,280],{"class":214},[200,23113,23114],{"class":202,"line":283},[200,23115,23116],{"class":210},"  overlay\n",[200,23118,23119,23121,23124,23126,23129],{"class":202,"line":300},[200,23120,3755],{"class":214},[200,23122,23123],{"class":223},"create",[200,23125,227],{"class":235},[200,23127,23128],{"class":210},"MyModal",[200,23130,416],{"class":235},[200,23132,23133,23135,23137],{"class":202,"line":310},[200,23134,3755],{"class":214},[200,23136,18904],{"class":223},[200,23138,466],{"class":235},[200,23140,23141],{"class":202,"line":336},[200,23142,1130],{"class":214},[200,23144,23145,23147,23149],{"class":202,"line":342},[200,23146,681],{"class":214},[200,23148,14892],{"class":235},[200,23150,648],{"class":214},[35,23152,23153],{},"Beautiful, isn't it? 😍",[35,23155,23156,23157,23164,23165,23172],{},"Under the hood, this method utilizes the ",[39,23158,23161],{"href":23159,"rel":23160},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fessentials\u002Fcomponent-basics.html#dynamic-components",[77],[156,23162,23163],{},"component"," directive with a simple ",[39,23166,23169],{"href":23167,"rel":23168},"https:\u002F\u002Fvuejs.org\u002Fapi\u002Fbuilt-in-directives.html#v-for",[77],[156,23170,23171],{},"v-for"," loop to render dialogs.",[192,23174,23177],{"className":21417,"code":23175,"filename":23176,"language":12060,"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,23178,23179,23199,23220,23228,23232,23240,23247,23261,23275,23289,23303,23317,23322],{"__ignoreMap":13},[200,23180,23181,23183,23185,23187,23189,23191,23193,23195,23197],{"class":202,"line":203},[200,23182,600],{"class":214},[200,23184,14892],{"class":235},[200,23186,14590],{"class":379},[200,23188,449],{"class":214},[200,23190,611],{"class":214},[200,23192,1373],{"class":245},[200,23194,611],{"class":214},[200,23196,21781],{"class":379},[200,23198,648],{"class":214},[200,23200,23201,23203,23205,23208,23210,23212,23214,23216,23218],{"class":202,"line":14},[200,23202,443],{"class":379},[200,23204,318],{"class":214},[200,23206,23207],{"class":210}," overlays",[200,23209,550],{"class":214},[200,23211,22457],{"class":210},[200,23213,413],{"class":214},[200,23215,510],{"class":214},[200,23217,23092],{"class":223},[200,23219,466],{"class":210},[200,23221,23222,23224,23226],{"class":202,"line":255},[200,23223,681],{"class":214},[200,23225,14892],{"class":235},[200,23227,648],{"class":214},[200,23229,23230],{"class":202,"line":272},[200,23231,529],{"emptyLinePlaceholder":20},[200,23233,23234,23236,23238],{"class":202,"line":283},[200,23235,600],{"class":214},[200,23237,21428],{"class":235},[200,23239,648],{"class":214},[200,23241,23242,23244],{"class":202,"line":300},[200,23243,657],{"class":214},[200,23245,23246],{"class":235},"component\n",[200,23248,23249,23252,23254,23256,23259],{"class":202,"line":310},[200,23250,23251],{"class":379},"    :is",[200,23253,449],{"class":214},[200,23255,611],{"class":214},[200,23257,23258],{"class":245},"overlay.component",[200,23260,617],{"class":214},[200,23262,23263,23266,23268,23270,23273],{"class":202,"line":336},[200,23264,23265],{"class":379},"    v-for",[200,23267,449],{"class":214},[200,23269,611],{"class":214},[200,23271,23272],{"class":245},"overlay in overlays",[200,23274,617],{"class":214},[200,23276,23277,23280,23282,23284,23287],{"class":202,"line":342},[200,23278,23279],{"class":379},"    :key",[200,23281,449],{"class":214},[200,23283,611],{"class":214},[200,23285,23286],{"class":245},"overlay.id",[200,23288,617],{"class":214},[200,23290,23291,23294,23296,23298,23301],{"class":202,"line":365},[200,23292,23293],{"class":379},"    v-bind",[200,23295,449],{"class":214},[200,23297,611],{"class":214},[200,23299,23300],{"class":245},"overlay.props",[200,23302,617],{"class":214},[200,23304,23305,23308,23310,23312,23315],{"class":202,"line":371},[200,23306,23307],{"class":379},"    v-model:open",[200,23309,449],{"class":214},[200,23311,611],{"class":214},[200,23313,23314],{"class":245},"overlay.modelValue",[200,23316,617],{"class":214},[200,23318,23319],{"class":202,"line":398},[200,23320,23321],{"class":214},"  \u002F>\n",[200,23323,23324,23326,23328],{"class":202,"line":405},[200,23325,681],{"class":214},[200,23327,21428],{"class":235},[200,23329,648],{"class":214},[939,23331,23332],{},[35,23333,23334,23336],{},[200,23335,945],{},"\nThis is a simplified code version.",[35,23338,23339],{},"With this method, extensive state management boilerplate is eliminated, eliminating the need for shared composables or global modals. This significantly improves readability, maintainability, and creation of interactive UI experiences.",[51,23341,23343],{"id":23342},"effortless-dialog-stacking","Effortless dialog stacking",[35,23345,23346],{},"The highlight of this approach is effortless dialog stacking. Merely rendering an array of dialogs via a loop allows simultaneous dialog opening, facilitating elegant stacking with smooth animations.",[35,23348,21414],{},[192,23350,23352],{"className":21417,"code":23351,"filename":21995,"language":12060,"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: 'First Modal',\n        description: 'This is the first modal'\n      },\n      destroyOnClose: true\n    })\n    .open()\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003CButton label=\"Open First Overlay\" @click=\"openOverlay\" \u002F>\n\n    \u003COverlayProvider \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[156,23353,23354,23374,23390,23394,23406,23417,23421,23436,23445,23461,23474,23478,23487,23493,23501,23505,23513,23517,23525,23533,23565,23569,23578,23586],{"__ignoreMap":13},[200,23355,23356,23358,23360,23362,23364,23366,23368,23370,23372],{"class":202,"line":203},[200,23357,600],{"class":214},[200,23359,14892],{"class":235},[200,23361,14590],{"class":379},[200,23363,449],{"class":214},[200,23365,611],{"class":214},[200,23367,1373],{"class":245},[200,23369,611],{"class":214},[200,23371,21781],{"class":379},[200,23373,648],{"class":214},[200,23375,23376,23378,23381,23383,23385,23388],{"class":202,"line":14},[200,23377,973],{"class":206},[200,23379,23380],{"class":210}," DialogFeature1 ",[200,23382,1472],{"class":206},[200,23384,242],{"class":214},[200,23386,23387],{"class":245},".\u002Fcomponents\u002FDialogFeature1.vue",[200,23389,992],{"class":214},[200,23391,23392],{"class":202,"line":255},[200,23393,529],{"emptyLinePlaceholder":20},[200,23395,23396,23398,23400,23402,23404],{"class":202,"line":272},[200,23397,443],{"class":379},[200,23399,23087],{"class":210},[200,23401,449],{"class":214},[200,23403,23092],{"class":223},[200,23405,466],{"class":210},[200,23407,23408,23410,23413,23415],{"class":202,"line":283},[200,23409,14904],{"class":379},[200,23411,23412],{"class":223}," openOverlay",[200,23414,1532],{"class":214},[200,23416,280],{"class":214},[200,23418,23419],{"class":202,"line":300},[200,23420,23116],{"class":210},[200,23422,23423,23425,23427,23429,23432,23434],{"class":202,"line":310},[200,23424,3755],{"class":214},[200,23426,23123],{"class":223},[200,23428,227],{"class":235},[200,23430,23431],{"class":210},"DialogFeature1",[200,23433,550],{"class":214},[200,23435,280],{"class":214},[200,23437,23438,23441,23443],{"class":202,"line":336},[200,23439,23440],{"class":235},"      props",[200,23442,239],{"class":214},[200,23444,280],{"class":214},[200,23446,23447,23450,23452,23454,23457,23459],{"class":202,"line":342},[200,23448,23449],{"class":235},"        title",[200,23451,239],{"class":214},[200,23453,242],{"class":214},[200,23455,23456],{"class":245},"First Modal",[200,23458,249],{"class":214},[200,23460,252],{"class":214},[200,23462,23463,23465,23467,23469,23472],{"class":202,"line":365},[200,23464,19994],{"class":235},[200,23466,239],{"class":214},[200,23468,242],{"class":214},[200,23470,23471],{"class":245},"This is the first modal",[200,23473,992],{"class":214},[200,23475,23476],{"class":202,"line":371},[200,23477,12083],{"class":214},[200,23479,23480,23483,23485],{"class":202,"line":398},[200,23481,23482],{"class":235},"      destroyOnClose",[200,23484,239],{"class":214},[200,23486,22192],{"class":2423},[200,23488,23489,23491],{"class":202,"line":405},[200,23490,2096],{"class":214},[200,23492,416],{"class":235},[200,23494,23495,23497,23499],{"class":202,"line":410},[200,23496,3755],{"class":214},[200,23498,18904],{"class":223},[200,23500,466],{"class":235},[200,23502,23503],{"class":202,"line":861},[200,23504,1130],{"class":214},[200,23506,23507,23509,23511],{"class":202,"line":890},[200,23508,681],{"class":214},[200,23510,14892],{"class":235},[200,23512,648],{"class":214},[200,23514,23515],{"class":202,"line":900},[200,23516,529],{"emptyLinePlaceholder":20},[200,23518,23519,23521,23523],{"class":202,"line":905},[200,23520,600],{"class":214},[200,23522,21428],{"class":235},[200,23524,648],{"class":214},[200,23526,23527,23529,23531],{"class":202,"line":931},[200,23528,657],{"class":214},[200,23530,11386],{"class":235},[200,23532,648],{"class":214},[200,23534,23535,23537,23539,23541,23543,23545,23548,23550,23552,23554,23556,23558,23561,23563],{"class":202,"line":3640},[200,23536,804],{"class":214},[200,23538,21473],{"class":235},[200,23540,21476],{"class":379},[200,23542,449],{"class":214},[200,23544,611],{"class":214},[200,23546,23547],{"class":245},"Open First Overlay",[200,23549,611],{"class":214},[200,23551,22373],{"class":214},[200,23553,22376],{"class":379},[200,23555,449],{"class":214},[200,23557,611],{"class":214},[200,23559,23560],{"class":210},"openOverlay",[200,23562,611],{"class":214},[200,23564,21487],{"class":214},[200,23566,23567],{"class":202,"line":3645},[200,23568,529],{"emptyLinePlaceholder":20},[200,23570,23571,23573,23576],{"class":202,"line":4020},[200,23572,804],{"class":214},[200,23574,23575],{"class":235},"OverlayProvider",[200,23577,21487],{"class":214},[200,23579,23580,23582,23584],{"class":202,"line":4475},[200,23581,893],{"class":214},[200,23583,11386],{"class":235},[200,23585,648],{"class":214},[200,23587,23588,23590,23592],{"class":202,"line":4481},[200,23589,681],{"class":214},[200,23591,21428],{"class":235},[200,23593,648],{"class":214},[192,23595,23598],{"className":21417,"code":23596,"filename":23597,"language":12060,"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: 'Second Modal',\n        description: 'This is the second 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=\"Open Second Modal\"\n        @click=\"openOverlay\"\n      \u002F>\n    \u003C\u002Ftemplate>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n","src\u002Fcomponents\u002FDialogFeature1.vue",[156,23599,23600,23620,23636,23640,23655,23665,23673,23680,23684,23696,23706,23710,23725,23733,23748,23761,23765,23773,23779,23787,23791,23799,23803,23811,23817,23837,23855,23859,23873,23880,23894,23909,23914,23922,23930],{"__ignoreMap":13},[200,23601,23602,23604,23606,23608,23610,23612,23614,23616,23618],{"class":202,"line":203},[200,23603,600],{"class":214},[200,23605,14892],{"class":235},[200,23607,14590],{"class":379},[200,23609,449],{"class":214},[200,23611,611],{"class":214},[200,23613,1373],{"class":245},[200,23615,611],{"class":214},[200,23617,21781],{"class":379},[200,23619,648],{"class":214},[200,23621,23622,23624,23627,23629,23631,23634],{"class":202,"line":14},[200,23623,973],{"class":206},[200,23625,23626],{"class":210}," DialogFeature2 ",[200,23628,1472],{"class":206},[200,23630,242],{"class":214},[200,23632,23633],{"class":245},".\u002FDialogFeature2.vue",[200,23635,992],{"class":214},[200,23637,23638],{"class":202,"line":255},[200,23639,529],{"emptyLinePlaceholder":20},[200,23641,23642,23644,23647,23649,23652],{"class":202,"line":272},[200,23643,443],{"class":379},[200,23645,23646],{"class":210}," props ",[200,23648,449],{"class":214},[200,23650,23651],{"class":223}," defineProps",[200,23653,23654],{"class":214},"\u003C{\n",[200,23656,23657,23660,23662],{"class":202,"line":283},[200,23658,23659],{"class":235},"  title",[200,23661,239],{"class":214},[200,23663,23664],{"class":1978}," string\n",[200,23666,23667,23669,23671],{"class":202,"line":300},[200,23668,258],{"class":235},[200,23670,239],{"class":214},[200,23672,23664],{"class":1978},[200,23674,23675,23678],{"class":202,"line":310},[200,23676,23677],{"class":214},"}>",[200,23679,466],{"class":210},[200,23681,23682],{"class":202,"line":336},[200,23683,529],{"emptyLinePlaceholder":20},[200,23685,23686,23688,23690,23692,23694],{"class":202,"line":342},[200,23687,443],{"class":379},[200,23689,23087],{"class":210},[200,23691,449],{"class":214},[200,23693,23092],{"class":223},[200,23695,466],{"class":210},[200,23697,23698,23700,23702,23704],{"class":202,"line":365},[200,23699,14904],{"class":379},[200,23701,23412],{"class":223},[200,23703,1532],{"class":214},[200,23705,280],{"class":214},[200,23707,23708],{"class":202,"line":371},[200,23709,23116],{"class":210},[200,23711,23712,23714,23716,23718,23721,23723],{"class":202,"line":398},[200,23713,3755],{"class":214},[200,23715,23123],{"class":223},[200,23717,227],{"class":235},[200,23719,23720],{"class":210},"DialogFeature2",[200,23722,550],{"class":214},[200,23724,280],{"class":214},[200,23726,23727,23729,23731],{"class":202,"line":405},[200,23728,23440],{"class":235},[200,23730,239],{"class":214},[200,23732,280],{"class":214},[200,23734,23735,23737,23739,23741,23744,23746],{"class":202,"line":410},[200,23736,23449],{"class":235},[200,23738,239],{"class":214},[200,23740,242],{"class":214},[200,23742,23743],{"class":245},"Second Modal",[200,23745,249],{"class":214},[200,23747,252],{"class":214},[200,23749,23750,23752,23754,23756,23759],{"class":202,"line":861},[200,23751,19994],{"class":235},[200,23753,239],{"class":214},[200,23755,242],{"class":214},[200,23757,23758],{"class":245},"This is the second modal",[200,23760,992],{"class":214},[200,23762,23763],{"class":202,"line":890},[200,23764,12083],{"class":214},[200,23766,23767,23769,23771],{"class":202,"line":900},[200,23768,23482],{"class":235},[200,23770,239],{"class":214},[200,23772,22192],{"class":2423},[200,23774,23775,23777],{"class":202,"line":905},[200,23776,2096],{"class":214},[200,23778,416],{"class":235},[200,23780,23781,23783,23785],{"class":202,"line":931},[200,23782,3755],{"class":214},[200,23784,18904],{"class":223},[200,23786,466],{"class":235},[200,23788,23789],{"class":202,"line":3640},[200,23790,1130],{"class":214},[200,23792,23793,23795,23797],{"class":202,"line":3645},[200,23794,681],{"class":214},[200,23796,14892],{"class":235},[200,23798,648],{"class":214},[200,23800,23801],{"class":202,"line":4020},[200,23802,529],{"emptyLinePlaceholder":20},[200,23804,23805,23807,23809],{"class":202,"line":4475},[200,23806,600],{"class":214},[200,23808,21428],{"class":235},[200,23810,648],{"class":214},[200,23812,23813,23815],{"class":202,"line":4481},[200,23814,657],{"class":214},[200,23816,21437],{"class":235},[200,23818,23819,23822,23824,23826,23828,23831,23833,23835],{"class":202,"line":4487},[200,23820,23821],{"class":214},"    :",[200,23823,3566],{"class":379},[200,23825,449],{"class":214},[200,23827,611],{"class":214},[200,23829,23830],{"class":210},"props",[200,23832,215],{"class":214},[200,23834,3566],{"class":210},[200,23836,617],{"class":214},[200,23838,23839,23841,23843,23845,23847,23849,23851,23853],{"class":202,"line":4493},[200,23840,23821],{"class":214},[200,23842,3582],{"class":379},[200,23844,449],{"class":214},[200,23846,611],{"class":214},[200,23848,23830],{"class":210},[200,23850,215],{"class":214},[200,23852,3582],{"class":210},[200,23854,617],{"class":214},[200,23856,23857],{"class":202,"line":5454},[200,23858,11536],{"class":214},[200,23860,23861,23863,23865,23868,23871],{"class":202,"line":5459},[200,23862,804],{"class":214},[200,23864,21428],{"class":235},[200,23866,23867],{"class":214}," #",[200,23869,23870],{"class":379},"body",[200,23872,648],{"class":214},[200,23874,23875,23877],{"class":202,"line":13227},[200,23876,22027],{"class":214},[200,23878,23879],{"class":235},"Button\n",[200,23881,23882,23885,23887,23889,23892],{"class":202,"line":13253},[200,23883,23884],{"class":379},"        label",[200,23886,449],{"class":214},[200,23888,611],{"class":214},[200,23890,23891],{"class":245},"Open Second Modal",[200,23893,617],{"class":214},[200,23895,23896,23899,23901,23903,23905,23907],{"class":202,"line":13269},[200,23897,23898],{"class":214},"        @",[200,23900,22376],{"class":379},[200,23902,449],{"class":214},[200,23904,611],{"class":214},[200,23906,23560],{"class":210},[200,23908,617],{"class":214},[200,23910,23911],{"class":202,"line":13313},[200,23912,23913],{"class":214},"      \u002F>\n",[200,23915,23916,23918,23920],{"class":202,"line":13319},[200,23917,15074],{"class":214},[200,23919,21428],{"class":235},[200,23921,648],{"class":214},[200,23923,23924,23926,23928],{"class":202,"line":13324},[200,23925,893],{"class":214},[200,23927,21494],{"class":235},[200,23929,648],{"class":214},[200,23931,23932,23934,23936],{"class":202,"line":13350},[200,23933,681],{"class":214},[200,23935,21428],{"class":235},[200,23937,648],{"class":214},[192,23939,23942],{"className":21417,"code":23940,"filename":23941,"language":12060,"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,23943,23944,23964,23976,23984,23992,23998,24006,24010,24018,24024,24042,24060,24064],{"__ignoreMap":13},[200,23945,23946,23948,23950,23952,23954,23956,23958,23960,23962],{"class":202,"line":203},[200,23947,600],{"class":214},[200,23949,14892],{"class":235},[200,23951,14590],{"class":379},[200,23953,449],{"class":214},[200,23955,611],{"class":214},[200,23957,1373],{"class":245},[200,23959,611],{"class":214},[200,23961,21781],{"class":379},[200,23963,648],{"class":214},[200,23965,23966,23968,23970,23972,23974],{"class":202,"line":14},[200,23967,443],{"class":379},[200,23969,23646],{"class":210},[200,23971,449],{"class":214},[200,23973,23651],{"class":223},[200,23975,23654],{"class":214},[200,23977,23978,23980,23982],{"class":202,"line":255},[200,23979,23659],{"class":235},[200,23981,239],{"class":214},[200,23983,23664],{"class":1978},[200,23985,23986,23988,23990],{"class":202,"line":272},[200,23987,258],{"class":235},[200,23989,239],{"class":214},[200,23991,23664],{"class":1978},[200,23993,23994,23996],{"class":202,"line":283},[200,23995,23677],{"class":214},[200,23997,466],{"class":210},[200,23999,24000,24002,24004],{"class":202,"line":300},[200,24001,681],{"class":214},[200,24003,14892],{"class":235},[200,24005,648],{"class":214},[200,24007,24008],{"class":202,"line":310},[200,24009,529],{"emptyLinePlaceholder":20},[200,24011,24012,24014,24016],{"class":202,"line":336},[200,24013,600],{"class":214},[200,24015,21428],{"class":235},[200,24017,648],{"class":214},[200,24019,24020,24022],{"class":202,"line":342},[200,24021,657],{"class":214},[200,24023,21437],{"class":235},[200,24025,24026,24028,24030,24032,24034,24036,24038,24040],{"class":202,"line":365},[200,24027,23821],{"class":214},[200,24029,3566],{"class":379},[200,24031,449],{"class":214},[200,24033,611],{"class":214},[200,24035,23830],{"class":210},[200,24037,215],{"class":214},[200,24039,3566],{"class":210},[200,24041,617],{"class":214},[200,24043,24044,24046,24048,24050,24052,24054,24056,24058],{"class":202,"line":371},[200,24045,23821],{"class":214},[200,24047,3582],{"class":379},[200,24049,449],{"class":214},[200,24051,611],{"class":214},[200,24053,23830],{"class":210},[200,24055,215],{"class":214},[200,24057,3582],{"class":210},[200,24059,617],{"class":214},[200,24061,24062],{"class":202,"line":398},[200,24063,23321],{"class":214},[200,24065,24066,24068,24070],{"class":202,"line":405},[200,24067,681],{"class":214},[200,24069,21428],{"class":235},[200,24071,648],{"class":214},[35,24073,21590],{},[35,24075,24076,24077],{},"Explore the complete code at ",[1582,24078,24080,24081,215],{"repo":24079},"barbapapazes\u002Fvue-dialog-stacking"," and even ",[39,24082,24085],{"href":24083,"rel":24084},"https:\u002F\u002Fvue-dialog-stacking.barbapapazes.dev\u002F",[77],"a live demo",[130,24087,132,24088,132,24096],{},[24089,24090],"iframe",{"src":24083,"width":24091,"height":24092,"frameBorder":3933,"scrolling":24093,"allowFullScreen":20,"className":24094},"100%",600,"no",[24095],"bg-white",[143,24097,24098],{},"Live demo of dialog stacking",[51,24100,24102],{"id":24101},"final-thoughts","Final thoughts",[35,24104,24105],{},"After years of grappling with dialogs and modals and trying several approaches, I finally found a method that works for me and may work for you.",[35,24107,24108,24109,24114,24115,24122],{},"I initially encountered this approach with Angular Material and longed for something similar in Vue. ",[39,24110,24113],{"href":24111,"rel":24112},"https:\u002F\u002Fgithub.com\u002Fantfu",[77],"Anthony Fu"," proposed a promising start with ",[39,24116,24119],{"href":24117,"rel":24118},"https:\u002F\u002Fgithub.com\u002Fantfu\u002Fvue-template-promise",[77],[156,24120,24121],{},"useTemplatePromise",", but it remained complex solely for dialog opening.",[35,24124,24125,24126,24131,24132,1638,24137,24142,24143,24145],{},"Finally, thanks to ",[39,24127,24130],{"href":24128,"rel":24129},"https:\u002F\u002Fgithub.com\u002Fzernonia",[77],"zernonia"," for ",[39,24133,24136],{"href":24134,"rel":24135},"https:\u002F\u002Freka-ui.com\u002F",[77],"Reka UI",[39,24138,24141],{"href":24139,"rel":24140},"https:\u002F\u002Fgithub.com\u002Fgenu",[77],"Eugen Istoc"," for the ",[156,24144,23054],{}," composable in Nuxt UI, I can confidently manage dialogs and modals properly in Vue.",[35,24147,24148],{},"I'm convinced this approach is a game changer for interactive UI experiences in Vue. Give it a try!",[1171,24150,24151],{},"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 .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 .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 .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 .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":24153},[24154,24155,24156,24157,24158,24159],{"id":21362,"depth":14,"text":21363},{"id":21657,"depth":14,"text":21658},{"id":21980,"depth":14,"text":21981},{"id":22864,"depth":14,"text":22865},{"id":23342,"depth":14,"text":23343},{"id":24101,"depth":14,"text":24102},"2025-03-29","After years of searching for an effective approach to managing dialogs and modals, I finally discovered a reliable pattern.",{},"\u002Fposts\u002Fa-journey-to-craft-interactive-ui-experiences-with-dialogs","---\ncontentId: 67862d90-d724-476f-9667-3f35683445ed\ntitle: \"A Journey to Craft Interactive UI Experiences with Dialogs\"\ndescription: \"After years of searching for an effective approach to managing dialogs and modals, I finally discovered a reliable pattern.\"\ndate: 2025-03-29\n---\n\nAs far as I remember, this challenge has been the most persistent one since I began my journey in **web development**. I recently uncovered a method to build a component library, which was a long endeavor. However, solving the intricacies of dialog and modal management has been even more extensive, complex, and crucial due to its ubiquitous presence in every application.\n\n\u003CCalloutReadMore id=\"f9754148-40bd-4df2-a144-f686e894b200\" \u002F>\n\nToday, I'm thrilled to share the solution I found, _I didn't develop it_, for managing dialogs and modals effectively within your applications. This resolution stems from years of experience, and I hope it benefits you as it has benefited me.\n\n## Understanding the issue\n\nBefore delving into the problem, let's align on what we're discussing. This article focuses on **dialogs**, **modals**, and **slideovers**. Each serves to display information or solicit confirmation, differing mainly in presentation and usage.\n\nTry to open the dialog by clicking the button below.\n\n\u003Cdiv class=\"flex justify-center\">\n  \u003CModal\n    title=\"Dialog\"\n    description=\"This is a dialog\"\n    :ui=\"{ content: 'min-w-[280px]' }\"\n  >\n    \u003CButton label=\"Open Dialog\" \u002F>\n  \u003C\u002FModal>\n\u003C\u002Fdiv>\n\nSimilarly, you can open a slideover by clicking the button.\n\n\u003Cdiv class=\"flex justify-center\">\n  \u003CSlideover\n    title=\"Slideover\"\n    description=\"This is a slideover\"\n    :ui=\"{ content: 'min-w-[280px]' }\"\n  >\n    \u003CButton label=\"Open Slideover\" \u002F>\n  \u003C\u002FSlideover>\n\u003C\u002Fdiv>\n\nThe design and dialog types might seem secondary; the important aspect lies in the underlying code. Let's examine it:\n\n::: code-group\n\n```vue [src\u002Fcomponents\u002FModal.vue]\n\u003Ctemplate>\n  \u003CModal\n    title=\"Dialog\"\n    description=\"This is a dialog\"\n  >\n    \u003CButton label=\"Open Dialog\" \u002F>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n```\n\n```vue [src\u002Fcomponents\u002FSlideover.vue]\n\u003Ctemplate>\n  \u003CSlideover\n    title=\"Slideover\"\n    description=\"This is a slideover\"\n  >\n    \u003CButton label=\"Open Slideover\" \u002F>\n  \u003C\u002FSlideover>\n\u003C\u002Ftemplate>\n```\n\n:::\n\nThey are quite similar, relying on the same component beneath the surface. The true intrigue is in how this code is utilized. These examples illustrate that dialogs integrate seamlessly as any HTML element, which is the crux of the problem.\n\nDialogs are a special type of component in an application:\n\n1. They can be invoked from anywhere within the application.\n2. They open through various methods, such as:\n    - Button clicks\n    - Key presses\n    - Route navigations\n3. The ubiquitous nature makes dialog-free applications nearly impractical without sacrificing user experience:\n    - Creating items\n    - Editing items\n    - Action confirmations\n    - Item visualizations\n    - Etc.\n\n    Each action might lead to immediate consequences or await a server response, complicating dialog usage possibilities.\n\nDue to these factors, dialogs demand meticulous care in development and usage; otherwise, they and their logic can quickly become chaotic.\n\nAdditionally, it has always been a personal preference of mine to enable dialog stacking with smooth animations, like this:\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>Stacking dialogs with a sleek animation.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n## Inline dialogs\n\nAn initial attempt to address this challenge is via **inline modals**. It's a prevalent approach and suits simple cases. The strategy involves incorporating a modal component directly within the template that requires it, as demonstrated earlier.\n\n```vue [src\u002Fcomponents\u002FModal.vue]\n\u003Ctemplate>\n  \u003CModal\n    title=\"Dialog\"\n    description=\"This is a dialog\"\n  >\n    \u003CButton label=\"Open Dialog\" \u002F>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n```\n\nDespite the straightforward implementation, this approach presents at least two significant drawbacks:\n\n1. Dialog components become tightly coupled with the utilizing component, complicating comprehension and refactoring.\n2. Overhead arises from boilerplate code managing dialog states, especially when opening dialogs without buttons or decoupling templates from dialogs. An example of opening a dialog using a key press follows:\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=\"Dialog\"\n      description=\"This is a dialog\"\n    \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\nThis method quickly becomes cumbersome and tough to maintain over time. Multiple dialogs intensify the boilerplate, requiring repeated additions in each component utilizing the dialog.\n\n## Global dialogs\n\nEnhancing dialog management involves programmatically opening dialogs universally, decoupling their invocation from any direct component relationship. This pattern facilitates code refactoring by separating dialogs physically from components, enhancing reusability across multiple components without redundant incorporations.\n\nAn `App.vue` file might look like this:\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!-- And so on... -->\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\nAchieving this setup mandates sharing a variable between the dialog-opening component and the dialog itself. In Vue, a straightforward--though not ideal--approach is to create a composable externalizing its state.\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\nUtilize this composable for both the dialog-opening component and the dialog itself.\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=\"Open Dialog\" @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=\"Dialog\"\n    description=\"This is a dialog\"\n  >\n    \u003CButton label=\"Close\" @click=\"close\" \u002F>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n```\n\nThis approach is certainly superior to the inline method but has drawbacks:\n\n1. Inclusion of modals at the app (or route) top-level creates potential code redundancy.\n2. Dialog management becomes elaborate, particularly with numerous dialogs or props, each requiring passage through the 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> Simplify composable writing with [`createSharedComposable`](https:\u002F\u002Fvueuse.org\u002Fshared\u002FcreateSharedComposable\u002F) from [`@vueuse\u002Fcore`](https:\u002F\u002Fvueuse.org\u002F) to craft a singleton composable, automatically sharing the same state between all utilizing components.\n\n## Programmatic dialogs\n\nThe latest, profoundly effective approach I discovered involves **programmatically managing dialogs**. This method resolves the difficulties mentioned earlier by allowing complete dialog control through code rather than templates.\n\nHaving used programmatic dialogs in **Angular** with Angular Material, I appreciated its capacity for code simplification.\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(`Dialog result: ${result}`) \u002F\u002F Pizza!\n})\n\ndialogRef.close('Pizza!')\n```\n\nFor a long time, I missed such a feature in Vue.\n\nUntil recently, when [Nuxt](https:\u002F\u002Fnuxt.com\u002F) released version 3 of [Nuxt UI](https:\u002F\u002Fui.nuxt.com\u002F) with the `useOverlay` composable. This shared application composable enables dialog opening from any location, mirroring Angular Material functionality.\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\nBeautiful, isn't it? 😍\n\nUnder the hood, this method utilizes the [`component`](https:\u002F\u002Fvuejs.org\u002Fguide\u002Fessentials\u002Fcomponent-basics.html#dynamic-components) directive with a simple [`v-for`](https:\u002F\u002Fvuejs.org\u002Fapi\u002Fbuilt-in-directives.html#v-for) loop to render dialogs.\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> This is a simplified code version.\n\nWith this method, extensive state management boilerplate is eliminated, eliminating the need for shared composables or global modals. This significantly improves readability, maintainability, and creation of interactive UI experiences.\n\n## Effortless dialog stacking\n\nThe highlight of this approach is effortless dialog stacking. Merely rendering an array of dialogs via a loop allows simultaneous dialog opening, facilitating elegant stacking with smooth animations.\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: 'First Modal',\n        description: 'This is the first modal'\n      },\n      destroyOnClose: true\n    })\n    .open()\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003CButton label=\"Open First 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: 'Second Modal',\n        description: 'This is the second 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=\"Open Second 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\nExplore the complete code at \u003CGitHubLink repo=\"barbapapazes\u002Fvue-dialog-stacking\" \u002F> and even [a live demo](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>Live demo of dialog stacking\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n## Final thoughts\n\nAfter years of grappling with dialogs and modals and trying several approaches, I finally found a method that works for me and may work for you.\n\nI initially encountered this approach with Angular Material and longed for something similar in Vue. [Anthony Fu](https:\u002F\u002Fgithub.com\u002Fantfu) proposed a promising start with [`useTemplatePromise`](https:\u002F\u002Fgithub.com\u002Fantfu\u002Fvue-template-promise), but it remained complex solely for dialog opening.\n\nFinally, thanks to [zernonia](https:\u002F\u002Fgithub.com\u002Fzernonia) for [Reka UI](https:\u002F\u002Freka-ui.com\u002F) and [Eugen Istoc](https:\u002F\u002Fgithub.com\u002Fgenu) for the `useOverlay` composable in Nuxt UI, I can confidently manage dialogs and modals properly in Vue.\n\nI'm convinced this approach is a game changer for interactive UI experiences in Vue. Give it a try!\n",{"title":21340,"description":24161},{"loc":24163},"posts\u002Fa-journey-to-craft-interactive-ui-experiences-with-dialogs","EmRK6pQdmxLwiO94ztAc0TWnokwe-gvzsvtf5LWoEvA",{"id":24170,"title":24171,"body":24172,"contentId":9252,"date":29190,"description":29191,"extension":18,"meta":29192,"navigation":20,"path":29193,"rawbody":29194,"robots":23,"schemaOrg":23,"seo":29195,"sitemap":29196,"stem":29204,"__hash__":29205},"posts\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components.md","Fusion or the Art of Writing PHP Into a Vue SFC Components",{"type":7,"value":24173,"toc":29176},[24174,24180,24195,24217,24223,24237,24244,24269,24272,24333,24335,24338,24341,24367,24377,24380,24387,24391,24403,24406,24409,24429,24436,24443,24668,24671,24674,24677,24685,24690,24693,24696,24706,24712,24715,24804,24818,24821,24827,24848,24973,24989,24992,25027,25133,25139,25169,25199,25208,25214,25231,25248,25251,25277,25282,25285,25291,25294,25297,25300,25336,25412,25415,25436,25624,25630,25637,25643,25646,25649,25655,25658,25665,25668,25680,25684,25687,25690,25698,25701,25705,25708,25723,25732,25737,25743,25754,25763,25772,25780,25788,25807,25858,25865,25927,25934,25937,26011,26014,26058,26061,26159,26174,26184,26191,26204,26218,26239,26257,26260,29173],[35,24175,24176,24177,5839],{},"This is truly a ",[6850,24178,24179],{},"special",[35,24181,24182,24183,24188,24189,106,24192],{},"Two weeks ago, on February 3rd and 4th, 2025, ",[39,24184,24187],{"href":24185,"rel":24186},"https:\u002F\u002Flaracon.eu\u002F",[77],"Laracon EU"," was held in Amsterdam. Although I wasn't able to attend in person, I followed the entire event via live stream. Conferences always provide a fantastic opportunity to gain new insights and to immerse further into the wonderful ",[6850,24190,24191],{},"Laravel ecosystem",[1217,24193,24194],{},"I hope to attend Laracon someday.",[35,24196,24197,24198,6853,24203,24206,24207,1638,24210,24213,24214,24216],{},"One of the talks that caught my attention was ",[39,24199,24202],{"href":24200,"rel":24201},"https:\u002F\u002Fyoutu.be\u002FlXjYStxGFIQ?si=ntTW6zP-tAzV0OZO",[77],"\"Introducing [REDACTED]: A New Paradigm for Laravel + Javascript\"",[39,24204,17864],{"href":17862,"rel":24205},[77],". This intrigued me since we already have ",[6850,24208,24209],{},"Inertia",[6850,24211,24212],{},"Livewire",", which are excellent tools for building modern applications with ",[6850,24215,19447],{},". So, what new advancements could be offered?",[35,24218,24219],{},[1690,24220],{"alt":24221,"src":24222},"Thumbnail of the video \"Introducing [REDACTED]: a new paradigm for Laravel + Javascript\" by Aaron Francis","\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fthumbnail.jpg",[35,24224,24225,24226,24233,24234,24236],{},"Aaron introduced ",[39,24227,24230],{"href":24228,"rel":24229},"https:\u002F\u002Fgithub.com\u002Ffusion-php",[77],[6850,24231,24232],{},"Fusion",", described as a novel approach to integrate your modern frontend with your Laravel backend. The concept allows writing PHP code directly in a frontend file, and through some processing, the PHP code is executed server-side, with results sent to the frontend. This is reminiscent of ",[6850,24235,24209],{},", but taken much further.",[939,24238,24239],{},[35,24240,24241,24243],{},[200,24242,945],{},"\nPlease watch the video. It's fascinating, easy to follow, and will significantly aid in understanding the rest of this article.",[35,24245,24246,24247,24252,24253,13731,24256,24258,24259,24264,24265,24268],{},"During ",[39,24248,24251],{"href":24249,"rel":24250},"https:\u002F\u002Fyoutu.be\u002FvqWlJCtoD-k?si=Xnd4MOa5-dcBvPhi",[77],"an interview on the Navbar podcast",", Aaron articulated Fusion's aim: to bridge the gap between the frontend and backend, simplifying development and transforming Laravel with Inertia to resemble current meta-frameworks like ",[6850,24254,24255],{},"Next",[6850,24257,2918],{},". With his business partner ",[39,24260,24263],{"href":24261,"rel":24262},"https:\u002F\u002Fx.com\u002Fsteve_tenuto",[77],"Steve",", Aaron discovered that typical Laravel conventions, such as routes in ",[156,24266,24267],{},"web.php",", were not intuitive for JavaScript developers who prefer file-based routing. Aaron's goal is to make Laravel more accessible to JavaScript developers.",[35,24270,24271],{},"With Fusion, a new block is introduced in the Vue SFC to write PHP code, making it an integral part of the Laravel application.",[192,24273,24275],{"className":21417,"code":24274,"language":12060,"meta":13,"style":13},"\u003Cphp>\n  \u002F\u002F Define a prop in PHP\n  $name = prop(Auth::user()->name);\n\u003C\u002Fphp>\n\n\u003Ctemplate>\n  \u003C!-- Use it in Vue! -->\n  Hello {{ name }}!\n\u003C\u002Ftemplate>\n",[156,24276,24277,24285,24290,24295,24303,24307,24315,24320,24325],{"__ignoreMap":13},[200,24278,24279,24281,24283],{"class":202,"line":203},[200,24280,600],{"class":214},[200,24282,19540],{"class":235},[200,24284,648],{"class":214},[200,24286,24287],{"class":202,"line":14},[200,24288,24289],{"class":210},"  \u002F\u002F Define a prop in PHP\n",[200,24291,24292],{"class":202,"line":255},[200,24293,24294],{"class":210},"  $name = prop(Auth::user()->name);\n",[200,24296,24297,24299,24301],{"class":202,"line":272},[200,24298,681],{"class":214},[200,24300,19540],{"class":235},[200,24302,648],{"class":214},[200,24304,24305],{"class":202,"line":283},[200,24306,529],{"emptyLinePlaceholder":20},[200,24308,24309,24311,24313],{"class":202,"line":300},[200,24310,600],{"class":214},[200,24312,21428],{"class":235},[200,24314,648],{"class":214},[200,24316,24317],{"class":202,"line":310},[200,24318,24319],{"class":401},"  \u003C!-- Use it in Vue! -->\n",[200,24321,24322],{"class":202,"line":336},[200,24323,24324],{"class":210},"  Hello {{ name }}!\n",[200,24326,24327,24329,24331],{"class":202,"line":342},[200,24328,681],{"class":214},[200,24330,21428],{"class":235},[200,24332,648],{"class":214},[1159,24334],{},[35,24336,24337],{},"This article doesn't aim to determine whether Fusion should be used in production or if embedding PHP in a Vue SFC is advisable. That debate is well-covered, especially on X or Reddit.",[35,24339,24340],{},"Instead, I intend to delve into Fusion's workings for two main reasons:",[68,24342,24343,24346],{},[71,24344,24345],{},"There's much to learn from examining code. Though I haven't read many books, I often peruse project codebases, stealing tricks and smart patterns overlooked elsewhere.",[71,24347,24348,24349,3076,24351,3080,24353,24356,24357,24360,24361,24366],{},"Fusion embodies a technical pinnacle of what's achievable with ",[6850,24350,19447],{},[6850,24352,17986],{},[6850,24354,24355],{},"Vite",". Thus, my curiosity to understand its workings led me to write this analysis. I'm studying the ",[39,24358,14039],{"href":14037,"rel":24359},[77]," library by ",[39,24362,24365],{"href":24363,"rel":24364},"https:\u002F\u002Fgithub.com\u002Fjohnsoncodehk",[77],"Johnson Chu"," similarly. Comprehending something deeply often involves articulating it to others, hence this article.",[939,24368,24369],{},[35,24370,24371,24373,24374,24376],{},[200,24372,945],{},"\nVite's integration with Laravel is exceptional, offering a lot of benefits to Laravel developers. If you haven't explored ",[6850,24375,24355],{}," yet, you definitely should.",[35,24378,24379],{},"Now, let's dive into the code.",[939,24381,24382],{},[35,24383,24384,24386],{},[200,24385,945],{},"\nReading the codebase is intriguing; it resembles reading PHP written in JavaScript.",[51,24388,24390],{"id":24389},"the-fusion-codebase","The Fusion codebase",[35,24392,24393,24394,24397,24398,24402],{},"A week after presenting at ",[39,24395,24187],{"href":24185,"rel":24396},[77],", Aaron made Fusion's codebase public. Curious about its mechanics, I speculated that a Vite plugin might extract the PHP from a Vue SFC, similar to how ",[39,24399,24401],{"href":13728,"rel":24400},[77],"Vite's Vue plugin"," handles script, template, and style tags.",[35,24404,24405],{},"Supporting HTML, CSS, and JavaScript is straightforward for Vite since they're within its scope. However, PHP presents a challenge since it's not supported inherently by Vite.",[35,24407,24408],{},"Let's pose questions we will answer later. First: how does Fusion manage PHP code?",[130,24410,132,24411,132,24416],{},[134,24412,136,24413,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,24414],{"src":24415,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fhype-video.mp4",[143,24417,24418,24419,1638,24421,3076,24424,24428],{},"\n    Fusion Hype Video by ",[39,24420,17864],{"href":17862,"target":7144},[39,24422,24423],{"href":24261,"target":7144},"Steve Tenuto",[39,24425,24427],{"href":24426,"target":7144},"https:\u002F\u002Fx.com\u002Faarondfrancis\u002Fstatus\u002F1886768725509976146","available on X",".\n  ",[35,24430,24431,24432,24435],{},"Watching the hype video, it's evident there's more than just extracting PHP code and executing it. Fusion passes PHP code results as props to the Vue component and supports RPC on the Laravel backend from the client. Additionally, the keyword ",[156,24433,24434],{},"sync"," enables synchronization between frontend and backend states.",[35,24437,24438,24439,24442],{},"For instance, a function called ",[156,24440,24441],{},"favorite"," becomes readily usable in the frontend:",[192,24444,24446],{"className":21417,"code":24445,"language":12060,"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,24447,24448,24456,24461,24466,24470,24475,24479,24484,24492,24496,24516,24532,24540,24544,24552,24559,24578,24597,24609,24656,24660],{"__ignoreMap":13},[200,24449,24450,24452,24454],{"class":202,"line":203},[200,24451,600],{"class":214},[200,24453,19540],{"class":235},[200,24455,648],{"class":214},[200,24457,24458],{"class":202,"line":14},[200,24459,24460],{"class":210},"use function \\Fusion\\{prop, expose};\n",[200,24462,24463],{"class":202,"line":255},[200,24464,24465],{"class":210},"use \\App\\Models\\Podcast;\n",[200,24467,24468],{"class":202,"line":272},[200,24469,529],{"emptyLinePlaceholder":20},[200,24471,24472],{"class":202,"line":283},[200,24473,24474],{"class":210},"$podcasts = prop(fn () => Podcast::all())->readonly();\n",[200,24476,24477],{"class":202,"line":300},[200,24478,529],{"emptyLinePlaceholder":20},[200,24480,24481],{"class":202,"line":310},[200,24482,24483],{"class":210},"expose(favorite: fn (Podcast $podcast) => response()->json($podcast->toggleFavorite()));\n",[200,24485,24486,24488,24490],{"class":202,"line":336},[200,24487,681],{"class":214},[200,24489,19540],{"class":235},[200,24491,648],{"class":214},[200,24493,24494],{"class":202,"line":342},[200,24495,529],{"emptyLinePlaceholder":20},[200,24497,24498,24500,24502,24504,24506,24508,24510,24512,24514],{"class":202,"line":365},[200,24499,600],{"class":214},[200,24501,14892],{"class":235},[200,24503,14590],{"class":379},[200,24505,449],{"class":214},[200,24507,611],{"class":214},[200,24509,1373],{"class":245},[200,24511,611],{"class":214},[200,24513,21781],{"class":379},[200,24515,648],{"class":214},[200,24517,24518,24520,24523,24525,24527,24530],{"class":202,"line":371},[200,24519,973],{"class":206},[200,24521,24522],{"class":210}," Podcast ",[200,24524,1472],{"class":206},[200,24526,242],{"class":214},[200,24528,24529],{"class":245},"@Components\u002FPodcast.vue",[200,24531,992],{"class":214},[200,24533,24534,24536,24538],{"class":202,"line":398},[200,24535,681],{"class":214},[200,24537,14892],{"class":235},[200,24539,648],{"class":214},[200,24541,24542],{"class":202,"line":405},[200,24543,529],{"emptyLinePlaceholder":20},[200,24545,24546,24548,24550],{"class":202,"line":410},[200,24547,600],{"class":214},[200,24549,21428],{"class":235},[200,24551,648],{"class":214},[200,24553,24554,24556],{"class":202,"line":861},[200,24555,657],{"class":214},[200,24557,24558],{"class":235},"Podcast\n",[200,24560,24561,24563,24565,24567,24570,24573,24576],{"class":202,"line":890},[200,24562,23265],{"class":206},[200,24564,449],{"class":214},[200,24566,611],{"class":214},[200,24568,24569],{"class":210},"podcast",[200,24571,24572],{"class":214}," in ",[200,24574,24575],{"class":210},"podcasts",[200,24577,617],{"class":214},[200,24579,24580,24582,24585,24587,24589,24591,24593,24595],{"class":202,"line":900},[200,24581,23821],{"class":214},[200,24583,24584],{"class":379},"key",[200,24586,449],{"class":214},[200,24588,611],{"class":214},[200,24590,24569],{"class":210},[200,24592,215],{"class":214},[200,24594,3280],{"class":210},[200,24596,617],{"class":214},[200,24598,24599,24601,24603,24605,24607],{"class":202,"line":905},[200,24600,23293],{"class":379},[200,24602,449],{"class":214},[200,24604,611],{"class":214},[200,24606,24569],{"class":210},[200,24608,617],{"class":214},[200,24610,24611,24614,24616,24618,24620,24622,24625,24627,24630,24632,24634,24637,24639,24642,24644,24647,24650,24652,24654],{"class":202,"line":931},[200,24612,24613],{"class":214},"    @",[200,24615,24441],{"class":379},[200,24617,449],{"class":214},[200,24619,611],{"class":214},[200,24621,24441],{"class":223},[200,24623,24624],{"class":214},"({ ",[200,24626,24569],{"class":210},[200,24628,24629],{"class":214}," }).",[200,24631,10929],{"class":223},[200,24633,227],{"class":214},[200,24635,24636],{"class":386},"fav",[200,24638,393],{"class":379},[200,24640,24641],{"class":210}," podcast",[200,24643,215],{"class":214},[200,24645,24646],{"class":210},"favorited",[200,24648,24649],{"class":214}," = ",[200,24651,24636],{"class":210},[200,24653,2089],{"class":214},[200,24655,617],{"class":214},[200,24657,24658],{"class":202,"line":3640},[200,24659,23321],{"class":214},[200,24661,24662,24664,24666],{"class":202,"line":3645},[200,24663,681],{"class":214},[200,24665,21428],{"class":235},[200,24667,648],{"class":214},[35,24669,24670],{},"Another question arises: what does Fusion do behind the scenes to enable this functionality?",[35,24672,24673],{},"Finally, Fusion is said to push Inertia far beyond its current limits. So, another question: how does Fusion leverage Inertia?",[35,24675,24676],{},"Let's address these questions one by one.",[68,24678,24679,24682],{},[71,24680,24681],{},"How does Fusion manage PHP code?",[71,24683,24684],{},"What mechanisms does Fusion employ behind the scenes to enable these capabilities?",[68,24686,24687],{"start":272},[71,24688,24689],{},"In what ways does Fusion utilize Inertia?",[35,24691,24692],{},"Ready for an intense exploration? Let's get started.",[51,24694,24681],{"id":24695},"how-does-fusion-manage-php-code",[35,24697,24698,24699,106,24703],{},"Everything begins with ",[39,24700,24355],{"href":24701,"rel":24702},"https:\u002F\u002Fvite.dev\u002F",[77],[1217,24704,24705],{},"As usual.",[35,24707,24708,24709,24711],{},"For those new to it, ",[6850,24710,24355],{}," is a remarkably fast frontend build tool powering the next generation of web applications. It brought a new tooling philosophy known as on-demand philosophy. Essentially, Vite acts as a proxy between the browser, which requests files, and the file system holding them. When the browser requests a file, Vite intercepts the request, retrieves the file, and sends it back. However, acting as a proxy allows Vite additional capabilities, such as file transformation, code injection, remote file fetching, and generating virtual files (files not present on the file system). It's akin to a pipeline. Undoubtedly, Vite's advent has been transformative for the frontend ecosystem. By adding plugins to its pipeline, it can be tailored to suit any requirement.",[35,24713,24714],{},"With this in mind, we can grasp how a SFC file undergoes processing by Vite.",[192,24716,24718],{"className":21417,"code":24717,"language":12060,"meta":13,"style":13},"\u003Cscript setup>\n\u002F\u002F JavaScript code will go here\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv \u002F>\n  \u003C!-- Your template code will go here -->\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n  \u002F* Your CSS code will go here *\u002F\n\u003C\u002Fstyle>\n",[156,24719,24720,24730,24735,24743,24747,24755,24763,24768,24776,24780,24791,24796],{"__ignoreMap":13},[200,24721,24722,24724,24726,24728],{"class":202,"line":203},[200,24723,600],{"class":214},[200,24725,14892],{"class":235},[200,24727,21781],{"class":379},[200,24729,648],{"class":214},[200,24731,24732],{"class":202,"line":14},[200,24733,24734],{"class":401},"\u002F\u002F JavaScript code will go here\n",[200,24736,24737,24739,24741],{"class":202,"line":255},[200,24738,681],{"class":214},[200,24740,14892],{"class":235},[200,24742,648],{"class":214},[200,24744,24745],{"class":202,"line":272},[200,24746,529],{"emptyLinePlaceholder":20},[200,24748,24749,24751,24753],{"class":202,"line":283},[200,24750,600],{"class":214},[200,24752,21428],{"class":235},[200,24754,648],{"class":214},[200,24756,24757,24759,24761],{"class":202,"line":300},[200,24758,657],{"class":214},[200,24760,11386],{"class":235},[200,24762,21487],{"class":214},[200,24764,24765],{"class":202,"line":310},[200,24766,24767],{"class":401},"  \u003C!-- Your template code will go here -->\n",[200,24769,24770,24772,24774],{"class":202,"line":336},[200,24771,681],{"class":214},[200,24773,21428],{"class":235},[200,24775,648],{"class":214},[200,24777,24778],{"class":202,"line":342},[200,24779,529],{"emptyLinePlaceholder":20},[200,24781,24782,24784,24786,24789],{"class":202,"line":365},[200,24783,600],{"class":214},[200,24785,1171],{"class":235},[200,24787,24788],{"class":379}," scoped",[200,24790,648],{"class":214},[200,24792,24793],{"class":202,"line":371},[200,24794,24795],{"class":401},"  \u002F* Your CSS code will go here *\u002F\n",[200,24797,24798,24800,24802],{"class":202,"line":398},[200,24799,681],{"class":214},[200,24801,1171],{"class":235},[200,24803,648],{"class":214},[35,24805,24806,24807,24809,24810,24813,24814,24817],{},"When a ",[156,24808,11851],{}," file request reaches the browser, a Vite plugin named ",[39,24811,13730],{"href":13728,"rel":24812},[77]," transforms the file, processing the script and template into readable JavaScript code. The CSS gets extracted and sent down the CSS pipeline before being dispatched to the browser. The cool aspect is the possibility to create a plugin capable of scrutinizing the transformation pipeline. Even better, there's no need to build it from scratch; that's precisely what the ",[39,24815,13569],{"href":13567,"rel":24816},[77]," plugin accomplishes.",[35,24819,24820],{},"For instance, a Vue SFC transformation looks like:",[35,24822,24823],{},[1690,24824],{"alt":24825,"src":24826},"Vue SFC transformation seen using the plugin vite-plugin-inspect","\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fvue-sfc-transformation.png",[35,24828,24829,24830,24832,24833,24840,24841,24847],{},"Alright, thanks for the explanation, but what transpires in Fusion? Exactly the same thing but involving the PHP block. When a request contacts the Vite server using a ",[156,24831,11851],{}," file, a plugin named ",[39,24834,24837],{"href":24835,"rel":24836},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L38-L40",[77],[156,24838,24839],{},"fusion-vue"," extracts the PHP block. This process initiates in the ",[39,24842,24845],{"href":24843,"rel":24844},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L112",[77],[156,24846,11770],{}," function below:",[192,24849,24851],{"className":1370,"code":24850,"language":1373,"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,24852,24853,24872,24899,24906,24910,24914,24927,24939,24946,24952,24957,24969],{"__ignoreMap":13},[200,24854,24855,24857,24859,24861,24863,24865,24868,24870],{"class":202,"line":203},[200,24856,14904],{"class":379},[200,24858,11815],{"class":223},[200,24860,227],{"class":214},[200,24862,156],{"class":386},[200,24864,550],{"class":214},[200,24866,24867],{"class":386}," filename",[200,24869,2089],{"class":214},[200,24871,280],{"class":214},[200,24873,24874,24876,24878,24880,24883,24885,24887,24889,24891,24893,24895,24897],{"class":202,"line":14},[200,24875,20075],{"class":206},[200,24877,1095],{"class":235},[200,24879,11837],{"class":214},[200,24881,24882],{"class":210},"filename",[200,24884,215],{"class":214},[200,24886,11844],{"class":223},[200,24888,227],{"class":235},[200,24890,249],{"class":214},[200,24892,11851],{"class":245},[200,24894,249],{"class":214},[200,24896,6466],{"class":235},[200,24898,230],{"class":214},[200,24900,24901,24903],{"class":202,"line":255},[200,24902,3296],{"class":206},[200,24904,24905],{"class":210}," code\n",[200,24907,24908],{"class":202,"line":272},[200,24909,4490],{"class":214},[200,24911,24912],{"class":202,"line":283},[200,24913,529],{"emptyLinePlaceholder":20},[200,24915,24916,24918,24920,24923,24925],{"class":202,"line":300},[200,24917,3743],{"class":206},[200,24919,12997],{"class":214},[200,24921,24922],{"class":223}," Transformer",[200,24924,227],{"class":235},[200,24926,230],{"class":214},[200,24928,24929,24932,24934,24937],{"class":202,"line":310},[200,24930,24931],{"class":235},"    config",[200,24933,239],{"class":214},[200,24935,24936],{"class":210}," fusionConfig",[200,24938,252],{"class":214},[200,24940,24941,24944],{"class":202,"line":336},[200,24942,24943],{"class":210},"    code",[200,24945,252],{"class":214},[200,24947,24948,24950],{"class":202,"line":342},[200,24949,5104],{"class":210},[200,24951,252],{"class":214},[200,24953,24954],{"class":202,"line":365},[200,24955,24956],{"class":210},"    isProd\n",[200,24958,24959,24961,24963,24965,24967],{"class":202,"line":371},[200,24960,5080],{"class":214},[200,24962,2089],{"class":235},[200,24964,215],{"class":214},[200,24966,11770],{"class":223},[200,24968,466],{"class":235},[200,24970,24971],{"class":202,"line":398},[200,24972,1130],{"class":214},[35,24974,419,24975,24977,24978,24981,24982,24985,24986,24988],{},[156,24976,11770],{}," function forms the crux of the Vite pipeline. It transforms files before they reach the browser. In Fusion's case, it passes the file (the entire content) to a class named ",[156,24979,24980],{},"Transformer",", which manages the file's transformation. You might notice the condition ",[156,24983,24984],{},"filename.endsWith('.vue')",", signifying that the transformation only applies to ",[156,24987,11851],{}," files. This approach is a prevalent pattern among Vite plugins.",[35,24990,24991],{},"The Transformer class is responsible for:",[68,24993,24994],{},[71,24995,24996,25001,25002,25007,25008,25010,25011,25016,25017,25026],{},[39,24997,25000],{"href":24998,"rel":24999},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L16-L19",[77],"Extracting the PHP block"," from the Vue SFC. Interestingly, it employs a parser from ",[39,25003,25006],{"href":25004,"rel":25005},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Ftree\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fcompiler-sfc",[77],"@vue\u002Fcompiler-sfc"," to achieve this task. Notably, the ",[156,25009,13730],{}," plugin uses this same parser, illustrating that adding a custom block to a Vue SFC is straightforward. I've observed this practice before; the ",[39,25012,25015],{"href":25013,"rel":25014},"https:\u002F\u002Fvue-i18n.intlify.dev\u002Fguide\u002Fintroduction.html",[77],"Vue I18n"," project supports ",[39,25018,25021,25022,25025],{"href":25019,"rel":25020},"https:\u002F\u002Fvue-i18n.intlify.dev\u002Fguide\u002Fadvanced\u002Fsfc.html",[77],"an ",[156,25023,25024],{},"i18n"," custom block"," specifying translations within a Vue SFC.",[192,25028,25032],{"className":25029,"code":25030,"language":25031,"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,25033,25034,25053,25057,25087],{"__ignoreMap":13},[200,25035,25036,25038,25040,25043,25045,25047,25049,25051],{"class":202,"line":203},[200,25037,973],{"class":206},[200,25039,318],{"class":214},[200,25041,25042],{"class":210}," parse",[200,25044,981],{"class":214},[200,25046,984],{"class":206},[200,25048,242],{"class":214},[200,25050,25006],{"class":245},[200,25052,992],{"class":214},[200,25054,25055],{"class":202,"line":14},[200,25056,529],{"emptyLinePlaceholder":20},[200,25058,25059,25061,25064,25066,25068,25071,25073,25075,25078,25080,25082,25084],{"class":202,"line":255},[200,25060,443],{"class":379},[200,25062,25063],{"class":210}," descriptor ",[200,25065,449],{"class":214},[200,25067,25042],{"class":223},[200,25069,25070],{"class":210},"(code",[200,25072,550],{"class":214},[200,25074,318],{"class":214},[200,25076,25077],{"class":210}," filename ",[200,25079,413],{"class":214},[200,25081,2089],{"class":210},[200,25083,215],{"class":214},[200,25085,25086],{"class":210},"descriptor\n",[200,25088,25089,25091,25094,25096,25099,25101,25104,25106,25108,25110,25113,25115,25118,25120,25123,25125,25127,25129,25131],{"class":202,"line":272},[200,25090,443],{"class":379},[200,25092,25093],{"class":210}," php ",[200,25095,449],{"class":214},[200,25097,25098],{"class":210}," descriptor",[200,25100,215],{"class":214},[200,25102,25103],{"class":210},"customBlocks",[200,25105,215],{"class":214},[200,25107,3266],{"class":223},[200,25109,227],{"class":210},[200,25111,25112],{"class":386},"block",[200,25114,393],{"class":379},[200,25116,25117],{"class":210}," block",[200,25119,215],{"class":214},[200,25121,25122],{"class":210},"type ",[200,25124,6546],{"class":214},[200,25126,242],{"class":214},[200,25128,19540],{"class":245},[200,25130,249],{"class":214},[200,25132,416],{"class":210},[35,25134,25135],{},[1690,25136],{"alt":25137,"src":25138},"The Vue SFC transformed within the Vite pipeline","\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fvue-sfc-with-php-transformation.png",[68,25140,25141,25149,25161],{"start":14},[71,25142,25143,25148],{},[39,25144,25147],{"href":25145,"rel":25146},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L88-L91",[77],"Removing the PHP block"," from the Vue SFC.",[71,25150,25151,25156,25157,215],{},[39,25152,25155],{"href":25153,"rel":25154},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L96",[77],"Determining if the PHP code warrants persistence",". Another critical aspect we'll delve into in the ",[39,25158,25160],{"href":25159},"#what-does-fusion-do-behind-the-scenes-to-make-this-possible","following section",[71,25162,25163,25168],{},[39,25164,25167],{"href":25165,"rel":25166},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L107",[77],"Validating and saving the PHP code",". This verification occurs through an external process executing a Laravel command.",[192,25170,25172],{"className":1370,"code":25171,"language":1373,"meta":13,"style":13},"await runPhp(['fusion:conform', this.relativeFilename])\n",[156,25173,25174],{"__ignoreMap":13},[200,25175,25176,25178,25181,25184,25186,25189,25191,25193,25196],{"class":202,"line":203},[200,25177,207],{"class":206},[200,25179,25180],{"class":223}," runPhp",[200,25182,25183],{"class":210},"([",[200,25185,249],{"class":214},[200,25187,25188],{"class":245},"fusion:conform",[200,25190,249],{"class":214},[200,25192,550],{"class":214},[200,25194,25195],{"class":214}," this.",[200,25197,25198],{"class":210},"relativeFilename])\n",[35,25200,25201,25202,25207],{},"Any PHP code errors trigger an error message ",[39,25203,25206],{"href":25204,"rel":25205},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L124",[77],"interpreted by Vite",", so accurately that it displays on the browser to indicate an error in the PHP code block.",[35,25209,25210],{},[1690,25211],{"alt":25212,"src":25213},"The Vite error overlay showing the error in the PHP code","\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fvite-error-overlay.png",[35,25215,25216,25217,25222,25223,25226,25227,215],{},"If functionality runs smoothly, the ",[39,25218,25221],{"href":25219,"rel":25220},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L68",[77],"PHP code, stored as an extracted file",", gets saved in the ",[156,25224,25225],{},"storage"," folder. This aspect is noteworthy, as we will explore further in ",[39,25228,25230],{"href":25229},"#how-does-fusion-handle-the-php-code","the following section",[68,25232,25233],{"start":283},[71,25234,25235,25236,25241,25242,25247],{},"Creating a JavaScript file ",[39,25237,25240],{"href":25238,"rel":25239},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L112",[77],"based on the PHP code"," that bridges ",[39,25243,25246],{"href":25244,"rel":25245},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FShim.php#L78-L113",[77],"any gaps between the frontend's needs"," and the PHP block's code. This interesting step demands further exploration.",[35,25249,25250],{},"Again, a Laravel command is invoked during this step:",[192,25252,25254],{"className":1370,"code":25253,"language":1373,"meta":13,"style":13},"await runPhp(['fusion:shim', this.relativeFilename])\n",[156,25255,25256],{"__ignoreMap":13},[200,25257,25258,25260,25262,25264,25266,25269,25271,25273,25275],{"class":202,"line":203},[200,25259,207],{"class":206},[200,25261,25180],{"class":223},[200,25263,25183],{"class":210},[200,25265,249],{"class":214},[200,25267,25268],{"class":245},"fusion:shim",[200,25270,249],{"class":214},[200,25272,550],{"class":214},[200,25274,25195],{"class":214},[200,25276,25198],{"class":210},[68,25278,25279],{"start":300},[71,25280,25281],{},"Finally, returns the transformed code to Vite, allowing the SFC to proceed on its path.",[35,25283,25284],{},"Consider this for a moment. Allow me to illustrate something:",[35,25286,25287],{},[1690,25288],{"alt":25289,"src":25290},"Schema showing all the steps we've seen so far","\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Ffusion-schema-vite-transform.png",[35,25292,25293],{},"It can operate in this manner. In Fusion, Vue SFC files function as a file-based router akin to Nuxt's mechanism. The first request typically reaches the PHP server, which responds with the initial client-side code, which then targets the Vite server for additional resource requests. However, if a Vue SFC route remains unvisited, the corresponding PHP functionality won't be ready to handle it, resulting in a 404 error from the Laravel server. Consequently, the Vite server remains untouched, impeding the transformation process. This poses a hurdle, a huge one.",[35,25295,25296],{},"To fully comprehend how this issue is tackled, we must examine all aspects of the Vite plugin.",[35,25298,25299],{},"Within the Vite configuration, we find two noteworthy features:",[68,25301,25302,25322],{},[71,25303,419,25304,25309,25310,25315,25316,25321],{},[39,25305,25308],{"href":25306,"rel":25307},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L42-L65",[77],"Vite configuration"," where Vite recognizes the location to find the generated JavaScript file (the shim), which receives ",[39,25311,25314],{"href":25312,"rel":25313},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L61",[77],"an alias"," assigned in the Vite resolver. This alias obscures the file's location, encouraging best practices like avoiding hardcoded pathnames and rendering code location agnostic to path changes. This Vite-related configuration derives from a PHP config file to eliminate redundant configurations, so the ",[39,25317,25320],{"href":25318,"rel":25319},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L45",[77],"Fusion Vite plugin spawns a process"," executing a Laravel command to resolve config necessities.",[71,25323,25324,25325,25330,25331,215],{},"A watcher ",[39,25326,25329],{"href":25327,"rel":25328},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L79",[77],"added to monitor project-wide changes"," utilizing the Vite watcher, a ",[39,25332,25335],{"href":25333,"rel":25334},"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],"Chokidar instance",[192,25337,25339],{"className":1370,"code":25338,"language":1373,"meta":13,"style":13},"function configureServer(server) {\n  server.watcher.on('all', (event, filename) => {\n    \u002F\u002F Do whatever you want\n  })\n}\n",[156,25340,25341,25356,25397,25402,25408],{"__ignoreMap":13},[200,25342,25343,25345,25348,25350,25352,25354],{"class":202,"line":203},[200,25344,14904],{"class":379},[200,25346,25347],{"class":223}," configureServer",[200,25349,227],{"class":214},[200,25351,3108],{"class":386},[200,25353,2089],{"class":214},[200,25355,280],{"class":214},[200,25357,25358,25361,25363,25366,25368,25371,25373,25375,25378,25380,25382,25384,25387,25389,25391,25393,25395],{"class":202,"line":14},[200,25359,25360],{"class":210},"  server",[200,25362,215],{"class":214},[200,25364,25365],{"class":210},"watcher",[200,25367,215],{"class":214},[200,25369,25370],{"class":223},"on",[200,25372,227],{"class":235},[200,25374,249],{"class":214},[200,25376,25377],{"class":245},"all",[200,25379,249],{"class":214},[200,25381,550],{"class":214},[200,25383,1095],{"class":214},[200,25385,25386],{"class":386},"event",[200,25388,550],{"class":214},[200,25390,24867],{"class":386},[200,25392,2089],{"class":214},[200,25394,393],{"class":379},[200,25396,280],{"class":214},[200,25398,25399],{"class":202,"line":255},[200,25400,25401],{"class":401},"    \u002F\u002F Do whatever you want\n",[200,25403,25404,25406],{"class":202,"line":272},[200,25405,5080],{"class":214},[200,25407,416],{"class":235},[200,25409,25410],{"class":202,"line":283},[200,25411,1130],{"class":214},[35,25413,25414],{},"This watcher supports two functions:",[68,25416,25417,25425],{},[71,25418,25419,25424],{},[39,25420,25423],{"href":25421,"rel":25422},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L88",[77],"Cleaning up"," the database and storage folder upon file or folder deletion. The storage folder contains transformed Vue SFC files, serving as the file-based router. Without removal, the user can still access deleted files.",[71,25426,25427,25432,25433,25435],{},[39,25428,25431],{"href":25429,"rel":25430},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L97-L99",[77],"Triggering file transformation",". This operates identically to the ",[156,25434,11770],{}," function previously encountered, requiring manual file loading. Only Vue SFC files undergo transformation.",[192,25437,25439],{"className":1370,"code":25438,"language":1373,"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,25440,25441,25455,25493,25517,25548,25552,25565,25576,25586,25593,25598,25610,25614,25620],{"__ignoreMap":13},[200,25442,25443,25445,25447,25449,25451,25453],{"class":202,"line":203},[200,25444,14904],{"class":379},[200,25446,25347],{"class":223},[200,25448,227],{"class":214},[200,25450,3108],{"class":386},[200,25452,2089],{"class":214},[200,25454,280],{"class":214},[200,25456,25457,25459,25461,25463,25465,25467,25469,25471,25473,25475,25477,25479,25481,25483,25485,25487,25489,25491],{"class":202,"line":14},[200,25458,25360],{"class":210},[200,25460,215],{"class":214},[200,25462,25365],{"class":210},[200,25464,215],{"class":214},[200,25466,25370],{"class":223},[200,25468,227],{"class":235},[200,25470,249],{"class":214},[200,25472,25377],{"class":245},[200,25474,249],{"class":214},[200,25476,550],{"class":214},[200,25478,380],{"class":379},[200,25480,1095],{"class":214},[200,25482,25386],{"class":386},[200,25484,550],{"class":214},[200,25486,24867],{"class":386},[200,25488,2089],{"class":214},[200,25490,393],{"class":379},[200,25492,280],{"class":214},[200,25494,25495,25497,25499,25501,25503,25505,25507,25509,25511,25513,25515],{"class":202,"line":255},[200,25496,5252],{"class":206},[200,25498,1095],{"class":235},[200,25500,24882],{"class":210},[200,25502,215],{"class":214},[200,25504,11844],{"class":223},[200,25506,227],{"class":235},[200,25508,249],{"class":214},[200,25510,11851],{"class":245},[200,25512,249],{"class":214},[200,25514,6466],{"class":235},[200,25516,230],{"class":214},[200,25518,25519,25521,25524,25526,25529,25531,25534,25536,25538,25540,25542,25544,25546],{"class":202,"line":272},[200,25520,20113],{"class":379},[200,25522,25523],{"class":210}," fileContent",[200,25525,510],{"class":214},[200,25527,25528],{"class":210}," fs",[200,25530,215],{"class":214},[200,25532,25533],{"class":223},"readFileSync",[200,25535,227],{"class":235},[200,25537,24882],{"class":210},[200,25539,550],{"class":214},[200,25541,242],{"class":214},[200,25543,14746],{"class":245},[200,25545,249],{"class":214},[200,25547,416],{"class":235},[200,25549,25550],{"class":202,"line":283},[200,25551,529],{"emptyLinePlaceholder":20},[200,25553,25554,25557,25559,25561,25563],{"class":202,"line":300},[200,25555,25556],{"class":206},"      await",[200,25558,12997],{"class":214},[200,25560,24922],{"class":223},[200,25562,227],{"class":235},[200,25564,230],{"class":214},[200,25566,25567,25570,25572,25574],{"class":202,"line":310},[200,25568,25569],{"class":235},"        config",[200,25571,239],{"class":214},[200,25573,24936],{"class":210},[200,25575,252],{"class":214},[200,25577,25578,25580,25582,25584],{"class":202,"line":336},[200,25579,11884],{"class":235},[200,25581,239],{"class":214},[200,25583,25523],{"class":210},[200,25585,252],{"class":214},[200,25587,25588,25591],{"class":202,"line":342},[200,25589,25590],{"class":210},"        filename",[200,25592,252],{"class":214},[200,25594,25595],{"class":202,"line":365},[200,25596,25597],{"class":210},"        isProd\n",[200,25599,25600,25602,25604,25606,25608],{"class":202,"line":371},[200,25601,2632],{"class":214},[200,25603,2089],{"class":235},[200,25605,215],{"class":214},[200,25607,11770],{"class":223},[200,25609,466],{"class":235},[200,25611,25612],{"class":202,"line":398},[200,25613,4962],{"class":214},[200,25615,25616,25618],{"class":202,"line":405},[200,25617,5080],{"class":214},[200,25619,416],{"class":235},[200,25621,25622],{"class":202,"line":410},[200,25623,1130],{"class":214},[35,25625,25626,25627,25629],{},"But why transform files upon creation or modification? This approach appears to contradict the on-demand philosophy and previously defined ",[156,25628,11770],{}," function. Yet, a rationale exists.",[35,25631,25632,25633,25636],{},"Consider an error in the PHP block, such as an overlook in importing a function or class, or attempting to hit a route for the first time. When reloading the page, the PHP server returns an error message, as Laravel does by default. Yet, correcting the Vue SFC's PHP block won't rectify the PHP server error since the Vite transformation won't trigger anew. Consequently, repeatedly hitting ",[156,25634,25635],{},"cmd+r"," serves no purpose.",[35,25638,25639,25640,25642],{},"For this reason, the file system must be monitored for Vue SFC changes, triggering a manual transformation. While suboptimal, this method is necessary. Aaron devised a strategy prioritizing ",[156,25641,11770],{}," method transformations over watcher-triggered ones, with throttling applied to the watcher.",[35,25644,25645],{},"This is also to solve the problem we've seen earlier: when the PHP server is hit first, the generated PHP file isn't created yet.",[35,25647,25648],{},"Let's summarize our learning thus far with this schematic:",[35,25650,25651],{},[1690,25652],{"alt":25653,"src":25654},"A schema showing all the steps that makes Fusion work.","\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Ffusion-schema.png",[35,25656,25657],{},"Though we haven't completely covered every component, this diagram aids our understanding of the article's remainder.",[939,25659,25660],{},[35,25661,25662,25664],{},[200,25663,945],{},"\nEnsure the Vite server operates correctly for everything to function seamlessly.",[35,25666,25667],{},"This exploration deeply engaged us in Fusion's Vite plugin. Let's now move onward to grasp how Fusion organizes its PHP code handling.",[939,25669,25670],{},[35,25671,25672,25674,25675,25679],{},[200,25673,945],{},"\nHave questions? ",[39,25676,25678],{"href":25677},"\u002Fama","Ask me anything"," or leave a comment below. I truly enjoy reading your feedback!",[51,25681,25683],{"id":25682},"what-does-fusion-do-behind-the-scenes-to-make-this-possible","What does Fusion do behind the scenes to make this possible?",[35,25685,25686],{},"Let's delve into the PHP-side operations.",[35,25688,25689],{},"We've seen the PHP code extracted, validated, and saved on the disk. We now need to unravel two pivotal steps to better comprehend Fusion's operation:",[68,25691,25692,25695],{},[71,25693,25694],{},"Determining if the PHP code should be persisted.",[71,25696,25697],{},"Saving the PHP code to the file system.",[35,25699,25700],{},"For ease of understanding, let's tackle the second step first. The first step will subsequently become clear.",[1913,25702,25704],{"id":25703},"writing-the-php-code-on-the-file-system","Writing the PHP code on the file system",[35,25706,25707],{},"Once extracted from the Vue SFC, the PHP code requires persistence on the file system. This process is crucial since the PHP written within the Vue SFC becomes an integral part of the Laravel application, not merely an independent PHP script. This generated file is essential for the Laravel application to respond to incoming requests.",[35,25709,25710,25711,25716,25717,25722],{},"After extraction, the PHP code is placed in ",[39,25712,25715],{"href":25713,"rel":25714},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L97",[77],"a temporary file",". This temporary file maintains the exact code content inherent to the Vue SFC. Subsequently, Fusion creates an entry in a ",[39,25718,25721],{"href":25719,"rel":25720},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FDatabase.js#L3",[77],"dedicated SQLite database",". We'll revisit this database later, understanding it facilitates communication between the PHP and JavaScript processes.",[35,25724,25725,25726,215],{},"Finally, the Vite plugin instigates the ",[39,25727,25729,25731],{"href":25165,"rel":25728},[77],[156,25730,25188],{}," command",[1913,25733,419,25735,25731],{"id":25734},"the-fusionconform-command",[156,25736,25188],{},[35,25738,25739,25740,25742],{},"We've delayed understanding this command; now's the moment to clarify. Within the ",[156,25741,25188],{}," command, a Laravel command written in PHP, neither Vite nor JavaScript applies, strictly PHP.",[35,25744,25745,25746,25748,25749,215],{},"Upon invocation, this command utilizes a ",[156,25747,11165],{}," argument representing the Vue SFC file's path. Acting like a unique identifier allows Fusion to ",[39,25750,25753],{"href":25751,"rel":25752},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L38",[77],"retrieve the pertinent database entry",[35,25755,25756,25757,25762],{},"This entry contains the destination path for the final PHP file to be used by the Laravel application. Currently, this file doesn't exist; only a temporary file holds the code. Thus, the next step involves reading ",[39,25758,25761],{"href":25759,"rel":25760},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L44-L47",[77],"the temporary file content"," prior to further processing.",[35,25764,25765,25766,25771],{},"The subsequent process enlists a ",[39,25767,25770],{"href":25768,"rel":25769},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L56",[77],"conformer class",", assigned to:",[68,25773,25774,25777],{},[71,25775,25776],{},"Altering the code to confer compatibility with the Laravel application.",[71,25778,25779],{},"Ensuring code validity by passing it through a PHP parser.",[35,25781,25782,25783,215],{},"The function examines PHP code's conformity by running its transformation through a sequence of ",[39,25784,25787],{"href":25785,"rel":25786},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConformity\u002FConformer.php#L65C24-L79",[77],"transformer classes",[35,25789,25790,25791,25793,25794,25797,25798,25801,25802,215],{},"Each transformer class extends a ",[156,25792,24980],{}," super-class, custom-built to expand ",[156,25795,25796],{},"NodeVisitorAbstract"," and deploys the ",[156,25799,25800],{},"TransformerInterface"," sourced from ",[39,25803,25806],{"href":25804,"rel":25805},"https:\u002F\u002Fgithub.com\u002Fnikic\u002FPHP-Parser",[77],"PHP Parser",[192,25808,25811],{"className":19537,"code":25809,"filename":25810,"language":19540,"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,25812,25813,25817,25821,25826,25830,25835,25839,25844,25849,25854],{"__ignoreMap":13},[200,25814,25815],{"class":202,"line":203},[200,25816,19577],{},[200,25818,25819],{"class":202,"line":14},[200,25820,529],{"emptyLinePlaceholder":20},[200,25822,25823],{"class":202,"line":255},[200,25824,25825],{},"namespace Fusion\\Conformity\\Transformers;\n",[200,25827,25828],{"class":202,"line":272},[200,25829,529],{"emptyLinePlaceholder":20},[200,25831,25832],{"class":202,"line":283},[200,25833,25834],{},"abstract class Transformer extends NodeVisitorAbstract implements TransformerInterface\n",[200,25836,25837],{"class":202,"line":300},[200,25838,230],{},[200,25840,25841],{"class":202,"line":310},[200,25842,25843],{},"    public function __construct(\n",[200,25845,25846],{"class":202,"line":336},[200,25847,25848],{},"        protected ?string $filename = null\n",[200,25850,25851],{"class":202,"line":342},[200,25852,25853],{},"    ) {}\n",[200,25855,25856],{"class":202,"line":365},[200,25857,1130],{},[35,25859,25860,25861,25864],{},"Each custom transformer, furthermore, implements the ",[156,25862,25863],{},"enterNode"," method to shift the code transformation.",[192,25866,25869],{"className":19537,"code":25867,"filename":25868,"language":19540,"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 Perform some operation\n        }\n    }\n}\n","PropTransformer.php",[156,25870,25871,25875,25879,25883,25887,25892,25896,25901,25905,25910,25915,25919,25923],{"__ignoreMap":13},[200,25872,25873],{"class":202,"line":203},[200,25874,19577],{},[200,25876,25877],{"class":202,"line":14},[200,25878,529],{"emptyLinePlaceholder":20},[200,25880,25881],{"class":202,"line":255},[200,25882,25825],{},[200,25884,25885],{"class":202,"line":272},[200,25886,529],{"emptyLinePlaceholder":20},[200,25888,25889],{"class":202,"line":283},[200,25890,25891],{},"class PropsTransformer extends Transformer\n",[200,25893,25894],{"class":202,"line":300},[200,25895,230],{},[200,25897,25898],{"class":202,"line":310},[200,25899,25900],{},"    public function enterNode(Node $node)\n",[200,25902,25903],{"class":202,"line":336},[200,25904,19638],{},[200,25906,25907],{"class":202,"line":342},[200,25908,25909],{},"        if ($node instanceof MethodCall) {\n",[200,25911,25912],{"class":202,"line":365},[200,25913,25914],{},"            \u002F\u002F Perform some operation\n",[200,25916,25917],{"class":202,"line":371},[200,25918,13459],{},[200,25920,25921],{"class":202,"line":398},[200,25922,4962],{},[200,25924,25925],{"class":202,"line":405},[200,25926,1130],{},[939,25928,25929],{},[35,25930,25931,25933],{},[200,25932,945],{},"\nDetailed exploration into transformers or PHP parsers falls outside this article's scope as they offer advanced functionality not immediately necessary to comprehend Fusion's operation.",[35,25935,25936],{},"In total, ten transformers are involved, each one with a specific responsibility. Conceive them as steps within a pipeline, a conventional pattern in programming designed to disassemble intricate tasks into smaller, approachable segments. Consequently, when the user code undergoes transformation and is rendered compatible with the Laravel app, the output from one transformer becomes the next's input.",[192,25938,25940],{"className":4277,"code":25939,"language":4279,"meta":13,"style":13},"PHP code from the Vue SFC\n    | |\n[Procedural Transformer]: Turns procedural code into an anonymous class method entitled runProceduralCode.\n[Props Transformer]: Changes prop() function calls into this object's method calls within the class.\n[Function Import Transformer]: Strips unnecessary function imports, specifically pre-known ones.\n[Prop Value Transformer]: Stipulates that $this->prop() method call chains terminate with ->value().\n[Expose Transformer]: Alters expose() function calls into this object method calls, barring variable assignments.\n[Mount Transformer]: Validates and modifies mount() function calls, ensuring single anonymous function arguments.\n[Prop Discovery Transformer]: Uncovers and documents prop() function property names.\n[Action Discovery Transformer]: Discovers and constructs methods for action handlers exposed via expose().\n[Anonymous Return Transformer]: Guarantees direct expression returns for anonymous class returns.\n[Anonymous Class Transformer]: Converts anonymous classes into named classes, within a generated namespace, gathering necessary requirements.\n    | |\nUsable PHP code\n",[156,25941,25942,25947,25952,25957,25962,25967,25972,25977,25982,25987,25992,25997,26002,26006],{"__ignoreMap":13},[200,25943,25944],{"class":202,"line":203},[200,25945,25946],{},"PHP code from the Vue SFC\n",[200,25948,25949],{"class":202,"line":14},[200,25950,25951],{},"    | |\n",[200,25953,25954],{"class":202,"line":255},[200,25955,25956],{},"[Procedural Transformer]: Turns procedural code into an anonymous class method entitled runProceduralCode.\n",[200,25958,25959],{"class":202,"line":272},[200,25960,25961],{},"[Props Transformer]: Changes prop() function calls into this object's method calls within the class.\n",[200,25963,25964],{"class":202,"line":283},[200,25965,25966],{},"[Function Import Transformer]: Strips unnecessary function imports, specifically pre-known ones.\n",[200,25968,25969],{"class":202,"line":300},[200,25970,25971],{},"[Prop Value Transformer]: Stipulates that $this->prop() method call chains terminate with ->value().\n",[200,25973,25974],{"class":202,"line":310},[200,25975,25976],{},"[Expose Transformer]: Alters expose() function calls into this object method calls, barring variable assignments.\n",[200,25978,25979],{"class":202,"line":336},[200,25980,25981],{},"[Mount Transformer]: Validates and modifies mount() function calls, ensuring single anonymous function arguments.\n",[200,25983,25984],{"class":202,"line":342},[200,25985,25986],{},"[Prop Discovery Transformer]: Uncovers and documents prop() function property names.\n",[200,25988,25989],{"class":202,"line":365},[200,25990,25991],{},"[Action Discovery Transformer]: Discovers and constructs methods for action handlers exposed via expose().\n",[200,25993,25994],{"class":202,"line":371},[200,25995,25996],{},"[Anonymous Return Transformer]: Guarantees direct expression returns for anonymous class returns.\n",[200,25998,25999],{"class":202,"line":398},[200,26000,26001],{},"[Anonymous Class Transformer]: Converts anonymous classes into named classes, within a generated namespace, gathering necessary requirements.\n",[200,26003,26004],{"class":202,"line":405},[200,26005,25951],{},[200,26007,26008],{"class":202,"line":410},[200,26009,26010],{},"Usable PHP code\n",[35,26012,26013],{},"Consider a concrete example before continuing. Assume the following PHP code exists within the Vue SFC:",[192,26015,26017],{"className":21417,"code":26016,"language":12060,"meta":13,"style":13},"\u003Cphp>\nuse function \\Fusion\\{prop};\n\n$name = prop('bienvenue');\n\n$name = strtoupper($name);\n\u003C\u002Fphp>\n",[156,26018,26019,26027,26032,26036,26041,26045,26050],{"__ignoreMap":13},[200,26020,26021,26023,26025],{"class":202,"line":203},[200,26022,600],{"class":214},[200,26024,19540],{"class":235},[200,26026,648],{"class":214},[200,26028,26029],{"class":202,"line":14},[200,26030,26031],{"class":210},"use function \\Fusion\\{prop};\n",[200,26033,26034],{"class":202,"line":255},[200,26035,529],{"emptyLinePlaceholder":20},[200,26037,26038],{"class":202,"line":272},[200,26039,26040],{"class":210},"$name = prop('bienvenue');\n",[200,26042,26043],{"class":202,"line":283},[200,26044,529],{"emptyLinePlaceholder":20},[200,26046,26047],{"class":202,"line":300},[200,26048,26049],{"class":210},"$name = strtoupper($name);\n",[200,26051,26052,26054,26056],{"class":202,"line":310},[200,26053,681],{"class":214},[200,26055,19540],{"class":235},[200,26057,648],{"class":214},[35,26059,26060],{},"After passing through the transformation, this results:",[192,26062,26064],{"className":19537,"code":26063,"language":19540,"meta":13,"style":13},"\u003C?php\n\n\u002F**\n * File auto-generated by Fusion.\n * Alterations aren't advisable.\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,26065,26066,26070,26074,26079,26084,26089,26094,26099,26103,26108,26112,26117,26122,26127,26132,26136,26141,26146,26151,26155],{"__ignoreMap":13},[200,26067,26068],{"class":202,"line":203},[200,26069,19577],{},[200,26071,26072],{"class":202,"line":14},[200,26073,529],{"emptyLinePlaceholder":20},[200,26075,26076],{"class":202,"line":255},[200,26077,26078],{},"\u002F**\n",[200,26080,26081],{"class":202,"line":272},[200,26082,26083],{}," * File auto-generated by Fusion.\n",[200,26085,26086],{"class":202,"line":283},[200,26087,26088],{}," * Alterations aren't advisable.\n",[200,26090,26091],{"class":202,"line":300},[200,26092,26093],{}," *\u002F\n",[200,26095,26096],{"class":202,"line":310},[200,26097,26098],{},"namespace Fusion\\Generated\\Pages;\n",[200,26100,26101],{"class":202,"line":336},[200,26102,529],{"emptyLinePlaceholder":20},[200,26104,26105],{"class":202,"line":342},[200,26106,26107],{},"class IndexGenerated extends \\Fusion\\FusionPage\n",[200,26109,26110],{"class":202,"line":365},[200,26111,230],{},[200,26113,26114],{"class":202,"line":371},[200,26115,26116],{},"    #[\\Fusion\\Attributes\\ServerOnly]\n",[200,26118,26119],{"class":202,"line":398},[200,26120,26121],{},"    public array $discoveredProps = ['name'];\n",[200,26123,26124],{"class":202,"line":405},[200,26125,26126],{},"    use \\Fusion\\Concerns\\IsProceduralPage;\n",[200,26128,26129],{"class":202,"line":410},[200,26130,26131],{},"    public function runProceduralCode()\n",[200,26133,26134],{"class":202,"line":861},[200,26135,19638],{},[200,26137,26138],{"class":202,"line":890},[200,26139,26140],{},"        $name = $this->prop(name: 'name', default: 'bienvenue')->value();\n",[200,26142,26143],{"class":202,"line":900},[200,26144,26145],{},"        $name = strtoupper($name);\n",[200,26147,26148],{"class":202,"line":905},[200,26149,26150],{},"        $this->syncProps(get_defined_vars());\n",[200,26152,26153],{"class":202,"line":931},[200,26154,4962],{},[200,26156,26157],{"class":202,"line":3640},[200,26158,1130],{},[35,26160,26161,26162,26165,26166,215],{},"This class ",[156,26163,26164],{},"IndexGenerated"," will be used by the Laravel application upon handling requests at the '\u002F' route. Visualize it as a mini-controller containing user code. Behind the scenes, much of the complexity is abstracted within the ",[39,26167,26170,26173],{"href":26168,"rel":26169},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L22",[77],[156,26171,26172],{},"FusionPage"," class",[35,26175,419,26176,26179,26180,26183],{},[156,26177,26178],{},"discoveredProps"," array records property names processed during ",[6850,26181,26182],{},"prop()"," function calls, ensuring synchronization between frontend and backend properties.",[35,26185,26186,26187,26190],{},"The method ",[156,26188,26189],{},"runProceduralCode",", invoked upon page loading, executes user code alongside Fusion-generated code, ensuring prop synchronization between the frontend and backend.",[35,26192,419,26193,26200,26201,26203],{},[39,26194,26197],{"href":26195,"rel":26196},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConcerns\u002FIsProceduralPage.php#L36",[77],[156,26198,26199],{},"syncProps"," method maintains backend-to-frontend property synchronization. It retrieves defined variables within the method, and discoveredProps array content, ensuring the generated ",[156,26202,23830],{}," property solely holds user-requested data.",[939,26205,26206],{},[35,26207,26208,26210,26211,26214,26215,215],{},[200,26209,945],{},"\nUsing ",[156,26212,26213],{},"get_defined_vars",", a built-in PHP function, returns a current scope-representative variable array. In this case, it constitutes ",[156,26216,26217],{},"['name' => 'bienvenue']",[939,26219,26220],{},[35,26221,26222,26224,26227,26228,26233,26234,215],{},[200,26223,945],{},[156,26225,26226],{},"#[ServerOnly]"," classifies properties for server-side application exclusively. Analogous ",[39,26229,26232],{"href":26230,"rel":26231},"https:\u002F\u002Fwww.typescriptlang.org\u002Fdocs\u002Fhandbook\u002Fdecorators.html",[77],"TypeScript"," decorators function similarly under ",[39,26235,26238],{"href":26236,"rel":26237},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Freflect-metadata",[77],"reflect-metadata",[35,26240,26241,26242,26245,26246,26251,26252,26256],{},"Finally, Fusion places transformed content in its database-provided file path, updating associated entries with the class's legislative name comprising namespace and class designation. The procedure remains enclosed in a ",[156,26243,26244],{},"try\u002Fcatch"," block, ready to address any transformational errors arising. Upon encountering errors, ",[39,26247,26250],{"href":26248,"rel":26249},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L131",[77],"a transient file gets deleted"," while ",[39,26253,26255],{"href":25204,"rel":26254},[77],"an error message directs to Vite",", as noted earlier.",[35,26258,26259],{},"At this stage, our file system now appears thus:",[26261,26262,26266,26273,26277,26280,26283,26377,26389,26397,26404,26407,26410,26413,26425,26478,26485,26493,26542,26552,26556,26559,26562,26591,26618,26645,26665,26668,26685,26699,26702,26720,26727,26979,26984,27297,27320,27326,27495,27498,27501,27563,27566,27771,27803,27814,27824,27833,28215,28235,28238,28278,28284,28303,28306,28395,28404,28437,28440,28607,28620,28630,28633,28642,28648,28660,28664,28678,28681,28683,28686,28693,28696,28699,28706,28709,28713,28724,28727,28730,28737,28752,29110,29120,29127,29136,29145,29153,29167,29170],"tree",{"name":26263,":items":26264,":default-expanded":26265},"A Fusion Application","[\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,26267,26268,26269,215],{},"Next, the Vite plugin resumes operation, as detailed in the ",[39,26270,26272],{"href":26271},"#handling-incoming-requests","next section",[1913,26274,26276],{"id":26275},"the-database","The database",[35,26278,26279],{},"Since discussing this article, we've referred to a database but withheld specifics until now. It's time to understand its purpose!",[35,26281,26282],{},"The database assists in bridging communication between the PHP and JavaScript processes, storing essential information for this purpose. With a glance at the database, we can discern stored data and its utilization.",[26284,26285,26286,26309],"table",{},[26287,26288,26289],"thead",{},[26290,26291,26292,26295,26297,26300,26303,26306],"tr",{},[26293,26294,3280],"th",{},[26293,26296,11165],{},[26293,26298,26299],{},"php_class",[26293,26301,26302],{},"php_path",[26293,26304,26305],{},"shim_path",[26293,26307,26308],{},"php_hash",[26310,26311,26312,26332,26351],"tbody",{},[26290,26313,26314,26317,26320,26323,26326,26329],{},[26315,26316,3188],"td",{},[26315,26318,26319],{},"\u002Fresources\u002Fjs\u002FPages\u002FIndex.vue",[26315,26321,26322],{},"\\Fusion\\Generated\\Pages\\IndexGenerated",[26315,26324,26325],{},"\u002Fstorage\u002Ffusion\u002FPHP\u002FPages\u002FIndexGenerated.php",[26315,26327,26328],{},"\u002Fstorage\u002Ffusion\u002FJavaScript\u002FPages\u002FIndex.js",[26315,26330,26331],{},"php_1e182535deedd9b60e12c277bef58603",[26290,26333,26334,26336,26339,26342,26345,26348],{},[26315,26335,4782],{},[26315,26337,26338],{},"\u002Fresources\u002Fjs\u002FPages\u002FSearch.vue",[26315,26340,26341],{},"\\Fusion\\Generated\\Pages\\SearchGenerated",[26315,26343,26344],{},"\u002Fstorage\u002Ffusion\u002FPHP\u002FPages\u002FSearchGenerated.php",[26315,26346,26347],{},"\u002Fstorage\u002Ffusion\u002FJavaScript\u002FPages\u002FSearch.js",[26315,26349,26350],{},"php_468b4badedbe0cb6ce4734fcc921ea5f",[26290,26352,26353,26356,26362,26365,26368,26374],{},[26315,26354,26355],{},"3",[26315,26357,26358,26359,11851],{},"\u002Fresources\u002Fjs\u002FPages\u002F",[200,26360,26361],{},"Podcast",[26315,26363,26364],{},"\\Fusion\\Generated\\Pages\\DynamicPodcastGenerated",[26315,26366,26367],{},"\u002Fstorage\u002Ffusion\u002FPHP\u002FPages\u002FDynamicPodcastGenerated.php",[26315,26369,26370,26371,26373],{},"\u002Fstorage\u002Ffusion\u002FJavaScript\u002FPages\u002F",[200,26372,26361],{},".js",[26315,26375,26376],{},"php_2ca978b1c880558f54c4a59ce7584e6c",[35,26378,26379,26380,26385,26386,26388],{},"During Vue SFC transformation and PHP code extraction, a ",[39,26381,26384],{"href":26382,"rel":26383},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L50",[77],"hash calculation"," occurs, saved within the ",[156,26387,26308],{}," column of the database.",[35,26390,26391,26392,26396],{},"The hash serves to verify whether ",[39,26393,26395],{"href":25153,"rel":26394},[77],"PHP code alteration"," occurred. A matching hash means unchanged PHP code, introducing an optimization to omit redundant transformations.",[939,26398,26399],{},[35,26400,26401,26403],{},[200,26402,945],{},"\nA hash is a one-way function generating a fixed-size character string from input, typically comprising numeric and alphanumeric sequences. A hash uniquely identifies its input, meaning identical hashes indicate identical inputs.",[35,26405,26406],{},"This database repository embodies additional data like the PHP and JavaScript file paths used mainly to handle incoming requests efficiently.",[35,26408,26409],{},"An additional question emerges: How does the SQLite database affect the developer experience?",[35,26411,26412],{},"While utilizing a SQLite database, Fusion does not dictate the Laravel application's database choice: the two remain entirely distinct.",[35,26414,26415,26416,26424],{},"Fusion's service provider assign ",[39,26417,26420,26421],{"href":26418,"rel":26419},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FProviders\u002FFusionServiceProvider.php#L103C1-L110C12",[77],"a database connection called ",[156,26422,26423],{},"__fusion"," leveraging SQLite, which does not interfere with the Laravel application database connection. This means that it's possible to have multiple database connections within a single Laravel application. Really nice to know!",[192,26426,26428],{"className":19537,"code":26427,"language":19540,"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,26429,26430,26435,26439,26444,26449,26454,26459,26464,26469,26474],{"__ignoreMap":13},[200,26431,26432],{"class":202,"line":203},[200,26433,26434],{},"public function boot()\n",[200,26436,26437],{"class":202,"line":14},[200,26438,230],{},[200,26440,26441],{"class":202,"line":255},[200,26442,26443],{},"  ConfigFacade::set('database.connections.__fusion', [\n",[200,26445,26446],{"class":202,"line":272},[200,26447,26448],{},"    'name' => '__fusion',\n",[200,26450,26451],{"class":202,"line":283},[200,26452,26453],{},"    'driver' => 'sqlite',\n",[200,26455,26456],{"class":202,"line":300},[200,26457,26458],{},"    'database' => Fusion::storage('fusion.sqlite'),\n",[200,26460,26461],{"class":202,"line":310},[200,26462,26463],{},"    'foreign_key_constraints' => true,\n",[200,26465,26466],{"class":202,"line":336},[200,26467,26468],{},"    'journal_mode' => 'OFF',\n",[200,26470,26471],{"class":202,"line":342},[200,26472,26473],{},"  ]);\n",[200,26475,26476],{"class":202,"line":365},[200,26477,1130],{},[939,26479,26480],{},[35,26481,26482,26484],{},[200,26483,945],{},"\nA Laravel service provider executes during application bootstrapping to register services or bind classes into the service container. It's a pivotal location to administer and configure services.",[35,26486,419,26487,26492],{},[39,26488,26491],{"href":26489,"rel":26490},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FModels\u002FComponent.php#L7",[77],"Component model"," now uses this connection:",[192,26494,26496],{"className":19537,"code":26495,"language":19540,"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,26497,26498,26502,26506,26511,26515,26520,26524,26529,26533,26538],{"__ignoreMap":13},[200,26499,26500],{"class":202,"line":203},[200,26501,19577],{},[200,26503,26504],{"class":202,"line":14},[200,26505,529],{"emptyLinePlaceholder":20},[200,26507,26508],{"class":202,"line":255},[200,26509,26510],{},"namespace Fusion\\Models;\n",[200,26512,26513],{"class":202,"line":272},[200,26514,529],{"emptyLinePlaceholder":20},[200,26516,26517],{"class":202,"line":283},[200,26518,26519],{},"use Illuminate\\Database\\Eloquent\\Model;\n",[200,26521,26522],{"class":202,"line":300},[200,26523,529],{"emptyLinePlaceholder":20},[200,26525,26526],{"class":202,"line":310},[200,26527,26528],{},"class Component extends Model\n",[200,26530,26531],{"class":202,"line":336},[200,26532,230],{},[200,26534,26535],{"class":202,"line":342},[200,26536,26537],{},"    protected $connection = '__fusion';\n",[200,26539,26540],{"class":202,"line":365},[200,26541,1130],{},[35,26543,26544,26545,26548,26549,26551],{},"With this configuration, the ",[156,26546,26547],{},"Component"," class utilizes the ",[156,26550,26423],{}," database connection. Consequently, the SQLite database is only used by Fusion; it exerts no visible impact on the Laravel app, allowing the application's free choice regarding its database connection method. It's a straightforward and smartly implemented strategy.",[1913,26553,26555],{"id":26554},"handling-incoming-requests","Handling incoming requests",[35,26557,26558],{},"Delve into perhaps the most intriguing article segment: handling incoming requests through Fusion.",[35,26560,26561],{},"Upon starting to read, we've largely covered development, including Vite plugin functioning and PHP transformation. Our task now incorporates understanding how Fusion harmonizes these elements for request management.",[35,26563,26564,26565,26572,26573,26578,26579,26587,26588,215],{},"Fusion employs a file-based router approach. Vue SFCs automatically register themselves as routes within the Laravel app, achieved by calling ",[39,26566,26569,26571],{"href":26567,"rel":26568},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusion.php",[77],[156,26570,24232],{}," as a facade",", supported by the ",[39,26574,26577],{"href":26575,"rel":26576},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionManager.php",[77],"Fusion manager",", then ",[39,26580,26583,26584],{"href":26581,"rel":26582},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FProviders\u002FFusionServiceProvider.php#L30",[77],"registered to the ",[156,26585,26586],{},"FusionServiceProvider"," as part of the typical routing configuration in ",[156,26589,26590],{},"routes\u002Fweb.php",[192,26592,26594],{"className":19537,"code":26593,"language":19540,"meta":13,"style":13},"\u003C?php\n\nuse Fusion\\Fusion;\n\nFusion::pages();\n",[156,26595,26596,26600,26604,26609,26613],{"__ignoreMap":13},[200,26597,26598],{"class":202,"line":203},[200,26599,19577],{},[200,26601,26602],{"class":202,"line":14},[200,26603,529],{"emptyLinePlaceholder":20},[200,26605,26606],{"class":202,"line":255},[200,26607,26608],{},"use Fusion\\Fusion;\n",[200,26610,26611],{"class":202,"line":272},[200,26612,529],{"emptyLinePlaceholder":20},[200,26614,26615],{"class":202,"line":283},[200,26616,26617],{},"Fusion::pages();\n",[35,26619,26620,26621,3076,26623,26628,26629,26632,26633,26638,26639,26644],{},"Simply stated, this appears to be the entire explanation! However, we continue, unraveling more complexities. Within the method ",[156,26622,5351],{},[39,26624,26627],{"href":26625,"rel":26626},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionManager.php#L67",[77],"all Vue SFC components"," housed within ",[156,26630,26631],{},"resources\u002Fjs\u002FPages"," are located and ",[39,26634,26637],{"href":26635,"rel":26636},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionManager.php#L83",[77],"registered meticulously",". Employing the familiar ",[39,26640,26643],{"href":26641,"rel":26642},"https:\u002F\u002Flaravel.com\u002Fdocs\u002F11.x\u002Frouting",[77],"Laravel router"," facilitates implementation.",[192,26646,26648],{"className":19537,"code":26647,"language":19540,"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,26649,26650,26655,26660],{"__ignoreMap":13},[200,26651,26652],{"class":202,"line":203},[200,26653,26654],{},"Route::any($uri, [FusionController::class, 'handle'])\n",[200,26656,26657],{"class":202,"line":14},[200,26658,26659],{},"  ->defaults('__component', $component)\n",[200,26661,26662],{"class":202,"line":255},[200,26663,26664],{},"  ->defaults('__class', Component::where('src', $src)->first()?->php_class);\n",[35,26666,26667],{},"Notably, the routing process assigns two default values:",[68,26669,26670,26676],{},[71,26671,26672,26675],{},[156,26673,26674],{},"__component",": Path leading to the Vue SFC file.",[71,26677,26678,26681,26682,26684],{},[156,26679,26680],{},"__class",": Full qualified PHP class name, this class was generated within the ",[156,26683,25188],{}," command.",[35,26686,26687,26688,26690,26691,26694,26695,26698],{},"Consequently, when a request accesses the ",[156,26689,2882],{}," route, the ",[156,26692,26693],{},"handle"," operation of the ",[156,26696,26697],{},"FusionController"," activates. Within the controller lies a multilayered matrix handling diverse potentialities while managing props, synchronizing actions, and interfacing seamlessly with the frontend.",[35,26700,26701],{},"Upon concluding request management, an appropriate response dispatches, guided by request nature as follows:",[68,26703,26704,26712],{},[71,26705,26706,26707,215],{},"HMR or action requests receive ",[39,26708,26711],{"href":26709,"rel":26710},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L61-L63",[77],"a JSON response",[71,26713,26714,26715,215],{},"Standard requests delegate rendering tasks ",[39,26716,26719],{"href":26717,"rel":26718},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L65-L67",[77],"to Inertia",[35,26721,26722,26723,26726],{},"On close inspection, the ",[156,26724,26725],{},"data-page"," attribute reveals the Inertia object alongside associated Fusion components:",[192,26728,26730],{"className":591,"code":26729,"language":593,"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,26731,26732],{"__ignoreMap":13},[200,26733,26734,26736,26738,26740,26742,26744,26747,26749,26752,26754,26756,26758,26761,26763,26765,26767,26769,26772,26774,26776,26778,26780,26782,26785,26787,26790,26792,26795,26797,26800,26802,26804,26806,26809,26811,26814,26816,26819,26821,26823,26825,26827,26829,26832,26834,26837,26839,26841,26843,26845,26847,26849,26851,26854,26856,26859,26861,26864,26866,26869,26871,26874,26876,26878,26880,26883,26885,26887,26889,26892,26894,26897,26899,26902,26904,26906,26908,26911,26913,26916,26918,26920,26922,26924,26926,26929,26931,26933,26935,26938,26940,26942,26944,26947,26949,26951,26953,26956,26958,26961,26963,26966,26968,26971,26973,26975,26977],{"class":202,"line":203},[200,26735,600],{"class":214},[200,26737,11386],{"class":235},[200,26739,3224],{"class":379},[200,26741,449],{"class":214},[200,26743,611],{"class":214},[200,26745,26746],{"class":245},"app",[200,26748,611],{"class":214},[200,26750,26751],{"class":379}," data-page",[200,26753,449],{"class":214},[200,26755,611],{"class":214},[200,26757,2752],{"class":245},[200,26759,26760],{"class":210},"&quot;",[200,26762,23163],{"class":245},[200,26764,26760],{"class":210},[200,26766,239],{"class":245},[200,26768,26760],{"class":210},[200,26770,26771],{"class":245},"Index",[200,26773,26760],{"class":210},[200,26775,550],{"class":245},[200,26777,26760],{"class":210},[200,26779,23830],{"class":245},[200,26781,26760],{"class":210},[200,26783,26784],{"class":245},":{",[200,26786,26760],{"class":210},[200,26788,26789],{"class":245},"errors",[200,26791,26760],{"class":210},[200,26793,26794],{"class":245},":{},",[200,26796,26760],{"class":210},[200,26798,26799],{"class":245},"auth",[200,26801,26760],{"class":210},[200,26803,26784],{"class":245},[200,26805,26760],{"class":210},[200,26807,26808],{"class":245},"user",[200,26810,26760],{"class":210},[200,26812,26813],{"class":245},":null},",[200,26815,26760],{"class":210},[200,26817,26818],{"class":245},"fusion",[200,26820,26760],{"class":210},[200,26822,26784],{"class":245},[200,26824,26760],{"class":210},[200,26826,2015],{"class":245},[200,26828,26760],{"class":210},[200,26830,26831],{"class":245},":[],",[200,26833,26760],{"class":210},[200,26835,26836],{"class":245},"state",[200,26838,26760],{"class":210},[200,26840,26784],{"class":245},[200,26842,26760],{"class":210},[200,26844,20089],{"class":245},[200,26846,26760],{"class":210},[200,26848,239],{"class":245},[200,26850,26760],{"class":210},[200,26852,26853],{"class":245},"AARON",[200,26855,26760],{"class":210},[200,26857,26858],{"class":245},"},",[200,26860,26760],{"class":210},[200,26862,26863],{"class":245},"actions",[200,26865,26760],{"class":210},[200,26867,26868],{"class":245},":[{",[200,26870,26760],{"class":210},[200,26872,26873],{"class":245},"handler",[200,26875,26760],{"class":210},[200,26877,239],{"class":245},[200,26879,26760],{"class":210},[200,26881,26882],{"class":245},"applyServerState",[200,26884,26760],{"class":210},[200,26886,550],{"class":245},[200,26888,26760],{"class":210},[200,26890,26891],{"class":245},"priority",[200,26893,26760],{"class":210},[200,26895,26896],{"class":245},":40,",[200,26898,26760],{"class":210},[200,26900,26901],{"class":245},"_handler",[200,26903,26760],{"class":210},[200,26905,239],{"class":245},[200,26907,26760],{"class":210},[200,26909,26910],{"class":245},"Fusion\\\\Http\\\\Response\\\\Actions\\\\ApplyServerState",[200,26912,26760],{"class":210},[200,26914,26915],{"class":245},"}]}},",[200,26917,26760],{"class":210},[200,26919,2020],{"class":245},[200,26921,26760],{"class":210},[200,26923,239],{"class":245},[200,26925,26760],{"class":210},[200,26927,26928],{"class":245},"\\\u002F",[200,26930,26760],{"class":210},[200,26932,550],{"class":245},[200,26934,26760],{"class":210},[200,26936,26937],{"class":245},"version",[200,26939,26760],{"class":210},[200,26941,239],{"class":245},[200,26943,26760],{"class":210},[200,26945,26946],{"class":245},"f7e418d032bf1c4cc3cc55d947cab622",[200,26948,26760],{"class":210},[200,26950,550],{"class":245},[200,26952,26760],{"class":210},[200,26954,26955],{"class":245},"clearHistory",[200,26957,26760],{"class":210},[200,26959,26960],{"class":245},":false,",[200,26962,26760],{"class":210},[200,26964,26965],{"class":245},"encryptHistory",[200,26967,26760],{"class":210},[200,26969,26970],{"class":245},":false}",[200,26972,611],{"class":214},[200,26974,10192],{"class":214},[200,26976,11386],{"class":235},[200,26978,648],{"class":214},[35,26980,419,26981,26983],{},[156,26982,26725],{}," attribute is reformatted for clarity:",[192,26985,26989],{"className":26986,"code":26987,"language":26988,"meta":13,"style":13},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"component\": \"Index\",\n  \"props\": {\n    \"errors\": {},\n    \"auth\": {\n      \"user\": null\n    },\n    \"fusion\": {\n      \"meta\": [],\n      \"state\": {\n        \"name\": \"AARON\"\n      },\n      \"actions\": [\n        {\n          \"handler\": \"applyServerState\",\n          \"priority\": 40,\n          \"_handler\": \"Fusion\\\\Http\\\\Response\\\\Actions\\\\ApplyServerState\"\n        }\n      ]\n    }\n  },\n  \"url\": \"\u002F\",\n  \"version\": \"f7e418d032bf1c4cc3cc55d947cab622\",\n  \"clearHistory\": false,\n  \"encryptHistory\": false\n}\n","json",[156,26990,26991,26995,27015,27027,27040,27052,27066,27070,27082,27095,27107,27124,27128,27140,27144,27163,27178,27215,27219,27224,27228,27232,27250,27268,27281,27293],{"__ignoreMap":13},[200,26992,26993],{"class":202,"line":203},[200,26994,230],{"class":214},[200,26996,26997,27000,27002,27004,27006,27009,27011,27013],{"class":202,"line":14},[200,26998,26999],{"class":214},"  \"",[200,27001,23163],{"class":379},[200,27003,611],{"class":214},[200,27005,239],{"class":214},[200,27007,27008],{"class":214}," \"",[200,27010,26771],{"class":245},[200,27012,611],{"class":214},[200,27014,252],{"class":214},[200,27016,27017,27019,27021,27023,27025],{"class":202,"line":255},[200,27018,26999],{"class":214},[200,27020,23830],{"class":379},[200,27022,611],{"class":214},[200,27024,239],{"class":214},[200,27026,280],{"class":214},[200,27028,27029,27032,27034,27036,27038],{"class":202,"line":272},[200,27030,27031],{"class":214},"    \"",[200,27033,26789],{"class":1978},[200,27035,611],{"class":214},[200,27037,239],{"class":214},[200,27039,12100],{"class":214},[200,27041,27042,27044,27046,27048,27050],{"class":202,"line":283},[200,27043,27031],{"class":214},[200,27045,26799],{"class":1978},[200,27047,611],{"class":214},[200,27049,239],{"class":214},[200,27051,280],{"class":214},[200,27053,27054,27057,27059,27061,27063],{"class":202,"line":300},[200,27055,27056],{"class":214},"      \"",[200,27058,26808],{"class":2045},[200,27060,611],{"class":214},[200,27062,239],{"class":214},[200,27064,27065],{"class":214}," null\n",[200,27067,27068],{"class":202,"line":310},[200,27069,339],{"class":214},[200,27071,27072,27074,27076,27078,27080],{"class":202,"line":336},[200,27073,27031],{"class":214},[200,27075,26818],{"class":1978},[200,27077,611],{"class":214},[200,27079,239],{"class":214},[200,27081,280],{"class":214},[200,27083,27084,27086,27088,27090,27092],{"class":202,"line":342},[200,27085,27056],{"class":214},[200,27087,2015],{"class":2045},[200,27089,611],{"class":214},[200,27091,239],{"class":214},[200,27093,27094],{"class":214}," [],\n",[200,27096,27097,27099,27101,27103,27105],{"class":202,"line":365},[200,27098,27056],{"class":214},[200,27100,26836],{"class":2045},[200,27102,611],{"class":214},[200,27104,239],{"class":214},[200,27106,280],{"class":214},[200,27108,27109,27112,27114,27116,27118,27120,27122],{"class":202,"line":371},[200,27110,27111],{"class":214},"        \"",[200,27113,20089],{"class":235},[200,27115,611],{"class":214},[200,27117,239],{"class":214},[200,27119,27008],{"class":214},[200,27121,26853],{"class":245},[200,27123,617],{"class":214},[200,27125,27126],{"class":202,"line":398},[200,27127,12083],{"class":214},[200,27129,27130,27132,27134,27136,27138],{"class":202,"line":405},[200,27131,27056],{"class":214},[200,27133,26863],{"class":2045},[200,27135,611],{"class":214},[200,27137,239],{"class":214},[200,27139,1400],{"class":214},[200,27141,27142],{"class":202,"line":410},[200,27143,4909],{"class":214},[200,27145,27146,27149,27151,27153,27155,27157,27159,27161],{"class":202,"line":861},[200,27147,27148],{"class":214},"          \"",[200,27150,26873],{"class":235},[200,27152,611],{"class":214},[200,27154,239],{"class":214},[200,27156,27008],{"class":214},[200,27158,26882],{"class":245},[200,27160,611],{"class":214},[200,27162,252],{"class":214},[200,27164,27165,27167,27169,27171,27173,27176],{"class":202,"line":890},[200,27166,27148],{"class":214},[200,27168,26891],{"class":235},[200,27170,611],{"class":214},[200,27172,239],{"class":214},[200,27174,27175],{"class":2045}," 40",[200,27177,252],{"class":214},[200,27179,27180,27182,27184,27186,27188,27190,27192,27195,27198,27200,27203,27205,27208,27210,27213],{"class":202,"line":900},[200,27181,27148],{"class":214},[200,27183,26901],{"class":235},[200,27185,611],{"class":214},[200,27187,239],{"class":214},[200,27189,27008],{"class":214},[200,27191,24232],{"class":245},[200,27193,27194],{"class":210},"\\\\",[200,27196,27197],{"class":245},"Http",[200,27199,27194],{"class":210},[200,27201,27202],{"class":245},"Response",[200,27204,27194],{"class":210},[200,27206,27207],{"class":245},"Actions",[200,27209,27194],{"class":210},[200,27211,27212],{"class":245},"ApplyServerState",[200,27214,617],{"class":214},[200,27216,27217],{"class":202,"line":905},[200,27218,13459],{"class":214},[200,27220,27221],{"class":202,"line":931},[200,27222,27223],{"class":214},"      ]\n",[200,27225,27226],{"class":202,"line":3640},[200,27227,4962],{"class":214},[200,27229,27230],{"class":202,"line":3645},[200,27231,368],{"class":214},[200,27233,27234,27236,27238,27240,27242,27244,27246,27248],{"class":202,"line":4020},[200,27235,26999],{"class":214},[200,27237,2020],{"class":379},[200,27239,611],{"class":214},[200,27241,239],{"class":214},[200,27243,27008],{"class":214},[200,27245,2882],{"class":245},[200,27247,611],{"class":214},[200,27249,252],{"class":214},[200,27251,27252,27254,27256,27258,27260,27262,27264,27266],{"class":202,"line":4475},[200,27253,26999],{"class":214},[200,27255,26937],{"class":379},[200,27257,611],{"class":214},[200,27259,239],{"class":214},[200,27261,27008],{"class":214},[200,27263,26946],{"class":245},[200,27265,611],{"class":214},[200,27267,252],{"class":214},[200,27269,27270,27272,27274,27276,27278],{"class":202,"line":4481},[200,27271,26999],{"class":214},[200,27273,26955],{"class":379},[200,27275,611],{"class":214},[200,27277,239],{"class":214},[200,27279,27280],{"class":214}," false,\n",[200,27282,27283,27285,27287,27289,27291],{"class":202,"line":4487},[200,27284,26999],{"class":214},[200,27286,26965],{"class":379},[200,27288,611],{"class":214},[200,27290,239],{"class":214},[200,27292,18889],{"class":214},[200,27294,27295],{"class":202,"line":4493},[200,27296,1130],{"class":214},[35,27298,419,27299,27301,27302,27305,27306,1638,27308,27310,27311,27314,27315,215],{},[156,27300,26818],{}," object is of interest. It's composed of data initialized in the Vue SFC's PHP block through the ",[6850,27303,27304],{},"prop"," function; comprising ",[6850,27307,26836],{},[6850,27309,26863],{}," injected onto the client accordingly. The ",[156,27312,27313],{},"PendingResponse"," class, responsible for handling, specifically classifies these operations within Fusion ",[39,27316,27319],{"href":27317,"rel":27318},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FHttp\u002FResponse\u002FPendingResponse.php#L31-L43",[77],"as follows",[35,27321,27322,27323,27325],{},"Specifically, the ",[156,27324,26882],{}," action employs:",[192,27327,27329],{"className":25029,"code":27328,"language":25031,"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,27330,27331,27354,27358,27389,27426,27461,27467,27471,27491],{"__ignoreMap":13},[200,27332,27333,27335,27337,27339,27341,27344,27346,27348,27350,27352],{"class":202,"line":203},[200,27334,973],{"class":206},[200,27336,318],{"class":214},[200,27338,18685],{"class":210},[200,27340,550],{"class":214},[200,27342,27343],{"class":210}," unref",[200,27345,981],{"class":214},[200,27347,984],{"class":206},[200,27349,242],{"class":214},[200,27351,12060],{"class":245},[200,27353,992],{"class":214},[200,27355,27356],{"class":202,"line":14},[200,27357,529],{"emptyLinePlaceholder":20},[200,27359,27360,27362,27364,27366,27369,27371,27374,27376,27379,27382,27385,27387],{"class":202,"line":255},[200,27361,1380],{"class":206},[200,27363,1383],{"class":206},[200,27365,4987],{"class":379},[200,27367,27368],{"class":223}," applyServerState",[200,27370,3951],{"class":214},[200,27372,27373],{"class":386}," fusion",[200,27375,550],{"class":214},[200,27377,27378],{"class":386}," state",[200,27380,27381],{"class":214}," },",[200,27383,27384],{"class":386}," next",[200,27386,2089],{"class":214},[200,27388,280],{"class":214},[200,27390,27391,27394,27396,27399,27401,27403,27405,27407,27409,27411,27414,27416,27418,27420,27422,27424],{"class":202,"line":272},[200,27392,27393],{"class":210},"  Object",[200,27395,215],{"class":214},[200,27397,27398],{"class":223},"keys",[200,27400,227],{"class":235},[200,27402,26818],{"class":210},[200,27404,215],{"class":214},[200,27406,26836],{"class":210},[200,27408,2089],{"class":235},[200,27410,215],{"class":214},[200,27412,27413],{"class":223},"forEach",[200,27415,227],{"class":235},[200,27417,227],{"class":214},[200,27419,24584],{"class":386},[200,27421,2089],{"class":214},[200,27423,393],{"class":379},[200,27425,280],{"class":214},[200,27427,27428,27431,27433,27435,27437,27439,27441,27443,27446,27448,27450,27452,27454,27456,27458],{"class":202,"line":283},[200,27429,27430],{"class":210},"    state",[200,27432,9858],{"class":235},[200,27434,24584],{"class":210},[200,27436,18836],{"class":235},[200,27438,449],{"class":214},[200,27440,18685],{"class":223},[200,27442,227],{"class":235},[200,27444,27445],{"class":223},"unref",[200,27447,227],{"class":235},[200,27449,26818],{"class":210},[200,27451,215],{"class":214},[200,27453,26836],{"class":210},[200,27455,9858],{"class":235},[200,27457,24584],{"class":210},[200,27459,27460],{"class":235},"]))\n",[200,27462,27463,27465],{"class":202,"line":300},[200,27464,5080],{"class":214},[200,27466,416],{"class":235},[200,27468,27469],{"class":202,"line":310},[200,27470,529],{"emptyLinePlaceholder":20},[200,27472,27473,27475,27477,27479,27481,27483,27485,27487,27489],{"class":202,"line":336},[200,27474,3743],{"class":206},[200,27476,27384],{"class":223},[200,27478,227],{"class":235},[200,27480,2752],{"class":214},[200,27482,27373],{"class":210},[200,27484,550],{"class":214},[200,27486,27378],{"class":210},[200,27488,981],{"class":214},[200,27490,416],{"class":235},[200,27492,27493],{"class":202,"line":342},[200,27494,1130],{"class":214},[35,27496,27497],{},"Developers benefit by automatically leveraging action processing by Fusion without manual calls.",[35,27499,27500],{},"I haven't disclosed everything yet. Besides extracting from the Vue SFC PHP block, Fusion injects JavaScript logic into the Vue SFC, preceding the transformation. What began as an initial component:",[192,27502,27504],{"className":21417,"code":27503,"language":12060,"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  Hello {{ name }}!\n\u003C\u002Ftemplate>\n",[156,27505,27506,27514,27518,27522,27527,27531,27539,27543,27551,27555],{"__ignoreMap":13},[200,27507,27508,27510,27512],{"class":202,"line":203},[200,27509,600],{"class":214},[200,27511,19540],{"class":235},[200,27513,648],{"class":214},[200,27515,27516],{"class":202,"line":14},[200,27517,26031],{"class":210},[200,27519,27520],{"class":202,"line":255},[200,27521,529],{"emptyLinePlaceholder":20},[200,27523,27524],{"class":202,"line":272},[200,27525,27526],{"class":210},"$name = prop('Aaron');\n",[200,27528,27529],{"class":202,"line":283},[200,27530,26049],{"class":210},[200,27532,27533,27535,27537],{"class":202,"line":300},[200,27534,681],{"class":214},[200,27536,19540],{"class":235},[200,27538,648],{"class":214},[200,27540,27541],{"class":202,"line":310},[200,27542,529],{"emptyLinePlaceholder":20},[200,27544,27545,27547,27549],{"class":202,"line":336},[200,27546,600],{"class":214},[200,27548,21428],{"class":235},[200,27550,648],{"class":214},[200,27552,27553],{"class":202,"line":342},[200,27554,24324],{"class":210},[200,27556,27557,27559,27561],{"class":202,"line":365},[200,27558,681],{"class":214},[200,27560,21428],{"class":235},[200,27562,648],{"class":214},[35,27564,27565],{},"Transmutes during the Fusion plugin transformation (later handled by Vite) as:",[192,27567,27569],{"className":21417,"code":27568,"language":12060,"meta":13,"style":13},"\u003Cscript setup>\nimport { useFusion } from '$fusion\u002FPages\u002FIndex.js'\nimport useHotFusion from '@fusion\u002Fvue\u002Fhmr'\n\n\u002F\u002F __props is a magic variable provided to script setup by 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  Hello {{ name }}!\n\u003C\u002Ftemplate>\n",[156,27570,27571,27581,27601,27617,27621,27626,27665,27669,27689,27693,27739,27747,27751,27759,27763],{"__ignoreMap":13},[200,27572,27573,27575,27577,27579],{"class":202,"line":203},[200,27574,600],{"class":214},[200,27576,14892],{"class":235},[200,27578,21781],{"class":379},[200,27580,648],{"class":214},[200,27582,27583,27585,27587,27590,27592,27594,27596,27599],{"class":202,"line":14},[200,27584,973],{"class":206},[200,27586,318],{"class":214},[200,27588,27589],{"class":210}," useFusion",[200,27591,981],{"class":214},[200,27593,984],{"class":206},[200,27595,242],{"class":214},[200,27597,27598],{"class":245},"$fusion\u002FPages\u002FIndex.js",[200,27600,992],{"class":214},[200,27602,27603,27605,27608,27610,27612,27615],{"class":202,"line":255},[200,27604,973],{"class":206},[200,27606,27607],{"class":210}," useHotFusion ",[200,27609,1472],{"class":206},[200,27611,242],{"class":214},[200,27613,27614],{"class":245},"@fusion\u002Fvue\u002Fhmr",[200,27616,992],{"class":214},[200,27618,27619],{"class":202,"line":272},[200,27620,529],{"emptyLinePlaceholder":20},[200,27622,27623],{"class":202,"line":283},[200,27624,27625],{"class":401},"\u002F\u002F __props is a magic variable provided to script setup by Vue.\n",[200,27627,27628,27630,27633,27635,27637,27639,27641,27643,27645,27647,27649,27651,27653,27655,27657,27660,27662],{"class":202,"line":300},[200,27629,443],{"class":379},[200,27631,27632],{"class":210}," __fusionData ",[200,27634,449],{"class":214},[200,27636,27589],{"class":223},[200,27638,25183],{"class":210},[200,27640,249],{"class":214},[200,27642,20089],{"class":245},[200,27644,249],{"class":214},[200,27646,550],{"class":214},[200,27648,242],{"class":214},[200,27650,26818],{"class":245},[200,27652,249],{"class":214},[200,27654,360],{"class":210},[200,27656,550],{"class":214},[200,27658,27659],{"class":210}," __props",[200,27661,215],{"class":214},[200,27663,27664],{"class":210},"fusion)\n",[200,27666,27667],{"class":202,"line":310},[200,27668,529],{"emptyLinePlaceholder":20},[200,27670,27671,27673,27675,27677,27679,27682,27684,27686],{"class":202,"line":336},[200,27672,443],{"class":379},[200,27674,318],{"class":214},[200,27676,701],{"class":210},[200,27678,550],{"class":214},[200,27680,27681],{"class":210}," fusion ",[200,27683,413],{"class":214},[200,27685,510],{"class":214},[200,27687,27688],{"class":210}," __fusionData\n",[200,27690,27691],{"class":202,"line":342},[200,27692,529],{"emptyLinePlaceholder":20},[200,27694,27695,27698,27701,27703,27705,27707,27709,27711,27714,27716,27718,27721,27723,27726,27728,27730,27732,27735,27737],{"class":202,"line":365},[200,27696,27697],{"class":223},"useHotFusion",[200,27699,27700],{"class":210},"(__fusionData",[200,27702,550],{"class":214},[200,27704,318],{"class":214},[200,27706,3224],{"class":235},[200,27708,239],{"class":214},[200,27710,242],{"class":214},[200,27712,27713],{"class":245},"hot_07f7c82af56ef80f306d905892430461",[200,27715,249],{"class":214},[200,27717,550],{"class":214},[200,27719,27720],{"class":235}," hot",[200,27722,239],{"class":214},[200,27724,27725],{"class":206}," import",[200,27727,215],{"class":214},[200,27729,2015],{"class":210},[200,27731,6524],{"class":214},[200,27733,27734],{"class":210},"hot ",[200,27736,413],{"class":214},[200,27738,416],{"class":210},[200,27740,27741,27743,27745],{"class":202,"line":371},[200,27742,681],{"class":214},[200,27744,14892],{"class":235},[200,27746,648],{"class":214},[200,27748,27749],{"class":202,"line":398},[200,27750,529],{"emptyLinePlaceholder":20},[200,27752,27753,27755,27757],{"class":202,"line":405},[200,27754,600],{"class":214},[200,27756,21428],{"class":235},[200,27758,648],{"class":214},[200,27760,27761],{"class":202,"line":410},[200,27762,24324],{"class":210},[200,27764,27765,27767,27769],{"class":202,"line":861},[200,27766,681],{"class":214},[200,27768,21428],{"class":235},[200,27770,648],{"class":214},[35,27772,27773,27774,27781,27782,27785,27786,27788,27789,27791,27792,27795,27796,27799,27800,27802],{},"Note the synthetically injected ",[39,27775,27778,25117],{"href":27776,"rel":27777},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Finjectors\u002FcreateScriptSetup.js",[77],[156,27779,27780],{},"script setup",", undemanding manual input. It features a ",[156,27783,27784],{},"useFusion"," composable, accepting two arguments, first detailing prop keys requiring extraction (honed through recognizing PHP variables via ",[6850,27787,27304],{}," calls) and a ",[156,27790,27304],{}," argument signifying props. Essentially akin to employing ",[156,27793,27794],{},"const props = defineProps()",". Fusion triggers the ",[156,27797,27798],{},"__props"," variable, provided by Vue, enabling developers to write their ",[156,27801,27780],{}," block uninhibited. This allows typical Vue SFC syntax unfettered by Fusion processes.",[35,27804,27805,27806,1638,27808,27810,27811,27813],{},"In essence, composable values ",[156,27807,20089],{},[156,27809,26818],{}," extract via ",[156,27812,27780],{}," with seamless template injection.",[35,27815,27816,27817,27819,27820,27823],{},"Where did ",[156,27818,27784],{}," originate? The ",[156,27821,27822],{},"$fusion\u002FPages"," prefix divulges the broader picture.",[35,27825,27826,27827,27829,27830,27832],{},"Concerning ",[156,27828,27822],{},", we've already seen its functionality in action via the Vite plugin-generated alias. This alias abstracts out the JavaScript file's genuine location, birthed from an earlier undertaking formulating the ",[156,27831,25268],{},". Its content reads:",[192,27834,27836],{"className":25029,"code":27835,"language":25031,"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,27837,27838,27854,27870,27874,27894,27907,27929,27933,27940,27944,27982,28028,28032,28046,28056,28060,28064,28075,28084,28116,28120,28124,28135,28154,28171,28192,28196,28200,28204,28211],{"__ignoreMap":13},[200,27839,27840,27842,27845,27847,27849,27852],{"class":202,"line":203},[200,27841,973],{"class":206},[200,27843,27844],{"class":210}," ActionFactory ",[200,27846,1472],{"class":206},[200,27848,242],{"class":214},[200,27850,27851],{"class":245},"@fusion\u002Fvue\u002FactionFactory",[200,27853,992],{"class":214},[200,27855,27856,27858,27861,27863,27865,27868],{"class":202,"line":14},[200,27857,973],{"class":206},[200,27859,27860],{"class":210}," Pipeline ",[200,27862,1472],{"class":206},[200,27864,242],{"class":214},[200,27866,27867],{"class":245},"@fusion\u002Fvue\u002Fpipeline",[200,27869,992],{"class":214},[200,27871,27872],{"class":202,"line":255},[200,27873,529],{"emptyLinePlaceholder":20},[200,27875,27876,27878,27880,27882,27884,27886,27888,27890,27892],{"class":202,"line":272},[200,27877,1380],{"class":206},[200,27879,14301],{"class":379},[200,27881,18946],{"class":210},[200,27883,449],{"class":214},[200,27885,350],{"class":210},[200,27887,249],{"class":214},[200,27889,20089],{"class":245},[200,27891,249],{"class":214},[200,27893,4496],{"class":210},[200,27895,27896,27898,27900,27903,27905],{"class":202,"line":283},[200,27897,1380],{"class":206},[200,27899,14301],{"class":379},[200,27901,27902],{"class":210}," actions ",[200,27904,449],{"class":214},[200,27906,6421],{"class":210},[200,27908,27909,27911,27913,27916,27918,27920,27922,27925,27927],{"class":202,"line":300},[200,27910,1380],{"class":206},[200,27912,14301],{"class":379},[200,27914,27915],{"class":210}," fusionActions ",[200,27917,449],{"class":214},[200,27919,350],{"class":210},[200,27921,249],{"class":214},[200,27923,27924],{"class":245},"fusionSync",[200,27926,249],{"class":214},[200,27928,4496],{"class":210},[200,27930,27931],{"class":202,"line":310},[200,27932,529],{"emptyLinePlaceholder":20},[200,27934,27935,27937],{"class":202,"line":336},[200,27936,12327],{"class":379},[200,27938,27939],{"class":210}," cachedState\n",[200,27941,27942],{"class":202,"line":342},[200,27943,529],{"emptyLinePlaceholder":20},[200,27945,27946,27948,27950,27952,27954,27956,27958,27961,27963,27966,27968,27971,27974,27976,27978,27980],{"class":202,"line":365},[200,27947,1380],{"class":206},[200,27949,4987],{"class":379},[200,27951,27589],{"class":223},[200,27953,227],{"class":214},[200,27955,27398],{"class":386},[200,27957,510],{"class":214},[200,27959,27960],{"class":210}," []",[200,27962,550],{"class":214},[200,27964,27965],{"class":386}," props",[200,27967,510],{"class":214},[200,27969,27970],{"class":214}," {},",[200,27972,27973],{"class":386}," useCachedState",[200,27975,510],{"class":214},[200,27977,16166],{"class":2423},[200,27979,2089],{"class":214},[200,27981,280],{"class":214},[200,27983,27984,27986,27988,27990,27992,27995,27997,28000,28002,28005,28007,28010,28012,28015,28017,28019,28021,28023,28026],{"class":202,"line":371},[200,27985,3723],{"class":379},[200,27987,27378],{"class":210},[200,27989,510],{"class":214},[200,27991,1095],{"class":235},[200,27993,27994],{"class":210},"useCachedState",[200,27996,14928],{"class":214},[200,27998,27999],{"class":210}," cachedState",[200,28001,5267],{"class":235},[200,28003,28004],{"class":214},"?",[200,28006,27999],{"class":210},[200,28008,28009],{"class":214}," :",[200,28011,12997],{"class":214},[200,28013,28014],{"class":223}," Pipeline",[200,28016,227],{"class":235},[200,28018,23830],{"class":210},[200,28020,2089],{"class":235},[200,28022,215],{"class":214},[200,28024,28025],{"class":223},"createState",[200,28027,466],{"class":235},[200,28029,28030],{"class":202,"line":398},[200,28031,529],{"emptyLinePlaceholder":20},[200,28033,28034,28036,28038,28040,28042,28044],{"class":202,"line":405},[200,28035,20075],{"class":206},[200,28037,1095],{"class":235},[200,28039,11837],{"class":214},[200,28041,27994],{"class":210},[200,28043,5267],{"class":235},[200,28045,230],{"class":214},[200,28047,28048,28051,28053],{"class":202,"line":410},[200,28049,28050],{"class":210},"    cachedState",[200,28052,510],{"class":214},[200,28054,28055],{"class":210}," state\n",[200,28057,28058],{"class":202,"line":861},[200,28059,4490],{"class":214},[200,28061,28062],{"class":202,"line":890},[200,28063,529],{"emptyLinePlaceholder":20},[200,28065,28066,28068,28071,28073],{"class":202,"line":900},[200,28067,3723],{"class":379},[200,28069,28070],{"class":210}," all",[200,28072,510],{"class":214},[200,28074,280],{"class":214},[200,28076,28077,28080,28082],{"class":202,"line":905},[200,28078,28079],{"class":214},"    ...",[200,28081,26836],{"class":210},[200,28083,252],{"class":214},[200,28085,28086,28089,28092,28094,28096,28098,28100,28103,28106,28108,28110,28112,28114],{"class":202,"line":931},[200,28087,28088],{"class":214},"    ...new",[200,28090,28091],{"class":223}," ActionFactory",[200,28093,25183],{"class":235},[200,28095,574],{"class":214},[200,28097,26863],{"class":210},[200,28099,550],{"class":214},[200,28101,28102],{"class":214}," ...",[200,28104,28105],{"class":210},"fusionActions",[200,28107,360],{"class":235},[200,28109,550],{"class":214},[200,28111,27378],{"class":210},[200,28113,2089],{"class":235},[200,28115,252],{"class":214},[200,28117,28118],{"class":202,"line":3640},[200,28119,4490],{"class":214},[200,28121,28122],{"class":202,"line":3645},[200,28123,529],{"emptyLinePlaceholder":20},[200,28125,28126,28128,28131,28133],{"class":202,"line":4020},[200,28127,3723],{"class":379},[200,28129,28130],{"class":210}," shouldExport",[200,28132,510],{"class":214},[200,28134,19891],{"class":214},[200,28136,28137,28139,28141,28143,28145,28147,28150,28152],{"class":202,"line":4475},[200,28138,6426],{"class":206},[200,28140,1095],{"class":235},[200,28142,443],{"class":379},[200,28144,18822],{"class":210},[200,28146,6445],{"class":214},[200,28148,28149],{"class":210}," keys",[200,28151,5267],{"class":235},[200,28153,230],{"class":214},[200,28155,28156,28158,28160,28162,28165,28167,28169],{"class":202,"line":4481},[200,28157,5252],{"class":206},[200,28159,1095],{"class":235},[200,28161,24584],{"class":210},[200,28163,28164],{"class":214}," in",[200,28166,28070],{"class":210},[200,28168,5267],{"class":235},[200,28170,230],{"class":214},[200,28172,28173,28176,28178,28180,28182,28184,28186,28188,28190],{"class":202,"line":4487},[200,28174,28175],{"class":210},"      shouldExport",[200,28177,9858],{"class":235},[200,28179,24584],{"class":210},[200,28181,18836],{"class":235},[200,28183,449],{"class":214},[200,28185,28070],{"class":210},[200,28187,9858],{"class":235},[200,28189,24584],{"class":210},[200,28191,4496],{"class":235},[200,28193,28194],{"class":202,"line":4493},[200,28195,4962],{"class":214},[200,28197,28198],{"class":202,"line":5454},[200,28199,4490],{"class":214},[200,28201,28202],{"class":202,"line":5459},[200,28203,529],{"emptyLinePlaceholder":20},[200,28205,28206,28208],{"class":202,"line":13227},[200,28207,3743],{"class":206},[200,28209,28210],{"class":210}," shouldExport\n",[200,28212,28213],{"class":202,"line":13253},[200,28214,1130],{"class":214},[35,28216,419,28217,1638,28219,28221,28222,1638,28224,28227,28228,28231,28232,28234],{},[156,28218,26836],{},[156,28220,26818],{}," variables live within this file, mirroring declarations from the Vue SFC in PHP functions ",[6850,28223,27304],{},[6850,28225,28226],{},"expose",". Accompanied by an ",[156,28229,28230],{},"ActionFactory"," responsible for applying server-mandated actions to the state, including ",[156,28233,26882],{},", automatic, and transparent synchronization occurs.",[35,28236,28237],{},"In total, Fusion provides several pre-established actions, such as:",[1576,28239,28240,28249,28259,28268],{},[71,28241,28242,28248],{},[39,28243,28246],{"href":28244,"rel":28245},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002FapplyServerState.js",[77],[156,28247,26882],{},": Synchronizes backend and client-side state data, updating state to match server expectations in real-time.",[71,28250,28251,28258],{},[39,28252,28255],{"href":28253,"rel":28254},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002FsyncQueryString.js",[77],[156,28256,28257],{},"syncQueryString",": Synchronizes query string parameters and state.",[71,28260,28261,28267],{},[39,28262,28265],{"href":28263,"rel":28264},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002Flog.js",[77],[156,28266,1118],{},": Allows logging for debugging purposes.",[71,28269,28270,28277],{},[39,28271,28274],{"href":28272,"rel":28273},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002FlogStack.js",[77],[156,28275,28276],{},"logStack",": Logs action progression for debugging.",[35,28279,28280,28281,28283],{},"The discussion becomes even more absorbing when seeing the ",[156,28282,28226],{}," function. This method exposes a PHP functionality server-side to the client, creating RPC-like client-server behaviors allowing direct client requests to trigger server-side action.",[939,28285,28286],{},[35,28287,28288,13519,28290,28292,28293,1638,28298,215],{},[200,28289,945],{},[156,28291,28226],{}," concept shares kinship with approaches like ",[39,28294,28297],{"href":28295,"rel":28296},"https:\u002F\u002Ftrpc.io\u002F",[77],"tRPC",[39,28299,28302],{"href":28300,"rel":28301},"https:\u002F\u002Freact.dev\u002Freference\u002Frsc\u002Fserver-functions",[77],"React Server Functions",[35,28304,28305],{},"An illustrative example:",[192,28307,28309],{"className":21417,"code":28308,"language":12060,"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    Favorite\n  \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n",[156,28310,28311,28319,28324,28329,28334,28342,28346,28354,28374,28379,28387],{"__ignoreMap":13},[200,28312,28313,28315,28317],{"class":202,"line":203},[200,28314,600],{"class":214},[200,28316,19540],{"class":235},[200,28318,648],{"class":214},[200,28320,28321],{"class":202,"line":14},[200,28322,28323],{"class":210},"expose(favorite: function(Podcast $podcast) {\n",[200,28325,28326],{"class":202,"line":255},[200,28327,28328],{"class":210},"    return response()->json($podcast->toggleFavorite());\n",[200,28330,28331],{"class":202,"line":272},[200,28332,28333],{"class":210},"});\n",[200,28335,28336,28338,28340],{"class":202,"line":283},[200,28337,681],{"class":214},[200,28339,19540],{"class":235},[200,28341,648],{"class":214},[200,28343,28344],{"class":202,"line":300},[200,28345,529],{"emptyLinePlaceholder":20},[200,28347,28348,28350,28352],{"class":202,"line":310},[200,28349,600],{"class":214},[200,28351,21428],{"class":235},[200,28353,648],{"class":214},[200,28355,28356,28358,28360,28363,28365,28367,28370,28372],{"class":202,"line":336},[200,28357,657],{"class":214},[200,28359,910],{"class":235},[200,28361,28362],{"class":379}," @click",[200,28364,449],{"class":214},[200,28366,611],{"class":214},[200,28368,28369],{"class":245},"favorite({ podcast }).then(fav => podcast.favorited = fav)",[200,28371,611],{"class":214},[200,28373,648],{"class":214},[200,28375,28376],{"class":202,"line":342},[200,28377,28378],{"class":210},"    Favorite\n",[200,28380,28381,28383,28385],{"class":202,"line":365},[200,28382,893],{"class":214},[200,28384,910],{"class":235},[200,28386,648],{"class":214},[200,28388,28389,28391,28393],{"class":202,"line":371},[200,28390,681],{"class":214},[200,28392,21428],{"class":235},[200,28394,648],{"class":214},[35,28396,28397,28398,28400,28401,28403],{},"Exposing a method like ",[156,28399,24441],{}," facilitates its use within the template. ",[156,28402,27784],{}," composable previously outlined handles functionality, yet Fusion ensures thorough processing enabling client-side button interaction to trigger server-backed operations.",[68,28405,28406,28421,28429],{},[71,28407,28408,28409,28414,28415,1638,28418,215],{},"The client ",[39,28410,28413],{"href":28411,"rel":28412},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FActionFactory.js#L72-L77",[77],"sends a server request"," furnished with headers ",[156,28416,28417],{},"x-fusion-action-request",[156,28419,28420],{},"x-fusion-action-handler",[71,28422,28423,28424,215],{},"Backend processes these requests, aided by the provided metadata, responding to the client accordingly like ",[39,28425,28428],{"href":28426,"rel":28427},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FHttp\u002FControllers\u002FFusionController.php#L50",[77],"how Inertia handles its protocol with headers",[71,28430,28431,28432,215],{},"Client-side state corresponds to server-sent feedback, ",[39,28433,28436],{"href":28434,"rel":28435},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FActionFactory.js#L87",[77],"after processing",[35,28438,28439],{},"Clicking on the favorite button from the example above generates this server-targeted request:",[192,28441,28445],{"className":28442,"code":28443,"language":28444,"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,28446,28447,28452,28457,28462,28467,28471,28475,28480,28485,28490,28495,28500,28505,28510,28515,28520,28525,28529,28533,28538,28543,28548,28552,28557,28562,28567,28572,28577,28582,28587,28591,28595,28599,28603],{"__ignoreMap":13},[200,28448,28449],{"class":202,"line":203},[200,28450,28451],{},"POST \u002Fsearch HTTP\u002F1.1\n",[200,28453,28454],{"class":202,"line":14},[200,28455,28456],{},"Accept: application\u002Fjson, text\u002Fplain, *\u002F*\n",[200,28458,28459],{"class":202,"line":255},[200,28460,28461],{},"X-Fusion-Action-Handler: favorite\n",[200,28463,28464],{"class":202,"line":272},[200,28465,28466],{},"X-Fusion-Action-Request: true\n",[200,28468,28469],{"class":202,"line":283},[200,28470,529],{"emptyLinePlaceholder":20},[200,28472,28473],{"class":202,"line":300},[200,28474,230],{},[200,28476,28477],{"class":202,"line":310},[200,28478,28479],{},"  \"fusion\": {\n",[200,28481,28482],{"class":202,"line":336},[200,28483,28484],{},"    \"args\": {\n",[200,28486,28487],{"class":202,"line":342},[200,28488,28489],{},"      \"podcast\": {\n",[200,28491,28492],{"class":202,"line":365},[200,28493,28494],{},"        \"id\": 1,\n",[200,28496,28497],{"class":202,"line":371},[200,28498,28499],{},"        \"title\": \"Art Of Product\",\n",[200,28501,28502],{"class":202,"line":398},[200,28503,28504],{},"        \"author\": \"Derrick Reimer & Ben Orenstein\",\n",[200,28506,28507],{"class":202,"line":405},[200,28508,28509],{},"        \"image\": \"\u002Fart\u002Fart-of-product.jpg\",\n",[200,28511,28512],{"class":202,"line":410},[200,28513,28514],{},"        \"favorited\": false,\n",[200,28516,28517],{"class":202,"line":861},[200,28518,28519],{},"        \"created_at\": \"2025-02-15T18:01:36.000000Z\",\n",[200,28521,28522],{"class":202,"line":890},[200,28523,28524],{},"        \"updated_at\": \"2025-02-19T20:49:13.000000Z\"\n",[200,28526,28527],{"class":202,"line":900},[200,28528,4478],{},[200,28530,28531],{"class":202,"line":905},[200,28532,339],{},[200,28534,28535],{"class":202,"line":931},[200,28536,28537],{},"    \"state\": {\n",[200,28539,28540],{"class":202,"line":3640},[200,28541,28542],{},"      \"search\": \"\",\n",[200,28544,28545],{"class":202,"line":3645},[200,28546,28547],{},"      \"podcasts\": [\n",[200,28549,28550],{"class":202,"line":4020},[200,28551,4909],{},[200,28553,28554],{"class":202,"line":4475},[200,28555,28556],{},"          \"id\": 1,\n",[200,28558,28559],{"class":202,"line":4481},[200,28560,28561],{},"          \"title\": \"Art Of Product\",\n",[200,28563,28564],{"class":202,"line":4487},[200,28565,28566],{},"          \"author\": \"Derrick Reimer & Ben Orenstein\",\n",[200,28568,28569],{"class":202,"line":4493},[200,28570,28571],{},"          \"image\": \"\u002Fart\u002Fart-of-product.jpg\",\n",[200,28573,28574],{"class":202,"line":5454},[200,28575,28576],{},"          \"favorited\": false,\n",[200,28578,28579],{"class":202,"line":5459},[200,28580,28581],{},"          \"created_at\": \"2025-02-15T18:01:36.000000Z\",\n",[200,28583,28584],{"class":202,"line":13227},[200,28585,28586],{},"          \"updated_at\": \"2025-02-19T20:49:13.000000Z\"\n",[200,28588,28589],{"class":202,"line":13253},[200,28590,13459],{},[200,28592,28593],{"class":202,"line":13269},[200,28594,27223],{},[200,28596,28597],{"class":202,"line":13313},[200,28598,4962],{},[200,28600,28601],{"class":202,"line":13319},[200,28602,4490],{},[200,28604,28605],{"class":202,"line":13324},[200,28606,1130],{},[35,28608,28609,28610,1638,28612,28614,28615,215],{},"Meanwhile, the server processes such a request by interpreting the headers ",[156,28611,28417],{},[156,28613,28420],{},". In its service provider, Fusion ",[39,28616,28619],{"href":28617,"rel":28618},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FProviders\u002FFusionServiceProvider.php#L62-L65",[77],"registers request macros for easy request type identification and handling",[35,28621,28622,28623,28626,28627,28629],{},"Inside the request body, ",[156,28624,28625],{},"args"," holds the method's arguments (podcast), while ",[156,28628,26836],{}," comprises the app's current state.",[35,28631,28632],{},"In response, the server sends the following:",[192,28634,28636],{"className":26986,"code":28635,"language":26988,"meta":13,"style":13},"true\n",[156,28637,28638],{"__ignoreMap":13},[200,28639,28640],{"class":202,"line":203},[200,28641,28635],{"class":214},[35,28643,28644,28645,28647],{},"Which is the response from the ",[156,28646,24441],{}," method. The client then updates the state accordingly.",[35,28649,28650,28651,28653,28654,28656,28657,28659],{},"Fusion offers a ",[156,28652,24434],{}," method fostering frontend-backend state synchronization. For example, a search field could filter items based on backend computations, achieved via ",[156,28655,24434],{},". Essentially, sync mimics an action categorized by the ",[156,28658,27924],{}," handler!",[51,28661,28663],{"id":28662},"how-does-fusion-use-inertia","How does Fusion use Inertia?",[35,28665,28666,28667,28671,28672,28677],{},"In truth, Fusion ingeniously simplifies and relies on Inertia's seamless integration to process Vue SFCs effectively without reinventing its frontend framework. The ",[39,28668,28670],{"href":26717,"rel":28669},[77],"Inertia::render"," method exemplifies Inertia-based rendering while ",[39,28673,28676],{"href":28674,"rel":28675},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fapps\u002Fvue\u002Fresources\u002Fjs\u002Fapp.js#L11",[77],"createInertiaApp"," establishes the Vue app environment efficiently, counting as the full extent of Inertia's footprint within the Fusion project.",[35,28679,28680],{},"Building on top of Inertia proves prudent, allowing Aaron to focus innovation on Fusion's purpose without distraction by Vue integration intricacies within Laravel. Inertia's utility renders any redundancy void. Additionally, transcending mid-journey evolves Fusion to encompass other frameworks like React seamlessly.",[51,28682,24102],{"id":24101},[35,28684,28685],{},"Fusion has a potential, especially for developers transitioning from Next or Nuxt to a full-stack capability, eagerly embracing Laravel with Inertia. By preserving concepts dear to developers of these frameworks, such as file-based routing and RPC-driven communication, while enriching them with Laravel's power, Fusion becomes a compelling choice. You should definitely try it out!",[35,28687,28688,28689,28692],{},"Within Fusion's roadmap, Aaron envisions React compatibility, achievable by standing on Inertia's shoulders. However, I'm eager to see Aaron's take on handling server code execution within JSX, perhaps using a ",[156,28690,28691],{},"'use php'"," directive? 🤔",[35,28694,28695],{},"This project illustrates Vite and Laravel's deep relationship's strength effectively. Accessing Vite's comprehensive utility effortlessly remains a groundbreaking capability. It also reaffirms the SFC's modular capacity in Vue, Fusion surely confirms it. There are no practical limits to what you can achieve using Vite and Vue SFC components!",[35,28697,28698],{},"Finally, Fusion's inter-process SQLite database communication reinforces an elegant, smart pattern!",[35,28700,28701,28702,28705],{},"The article made for an exciting write while I was exploring Fusion's codebase. I hope it unearthed new insights for you, and if keen on delving into the intricacies of another library, feel free to comment or use the ",[39,28703,28704],{"href":25677},"Ask Me Anything"," page.",[35,28707,28708],{},"But I've got an additional treat for you! 👇",[51,28710,28712],{"id":28711},"one-more-thing","One more thing",[35,28714,28715,28716,28719,28720,28723],{},"While drafting this article, prevalent PHP block code examples lacked appropriate syntax highlighting. I pondered and decided to tackle this task. Since ",[6850,28717,28718],{},"Shiki"," is used for syntax highlighting code in this website and it uses ",[6850,28721,28722],{},"TextMate grammars",", I know that I could add support for the PHP block in VSCode.",[35,28725,28726],{},"But how?",[35,28728,28729],{},"Diving in, I realized embedding languages in single-file contexts isn't new. Vue SFCs are notable, as are Markdown, Angular, and even HTML entailing different languages. Subsequently, I focused on Vue SFC's TextMate grammar.",[939,28731,28732],{},[35,28733,28734,28736],{},[200,28735,945],{},"\nA TextMate grammar defines coding language syntax highlights through patterns and sets rules. Operating behind VSCode and Shiki, it's the syntax highlighter's engine.",[35,28738,28739,28740,28745,28746,28751],{},"Examining ",[39,28741,28744],{"href":28742,"rel":28743},"https:\u002F\u002Fgithub.com\u002Fshikijs\u002Ftextmate-grammars-themes\u002F",[77],"Shikijs' textmate-grammars-themes repository",", catering to every language and theme supported by Shiki unearthed the ",[39,28747,28750],{"href":28748,"rel":28749},"https:\u002F\u002Fgithub.com\u002Fshikijs\u002Ftextmate-grammars-themes\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Ftm-grammars\u002Fgrammars\u002Fvue.json",[77],"Vue grammar",". Although lengthy, illuminating concepts surface when reading through, such as HTML, CSS, and JavaScript embeddings within respective blocks:",[192,28753,28755],{"className":26986,"code":28754,"language":26988,"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,28756,28757,28761,28785,28798,28810,28827,28831,28835,28865,28878,28890,28907,28911,28923,28940,28944,28948,28961,28965,28983,28987,28991,29010,29039,29058,29070,29074,29090,29094,29098,29102,29106],{"__ignoreMap":13},[200,28758,28759],{"class":202,"line":203},[200,28760,230],{"class":214},[200,28762,28763,28765,28768,28770,28772,28774,28777,28779,28781,28783],{"class":202,"line":14},[200,28764,26999],{"class":214},[200,28766,28767],{"class":379},"begin",[200,28769,611],{"class":214},[200,28771,239],{"class":214},[200,28773,27008],{"class":214},[200,28775,28776],{"class":245},"(script)",[200,28778,27194],{"class":210},[200,28780,3884],{"class":245},[200,28782,611],{"class":214},[200,28784,252],{"class":214},[200,28786,28787,28789,28792,28794,28796],{"class":202,"line":255},[200,28788,26999],{"class":214},[200,28790,28791],{"class":379},"beginCaptures",[200,28793,611],{"class":214},[200,28795,239],{"class":214},[200,28797,280],{"class":214},[200,28799,28800,28802,28804,28806,28808],{"class":202,"line":272},[200,28801,27031],{"class":214},[200,28803,3188],{"class":1978},[200,28805,611],{"class":214},[200,28807,239],{"class":214},[200,28809,280],{"class":214},[200,28811,28812,28814,28816,28818,28820,28822,28825],{"class":202,"line":283},[200,28813,27056],{"class":214},[200,28815,20089],{"class":2045},[200,28817,611],{"class":214},[200,28819,239],{"class":214},[200,28821,27008],{"class":214},[200,28823,28824],{"class":245},"entity.name.tag.$1.html.vue",[200,28826,617],{"class":214},[200,28828,28829],{"class":202,"line":300},[200,28830,4962],{"class":214},[200,28832,28833],{"class":202,"line":310},[200,28834,368],{"class":214},[200,28836,28837,28839,28842,28844,28846,28848,28851,28853,28856,28858,28861,28863],{"class":202,"line":336},[200,28838,26999],{"class":214},[200,28840,28841],{"class":379},"end",[200,28843,611],{"class":214},[200,28845,239],{"class":214},[200,28847,27008],{"class":214},[200,28849,28850],{"class":245},"(\u003C\u002F)(",[200,28852,27194],{"class":210},[200,28854,28855],{"class":245},"1)",[200,28857,27194],{"class":210},[200,28859,28860],{"class":245},"s*(?=>)",[200,28862,611],{"class":214},[200,28864,252],{"class":214},[200,28866,28867,28869,28872,28874,28876],{"class":202,"line":342},[200,28868,26999],{"class":214},[200,28870,28871],{"class":379},"endCaptures",[200,28873,611],{"class":214},[200,28875,239],{"class":214},[200,28877,280],{"class":214},[200,28879,28880,28882,28884,28886,28888],{"class":202,"line":365},[200,28881,27031],{"class":214},[200,28883,3188],{"class":1978},[200,28885,611],{"class":214},[200,28887,239],{"class":214},[200,28889,280],{"class":214},[200,28891,28892,28894,28896,28898,28900,28902,28905],{"class":202,"line":371},[200,28893,27056],{"class":214},[200,28895,20089],{"class":2045},[200,28897,611],{"class":214},[200,28899,239],{"class":214},[200,28901,27008],{"class":214},[200,28903,28904],{"class":245},"punctuation.definition.tag.begin.html.vue",[200,28906,617],{"class":214},[200,28908,28909],{"class":202,"line":398},[200,28910,339],{"class":214},[200,28912,28913,28915,28917,28919,28921],{"class":202,"line":405},[200,28914,27031],{"class":214},[200,28916,4782],{"class":1978},[200,28918,611],{"class":214},[200,28920,239],{"class":214},[200,28922,280],{"class":214},[200,28924,28925,28927,28929,28931,28933,28935,28938],{"class":202,"line":410},[200,28926,27056],{"class":214},[200,28928,20089],{"class":2045},[200,28930,611],{"class":214},[200,28932,239],{"class":214},[200,28934,27008],{"class":214},[200,28936,28937],{"class":245},"entity.name.tag.$2.html.vue",[200,28939,617],{"class":214},[200,28941,28942],{"class":202,"line":861},[200,28943,4962],{"class":214},[200,28945,28946],{"class":202,"line":890},[200,28947,368],{"class":214},[200,28949,28950,28952,28955,28957,28959],{"class":202,"line":900},[200,28951,26999],{"class":214},[200,28953,28954],{"class":379},"patterns",[200,28956,611],{"class":214},[200,28958,239],{"class":214},[200,28960,1400],{"class":214},[200,28962,28963],{"class":202,"line":905},[200,28964,19638],{"class":214},[200,28966,28967,28969,28972,28974,28976,28978,28981],{"class":202,"line":931},[200,28968,27056],{"class":214},[200,28970,28971],{"class":1978},"include",[200,28973,611],{"class":214},[200,28975,239],{"class":214},[200,28977,27008],{"class":214},[200,28979,28980],{"class":245},"#tag-stuff",[200,28982,617],{"class":214},[200,28984,28985],{"class":202,"line":3640},[200,28986,339],{"class":214},[200,28988,28989],{"class":202,"line":3645},[200,28990,19638],{"class":214},[200,28992,28993,28995,28997,28999,29001,29003,29006,29008],{"class":202,"line":4020},[200,28994,27056],{"class":214},[200,28996,28767],{"class":1978},[200,28998,611],{"class":214},[200,29000,239],{"class":214},[200,29002,27008],{"class":214},[200,29004,29005],{"class":245},"(?\u003C=>)",[200,29007,611],{"class":214},[200,29009,252],{"class":214},[200,29011,29012,29014,29016,29018,29020,29022,29025,29027,29030,29032,29035,29037],{"class":202,"line":4475},[200,29013,27056],{"class":214},[200,29015,28841],{"class":1978},[200,29017,611],{"class":214},[200,29019,239],{"class":214},[200,29021,27008],{"class":214},[200,29023,29024],{"class":245},"(?=\u003C",[200,29026,27194],{"class":210},[200,29028,29029],{"class":245},"\u002Fscript",[200,29031,27194],{"class":210},[200,29033,29034],{"class":245},"b)",[200,29036,611],{"class":214},[200,29038,252],{"class":214},[200,29040,29041,29043,29045,29047,29049,29051,29054,29056],{"class":202,"line":4481},[200,29042,27056],{"class":214},[200,29044,20089],{"class":1978},[200,29046,611],{"class":214},[200,29048,239],{"class":214},[200,29050,27008],{"class":214},[200,29052,29053],{"class":245},"source.js",[200,29055,611],{"class":214},[200,29057,252],{"class":214},[200,29059,29060,29062,29064,29066,29068],{"class":202,"line":4487},[200,29061,27056],{"class":214},[200,29063,28954],{"class":1978},[200,29065,611],{"class":214},[200,29067,239],{"class":214},[200,29069,1400],{"class":214},[200,29071,29072],{"class":202,"line":4493},[200,29073,4909],{"class":214},[200,29075,29076,29078,29080,29082,29084,29086,29088],{"class":202,"line":5454},[200,29077,27148],{"class":214},[200,29079,28971],{"class":2045},[200,29081,611],{"class":214},[200,29083,239],{"class":214},[200,29085,27008],{"class":214},[200,29087,29053],{"class":245},[200,29089,617],{"class":214},[200,29091,29092],{"class":202,"line":5459},[200,29093,13459],{"class":214},[200,29095,29096],{"class":202,"line":13227},[200,29097,27223],{"class":214},[200,29099,29100],{"class":202,"line":13253},[200,29101,4962],{"class":214},[200,29103,29104],{"class":202,"line":13269},[200,29105,13674],{"class":214},[200,29107,29108],{"class":202,"line":13313},[200,29109,1130],{"class":214},[35,29111,29112,29113,29116,29117,29119],{},"This handles JavaScript highlighting within ",[156,29114,29115],{},"\u003Cscript>"," blocks using the JavaScript grammar inclusion through ",[156,29118,28971],{},". The same pattern presumably applies to other languages, including PHP.",[35,29121,29122,29123,29126],{},"This is exactly what I want to achieve, but with ",[156,29124,29125],{},"\u003Cphp>"," and PHP grammar. Of course, there's already a PHP grammar, so I just need to include it the same way. I try, and it works. Youpi! 🥳",[35,29128,29129,29130,29135],{},"But wait, I can't submit a PR to ",[39,29131,29134],{"href":29132,"rel":29133},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Flanguage-tools",[77],"Vue Language Tools"," (Shiki fetches the grammar from this repo) to add support for PHP grammar. That makes no sense, and there's no reason for them to accept it. So I need to find another solution.",[35,29137,29138,29139,29144],{},"After continuing my search, I found the ",[39,29140,29143],{"href":29141,"rel":29142},"https:\u002F\u002Fgithub.com\u002Fshikijs\u002Ftextmate-grammars-themes\u002Fblob\u002Fmain\u002Fpackages\u002Ftm-grammars\u002Fgrammars\u002Fangular-inline-style.json",[77],"angular-inline-style"," grammar. This one is used to highlight styles (CSS, SCSS) inside an Angular component (TypeScript). To do so, the grammar uses the keyword injectTo, which acts as an extension of an existing grammar, in this case, source.ts.ng. This is perfect! What if I created a new grammar that extends the Vue grammar by injecting the PHP grammar? In retrospect, this sounded easier than it actually was.",[35,29146,29147,29148,215],{},"After a couple of hours of trial and error, I finally managed to create a grammar that works in Shiki. One of the most complicated parts was figuring out what was wrong when things didn't work. With this kind of setup, there are no error messages, no warnings, nothing. You just have to guess what's wrong. You can find the working solution in the source code of ",[39,29149,29152],{"href":29150,"rel":29151},"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fcode.soubiran.dev",[77],"my code-to-image generator",[35,29154,29155,29156,29161,29162,215],{},"Finally, I created a VSCode extension that anyone can use to add support for PHP blocks in Vue SFCs. You can find it on the ",[39,29157,29160],{"href":29158,"rel":29159},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Ffusion-vscode",[77],"marketplace"," along with the working grammar: ",[39,29163,29166],{"href":29164,"rel":29165},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Ffusion-vscode\u002Fblob\u002Fmain\u002Fsyntaxes\u002Fvue.php.tmLanguage.json",[77],"source.vue.php",[35,29168,29169],{},"If Fusion becomes popular, I might eventually submit a PR to add the language to Shiki, making it easier for everyone to highlight PHP blocks.",[35,29171,29172],{},"Happy coding! 👨‍💻",[1171,29174,29175],{},"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 .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":29177},[29178,29179,29180,29187,29188,29189],{"id":24389,"depth":14,"text":24390},{"id":24695,"depth":14,"text":24681},{"id":25682,"depth":14,"text":25683,"children":29181},[29182,29183,29185,29186],{"id":25703,"depth":255,"text":25704},{"id":25734,"depth":255,"text":29184},"The fusion:conform command",{"id":26275,"depth":255,"text":26276},{"id":26554,"depth":255,"text":26555},{"id":28662,"depth":14,"text":28663},{"id":24101,"depth":14,"text":24102},{"id":28711,"depth":14,"text":28712},"2025-02-18","Bridging the gap between the frontend and backend is a challenge that tries to solve Fusion. On its way to do so, it brings a lot of things to learn from.",{},"\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components","---\ncontentId: 769fccfd-feb9-4164-a73f-f80f0ad2bb8d\ntitle: Fusion or the Art of Writing PHP Into a Vue SFC Components\ndescription: Bridging the gap between the frontend and backend is a challenge that tries to solve Fusion. On its way to do so, it brings a lot of things to learn from.\ndate: 2025-02-18\n---\n\nThis is truly a **special** article.\n\nTwo weeks ago, on February 3rd and 4th, 2025, [Laracon EU](https:\u002F\u002Flaracon.eu\u002F) was held in Amsterdam. Although I wasn't able to attend in person, I followed the entire event via live stream. Conferences always provide a fantastic opportunity to gain new insights and to immerse further into the wonderful **Laravel ecosystem**. _I hope to attend Laracon someday._\n\nOne of the talks that caught my attention was [\"Introducing \\[REDACTED\\]: A New Paradigm for Laravel + Javascript\"](https:\u002F\u002Fyoutu.be\u002FlXjYStxGFIQ?si=ntTW6zP-tAzV0OZO) by [Aaron Francis](https:\u002F\u002Fx.com\u002Faarondfrancis). This intrigued me since we already have **Inertia** and **Livewire**, which are excellent tools for building modern applications with **Laravel**. So, what new advancements could be offered?\n\n![Thumbnail of the video \"Introducing \\[REDACTED\\]: a new paradigm for Laravel + Javascript\" by Aaron Francis](\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fthumbnail.jpg)\n\nAaron introduced [**Fusion**](https:\u002F\u002Fgithub.com\u002Ffusion-php), described as a novel approach to integrate your modern frontend with your Laravel backend. The concept allows writing PHP code directly in a frontend file, and through some processing, the PHP code is executed server-side, with results sent to the frontend. This is reminiscent of **Inertia**, but taken much further.\n\n> [!NOTE]\n> Please watch the video. It's fascinating, easy to follow, and will significantly aid in understanding the rest of this article.\n\nDuring [an interview on the Navbar podcast](https:\u002F\u002Fyoutu.be\u002FvqWlJCtoD-k?si=Xnd4MOa5-dcBvPhi), Aaron articulated Fusion's aim: to bridge the gap between the frontend and backend, simplifying development and transforming Laravel with Inertia to resemble current meta-frameworks like **Next** or **Nuxt**. With his business partner [Steve](https:\u002F\u002Fx.com\u002Fsteve_tenuto), Aaron discovered that typical Laravel conventions, such as routes in `web.php`, were not intuitive for JavaScript developers who prefer file-based routing. Aaron's goal is to make Laravel more accessible to JavaScript developers.\n\nWith Fusion, a new block is introduced in the Vue SFC to write PHP code, making it an integral part of the Laravel application.\n\n```vue\n\u003Cphp>\n  \u002F\u002F Define a prop in PHP\n  $name = prop(Auth::user()->name);\n\u003C\u002Fphp>\n\n\u003Ctemplate>\n  \u003C!-- Use it in Vue! -->\n  Hello {{ name }}!\n\u003C\u002Ftemplate>\n```\n\n---\n\nThis article doesn't aim to determine whether Fusion should be used in production or if embedding PHP in a Vue SFC is advisable. That debate is well-covered, especially on X or Reddit.\n\nInstead, I intend to delve into Fusion's workings for two main reasons:\n\n1. There's much to learn from examining code. Though I haven't read many books, I often peruse project codebases, stealing tricks and smart patterns overlooked elsewhere.\n2. Fusion embodies a technical pinnacle of what's achievable with **Laravel**, **Vue**, and **Vite**. Thus, my curiosity to understand its workings led me to write this analysis. I'm studying the [Alien Signals](https:\u002F\u002Fgithub.com\u002Fstackblitz\u002Falien-signals) library by [Johnson Chu](https:\u002F\u002Fgithub.com\u002Fjohnsoncodehk) similarly. Comprehending something deeply often involves articulating it to others, hence this article.\n\n> [!NOTE]\n> Vite's integration with Laravel is exceptional, offering a lot of benefits to Laravel developers. If you haven't explored **Vite** yet, you definitely should.\n\nNow, let's dive into the code.\n\n> [!NOTE]\n> Reading the codebase is intriguing; it resembles reading PHP written in JavaScript.\n\n## The Fusion codebase\n\nA week after presenting at [Laracon EU](https:\u002F\u002Flaracon.eu\u002F), Aaron made Fusion's codebase public. Curious about its mechanics, I speculated that a Vite plugin might extract the PHP from a Vue SFC, similar to how [Vite's Vue plugin](https:\u002F\u002Fgithub.com\u002Fvitejs\u002Fvite-plugin-vue) handles script, template, and style tags.\n\nSupporting HTML, CSS, and JavaScript is straightforward for Vite since they're within its scope. However, PHP presents a challenge since it's not supported inherently by Vite.\n\nLet's pose questions we will answer later. First: how does Fusion manage PHP code?\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    Fusion Hype Video by \u003Ca href=\"https:\u002F\u002Fx.com\u002Faarondfrancis\" target=\"_blank\">Aaron Francis\u003C\u002Fa> and \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\">available on X\u003C\u002Fa>.\n  \u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nWatching the hype video, it's evident there's more than just extracting PHP code and executing it. Fusion passes PHP code results as props to the Vue component and supports RPC on the Laravel backend from the client. Additionally, the keyword `sync` enables synchronization between frontend and backend states.\n\nFor instance, a function called `favorite` becomes readily usable in the 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\nAnother question arises: what does Fusion do behind the scenes to enable this functionality?\n\n\u003C!-- During its presentation, Aaron mentioned that Fusion supports Hot Module Reloading (HMR). This is a big deal, and I take up these works: \"if the experience doesn't feel nice, nobody's gonna use it\". I completely agree with this. But, third question: how does HMR work within Fusion? -->\n\nFinally, Fusion is said to push Inertia far beyond its current limits. So, another question: how does Fusion leverage Inertia?\n\nLet's address these questions one by one.\n\n1. How does Fusion manage PHP code?\n2. What mechanisms does Fusion employ behind the scenes to enable these capabilities?\n\u003C!-- 3. How does HMR work within Fusion? -->\n4. In what ways does Fusion utilize Inertia?\n\nReady for an intense exploration? Let's get started.\n\n## How does Fusion manage PHP code?\n\nEverything begins with [Vite](https:\u002F\u002Fvite.dev\u002F). _As usual._\n\nFor those new to it, **Vite** is a remarkably fast frontend build tool powering the next generation of web applications. It brought a new tooling philosophy known as on-demand philosophy. Essentially, Vite acts as a proxy between the browser, which requests files, and the file system holding them. When the browser requests a file, Vite intercepts the request, retrieves the file, and sends it back. However, acting as a proxy allows Vite additional capabilities, such as file transformation, code injection, remote file fetching, and generating virtual files (files not present on the file system). It's akin to a pipeline. Undoubtedly, Vite's advent has been transformative for the frontend ecosystem. By adding plugins to its pipeline, it can be tailored to suit any requirement.\n\nWith this in mind, we can grasp how a SFC file undergoes processing by Vite.\n\n```vue\n\u003Cscript setup>\n\u002F\u002F JavaScript code will go here\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv \u002F>\n  \u003C!-- Your template code will go here -->\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n  \u002F* Your CSS code will go here *\u002F\n\u003C\u002Fstyle>\n```\n\nWhen a `.vue` file request reaches the browser, a Vite plugin named [vite-plugin-vue](https:\u002F\u002Fgithub.com\u002Fvitejs\u002Fvite-plugin-vue) transforms the file, processing the script and template into readable JavaScript code. The CSS gets extracted and sent down the CSS pipeline before being dispatched to the browser. The cool aspect is the possibility to create a plugin capable of scrutinizing the transformation pipeline. Even better, there's no need to build it from scratch; that's precisely what the [vite-plugin-inspect](https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-plugin-inspect) plugin accomplishes.\n\nFor instance, a Vue SFC transformation looks like:\n\n![Vue SFC transformation seen using the plugin vite-plugin-inspect](\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fvue-sfc-transformation.png)\n\nAlright, thanks for the explanation, but what transpires in Fusion? Exactly the same thing but involving the PHP block. When a request contacts the Vite server using a `.vue` file, a plugin named [`fusion-vue`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L38-L40) extracts the PHP block. This process initiates in the [`transform`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L112) function below:\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\nThe `transform` function forms the crux of the Vite pipeline. It transforms files before they reach the browser. In Fusion's case, it passes the file (the entire content) to a class named `Transformer`, which manages the file's transformation. You might notice the condition `filename.endsWith('.vue')`, signifying that the transformation only applies to `.vue` files. This approach is a prevalent pattern among Vite plugins.\n\nThe Transformer class is responsible for:\n\n1. [Extracting the PHP block](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L16-L19) from the Vue SFC. Interestingly, it employs a parser from [@vue\u002Fcompiler-sfc](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Ftree\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fcompiler-sfc) to achieve this task. Notably, the `vite-plugin-vue` plugin uses this same parser, illustrating that adding a custom block to a Vue SFC is straightforward. I've observed this practice before; the [Vue I18n](https:\u002F\u002Fvue-i18n.intlify.dev\u002Fguide\u002Fintroduction.html) project supports [an `i18n` custom block](https:\u002F\u002Fvue-i18n.intlify.dev\u002Fguide\u002Fadvanced\u002Fsfc.html) specifying translations within a Vue SFC.\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![The Vue SFC transformed within the Vite pipeline](\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fvue-sfc-with-php-transformation.png)\n\n2. [Removing the PHP block](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L88-L91) from the Vue SFC.\n3. [Determining if the PHP code warrants persistence](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L96). Another critical aspect we'll delve into in the [following section](#what-does-fusion-do-behind-the-scenes-to-make-this-possible).\n4. [Validating and saving the PHP code](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L107). This verification occurs through an external process executing a Laravel command.\n\n```ts\nawait runPhp(['fusion:conform', this.relativeFilename])\n```\n\nAny PHP code errors trigger an error message [interpreted by Vite](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L124), so accurately that it displays on the browser to indicate an error in the PHP code block.\n\n![The Vite error overlay showing the error in the PHP code](\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fvite-error-overlay.png)\n\nIf functionality runs smoothly, the [PHP code, stored as an extracted file](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L68), gets saved in the `storage` folder. This aspect is noteworthy, as we will explore further in [the following section](#how-does-fusion-handle-the-php-code).\n\n5. Creating a JavaScript file [based on the PHP code](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L112) that bridges [any gaps between the frontend's needs](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FShim.php#L78-L113) and the PHP block's code. This interesting step demands further exploration.\n\nAgain, a Laravel command is invoked during this step:\n\n```ts\nawait runPhp(['fusion:shim', this.relativeFilename])\n```\n\n6. Finally, returns the transformed code to Vite, allowing the SFC to proceed on its path.\n\nConsider this for a moment. Allow me to illustrate something:\n\n![Schema showing all the steps we've seen so far](\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Ffusion-schema-vite-transform.png)\n\nIt can operate in this manner. In Fusion, Vue SFC files function as a file-based router akin to Nuxt's mechanism. The first request typically reaches the PHP server, which responds with the initial client-side code, which then targets the Vite server for additional resource requests. However, if a Vue SFC route remains unvisited, the corresponding PHP functionality won't be ready to handle it, resulting in a 404 error from the Laravel server. Consequently, the Vite server remains untouched, impeding the transformation process. This poses a hurdle, a huge one.\n\nTo fully comprehend how this issue is tackled, we must examine all aspects of the Vite plugin.\n\nWithin the Vite configuration, we find two noteworthy features:\n\n1. The [Vite configuration](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L42-L65) where Vite recognizes the location to find the generated JavaScript file (the shim), which receives [an alias](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L61) assigned in the Vite resolver. This alias obscures the file's location, encouraging best practices like avoiding hardcoded pathnames and rendering code location agnostic to path changes. This Vite-related configuration derives from a PHP config file to eliminate redundant configurations, so the [Fusion Vite plugin spawns a process](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L45) executing a Laravel command to resolve config necessities.\n2. A watcher [added to monitor project-wide changes](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L79) utilizing the Vite watcher, a [Chokidar instance](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 Do whatever you want\n  })\n}\n```\n\nThis watcher supports two functions:\n\n1. [Cleaning up](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L88) the database and storage folder upon file or folder deletion. The storage folder contains transformed Vue SFC files, serving as the file-based router. Without removal, the user can still access deleted files.\n2. [Triggering file transformation](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L97-L99). This operates identically to the `transform` function previously encountered, requiring manual file loading. Only Vue SFC files undergo 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\nBut why transform files upon creation or modification? This approach appears to contradict the on-demand philosophy and previously defined `transform` function. Yet, a rationale exists.\n\nConsider an error in the PHP block, such as an overlook in importing a function or class, or attempting to hit a route for the first time. When reloading the page, the PHP server returns an error message, as Laravel does by default. Yet, correcting the Vue SFC's PHP block won't rectify the PHP server error since the Vite transformation won't trigger anew. Consequently, repeatedly hitting `cmd+r` serves no purpose.\n\nFor this reason, the file system must be monitored for Vue SFC changes, triggering a manual transformation. While suboptimal, this method is necessary. Aaron devised a strategy prioritizing `transform` method transformations over watcher-triggered ones, with throttling applied to the watcher.\n\nThis is also to solve the problem we've seen earlier: when the PHP server is hit first, the generated PHP file isn't created yet.\n\nLet's summarize our learning thus far with this schematic:\n\n![A schema showing all the steps that makes Fusion work.](\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Ffusion-schema.png)\n\nThough we haven't completely covered every component, this diagram aids our understanding of the article's remainder.\n\n> [!NOTE]\n> Ensure the Vite server operates correctly for everything to function seamlessly.\n\nThis exploration deeply engaged us in Fusion's Vite plugin. Let's now move onward to grasp how Fusion organizes its PHP code handling.\n\n> [!NOTE]\n> Have questions? [Ask me anything](\u002Fama) or leave a comment below. I truly enjoy reading your feedback!\n\n## What does Fusion do behind the scenes to make this possible?\n\nLet's delve into the PHP-side operations.\n\nWe've seen the PHP code extracted, validated, and saved on the disk. We now need to unravel two pivotal steps to better comprehend Fusion's operation:\n\n1. Determining if the PHP code should be persisted.\n2. Saving the PHP code to the file system.\n\nFor ease of understanding, let's tackle the second step first. The first step will subsequently become clear.\n\n### Writing the PHP code on the file system\n\nOnce extracted from the Vue SFC, the PHP code requires persistence on the file system. This process is crucial since the PHP written within the Vue SFC becomes an integral part of the Laravel application, not merely an independent PHP script. This generated file is essential for the Laravel application to respond to incoming requests.\n\nAfter extraction, the PHP code is placed in [a temporary file](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L97). This temporary file maintains the exact code content inherent to the Vue SFC. Subsequently, Fusion creates an entry in a [dedicated SQLite database](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FDatabase.js#L3). We'll revisit this database later, understanding it facilitates communication between the PHP and JavaScript processes.\n\nFinally, the Vite plugin instigates the [`fusion:conform` command](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L107).\n\n### The `fusion:conform` command\n\nWe've delayed understanding this command; now's the moment to clarify. Within the `fusion:conform` command, a Laravel command written in PHP, neither Vite nor JavaScript applies, strictly PHP.\n\nUpon invocation, this command utilizes a `src` argument representing the Vue SFC file's path. Acting like a unique identifier allows Fusion to [retrieve the pertinent database entry](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L38).\n\nThis entry contains the destination path for the final PHP file to be used by the Laravel application. Currently, this file doesn't exist; only a temporary file holds the code. Thus, the next step involves reading [the temporary file content](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L44-L47) prior to further processing.\n\nThe subsequent process enlists a [conformer class](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L56), assigned to:\n\n1. Altering the code to confer compatibility with the Laravel application.\n2. Ensuring code validity by passing it through a PHP parser.\n\nThe function examines PHP code's conformity by running its transformation through a sequence of [transformer classes](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConformity\u002FConformer.php#L65C24-L79).\n\nEach transformer class extends a `Transformer` super-class, custom-built to expand `NodeVisitorAbstract` and deploys the `TransformerInterface` sourced from [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\nEach custom transformer, furthermore, implements the `enterNode` method to shift the code transformation.\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 Perform some operation\n        }\n    }\n}\n```\n\n> [!NOTE]\n> Detailed exploration into transformers or PHP parsers falls outside this article's scope as they offer advanced functionality not immediately necessary to comprehend Fusion's operation.\n\nIn total, ten transformers are involved, each one with a specific responsibility. Conceive them as steps within a pipeline, a conventional pattern in programming designed to disassemble intricate tasks into smaller, approachable segments. Consequently, when the user code undergoes transformation and is rendered compatible with the Laravel app, the output from one transformer becomes the next's input.\n\n```txt\nPHP code from the Vue SFC\n    | |\n[Procedural Transformer]: Turns procedural code into an anonymous class method entitled runProceduralCode.\n[Props Transformer]: Changes prop() function calls into this object's method calls within the class.\n[Function Import Transformer]: Strips unnecessary function imports, specifically pre-known ones.\n[Prop Value Transformer]: Stipulates that $this->prop() method call chains terminate with ->value().\n[Expose Transformer]: Alters expose() function calls into this object method calls, barring variable assignments.\n[Mount Transformer]: Validates and modifies mount() function calls, ensuring single anonymous function arguments.\n[Prop Discovery Transformer]: Uncovers and documents prop() function property names.\n[Action Discovery Transformer]: Discovers and constructs methods for action handlers exposed via expose().\n[Anonymous Return Transformer]: Guarantees direct expression returns for anonymous class returns.\n[Anonymous Class Transformer]: Converts anonymous classes into named classes, within a generated namespace, gathering necessary requirements.\n    | |\nUsable PHP code\n```\n\nConsider a concrete example before continuing. Assume the following PHP code exists within the Vue SFC:\n\n```vue\n\u003Cphp>\nuse function \\Fusion\\{prop};\n\n$name = prop('bienvenue');\n\n$name = strtoupper($name);\n\u003C\u002Fphp>\n```\n\nAfter passing through the transformation, this results:\n\n```php\n\u003C?php\n\n\u002F**\n * File auto-generated by Fusion.\n * Alterations aren't advisable.\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\nThis class `IndexGenerated` will be used by the Laravel application upon handling requests at the '\u002F' route. Visualize it as a mini-controller containing user code. Behind the scenes, much of the complexity is abstracted within the [`FusionPage` class](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L22).\n\nThe `discoveredProps` array records property names processed during **prop()** function calls, ensuring synchronization between frontend and backend properties.\n\nThe method `runProceduralCode`, invoked upon page loading, executes user code alongside Fusion-generated code, ensuring prop synchronization between the frontend and backend.\n\nThe [`syncProps`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConcerns\u002FIsProceduralPage.php#L36) method maintains backend-to-frontend property synchronization. It retrieves defined variables within the method, and discoveredProps array content, ensuring the generated `props` property solely holds user-requested data.\n\n> [!NOTE]\n> Using `get_defined_vars`, a built-in PHP function, returns a current scope-representative variable array. In this case, it constitutes `['name' => 'bienvenue']`.\n\n> [!NOTE]\n> `#[ServerOnly]` classifies properties for server-side application exclusively. Analogous [TypeScript](https:\u002F\u002Fwww.typescriptlang.org\u002Fdocs\u002Fhandbook\u002Fdecorators.html) decorators function similarly under [reflect-metadata](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Freflect-metadata).\n\nFinally, Fusion places transformed content in its database-provided file path, updating associated entries with the class's legislative name comprising namespace and class designation. The procedure remains enclosed in a `try\u002Fcatch` block, ready to address any transformational errors arising. Upon encountering errors, [a transient file gets deleted](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L131) while [an error message directs to Vite](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L124), as noted earlier.\n\nAt this stage, our file system now appears thus:\n\n\u003CTree\n  name=\"A Fusion Application\"\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\nNext, the Vite plugin resumes operation, as detailed in the [next section](#handling-incoming-requests).\n\n### The database\n\nSince discussing this article, we've referred to a database but withheld specifics until now. It's time to understand its purpose!\n\nThe database assists in bridging communication between the PHP and JavaScript processes, storing essential information for this purpose. With a glance at the database, we can discern stored data and its utilization.\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\nDuring Vue SFC transformation and PHP code extraction, a [hash calculation](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L50) occurs, saved within the `php_hash` column of the database.\n\nThe hash serves to verify whether [PHP code alteration](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L96) occurred. A matching hash means unchanged PHP code, introducing an optimization to omit redundant transformations.\n\n> [!NOTE]\n> A hash is a one-way function generating a fixed-size character string from input, typically comprising numeric and alphanumeric sequences. A hash uniquely identifies its input, meaning identical hashes indicate identical inputs.\n\nThis database repository embodies additional data like the PHP and JavaScript file paths used mainly to handle incoming requests efficiently.\n\nAn additional question emerges: How does the SQLite database affect the developer experience?\n\nWhile utilizing a SQLite database, Fusion does not dictate the Laravel application's database choice: the two remain entirely distinct.\n\nFusion's service provider assign [a database connection called `__fusion`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FProviders\u002FFusionServiceProvider.php#L103C1-L110C12) leveraging SQLite, which does not interfere with the Laravel application database connection. This means that it's possible to have multiple database connections within a single Laravel application. Really nice to know!\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> A Laravel service provider executes during application bootstrapping to register services or bind classes into the service container. It's a pivotal location to administer and configure services.\n\nThe [Component model](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FModels\u002FComponent.php#L7) now uses this connection:\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\nWith this configuration, the `Component` class utilizes the `__fusion` database connection. Consequently, the SQLite database is only used by Fusion; it exerts no visible impact on the Laravel app, allowing the application's free choice regarding its database connection method. It's a straightforward and smartly implemented strategy.\n\n### Handling incoming requests\n\nDelve into perhaps the most intriguing article segment: handling incoming requests through Fusion.\n\nUpon starting to read, we've largely covered development, including Vite plugin functioning and PHP transformation. Our task now incorporates understanding how Fusion harmonizes these elements for request management.\n\nFusion employs a file-based router approach. Vue SFCs automatically register themselves as routes within the Laravel app, achieved by calling [`Fusion` as a facade](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusion.php), supported by the [Fusion manager](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionManager.php), then [registered to the `FusionServiceProvider`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FProviders\u002FFusionServiceProvider.php#L30) as part of the typical routing configuration in `routes\u002Fweb.php`.\n\n```php\n\u003C?php\n\nuse Fusion\\Fusion;\n\nFusion::pages();\n```\n\nSimply stated, this appears to be the entire explanation! However, we continue, unraveling more complexities. Within the method `pages`, [all Vue SFC components](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionManager.php#L67) housed within `resources\u002Fjs\u002FPages` are located and [registered meticulously](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionManager.php#L83). Employing the familiar [Laravel router](https:\u002F\u002Flaravel.com\u002Fdocs\u002F11.x\u002Frouting) facilitates implementation.\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\nNotably, the routing process assigns two default values:\n\n1. `__component`: Path leading to the Vue SFC file.\n2. `__class`: Full qualified PHP class name, this class was generated within the `fusion:conform` command.\n\nConsequently, when a request accesses the `\u002F` route, the `handle` operation of the `FusionController` activates. Within the controller lies a multilayered matrix handling diverse potentialities while managing props, synchronizing actions, and interfacing seamlessly with the frontend.\n\nUpon concluding request management, an appropriate response dispatches, guided by request nature as follows:\n\n1. HMR or action requests receive [a JSON response](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L61-L63).\n2. Standard requests delegate rendering tasks [to Inertia](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L65-L67).\n\nOn close inspection, the `data-page` attribute reveals the Inertia object alongside associated Fusion components:\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\nThe `data-page` attribute is reformatted for clarity:\n\n```json\n{\n  \"component\": \"Index\",\n  \"props\": {\n    \"errors\": {},\n    \"auth\": {\n      \"user\": null\n    },\n    \"fusion\": {\n      \"meta\": [],\n      \"state\": {\n        \"name\": \"AARON\"\n      },\n      \"actions\": [\n        {\n          \"handler\": \"applyServerState\",\n          \"priority\": 40,\n          \"_handler\": \"Fusion\\\\Http\\\\Response\\\\Actions\\\\ApplyServerState\"\n        }\n      ]\n    }\n  },\n  \"url\": \"\u002F\",\n  \"version\": \"f7e418d032bf1c4cc3cc55d947cab622\",\n  \"clearHistory\": false,\n  \"encryptHistory\": false\n}\n```\n\nThe `fusion` object is of interest. It's composed of data initialized in the Vue SFC's PHP block through the **prop** function; comprising **state** and **actions** injected onto the client accordingly. The `PendingResponse` class, responsible for handling, specifically classifies these operations within Fusion [as follows](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FHttp\u002FResponse\u002FPendingResponse.php#L31-L43).\n\nSpecifically, the `applyServerState` action employs:\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\nDevelopers benefit by automatically leveraging action processing by Fusion without manual calls.\n\nI haven't disclosed everything yet. Besides extracting from the Vue SFC PHP block, Fusion injects JavaScript logic into the Vue SFC, preceding the transformation. What began as an initial component:\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  Hello {{ name }}!\n\u003C\u002Ftemplate>\n```\n\nTransmutes during the Fusion plugin transformation (later handled by Vite) as:\n\n```vue\n\u003Cscript setup>\nimport { useFusion } from '$fusion\u002FPages\u002FIndex.js'\nimport useHotFusion from '@fusion\u002Fvue\u002Fhmr'\n\n\u002F\u002F __props is a magic variable provided to script setup by 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  Hello {{ name }}!\n\u003C\u002Ftemplate>\n```\n\nNote the synthetically injected [`script setup` block](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Finjectors\u002FcreateScriptSetup.js), undemanding manual input. It features a `useFusion` composable, accepting two arguments, first detailing prop keys requiring extraction (honed through recognizing PHP variables via **prop** calls) and a `prop` argument signifying props. Essentially akin to employing `const props = defineProps()`. Fusion triggers the `__props` variable, provided by Vue, enabling developers to write their `script setup` block uninhibited. This allows typical Vue SFC syntax unfettered by Fusion processes.\n\nIn essence, composable values `name` and `fusion` extract via `script setup` with seamless template injection.\n\nWhere did `useFusion` originate? The `$fusion\u002FPages` prefix divulges the broader picture.\n\nConcerning `$fusion\u002FPages`, we've already seen its functionality in action via the Vite plugin-generated alias. This alias abstracts out the JavaScript file's genuine location, birthed from an earlier undertaking formulating the `fusion:shim`. Its content reads:\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\nThe `state` and `fusion` variables live within this file, mirroring declarations from the Vue SFC in PHP functions **prop** and **expose**. Accompanied by an `ActionFactory` responsible for applying server-mandated actions to the state, including `applyServerState`, automatic, and transparent synchronization occurs.\n\nIn total, Fusion provides several pre-established actions, such as:\n\n- [`applyServerState`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002FapplyServerState.js): Synchronizes backend and client-side state data, updating state to match server expectations in real-time.\n- [`syncQueryString`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002FsyncQueryString.js): Synchronizes query string parameters and state.\n- [`log`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002Flog.js): Allows logging for debugging purposes.\n- [`logStack`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002FlogStack.js): Logs action progression for debugging.\n\nThe discussion becomes even more absorbing when seeing the `expose` function. This method exposes a PHP functionality server-side to the client, creating RPC-like client-server behaviors allowing direct client requests to trigger server-side action.\n\n> [!NOTE]\n> The `expose` concept shares kinship with approaches like [tRPC](https:\u002F\u002Ftrpc.io\u002F) and [React Server Functions](https:\u002F\u002Freact.dev\u002Freference\u002Frsc\u002Fserver-functions).\n\nAn illustrative example:\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    Favorite\n  \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n```\n\nExposing a method like `favorite` facilitates its use within the template. `useFusion` composable previously outlined handles functionality, yet Fusion ensures thorough processing enabling client-side button interaction to trigger server-backed operations.\n\n1. The client [sends a server request](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FActionFactory.js#L72-L77) furnished with headers `x-fusion-action-request` and `x-fusion-action-handler`.\n2. Backend processes these requests, aided by the provided metadata, responding to the client accordingly like [how Inertia handles its protocol with headers](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FHttp\u002FControllers\u002FFusionController.php#L50).\n3. Client-side state corresponds to server-sent feedback, [after processing](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FActionFactory.js#L87).\n\nClicking on the favorite button from the example above generates this server-targeted request:\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\nMeanwhile, the server processes such a request by interpreting the headers `x-fusion-action-request` and `x-fusion-action-handler`. In its service provider, Fusion [registers request macros for easy request type identification and handling](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FProviders\u002FFusionServiceProvider.php#L62-L65).\n\nInside the request body, `args` holds the method's arguments (podcast), while `state` comprises the app's current state.\n\nIn response, the server sends the following:\n\n```json\ntrue\n```\n\nWhich is the response from the `favorite` method. The client then updates the state accordingly.\n\nFusion offers a `sync` method fostering frontend-backend state synchronization. For example, a search field could filter items based on backend computations, achieved via `sync`. Essentially, sync mimics an action categorized by the `fusionSync` handler!\n\n\u003C!--## How does HMR work within Fusion?\n\nIn ensuring the best developer experience, Fusion leverages Vite's Hot Module Replacement (HMR). Consequently, making changes to your Vue SFC transcends full-page reloads.\n\n**Awaiting Aaron's input**\n\n-->\n\n## How does Fusion use Inertia?\n\nIn truth, Fusion ingeniously simplifies and relies on Inertia's seamless integration to process Vue SFCs effectively without reinventing its frontend framework. The [Inertia::render](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L65-L67) method exemplifies Inertia-based rendering while [createInertiaApp](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fapps\u002Fvue\u002Fresources\u002Fjs\u002Fapp.js#L11) establishes the Vue app environment efficiently, counting as the full extent of Inertia's footprint within the Fusion project.\n\nBuilding on top of Inertia proves prudent, allowing Aaron to focus innovation on Fusion's purpose without distraction by Vue integration intricacies within Laravel. Inertia's utility renders any redundancy void. Additionally, transcending mid-journey evolves Fusion to encompass other frameworks like React seamlessly.\n\n## Final thoughts\n\nFusion has a potential, especially for developers transitioning from Next or Nuxt to a full-stack capability, eagerly embracing Laravel with Inertia. By preserving concepts dear to developers of these frameworks, such as file-based routing and RPC-driven communication, while enriching them with Laravel's power, Fusion becomes a compelling choice. You should definitely try it out!\n\nWithin Fusion's roadmap, Aaron envisions React compatibility, achievable by standing on Inertia's shoulders. However, I'm eager to see Aaron's take on handling server code execution within JSX, perhaps using a `'use php'` directive? 🤔\n\nThis project illustrates Vite and Laravel's deep relationship's strength effectively. Accessing Vite's comprehensive utility effortlessly remains a groundbreaking capability. It also reaffirms the SFC's modular capacity in Vue, Fusion surely confirms it. There are no practical limits to what you can achieve using Vite and Vue SFC components!\n\nFinally, Fusion's inter-process SQLite database communication reinforces an elegant, smart pattern!\n\nThe article made for an exciting write while I was exploring Fusion's codebase. I hope it unearthed new insights for you, and if keen on delving into the intricacies of another library, feel free to comment or use the [Ask Me Anything](\u002Fama) page.\n\nBut I've got an additional treat for you! 👇\n\n## One more thing\n\nWhile drafting this article, prevalent PHP block code examples lacked appropriate syntax highlighting. I pondered and decided to tackle this task. Since **Shiki** is used for syntax highlighting code in this website and it uses **TextMate grammars**, I know that I could add support for the PHP block in VSCode.\n\nBut how?\n\nDiving in, I realized embedding languages in single-file contexts isn't new. Vue SFCs are notable, as are Markdown, Angular, and even HTML entailing different languages. Subsequently, I focused on Vue SFC's TextMate grammar.\n\n> [!NOTE]\n> A TextMate grammar defines coding language syntax highlights through patterns and sets rules. Operating behind VSCode and Shiki, it's the syntax highlighter's engine.\n\nExamining [Shikijs' textmate-grammars-themes repository](https:\u002F\u002Fgithub.com\u002Fshikijs\u002Ftextmate-grammars-themes\u002F), catering to every language and theme supported by Shiki unearthed the [Vue grammar](https:\u002F\u002Fgithub.com\u002Fshikijs\u002Ftextmate-grammars-themes\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Ftm-grammars\u002Fgrammars\u002Fvue.json). Although lengthy, illuminating concepts surface when reading through, such as HTML, CSS, and JavaScript embeddings within respective blocks:\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\nThis handles JavaScript highlighting within `\u003Cscript>` blocks using the JavaScript grammar inclusion through `include`. The same pattern presumably applies to other languages, including PHP.\n\nThis is exactly what I want to achieve, but with `\u003Cphp>` and PHP grammar. Of course, there's already a PHP grammar, so I just need to include it the same way. I try, and it works. Youpi! 🥳\n\nBut wait, I can't submit a PR to [Vue Language Tools](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Flanguage-tools) (Shiki fetches the grammar from this repo) to add support for PHP grammar. That makes no sense, and there's no reason for them to accept it. So I need to find another solution.\n\nAfter continuing my search, I found the [angular-inline-style](https:\u002F\u002Fgithub.com\u002Fshikijs\u002Ftextmate-grammars-themes\u002Fblob\u002Fmain\u002Fpackages\u002Ftm-grammars\u002Fgrammars\u002Fangular-inline-style.json) grammar. This one is used to highlight styles (CSS, SCSS) inside an Angular component (TypeScript). To do so, the grammar uses the keyword injectTo, which acts as an extension of an existing grammar, in this case, source.ts.ng. This is perfect! What if I created a new grammar that extends the Vue grammar by injecting the PHP grammar? In retrospect, this sounded easier than it actually was.\n\nAfter a couple of hours of trial and error, I finally managed to create a grammar that works in Shiki. One of the most complicated parts was figuring out what was wrong when things didn't work. With this kind of setup, there are no error messages, no warnings, nothing. You just have to guess what's wrong. You can find the working solution in the source code of [my code-to-image generator](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fcode.soubiran.dev).\n\nFinally, I created a VSCode extension that anyone can use to add support for PHP blocks in Vue SFCs. You can find it on the [marketplace](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Ffusion-vscode) along with the working grammar: [source.vue.php](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Ffusion-vscode\u002Fblob\u002Fmain\u002Fsyntaxes\u002Fvue.php.tmLanguage.json).\n\nIf Fusion becomes popular, I might eventually submit a PR to add the language to Shiki, making it easier for everyone to highlight PHP blocks.\n\nHappy coding! 👨‍💻\n",{"title":24171,"description":29191},{"loc":29193,"images":29197},[29198,29199,29200,29201,29202,29203],{"loc":24222},{"loc":24826},{"loc":25138},{"loc":25213},{"loc":25290},{"loc":25654},"posts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components","JkTTBiM8aHS7De3tyWhnrjwQsnWcbFXWo533u___Seo",{"id":29207,"title":29208,"body":29209,"contentId":9247,"date":30701,"description":30702,"extension":18,"meta":30703,"navigation":20,"path":30704,"rawbody":30705,"robots":23,"schemaOrg":23,"seo":30706,"sitemap":30707,"stem":30713,"__hash__":30714},"posts\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin.md","Laravel and Vite: A Love Story Ruined with Cross-Origin",{"type":7,"value":29210,"toc":30694},[29211,29216,29222,29235,29239,29248,29254,29263,29271,29274,29280,29283,29291,29329,29337,29343,29346,29350,29362,29368,29371,29374,29377,29391,29394,29521,29524,29657,29668,29671,29684,29688,29691,29694,29702,29712,29720,29723,29766,29769,29813,29816,29831,29834,29899,29902,29905,29908,30079,30082,30095,30106,30110,30113,30116,30125,30132,30223,30235,30238,30247,30252,30255,30264,30267,30270,30276,30513,30526,30529,30532,30535,30538,30552,30571,30576,30650,30657,30660,30666,30669,30672,30678,30682,30685,30691],[35,29212,29213],{},[1217,29214,29215],{},"This title might sound dramatic, but trust me, it's worth your read. Dive in!",[35,29217,29218,29219,29221],{},"It's great to be back to my blog. I've been away due to numerous conference preparations and a major internal updates to this website. Although these aren't today's topics, I'll certainly cover them in future posts. Also, this is my first article on ",[6850,29220,19447],{},", and it won't be the last!",[35,29223,29224,29225,1638,29227,29229,29230,215],{},"Today, I want to share an experience I had with ",[6850,29226,19447],{},[6850,29228,24355],{}," because of the strictness of the ",[39,29231,29234],{"href":29232,"rel":29233},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_Workers_API\u002FUsing_web_workers",[77],"Web Worker API",[51,29236,29238],{"id":29237},"the-laravel-and-vite-integration","The Laravel and Vite integration",[35,29240,29241,29242,29244,29245,29247],{},"Before delving into the problem, it's crucial to understand how ",[6850,29243,24355],{}," operates within a ",[6850,29246,19447],{}," project.",[35,29249,29250,29251,29253],{},"First, what is ",[6850,29252,24355],{},"? It's a fast frontend build tool that powers next-gen web applications. It allows you to construct your frontend assets (JavaScript, CSS, etc.) efficiently, especially during development. The core of Vite is a plugin pipeline that transforms source files into the required outputs, a point we'll revisit soon.",[35,29255,29256,29257,29259,29260],{},"And ",[6850,29258,19447],{},"? It's a web application framework known for its expressive, elegant syntax. Laravel eases common tasks like authentication, routing, sessions, and caching, aiming to make development enjoyable without compromising functionality. ",[1217,29261,29262],{},"And it excels at it!",[35,29264,29265,29266,1638,29268,29270],{},"When you embrace ",[6850,29267,19447],{},[6850,29269,24355],{},", you get a formidable stack for web application development.",[35,29272,29273],{},"Sounds great, right? But how does it works practically? Let me illustrate.",[35,29275,29276],{},[1690,29277],{"alt":29278,"src":29279},"Laravel and Vite integration","\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Flaravel-vite-request-response.png",[35,29281,29282],{},"This image offers a high-level overview of the Laravel and Vite architecture. During development, two servers run: one for Laravel and one for Vite.",[68,29284,29285,29288],{},[71,29286,29287],{},"A user requests a page from the Laravel server.",[71,29289,29290],{},"The Laravel server sends the HTML page to the user. Before sending the response, Laravel adds a link tag to the entry script served by the Vite server. For example, you might find this line in the HTML returned by the Laravel server:",[192,29292,29294],{"className":591,"code":29293,"language":593,"meta":13,"style":13},"\u003Cscript type=\"module\" src=\"http:\u002F\u002Flocalhost:5173\u002Findex.ts\">\u003C\u002Fscript>\n",[156,29295,29296],{"__ignoreMap":13},[200,29297,29298,29300,29302,29304,29306,29308,29310,29312,29314,29316,29318,29321,29323,29325,29327],{"class":202,"line":203},[200,29299,600],{"class":214},[200,29301,14892],{"class":235},[200,29303,321],{"class":379},[200,29305,449],{"class":214},[200,29307,611],{"class":214},[200,29309,15093],{"class":245},[200,29311,611],{"class":214},[200,29313,10106],{"class":379},[200,29315,449],{"class":214},[200,29317,611],{"class":214},[200,29319,29320],{"class":245},"http:\u002F\u002Flocalhost:5173\u002Findex.ts",[200,29322,611],{"class":214},[200,29324,10192],{"class":214},[200,29326,14892],{"class":235},[200,29328,648],{"class":214},[68,29330,29331,29334],{"start":255},[71,29332,29333],{},"The user's browser loads the HTML and detects the script tag pointing to the Vite server, then requests the script from the Vite server.",[71,29335,29336],{},"The Vite server delivers the script to the user's browser.",[35,29338,29339,29340],{},"This process is straightforward and highly effective during development. It gives you access to Vite's full power with the plugin pipeline and hot module replacement (HMR). ",[1217,29341,29342],{},"Many developers might not fully appreciate how seamlessly this integration works compared to other tools.",[35,29344,29345],{},"However, sometimes things get tricky. And that's where our story begins.",[51,29347,29349],{"id":29348},"the-context","The context",[35,29351,29352,29353,29358,29359],{},"For a project, I had to use the ",[39,29354,29357],{"href":29355,"rel":29356},"https:\u002F\u002Fmicrosoft.github.io\u002Fmonaco-editor\u002F",[77],"Monaco Editor",", a browser-based code editor and the core of Visual Studio Code. The aim was to display a JSON in the code editor and validate it against a JSON schema. Users can edit this JSON, and the Monaco Editor, with JSON schema validation, will show in real time whether the JSON is valid or suggest corrections. Amazingly efficient when it works! ",[1217,29360,29361],{},"And it has been much more complexe than expected.",[35,29363,29364],{},[1690,29365],{"alt":29366,"src":29367},"Monaco Editor with a familiar interface if you're using Visual Studio Code","\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Fmonaco-editor.png",[35,29369,29370],{},"To achieve this, the Monaco Editor requires starting some Web Workers. Why and what are Web Workers?",[35,29372,29373],{},"JavaScript, especially in browser environments, operates on a single thread, meaning it executes one task at a time. So, if a piece of code takes long to execute, the browser becomes unresponsive which degrades the user experience. This is why web developers advise against blocking the main thread.",[35,29375,29376],{},"To handle long tasks without affecting performance, we have two options:",[1576,29378,29379,29385],{},[71,29380,29381,29384],{},[6850,29382,29383],{},"Asynchronous JavaScript",": This involves breaking down tasks into smaller chunks and executing them asynchronously. This way, the main thread remains free to handle other tasks.",[71,29386,29387,29390],{},[6850,29388,29389],{},"Web Workers",": For more complex tasks, especially those requiring heavy computation, we use Web Workers. A Web Worker is essentially a JavaScript file running in the background, separate from other scripts, with which it can communicate via messages.",[35,29392,29393],{},"Here's a simple example of a Web Worker script:",[192,29395,29398],{"className":1370,"code":29396,"filename":29397,"language":1373,"meta":13,"style":13},"globThis.onmessage = function (e) {\n  console.log('Message received from main script')\n  const workerResult = `Result: ${e.data[0] * e.data[1]}`\n  console.log('Posting message back to main script')\n  postMessage(workerResult)\n}\n","worker.js",[156,29399,29400,29423,29442,29486,29505,29517],{"__ignoreMap":13},[200,29401,29402,29405,29407,29410,29412,29414,29416,29419,29421],{"class":202,"line":203},[200,29403,29404],{"class":210},"globThis",[200,29406,215],{"class":214},[200,29408,29409],{"class":223},"onmessage",[200,29411,510],{"class":214},[200,29413,4987],{"class":379},[200,29415,1095],{"class":214},[200,29417,29418],{"class":386},"e",[200,29420,2089],{"class":214},[200,29422,280],{"class":214},[200,29424,29425,29427,29429,29431,29433,29435,29438,29440],{"class":202,"line":14},[200,29426,1113],{"class":210},[200,29428,215],{"class":214},[200,29430,1118],{"class":223},[200,29432,227],{"class":235},[200,29434,249],{"class":214},[200,29436,29437],{"class":245},"Message received from main script",[200,29439,249],{"class":214},[200,29441,416],{"class":235},[200,29443,29444,29446,29449,29451,29453,29456,29458,29460,29462,29465,29467,29469,29472,29475,29477,29479,29481,29483],{"class":202,"line":255},[200,29445,3723],{"class":379},[200,29447,29448],{"class":210}," workerResult",[200,29450,510],{"class":214},[200,29452,11139],{"class":214},[200,29454,29455],{"class":245},"Result: ",[200,29457,4853],{"class":214},[200,29459,29418],{"class":210},[200,29461,215],{"class":214},[200,29463,29464],{"class":210},"data[",[200,29466,3933],{"class":2045},[200,29468,18836],{"class":210},[200,29470,29471],{"class":214},"*",[200,29473,29474],{"class":210}," e",[200,29476,215],{"class":214},[200,29478,29464],{"class":210},[200,29480,3188],{"class":2045},[200,29482,360],{"class":210},[200,29484,29485],{"class":214},"}`\n",[200,29487,29488,29490,29492,29494,29496,29498,29501,29503],{"class":202,"line":272},[200,29489,1113],{"class":210},[200,29491,215],{"class":214},[200,29493,1118],{"class":223},[200,29495,227],{"class":235},[200,29497,249],{"class":214},[200,29499,29500],{"class":245},"Posting message back to main script",[200,29502,249],{"class":214},[200,29504,416],{"class":235},[200,29506,29507,29510,29512,29515],{"class":202,"line":283},[200,29508,29509],{"class":223},"  postMessage",[200,29511,227],{"class":235},[200,29513,29514],{"class":210},"workerResult",[200,29516,416],{"class":235},[200,29518,29519],{"class":202,"line":300},[200,29520,1130],{"class":214},[35,29522,29523],{},"With its main script:",[192,29525,29528],{"className":1370,"code":29526,"filename":29527,"language":1373,"meta":13,"style":13},"const worker = new Worker('worker.js')\n\nworker.addEventListener('message', (e) => {\n  console.log('Message received from worker')\n  console.log(e.data)\n})\n\nworker.postMessage([2, 3])\n","main.js",[156,29529,29530,29554,29558,29589,29608,29626,29632,29636],{"__ignoreMap":13},[200,29531,29532,29534,29537,29539,29541,29544,29546,29548,29550,29552],{"class":202,"line":203},[200,29533,443],{"class":379},[200,29535,29536],{"class":210}," worker ",[200,29538,449],{"class":214},[200,29540,12997],{"class":214},[200,29542,29543],{"class":223}," Worker",[200,29545,227],{"class":210},[200,29547,249],{"class":214},[200,29549,29397],{"class":245},[200,29551,249],{"class":214},[200,29553,416],{"class":210},[200,29555,29556],{"class":202,"line":14},[200,29557,529],{"emptyLinePlaceholder":20},[200,29559,29560,29563,29565,29568,29570,29572,29575,29577,29579,29581,29583,29585,29587],{"class":202,"line":255},[200,29561,29562],{"class":210},"worker",[200,29564,215],{"class":214},[200,29566,29567],{"class":223},"addEventListener",[200,29569,227],{"class":210},[200,29571,249],{"class":214},[200,29573,29574],{"class":245},"message",[200,29576,249],{"class":214},[200,29578,550],{"class":214},[200,29580,1095],{"class":214},[200,29582,29418],{"class":386},[200,29584,2089],{"class":214},[200,29586,393],{"class":379},[200,29588,280],{"class":214},[200,29590,29591,29593,29595,29597,29599,29601,29604,29606],{"class":202,"line":272},[200,29592,1113],{"class":210},[200,29594,215],{"class":214},[200,29596,1118],{"class":223},[200,29598,227],{"class":235},[200,29600,249],{"class":214},[200,29602,29603],{"class":245},"Message received from worker",[200,29605,249],{"class":214},[200,29607,416],{"class":235},[200,29609,29610,29612,29614,29616,29618,29620,29622,29624],{"class":202,"line":283},[200,29611,1113],{"class":210},[200,29613,215],{"class":214},[200,29615,1118],{"class":223},[200,29617,227],{"class":235},[200,29619,29418],{"class":210},[200,29621,215],{"class":214},[200,29623,12726],{"class":210},[200,29625,416],{"class":235},[200,29627,29628,29630],{"class":202,"line":300},[200,29629,413],{"class":214},[200,29631,416],{"class":210},[200,29633,29634],{"class":202,"line":310},[200,29635,529],{"emptyLinePlaceholder":20},[200,29637,29638,29640,29642,29645,29647,29649,29651,29654],{"class":202,"line":336},[200,29639,29562],{"class":210},[200,29641,215],{"class":214},[200,29643,29644],{"class":223},"postMessage",[200,29646,25183],{"class":210},[200,29648,4782],{"class":2045},[200,29650,550],{"class":214},[200,29652,29653],{"class":2045}," 3",[200,29655,29656],{"class":210},"])\n",[35,29658,29659,29660,29662,29663,29665,29666,215],{},"In this example, the main script creates a new worker and sends it a message using ",[156,29661,29644],{},". The worker receives and responds with ",[156,29664,29644],{},". Due to JavaScript's event-driven nature, the main script listens for the worker's message using ",[156,29667,29567],{},[35,29669,29670],{},"In our case, Monaco Editor leverages Web Workers for certain features, including JSON schema validation.",[939,29672,29673],{},[35,29674,29675,29677,29678,29683],{},[200,29676,945],{},"\nUsing a Web Worker allows the Monaco Editor to run a ",[39,29679,29682],{"href":29680,"rel":29681},"https:\u002F\u002Fmicrosoft.github.io\u002Flanguage-server-protocol\u002F",[77],"LSP"," server in the background for each language, providing more advanced features without impacting the main thread and the user experience.",[51,29685,29687],{"id":29686},"the-issue","The issue",[35,29689,29690],{},"But here's the catch:",[35,29692,29693],{},"The script passed to the Web Worker must be accessible from the same origin as the main script. This is where the problem arises.",[939,29695,29696],{},[35,29697,29698,29701],{},[200,29699,29700],{},"!WARNING","\nThis is a security feature to prevent cross-origin attacks so it's impossible to bypass it.",[35,29703,29704,29705,1638,29708,29711],{},"An origin is defined by a URL's scheme, host, and port. This means ",[156,29706,29707],{},"https:\u002F\u002Fexample.com",[156,29709,29710],{},"https:\u002F\u002Fexample.com:1024"," are considered different origins.",[35,29713,29714,29715,29717,29718,215],{},"Imagine we're on the page ",[156,29716,29707],{}," and you want to create a Web Worker. The script URL passed to the Web Worker must be accessible from ",[156,29719,29707],{},[35,29721,29722],{},"This means that this works:",[192,29724,29726],{"className":591,"code":29725,"language":593,"meta":13,"style":13},"\u003Cscript>\n  const worker = new Worker('worker.js')\n\u003C\u002Fscript>\n",[156,29727,29728,29736,29758],{"__ignoreMap":13},[200,29729,29730,29732,29734],{"class":202,"line":203},[200,29731,600],{"class":214},[200,29733,14892],{"class":235},[200,29735,648],{"class":214},[200,29737,29738,29740,29742,29744,29746,29748,29750,29752,29754,29756],{"class":202,"line":14},[200,29739,3723],{"class":379},[200,29741,29536],{"class":210},[200,29743,449],{"class":214},[200,29745,12997],{"class":214},[200,29747,29543],{"class":223},[200,29749,227],{"class":210},[200,29751,249],{"class":214},[200,29753,29397],{"class":245},[200,29755,249],{"class":214},[200,29757,416],{"class":210},[200,29759,29760,29762,29764],{"class":202,"line":255},[200,29761,681],{"class":214},[200,29763,14892],{"class":235},[200,29765,648],{"class":214},[35,29767,29768],{},"But this doesn't:",[192,29770,29772],{"className":591,"code":29771,"language":593,"meta":13,"style":13},"\u003Cscript>\n  const worker = new Worker('https:\u002F\u002Fanother-origin.example.com\u002Fworker.js')\n\u003C\u002Fscript>\n",[156,29773,29774,29782,29805],{"__ignoreMap":13},[200,29775,29776,29778,29780],{"class":202,"line":203},[200,29777,600],{"class":214},[200,29779,14892],{"class":235},[200,29781,648],{"class":214},[200,29783,29784,29786,29788,29790,29792,29794,29796,29798,29801,29803],{"class":202,"line":14},[200,29785,3723],{"class":379},[200,29787,29536],{"class":210},[200,29789,449],{"class":214},[200,29791,12997],{"class":214},[200,29793,29543],{"class":223},[200,29795,227],{"class":210},[200,29797,249],{"class":214},[200,29799,29800],{"class":245},"https:\u002F\u002Fanother-origin.example.com\u002Fworker.js",[200,29802,249],{"class":214},[200,29804,416],{"class":210},[200,29806,29807,29809,29811],{"class":202,"line":255},[200,29808,681],{"class":214},[200,29810,14892],{"class":235},[200,29812,648],{"class":214},[35,29814,29815],{},"Do you see the issue now?",[35,29817,29818,29819,29822,29823,106,29826,1638,29828,29830],{},"Our Laravel server runs on ",[156,29820,29821],{},"http:\u002F\u002Flocalhost:8000",", while Vite is on ",[156,29824,29825],{},"http:\u002F\u002Flocalhost:5173",[156,29827,29821],{},[156,29829,29825],{}," are two different origins. This means the browser won't load the Web Worker script from the Vite server, resulting in a cross-origin issue due to running dual servers.",[35,29832,29833],{},"In the browser, we can easily spot this error with the following warning:",[192,29835,29837],{"className":1355,"code":29836,"language":1358,"meta":13,"style":13},"app.js:21 Failed to construct 'Worker': Script at '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' cannot be accessed from origin 'http:\u002F\u002Flocalhost:'.\n",[156,29838,29839],{"__ignoreMap":13},[200,29840,29841,29844,29847,29850,29853,29855,29858,29860,29862,29865,29868,29870,29873,29875,29878,29881,29884,29886,29889,29891,29894,29896],{"class":202,"line":203},[200,29842,29843],{"class":1978},"app.js:21",[200,29845,29846],{"class":245}," Failed",[200,29848,29849],{"class":245}," to",[200,29851,29852],{"class":245}," construct",[200,29854,242],{"class":214},[200,29856,29857],{"class":245},"Worker",[200,29859,249],{"class":214},[200,29861,239],{"class":245},[200,29863,29864],{"class":245}," Script",[200,29866,29867],{"class":245}," at",[200,29869,242],{"class":214},[200,29871,29872],{"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,29874,249],{"class":214},[200,29876,29877],{"class":245}," cannot",[200,29879,29880],{"class":245}," be",[200,29882,29883],{"class":245}," accessed",[200,29885,984],{"class":245},[200,29887,29888],{"class":245}," origin",[200,29890,242],{"class":214},[200,29892,29893],{"class":245},"http:\u002F\u002Flocalhost:",[200,29895,249],{"class":214},[200,29897,29898],{"class":245},".\n",[35,29900,29901],{},"This error is a showstopper. The Monaco Editor can't function properly without the Web Worker script. Let's find a solution.",[35,29903,29904],{},"Spoiler alert, I searched high and low online for a working solution. Sadly, I couldn't find one. So I try to tackle the issue myself. After extensive research and trials, I found a solution that I'm sharing it with you in this article.",[35,29906,29907],{},"The problematic code looks like this:",[192,29909,29911],{"className":1370,"code":29910,"language":1373,"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 Monaco Editor initialization and Diagnostic feature\n",[156,29912,29913,29934,29950,29966,29970,29984,30002,30015,30029,30040,30047,30058,30062,30066,30070,30074],{"__ignoreMap":13},[200,29914,29915,29917,29920,29922,29925,29927,29929,29932],{"class":202,"line":203},[200,29916,973],{"class":206},[200,29918,29919],{"class":214}," *",[200,29921,14674],{"class":206},[200,29923,29924],{"class":210}," monaco ",[200,29926,1472],{"class":206},[200,29928,242],{"class":214},[200,29930,29931],{"class":245},"monaco-editor",[200,29933,992],{"class":214},[200,29935,29936,29938,29941,29943,29945,29948],{"class":202,"line":14},[200,29937,973],{"class":206},[200,29939,29940],{"class":210}," EditorWorker ",[200,29942,1472],{"class":206},[200,29944,242],{"class":214},[200,29946,29947],{"class":245},"monaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker?worker",[200,29949,992],{"class":214},[200,29951,29952,29954,29957,29959,29961,29964],{"class":202,"line":255},[200,29953,973],{"class":206},[200,29955,29956],{"class":210}," JsonWorker ",[200,29958,1472],{"class":206},[200,29960,242],{"class":214},[200,29962,29963],{"class":245},"monaco-editor\u002Fesm\u002Fvs\u002Flanguage\u002Fjson\u002Fjson.worker?worker",[200,29965,992],{"class":214},[200,29967,29968],{"class":202,"line":272},[200,29969,529],{"emptyLinePlaceholder":20},[200,29971,29972,29975,29977,29980,29982],{"class":202,"line":283},[200,29973,29974],{"class":210},"globalThis",[200,29976,215],{"class":214},[200,29978,29979],{"class":210},"MonacoEnvironment ",[200,29981,449],{"class":214},[200,29983,280],{"class":214},[200,29985,29986,29989,29991,29994,29996,29998,30000],{"class":202,"line":300},[200,29987,29988],{"class":235},"  getWorker",[200,29990,227],{"class":214},[200,29992,29993],{"class":386},"workerId",[200,29995,550],{"class":214},[200,29997,21476],{"class":386},[200,29999,2089],{"class":214},[200,30001,280],{"class":214},[200,30003,30004,30007,30009,30011,30013],{"class":202,"line":310},[200,30005,30006],{"class":206},"    switch",[200,30008,1095],{"class":235},[200,30010,660],{"class":210},[200,30012,5267],{"class":235},[200,30014,230],{"class":214},[200,30016,30017,30020,30022,30024,30026],{"class":202,"line":336},[200,30018,30019],{"class":206},"      case",[200,30021,242],{"class":214},[200,30023,26988],{"class":245},[200,30025,249],{"class":214},[200,30027,30028],{"class":214},":\n",[200,30030,30031,30033,30035,30038],{"class":202,"line":342},[200,30032,13403],{"class":206},[200,30034,12997],{"class":214},[200,30036,30037],{"class":223}," JsonWorker",[200,30039,466],{"class":235},[200,30041,30042,30045],{"class":202,"line":365},[200,30043,30044],{"class":206},"      default",[200,30046,30028],{"class":214},[200,30048,30049,30051,30053,30056],{"class":202,"line":371},[200,30050,13403],{"class":206},[200,30052,12997],{"class":214},[200,30054,30055],{"class":223}," EditorWorker",[200,30057,466],{"class":235},[200,30059,30060],{"class":202,"line":398},[200,30061,4962],{"class":214},[200,30063,30064],{"class":202,"line":405},[200,30065,368],{"class":214},[200,30067,30068],{"class":202,"line":410},[200,30069,1130],{"class":214},[200,30071,30072],{"class":202,"line":861},[200,30073,529],{"emptyLinePlaceholder":20},[200,30075,30076],{"class":202,"line":890},[200,30077,30078],{"class":401},"\u002F\u002F Monaco Editor initialization and Diagnostic feature\n",[35,30080,30081],{},"When Monaco is initialized, it tries to load the Web Worker script from the Vite server. This is where the cross-origin issue arises.",[939,30083,30084],{},[35,30085,30086,30088,30089,30094],{},[200,30087,945],{},"\nIn frameworks like ",[39,30090,30093],{"href":30091,"rel":30092},"https:\u002F\u002Fadonisjs.com\u002F",[77],"Adonis",", where Vite serves as middleware, this issue doesn't occur because Vite runs within the same application as the server. But in Laravel, with two separate servers, we needed a workaround.",[939,30096,30097],{},[35,30098,30099,30101,30102,30105],{},[200,30100,945],{},"\nThis issue doesn't exist if assets are built and loaded from ",[156,30103,30104],{},"\u002Fpublic\u002Fbuild",", as everything is served by the Laravel server. However, this significantly impacts the developer experience, requiring asset rebuilding and page refreshing for every change. This isn't aligned with Vite's experience or its on-demand philosophy.",[51,30107,30109],{"id":30108},"the-solution","The solution",[35,30111,30112],{},"Now we have a clear understanding of the issue and the origin of the problem. Let's dive into the solution.",[35,30114,30115],{},"First, we can examine the worker script URL:",[192,30117,30119],{"className":4277,"code":30118,"language":4279,"meta":13,"style":13},"monaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker?worker\n",[156,30120,30121],{"__ignoreMap":13},[200,30122,30123],{"class":202,"line":203},[200,30124,30118],{},[35,30126,30127,30128,30131],{},"It's a worker script, not intended for direct browser loading. Notice the ",[156,30129,30130],{},"?worker"," query parameter, a Vite hint to wrap the script for easy use. We can inspect the response to have a better understanding.",[192,30133,30135],{"className":25029,"code":30134,"language":25031,"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,30136,30137,30157,30167,30178,30182,30197,30210,30214,30219],{"__ignoreMap":13},[200,30138,30139,30141,30143,30145,30148,30150,30153,30155],{"class":202,"line":203},[200,30140,1380],{"class":206},[200,30142,1383],{"class":206},[200,30144,4987],{"class":379},[200,30146,30147],{"class":223}," WorkerWrapper",[200,30149,227],{"class":214},[200,30151,30152],{"class":386},"options",[200,30154,2089],{"class":214},[200,30156,280],{"class":214},[200,30158,30159,30161,30163,30165],{"class":202,"line":14},[200,30160,3743],{"class":206},[200,30162,12997],{"class":214},[200,30164,29543],{"class":223},[200,30166,3115],{"class":235},[200,30168,30169,30171,30174,30176],{"class":202,"line":255},[200,30170,1405],{"class":214},[200,30172,30173],{"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,30175,249],{"class":214},[200,30177,252],{"class":214},[200,30179,30180],{"class":202,"line":272},[200,30181,19638],{"class":214},[200,30183,30184,30187,30189,30191,30193,30195],{"class":202,"line":283},[200,30185,30186],{"class":235},"      type",[200,30188,239],{"class":214},[200,30190,242],{"class":214},[200,30192,15093],{"class":245},[200,30194,249],{"class":214},[200,30196,252],{"class":214},[200,30198,30199,30201,30203,30205,30207],{"class":202,"line":300},[200,30200,2053],{"class":235},[200,30202,239],{"class":214},[200,30204,9828],{"class":210},[200,30206,6524],{"class":214},[200,30208,30209],{"class":210},"name\n",[200,30211,30212],{"class":202,"line":310},[200,30213,4962],{"class":214},[200,30215,30216],{"class":202,"line":336},[200,30217,30218],{"class":235},"  )\n",[200,30220,30221],{"class":202,"line":342},[200,30222,1130],{"class":214},[35,30224,30225,30226,30228,30229,30231,30232,30234],{},"Thanks to the ",[156,30227,30130],{}," query parameter, Vite automatically wraps the script in a function that returns a new ",[156,30230,29857],{}," instance. So when we import the script, we can easily invoke the function to start the worker without having to manually create a new ",[156,30233,29857],{}," instance with the correct URL and options.",[35,30236,30237],{},"The worker use the following script:",[192,30239,30241],{"className":4277,"code":30240,"language":4279,"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,30242,30243],{"__ignoreMap":13},[200,30244,30245],{"class":202,"line":203},[200,30246,30240],{},[35,30248,419,30249,30251],{},[156,30250,29825],{}," origin belongs to the Vite server and it's the root of the issue.",[35,30253,30254],{},"To be successful, the script URL must match our Laravel server's origin:",[192,30256,30258],{"className":4277,"code":30257,"language":4279,"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,30259,30260],{"__ignoreMap":13},[200,30261,30262],{"class":202,"line":203},[200,30263,30257],{},[35,30265,30266],{},"But how to modify the URL dynamically to match the Laravel server origin?",[35,30268,30269],{},"With a Vite plugin! It's the core of Vite to transform source files on the fly. With a plugin, we could intercept the worker script request and adjust the URL to match the Laravel server origin.",[35,30271,30272,30273,30275],{},"In our ",[156,30274,1460],{},", we can define a plugin to rewrite the worker script URL:",[192,30277,30279],{"className":1370,"code":30278,"language":1373,"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,30280,30281,30299,30303,30315,30323,30333,30339,30354,30371,30401,30406,30411,30415,30439,30452,30464,30475,30480,30484,30488,30492,30501,30507],{"__ignoreMap":13},[200,30282,30283,30285,30287,30289,30291,30293,30295,30297],{"class":202,"line":203},[200,30284,973],{"class":206},[200,30286,318],{"class":214},[200,30288,1488],{"class":210},[200,30290,981],{"class":214},[200,30292,984],{"class":206},[200,30294,242],{"class":214},[200,30296,1497],{"class":245},[200,30298,992],{"class":214},[200,30300,30301],{"class":202,"line":14},[200,30302,529],{"emptyLinePlaceholder":20},[200,30304,30305,30307,30309,30311,30313],{"class":202,"line":255},[200,30306,1380],{"class":206},[200,30308,1383],{"class":206},[200,30310,1488],{"class":223},[200,30312,227],{"class":210},[200,30314,230],{"class":214},[200,30316,30317,30319,30321],{"class":202,"line":272},[200,30318,1520],{"class":235},[200,30320,239],{"class":214},[200,30322,1400],{"class":210},[200,30324,30325,30327,30329,30331],{"class":202,"line":283},[200,30326,11704],{"class":210},[200,30328,1532],{"class":214},[200,30330,393],{"class":379},[200,30332,280],{"class":214},[200,30334,30335,30337],{"class":202,"line":300},[200,30336,11715],{"class":206},[200,30338,280],{"class":214},[200,30340,30341,30343,30345,30347,30350,30352],{"class":202,"line":310},[200,30342,11722],{"class":235},[200,30344,239],{"class":214},[200,30346,242],{"class":214},[200,30348,30349],{"class":245},"monaco-editor:rewrite-worker",[200,30351,249],{"class":214},[200,30353,252],{"class":214},[200,30355,30356,30359,30361,30363,30365,30367,30369],{"class":202,"line":336},[200,30357,30358],{"class":235},"        transform",[200,30360,227],{"class":214},[200,30362,156],{"class":386},[200,30364,550],{"class":214},[200,30366,3224],{"class":386},[200,30368,2089],{"class":214},[200,30370,280],{"class":214},[200,30372,30373,30376,30378,30380,30383,30385,30388,30390,30392,30395,30397,30399],{"class":202,"line":342},[200,30374,30375],{"class":206},"          if",[200,30377,1095],{"class":235},[200,30379,4726],{"class":214},[200,30381,30382],{"class":210},"environment",[200,30384,215],{"class":214},[200,30386,30387],{"class":210},"mode",[200,30389,13388],{"class":214},[200,30391,242],{"class":214},[200,30393,30394],{"class":245},"dev",[200,30396,249],{"class":214},[200,30398,5267],{"class":235},[200,30400,230],{"class":214},[200,30402,30403],{"class":202,"line":365},[200,30404,30405],{"class":206},"            return\n",[200,30407,30408],{"class":202,"line":371},[200,30409,30410],{"class":214},"          }\n",[200,30412,30413],{"class":202,"line":398},[200,30414,529],{"emptyLinePlaceholder":20},[200,30416,30417,30419,30421,30423,30425,30427,30429,30431,30433,30435,30437],{"class":202,"line":405},[200,30418,30375],{"class":206},[200,30420,1095],{"class":235},[200,30422,3280],{"class":210},[200,30424,215],{"class":214},[200,30426,3841],{"class":223},[200,30428,227],{"class":235},[200,30430,249],{"class":214},[200,30432,29562],{"class":245},[200,30434,249],{"class":214},[200,30436,6466],{"class":235},[200,30438,230],{"class":214},[200,30440,30441,30444,30446,30448,30450],{"class":202,"line":410},[200,30442,30443],{"class":206},"            return",[200,30445,4164],{"class":210},[200,30447,215],{"class":214},[200,30449,6136],{"class":223},[200,30451,3115],{"class":235},[200,30453,30454,30457,30460,30462],{"class":202,"line":861},[200,30455,30456],{"class":214},"              '",[200,30458,30459],{"class":245},"__laravel_vite_placeholder__.test",[200,30461,249],{"class":214},[200,30463,252],{"class":214},[200,30465,30466,30468,30471,30473],{"class":202,"line":890},[200,30467,30456],{"class":214},[200,30469,30470],{"class":245},"localhost:8000",[200,30472,249],{"class":214},[200,30474,252],{"class":214},[200,30476,30477],{"class":202,"line":900},[200,30478,30479],{"class":235},"            )\n",[200,30481,30482],{"class":202,"line":905},[200,30483,30410],{"class":214},[200,30485,30486],{"class":202,"line":931},[200,30487,4950],{"class":214},[200,30489,30490],{"class":202,"line":3640},[200,30491,4478],{"class":214},[200,30493,30494,30496,30499],{"class":202,"line":3645},[200,30495,2096],{"class":214},[200,30497,30498],{"class":210},")()",[200,30500,252],{"class":214},[200,30502,30503,30505],{"class":202,"line":4020},[200,30504,1417],{"class":210},[200,30506,252],{"class":214},[200,30508,30509,30511],{"class":202,"line":4475},[200,30510,413],{"class":214},[200,30512,416],{"class":210},[939,30514,30515],{},[35,30516,30517,30519,30520,30522,30523,30525],{},[200,30518,945],{},"\nHere, we're replacing ",[156,30521,30459],{}," instead of ",[156,30524,29825],{},", as this URL is typically managed by the Laravel Vite plugin.",[35,30527,30528],{},"With this plugin, we can now load the Monaco Editor worker script since the origin matches the Laravel server. In production, the script URL will be correct and working as expected so we don't need to rewrite the URL. Build files are served from the same origin using a single server like Nginx, Apache or Caddy.",[35,30530,30531],{},"However, this poses another challenge: the worker script loads from the Laravel server, but only Vite can serves it, the Laravel server isn't aware of this script and the request will just return a 404 error.",[35,30533,30534],{},"But, we have full control over the Laravel server. We can create a route to proxy requests to the Vite server, allowing the Laravel server to serve the worker script.",[35,30536,30537],{},"This seems viable, faisable and elegant enough. Let's implement it.",[35,30539,30540,30541,30544,30545,30548,30549,30551],{},"We can create a Laravel route, available only in development, to proxy requests to the Vite server. Let's craft a ",[156,30542,30543],{},"proxy.dev.php"," file within Laravel's ",[156,30546,30547],{},"routes"," directory and including it in ",[156,30550,26590],{}," just during development.",[192,30553,30555],{"className":19537,"code":30554,"language":19540,"meta":13,"style":13},"if (!app()->isProduction()) {\n    require __DIR__ . '\u002Fproxy.dev.php';\n}\n",[156,30556,30557,30562,30567],{"__ignoreMap":13},[200,30558,30559],{"class":202,"line":203},[200,30560,30561],{},"if (!app()->isProduction()) {\n",[200,30563,30564],{"class":202,"line":14},[200,30565,30566],{},"    require __DIR__ . '\u002Fproxy.dev.php';\n",[200,30568,30569],{"class":202,"line":255},[200,30570,1130],{},[35,30572,30573,30574,239],{},"Next, define our proxy route in ",[156,30575,30543],{},[192,30577,30579],{"className":19537,"code":30578,"language":19540,"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,30580,30581,30585,30589,30593,30597,30602,30607,30612,30617,30622,30627,30632,30640,30645],{"__ignoreMap":13},[200,30582,30583],{"class":202,"line":203},[200,30584,19577],{},[200,30586,30587],{"class":202,"line":14},[200,30588,529],{"emptyLinePlaceholder":20},[200,30590,30591],{"class":202,"line":255},[200,30592,19552],{},[200,30594,30595],{"class":202,"line":272},[200,30596,529],{"emptyLinePlaceholder":20},[200,30598,30599],{"class":202,"line":283},[200,30600,30601],{},"Route::get('\u002Fnode_modules\u002F{any}', function ($any) {\n",[200,30603,30604],{"class":202,"line":300},[200,30605,30606],{},"    $url = \"http:\u002F\u002Flocalhost:5173\u002Fnode_modules\u002F{$any}\";\n",[200,30608,30609],{"class":202,"line":310},[200,30610,30611],{},"    return response()->stream(function () use ($url) {\n",[200,30613,30614],{"class":202,"line":336},[200,30615,30616],{},"        \u002F\u002F Directly stream the remote content without loading it fully into memory\n",[200,30618,30619],{"class":202,"line":342},[200,30620,30621],{},"        readfile($url);\n",[200,30623,30624],{"class":202,"line":365},[200,30625,30626],{},"    }, 200, [\n",[200,30628,30629],{"class":202,"line":371},[200,30630,30631],{},"      'Content-Type' => 'text\u002Fjavascript'\n",[200,30633,30634,30637],{"class":202,"line":398},[200,30635,30636],{},"      'Cache-Control' => 'public, max-age=86400'",[200,30638,30639],{}," \u002F\u002F Cache for 1 day\n",[200,30641,30642],{"class":202,"line":405},[200,30643,30644],{},"    ]);\n",[200,30646,30647],{"class":202,"line":410},[200,30648,30649],{},"})->where('any', '(.*)');\n",[939,30651,30652],{},[35,30653,30654,30656],{},[200,30655,945],{},"\nI do my best to optimize the proxy to efficiently stream the content without adding too much overhead. If you think of a better way to do it, feel free to share it with me in the comments.",[35,30658,30659],{},"And there you have it! The worker script loads, and the Monaco Editor operates perfectly.",[35,30661,30662],{},[1690,30663],{"alt":30664,"src":30665},"Monaco Editor with a working JSON schema validation","\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Fworking-monaco-editor-worker.png",[35,30667,30668],{},"One challenge was finding a solution that works well in development, during build, and in production. This approach seems to be an elegant compromise, simple, effective, and clean. So far, it's working perfectly.",[35,30670,30671],{},"Here's an overview of the solution:",[35,30673,30674],{},[1690,30675],{"alt":30676,"src":30677},"Architecture of the solution","\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Flarave-proxying-to-vite.png",[51,30679,30681],{"id":30680},"conclusion","Conclusion",[35,30683,30684],{},"This issue was indeed a challenge. Integrating the Monaco Editor with JSON schema validation was essential for our project. Unfortunately, the cross-origin headache and scant online resources forced me to create my own solution. But I found one!",[35,30686,30687,30688,30690],{},"I hope this article assists those facing similar issues. The key takeaway: mastering your tools is crucial. Although ",[6850,30689,24355],{}," plugins might seem daunting, understanding them removes all limitations allowing you to solve complex problems.",[1171,30692,30693],{},"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":30695},[30696,30697,30698,30699,30700],{"id":29237,"depth":14,"text":29238},{"id":29348,"depth":14,"text":29349},{"id":29686,"depth":14,"text":29687},{"id":30108,"depth":14,"text":30109},{"id":30680,"depth":14,"text":30681},"2025-02-06","Explore an integration problem with Laravel and Vite, focusing on cross-origin issues and solved thanks to an in-depth knowledge of tools.",{},"\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin","---\ncontentId: 0b59cb4d-f145-4011-9d8c-ef32e2770273\ntitle: \"Laravel and Vite: A Love Story Ruined with Cross-Origin\"\ndescription: \"Explore an integration problem with Laravel and Vite, focusing on cross-origin issues and solved thanks to an in-depth knowledge of tools.\"\ndate: 2025-02-06\n---\n\n_This title might sound dramatic, but trust me, it's worth your read. Dive in!_\n\nIt's great to be back to my blog. I've been away due to numerous conference preparations and a major internal updates to this website. Although these aren't today's topics, I'll certainly cover them in future posts. Also, this is my first article on **Laravel**, and it won't be the last!\n\nToday, I want to share an experience I had with **Laravel** and **Vite** because of the strictness of the [Web Worker API](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_Workers_API\u002FUsing_web_workers).\n\n## The Laravel and Vite integration\n\nBefore delving into the problem, it's crucial to understand how **Vite** operates within a **Laravel** project.\n\nFirst, what is **Vite**? It's a fast frontend build tool that powers next-gen web applications. It allows you to construct your frontend assets (JavaScript, CSS, etc.) efficiently, especially during development. The core of Vite is a plugin pipeline that transforms source files into the required outputs, a point we'll revisit soon.\n\nAnd **Laravel**? It's a web application framework known for its expressive, elegant syntax. Laravel eases common tasks like authentication, routing, sessions, and caching, aiming to make development enjoyable without compromising functionality. _And it excels at it!_\n\nWhen you embrace **Laravel** and **Vite**, you get a formidable stack for web application development.\n\nSounds great, right? But how does it works practically? Let me illustrate.\n\n![Laravel and Vite integration](\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Flaravel-vite-request-response.png)\n\nThis image offers a high-level overview of the Laravel and Vite architecture. During development, two servers run: one for Laravel and one for Vite.\n\n1. A user requests a page from the Laravel server.\n2. The Laravel server sends the HTML page to the user. Before sending the response, Laravel adds a link tag to the entry script served by the Vite server. For example, you might find this line in the HTML returned by the Laravel server:\n\n```html\n\u003Cscript type=\"module\" src=\"http:\u002F\u002Flocalhost:5173\u002Findex.ts\">\u003C\u002Fscript>\n```\n\n3. The user's browser loads the HTML and detects the script tag pointing to the Vite server, then requests the script from the Vite server.\n4. The Vite server delivers the script to the user's browser.\n\nThis process is straightforward and highly effective during development. It gives you access to Vite's full power with the plugin pipeline and hot module replacement (HMR). _Many developers might not fully appreciate how seamlessly this integration works compared to other tools._\n\nHowever, sometimes things get tricky. And that's where our story begins.\n\n## The context\n\nFor a project, I had to use the [Monaco Editor](https:\u002F\u002Fmicrosoft.github.io\u002Fmonaco-editor\u002F), a browser-based code editor and the core of Visual Studio Code. The aim was to display a JSON in the code editor and validate it against a JSON schema. Users can edit this JSON, and the Monaco Editor, with JSON schema validation, will show in real time whether the JSON is valid or suggest corrections. Amazingly efficient when it works! _And it has been much more complexe than expected._\n\n![Monaco Editor with a familiar interface if you're using Visual Studio Code](\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Fmonaco-editor.png)\n\nTo achieve this, the Monaco Editor requires starting some Web Workers. Why and what are Web Workers?\n\nJavaScript, especially in browser environments, operates on a single thread, meaning it executes one task at a time. So, if a piece of code takes long to execute, the browser becomes unresponsive which degrades the user experience. This is why web developers advise against blocking the main thread.\n\nTo handle long tasks without affecting performance, we have two options:\n\n- **Asynchronous JavaScript**: This involves breaking down tasks into smaller chunks and executing them asynchronously. This way, the main thread remains free to handle other tasks.\n\n- **Web Workers**: For more complex tasks, especially those requiring heavy computation, we use Web Workers. A Web Worker is essentially a JavaScript file running in the background, separate from other scripts, with which it can communicate via messages.\n\nHere's a simple example of a Web Worker script:\n\n```ts [worker.js]\nglobThis.onmessage = function (e) {\n  console.log('Message received from main script')\n  const workerResult = `Result: ${e.data[0] * e.data[1]}`\n  console.log('Posting message back to main script')\n  postMessage(workerResult)\n}\n```\n\nWith its main script:\n\n```ts [main.js]\nconst worker = new Worker('worker.js')\n\nworker.addEventListener('message', (e) => {\n  console.log('Message received from worker')\n  console.log(e.data)\n})\n\nworker.postMessage([2, 3])\n```\n\nIn this example, the main script creates a new worker and sends it a message using `postMessage`. The worker receives and responds with `postMessage`. Due to JavaScript's event-driven nature, the main script listens for the worker's message using `addEventListener`.\n\nIn our case, Monaco Editor leverages Web Workers for certain features, including JSON schema validation.\n\n> [!NOTE]\n> Using a Web Worker allows the Monaco Editor to run a [LSP](https:\u002F\u002Fmicrosoft.github.io\u002Flanguage-server-protocol\u002F) server in the background for each language, providing more advanced features without impacting the main thread and the user experience.\n\n## The issue\n\nBut here's the catch:\n\nThe script passed to the Web Worker must be accessible from the same origin as the main script. This is where the problem arises.\n\n> [!WARNING]\n> This is a security feature to prevent cross-origin attacks so it's impossible to bypass it.\n\nAn origin is defined by a URL's scheme, host, and port. This means `https:\u002F\u002Fexample.com` and `https:\u002F\u002Fexample.com:1024` are considered different origins.\n\nImagine we're on the page `https:\u002F\u002Fexample.com` and you want to create a Web Worker. The script URL passed to the Web Worker must be accessible from `https:\u002F\u002Fexample.com`.\n\nThis means that this works:\n\n```html\n\u003Cscript>\n  const worker = new Worker('worker.js')\n\u003C\u002Fscript>\n```\n\nBut this doesn't:\n\n```html\n\u003Cscript>\n  const worker = new Worker('https:\u002F\u002Fanother-origin.example.com\u002Fworker.js')\n\u003C\u002Fscript>\n```\n\nDo you see the issue now?\n\nOur Laravel server runs on `http:\u002F\u002Flocalhost:8000`, while Vite is on `http:\u002F\u002Flocalhost:5173`. `http:\u002F\u002Flocalhost:8000` and `http:\u002F\u002Flocalhost:5173` are two different origins. This means the browser won't load the Web Worker script from the Vite server, resulting in a cross-origin issue due to running dual servers.\n\nIn the browser, we can easily spot this error with the following warning:\n\n```sh\napp.js:21 Failed to construct 'Worker': Script at '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' cannot be accessed from origin 'http:\u002F\u002Flocalhost:'.\n```\n\nThis error is a showstopper. The Monaco Editor can't function properly without the Web Worker script. Let's find a solution.\n\nSpoiler alert, I searched high and low online for a working solution. Sadly, I couldn't find one. So I try to tackle the issue myself. After extensive research and trials, I found a solution that I'm sharing it with you in this article.\n\nThe problematic code looks like this:\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 Monaco Editor initialization and Diagnostic feature\n```\n\nWhen Monaco is initialized, it tries to load the Web Worker script from the Vite server. This is where the cross-origin issue arises.\n\n> [!NOTE]\n> In frameworks like [Adonis](https:\u002F\u002Fadonisjs.com\u002F), where Vite serves as middleware, this issue doesn't occur because Vite runs within the same application as the server. But in Laravel, with two separate servers, we needed a workaround.\n\n> [!NOTE]\n> This issue doesn't exist if assets are built and loaded from `\u002Fpublic\u002Fbuild`, as everything is served by the Laravel server. However, this significantly impacts the developer experience, requiring asset rebuilding and page refreshing for every change. This isn't aligned with Vite's experience or its on-demand philosophy.\n\n## The solution\n\nNow we have a clear understanding of the issue and the origin of the problem. Let's dive into the solution.\n\nFirst, we can examine the worker script URL:\n\n```txt\nmonaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker?worker\n```\n\nIt's a worker script, not intended for direct browser loading. Notice the `?worker` query parameter, a Vite hint to wrap the script for easy use. We can inspect the response to have a better understanding.\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\nThanks to the `?worker` query parameter, Vite automatically wraps the script in a function that returns a new `Worker` instance. So when we import the script, we can easily invoke the function to start the worker without having to manually create a new `Worker` instance with the correct URL and options.\n\nThe worker use the following script:\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\nThe `http:\u002F\u002Flocalhost:5173` origin belongs to the Vite server and it's the root of the issue.\n\nTo be successful, the script URL must match our Laravel server's origin:\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\nBut how to modify the URL dynamically to match the Laravel server origin?\n\nWith a Vite plugin! It's the core of Vite to transform source files on the fly. With a plugin, we could intercept the worker script request and adjust the URL to match the Laravel server origin.\n\nIn our `vite.config.ts`, we can define a plugin to rewrite the worker script URL:\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> Here, we're replacing `__laravel_vite_placeholder__.test` instead of `http:\u002F\u002Flocalhost:5173`, as this URL is typically managed by the Laravel Vite plugin.\n\nWith this plugin, we can now load the Monaco Editor worker script since the origin matches the Laravel server. In production, the script URL will be correct and working as expected so we don't need to rewrite the URL. Build files are served from the same origin using a single server like Nginx, Apache or Caddy.\n\nHowever, this poses another challenge: the worker script loads from the Laravel server, but only Vite can serves it, the Laravel server isn't aware of this script and the request will just return a 404 error.\n\nBut, we have full control over the Laravel server. We can create a route to proxy requests to the Vite server, allowing the Laravel server to serve the worker script.\n\nThis seems viable, faisable and elegant enough. Let's implement it.\n\nWe can create a Laravel route, available only in development, to proxy requests to the Vite server. Let's craft a `proxy.dev.php` file within Laravel's `routes` directory and including it in `routes\u002Fweb.php` just during development.\n\n```php\nif (!app()->isProduction()) {\n    require __DIR__ . '\u002Fproxy.dev.php';\n}\n```\n\nNext, define our proxy route in `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> I do my best to optimize the proxy to efficiently stream the content without adding too much overhead. If you think of a better way to do it, feel free to share it with me in the comments.\n\nAnd there you have it! The worker script loads, and the Monaco Editor operates perfectly.\n\n![Monaco Editor with a working JSON schema validation](\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Fworking-monaco-editor-worker.png)\n\nOne challenge was finding a solution that works well in development, during build, and in production. This approach seems to be an elegant compromise, simple, effective, and clean. So far, it's working perfectly.\n\nHere's an overview of the solution:\n\n![Architecture of the solution](\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Flarave-proxying-to-vite.png)\n\n## Conclusion\n\nThis issue was indeed a challenge. Integrating the Monaco Editor with JSON schema validation was essential for our project. Unfortunately, the cross-origin headache and scant online resources forced me to create my own solution. But I found one!\n\nI hope this article assists those facing similar issues. The key takeaway: mastering your tools is crucial. Although **Vite** plugins might seem daunting, understanding them removes all limitations allowing you to solve complex problems.\n",{"title":29208,"description":30702},{"loc":30704,"images":30708},[30709,30710,30711,30712],{"loc":29279},{"loc":29367},{"loc":30665},{"loc":30677},"posts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin","nM8grVfBoNFFJB-CQTGvsDXUu5Nqw11xVi5xyEQwKPg",{"id":30716,"title":30717,"body":30718,"contentId":8782,"date":31188,"description":31189,"extension":18,"meta":31190,"navigation":20,"path":8951,"rawbody":31191,"robots":23,"schemaOrg":23,"seo":31192,"sitemap":31193,"stem":31202,"__hash__":31203},"posts\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next.md","2024: Reflections on a Year of Change and What Comes Next",{"type":7,"value":30719,"toc":31177},[30720,30723,30735,30738,30743,30746,30750,30765,30773,30779,30782,30786,30807,30816,30823,30826,30832,30841,30844,30856,30863,30870,30872,30878,30891,30897,30908,30914,30930,30936,30943,30954,30960,30967,30973,30977,30987,30998,31002,31018,31024,31027,31031,31034,31046,31056,31066,31082,31091,31097,31100,31102,31105,31125,31140,31146,31159,31162,31165,31168,31171],[35,30721,30722],{},"Hey there! 👋",[35,30724,30725,30726,30729,30730,30734],{},"This post will be a bit different from the usual technical articles. I want to take a moment to reflect on ",[6850,30727,30728],{},"2024"," and share some thoughts on what's next. This is definitely a personal post, but for me, it's also a way to track my journey and see how I evolve over time. Last year, I wrote ",[39,30731,30733],{"href":8785,"rel":30732},[77],"a similar post",", and I love revisiting it occasionally to see how much I've changed.",[35,30736,30737],{},"I finished last year's post with these words:",[939,30739,30740],{},[35,30741,30742],{},"For 2024, I'm continuing my momentum, and new things are in the pipeline from February with my departure for Switzerland and perhaps a Devoxx 2024!",[35,30744,30745],{},"Let's see how it went.",[51,30747,30749],{"id":30748},"switzerland","Switzerland",[35,30751,30752,30753,30755,30756,30758,30759,30761,30762,30764],{},"For the final year of my engineering studies, I had the opportunity to do an internship at ",[6850,30754,21117],{}," in Geneva, ",[6850,30757,30749],{},". Over six months, I worked on the internal component library with ",[6850,30760,22878],{},". Despite the incredible team and company, I must admit that working with ",[6850,30763,22878],{}," was not always enjoyable.",[35,30766,30767,30768,13731,30770,30772],{},"Over the last three years, the Angular team introduced many new features, improvements, and methodologies. While these are great, there aren't many explanations on how to apply them in real projects. Meanwhile, the component library was built from scratch to migrate from Material to a custom design system. It's a significant task and somewhat frustrating when you see many headless libraries with ",[6850,30769,17991],{},[6850,30771,17986],{},". Nonetheless, I learned a lot, and I'm grateful for the experience. Knowing how to build a component is essential for any front-end project, regardless of the framework.",[35,30774,30775],{},[1690,30776],{"alt":30777,"src":30778},"Me in front of Geneva.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fgeneva.jpeg",[35,30780,30781],{},"During this internship, I had ample free time to work on my side projects.",[1913,30783,30785],{"id":30784},"new-blog","New blog",[35,30787,30788,30789,30792,30793,30798,30799,30802,30803,30806],{},"At the beginning of the year, I wrote some articles for the ",[6850,30790,30791],{},"UnJS"," website. I was thrilled to share them with the community, but unfortunately, I didn't have the chance to publish them. Back then, my blog was on ",[39,30794,30797],{"href":30795,"rel":30796},"https:\u002F\u002Festeban-soubiran.site",[77],"esteban-soubiran.site"," and only available in ",[6850,30800,30801],{},"French",". I've wanted to write in ",[6850,30804,30805],{},"English"," for a long time, and I received multiple feedbacks about that after my articles on UnJS packages.",[35,30808,30809,30810,30812,30813],{},"So, I decided to create a new blog with a new domain name, in ",[6850,30811,30805],{},", and with a new design focused on simplicity to make writing easier. I detailed the process in this article ",[3029,30814,215],{"id":30815},"674a3932-aaba-445c-82bb-0896ce3a8e7e",[35,30817,30818,30819],{},"I published my first article, ",[3029,30820,30822],{"id":30821},"5b969fcd-0a41-4bc1-ae7e-3aca6d1f48a5",", on March 19th, but initially, my blog consisted of only one prose component to allow you to read the article. There was no homepage, no menu, nothing.",[1913,30824,18569],{"id":30825},"twitch",[35,30827,30828,30829,30831],{},"I also started streaming on ",[6850,30830,18569],{},". This has been on my mind for a long time, but having a conducive environment was a hurdle. My bedroom during my internship was perfect, calm with a good internet connection.",[35,30833,30834,30835,30837,30838],{},"I began some streams, and together we built a full-stack ",[6850,30836,2918],{}," application with NuxtHub, about which I wrote a whole article, ",[3029,30839,215],{"id":30840},"c21a371e-abdd-4325-b296-dadcb8deec25",[35,30842,30843],{},"We learned a lot together, especially regarding full-stack development with Nuxt. I shared all of this in two articles:",[1576,30845,30846,30851],{},[71,30847,30848],{},[3029,30849],{"id":30850},"6df6b96b-58fc-4294-8a38-d209f2b36d19",[71,30852,30853],{},[3029,30854],{"id":30855},"f47ef865-e311-48e6-ab6e-6f7ee6bde6e8",[35,30857,30858,30859,215],{},"This led to the creation of a package called ",[39,30860,9221],{"href":30861,"rel":30862},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fnuxt-authorization",[77],[35,30864,30865,30866,30869],{},"This was definitely a great experience that I had to pause because I changed my computer to a ",[6850,30867,30868],{},"MacBook Air",", which didn't have sufficient graphics power to stream. But in 2025, I plan to buy a second computer to resume streaming.",[51,30871,8945],{"id":8944},[35,30873,30874,30875,30877],{},"In 2023, I spoke for the first time at ",[6850,30876,7142],{},". In 2024, I had the opportunity to speak six times.",[35,30879,30880,30881,30884,30885,30887,30888],{},"In March, ",[6850,30882,30883],{},"StrasbourgJS"," invited me to talk about the ",[6850,30886,30791],{}," ecosystem. It was really cool to share this knowledge and finally meet some community members like Romain Lanz, Michel, and Thomas. This was excellent preparation for my next talk at Devoxx France. I wrote an article about it, ",[3029,30889,215],{"id":30890},"ba562d1f-534c-4cef-9004-2fefc09829a6",[35,30892,30893],{},[1690,30894],{"alt":30895,"src":30896},"StrasbourgJS Team and Speakers.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fstrasbourgjs.jpeg",[35,30898,30899,30900,30902,30903,30905,30906],{},"For the second time, I had the privilege of speaking at ",[6850,30901,7142],{},". This time, I discussed ",[6850,30904,30791],{}," packages during a quickie, a highly challenging format because you must be very concise and clear. I was pleased with the outcome and received a lot of positive feedback. I wrote an article about it, ",[3029,30907,215],{"id":20826},[35,30909,30910],{},[1690,30911],{"alt":30912,"src":30913},"Me at Devoxx France.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fdevoxx-france.png",[35,30915,30916,30917,30920,30921,30923,30924,30926,30927],{},"In September, I spoke at the ",[6850,30918,30919],{},"Prague Vue Conference",". This was my first conference in English, so I prepared and trained extensively to be ready. I discussed the full-stack capabilities of ",[6850,30922,2918],{}," with NuxtHub. I also met some notable figures in the ",[6850,30925,17986],{}," community, like Daniel Roe and Alexander Lichter. I documented the experience in an article, ",[3029,30928,215],{"id":30929},"5665d6c9-f03c-4cbb-a69b-8cabd781e7a1",[35,30931,30932],{},[1690,30933],{"alt":30934,"src":30935},"PragVue Team and Speakers.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fpragvue.jpg",[35,30937,30938,30939,30942],{},"In November, I spoke at ",[6850,30940,30941],{},"NuxtNations",", once again, about the Nuxt full-stack capabilities. It was an online conference, but I was definitely a bit anxious before my talk.",[35,30944,30945,30946,30949,30950,30953],{},"Then, I continued with a talk at the ",[6850,30947,30948],{},"TakiMeet meetup",". It's organized by a company in Paris, where I discussed user experience and optimistic ",[6850,30951,30952],{},"UI",". Between the two talks, I only had a week to prepare, finishing my slides just a couple of hours before the presentation. I wouldn't recommend this approach, but I was pleased with the result. Special thanks to Camille for the invite and the entire team for the organization.",[35,30955,30956],{},[1690,30957],{"alt":30958,"src":30959},"The audience at TakiMeet during my talk.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Ftakimeet.jpg",[35,30961,30962,30963,30966],{},"Finally, I spoke at the ",[6850,30964,30965],{},"VueJS Paris meetup"," about the Nuxt full-stack capabilities. This was similar to my previous talks in Prague and NuxtNations, but in French. People were very interested and curious about this emerging technology. Thanks to Elise and Eduardo for the invitation and organization.",[35,30968,30969],{},[1690,30970],{"alt":30971,"src":30972},"The audience at VueJS Paris during my talk.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fvuejs-paris.jpg",[51,30974,30976],{"id":30975},"end-of-my-studies-and-my-new-job","End of my studies and my new job",[35,30978,30979,30980,30983,30984],{},"I completed my engineering studies in September, culminating in an internship in Switzerland, with a diploma in ",[6850,30981,30982],{},"IT security",". Six years of studies, two internships, a gap year, and numerous projects, associations, and experiences. This was an incredible journey where I met a lot of amazing people. I wrote an article about this, ",[3029,30985,215],{"id":30986},"029ab8e8-401f-4728-adf9-4e0a2899fd05",[35,30988,30989,30990,30993,30994,30997],{},"Today, I'm thrilled to have completed this chapter of my life and begin a new one. I'm now a full-time software engineer at ",[6850,30991,30992],{},"MaiaSpace",", a French company that designs, manufactures, operates, and commercializes competitive and sustainable solutions for ",[6850,30995,30996],{},"space launch and mobility",". I never imagined working in the space industry, especially as a web developer. This is a unique opportunity that I couldn't pass up, but also a significant challenge with much to learn. I can't wait for the launch in 2026 (though there is plenty of time, as another retrospective will be written in the meantime). 🧑‍🚀",[51,30999,31001],{"id":31000},"learning-always-learning","Learning, always learning",[35,31003,31004,31005,31007,31008,3076,31011,3080,31014,31017],{},"For six years now, I've been learning ",[6850,31006,21348],{},". Initially, I learned a lot about ",[6850,31009,31010],{},"HTML",[6850,31012,31013],{},"CSS",[6850,31015,31016],{},"JavaScript",", but in recent years, I stagnated somewhat and was not entirely satisfied with that. I wanted to build a full-stack application by myself and understand all its components.",[35,31019,31020,31021,31023],{},"So in June, I started learning ",[6850,31022,19447],{}," with the excellent platform Laracasts. This changed my life. I learned so much in little time. I've never had so much fun coding and creating applications. This decision helped me secure my position at MaiaSpace.",[35,31025,31026],{},"This might be the biggest change in my life in 2024 and likely my best decision.",[51,31028,31030],{"id":31029},"my-blog","My blog",[35,31032,31033],{},"I've mentioned it, but I created a new technical blog in March 2024 with a single idea in mind: writing articles that I would have loved to read when starting my web development journey. I wanted my articles to be clear, technical, and comprehensive. I'm tired of all those clickbait articles on Medium or Dev Community that lack structure and where the content is just as basic as the documentation.",[35,31035,31036,31037,3076,31039,3076,31041,3080,31043,31045],{},"With this guideline in mind, I wrote multiple articles about ",[6850,31038,2918],{},[6850,31040,17986],{},[6850,31042,30791],{},[6850,31044,19447],{},". For me, writing is a way to solidify my knowledge and ensure I truly understand the subject. I also love to share my knowledge with the community and help people learn new and useful things.",[35,31047,31048,31049,31051,31052],{},"In October, I decided to write about ",[6850,31050,2913],{}," and its customizability. The concept of a standalone post no longer fulfilled me, and I wanted to create a series of articles. I introduced the concept of series on my blog with the series ",[3029,31053,31055],{"id":31054},"43c95b33-041c-4108-ab98-0d21ef60ed7f",". These series are perfect for delving deeply into a subject and explaining complex topics in a simple and actionable manner.",[35,31057,31058,31059],{},"In December, I released a second series called ",[3029,31060,31062,31063,31065],{"id":31061},"55d2cdcf-ba27-4bfe-9bc1-492e866ff3b1",". Since my first encounter with the ",[6850,31064,17986],{}," ecosystem six years ago, I have always wanted to find something that thoroughly explains how to build a component library professionally, with all the tools and techniques used in the industry. Now, it's done, and I'm very proud of it!",[35,31067,31068,31069,31072,31073],{},"Along the way, I continually improved my blog. I reached a point where I was very satisfied, but something was still missing. I wanted to allow people to interact with my blog, ask questions, share thoughts, and provide feedback. So, I decided to create an ",[6850,31070,31071],{},"API"," using Laravel to facilitate this. I wrote multiple articles about it, ",[3029,31074,3076,31076],{"id":31075},"0d4cb8be-230e-444e-9419-fce9adf0ef6b",[3029,31077,3080,31079],{"id":31078},"c8faba17-5480-49fb-a201-d426ea583097",[3029,31080,215],{"id":31081},"600cf474-9e89-4045-9030-021332c2dfd6",[35,31083,31084,31085,31090],{},"It wasn't planned, but a dark theme was also added thanks to ",[39,31086,31089],{"href":31087,"rel":31088},"https:\u002F\u002Fhrynevych.dev\u002F",[77],"Roman",". He sent me a message on X and proposed to add a dark theme to my blog. I quickly accepted, and I must say that I find it absolutely stunning.",[35,31092,31093,31094,31096],{},"Finally, I launched my sponsorship program on ",[6850,31095,11622],{}," and through my blog. This first step is a way to support me and my work. I'm very grateful to those who already support me, and I'm excited to see how this will evolve in the future. Big plans for this program are in the works, and I can't wait to share them with you.",[35,31098,31099],{},"My goal for 2024 was to publish one article per week. I've published 40 articles since March, so this is a success. 🥳",[51,31101,9336],{"id":9335},[35,31103,31104],{},"2024 was a year of change, learning, and growth. I'm proud of my accomplishments and the progress I've made. But I'm not done yet. There's still so much to learn, explore, and create.",[35,31106,31107,31108,31110,31111,31114,31115,14177,31118,1638,31121,31124],{},"In 2025, I want to continue my journey with ",[6850,31109,19447],{}," and to continue to dive deeper into it. I'm excited to explore more advanced topics and build more complex applications. With the knowledge I've gained, I'm confident I can tackle new challenges and create amazing projects. Building applications is a great start, but I also want to learn more about ",[6850,31112,31113],{},"infrastructure and deployment",". I aim to deploy my applications professionally by managing my ",[6850,31116,31117],{},"VPS",[6850,31119,31120],{},"Ansible",[6850,31122,31123],{},"Terraform",". I want to monitor my applications, troubleshoot if something goes wrong, and scale them as needed. This will take time, but that's okay. I have the year ahead of me to learn and grow.",[35,31126,31127,31128,31131,31132,31135,31136,31139],{},"I will continue to explore the ",[6850,31129,31130],{},"Vue ecosystem",". I'm excited to delve deeper into the ",[6850,31133,31134],{},"3D"," with Three.js and explore how it can be integrated into my projects. I buy the ",[39,31137,8864],{"href":8862,"rel":31138},[77]," course, and I can't wait to dive into it. I would love to be able to share this journey with you on Twitch. This is one of the points for the second semester of 2025, being able to stream again.",[35,31141,31142,31143,31145],{},"I have many ideas for new projects, but I would love to launch ",[6850,31144,7881],{},", my first SaaS, that makes your talks more engaging with real-time interactions. This is really important to me, and I can't wait to share more about it in the future, maybe during live coding sessions on Twitch.",[35,31147,31148,31149,31152,31153,1638,31155,31158],{},"I will continue to speak at conferences and meetups. In December, I submitted 5 proposals for ",[6850,31150,31151],{},"Vue Amsterdam",", and I hope to be selected. I have submitted some talks to meetups in Paris, and in January, I will submit some talks to ",[6850,31154,7142],{},[6850,31156,31157],{},"MiXit",". I really hope this year will be as rich as 2024. I love sharing my knowledge with the community and connecting with other developers. Feel free to reach out if you're organizing an event and would like me to speak.",[35,31160,31161],{},"Finally, I will continue to write articles and create series on my blog. I'm excited to explore new topics and share insights with you. I have exciting changes planned for my blog to make it more interactive and engaging. I can't wait to share them with you.",[35,31163,31164],{},"2024 was a year of change, growth, and learning. I'm proud of what I've accomplished and excited for what's to come. Thank you for being part of this journey with me.",[35,31166,31167],{},"2025 is an ambitious year, but I've never been as motivated and confident as I am now. It will be a great year, and I can't wait to share it with you.",[35,31169,31170],{},"Take care, and happy new year! 🫶",[35,31172,31173],{},[1690,31174],{"alt":31175,"src":31176},"Me skiing in costume for a wedding.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fskiing.jpg",{"title":13,"searchDepth":14,"depth":14,"links":31178},[31179,31183,31184,31185,31186,31187],{"id":30748,"depth":14,"text":30749,"children":31180},[31181,31182],{"id":30784,"depth":255,"text":30785},{"id":30825,"depth":255,"text":18569},{"id":8944,"depth":14,"text":8945},{"id":30975,"depth":14,"text":30976},{"id":31000,"depth":14,"text":31001},{"id":31029,"depth":14,"text":31030},{"id":9335,"depth":14,"text":9336},"2024-12-31","Reflecting on the transformative journey of 2024 and gearing up for future challenges and explorations. 2025 will be a year of learning, and growth.",{},"---\ncontentId: d86a42c6-47d0-4078-b33c-805f44046b17\ntitle: \"2024: Reflections on a Year of Change and What Comes Next\"\ndescription: \"Reflecting on the transformative journey of 2024 and gearing up for future challenges and explorations. 2025 will be a year of learning, and growth.\"\ndate: 2024-12-31\n---\n\nHey there! 👋\n\nThis post will be a bit different from the usual technical articles. I want to take a moment to reflect on **2024** and share some thoughts on what's next. This is definitely a personal post, but for me, it's also a way to track my journey and see how I evolve over time. Last year, I wrote [a similar post](https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Frecap-2023), and I love revisiting it occasionally to see how much I've changed.\n\nI finished last year's post with these words:\n\n> For 2024, I'm continuing my momentum, and new things are in the pipeline from February with my departure for Switzerland and perhaps a Devoxx 2024!\n\nLet's see how it went.\n\n## Switzerland\n\nFor the final year of my engineering studies, I had the opportunity to do an internship at **Infomaniak** in Geneva, **Switzerland**. Over six months, I worked on the internal component library with **Angular**. Despite the incredible team and company, I must admit that working with **Angular** was not always enjoyable.\n\nOver the last three years, the Angular team introduced many new features, improvements, and methodologies. While these are great, there aren't many explanations on how to apply them in real projects. Meanwhile, the component library was built from scratch to migrate from Material to a custom design system. It's a significant task and somewhat frustrating when you see many headless libraries with **React** or **Vue**. Nonetheless, I learned a lot, and I'm grateful for the experience. Knowing how to build a component is essential for any front-end project, regardless of the framework.\n\n![Me in front of Geneva.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fgeneva.jpeg)\n\nDuring this internship, I had ample free time to work on my side projects.\n\n### New blog\n\nAt the beginning of the year, I wrote some articles for the **UnJS** website. I was thrilled to share them with the community, but unfortunately, I didn't have the chance to publish them. Back then, my blog was on [esteban-soubiran.site](https:\u002F\u002Festeban-soubiran.site) and only available in **French**. I've wanted to write in **English** for a long time, and I received multiple feedbacks about that after my articles on UnJS packages.\n\nSo, I decided to create a new blog with a new domain name, in **English**, and with a new design focused on simplicity to make writing easier. I detailed the process in this article \u003CLinkToArticle id=\"674a3932-aaba-445c-82bb-0896ce3a8e7e\" \u002F>.\n\nI published my first article, \u003CLinkToArticle id=\"5b969fcd-0a41-4bc1-ae7e-3aca6d1f48a5\" \u002F>, on March 19th, but initially, my blog consisted of only one prose component to allow you to read the article. There was no homepage, no menu, nothing.\n\n### Twitch\n\nI also started streaming on **Twitch**. This has been on my mind for a long time, but having a conducive environment was a hurdle. My bedroom during my internship was perfect, calm with a good internet connection.\n\nI began some streams, and together we built a full-stack **Nuxt** application with NuxtHub, about which I wrote a whole article, \u003CLinkToArticle id=\"c21a371e-abdd-4325-b296-dadcb8deec25\" \u002F>.\n\nWe learned a lot together, especially regarding full-stack development with Nuxt. I shared all of this in two articles:\n\n- \u003CLinkToArticle id=\"6df6b96b-58fc-4294-8a38-d209f2b36d19\" \u002F>\n- \u003CLinkToArticle id=\"f47ef865-e311-48e6-ab6e-6f7ee6bde6e8\" \u002F>\n\nThis led to the creation of a package called [Nuxt Authorization](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fnuxt-authorization).\n\nThis was definitely a great experience that I had to pause because I changed my computer to a **MacBook Air**, which didn't have sufficient graphics power to stream. But in 2025, I plan to buy a second computer to resume streaming.\n\n## Meetups and conferences\n\nIn 2023, I spoke for the first time at **Devoxx France**. In 2024, I had the opportunity to speak six times.\n\nIn March, **StrasbourgJS** invited me to talk about the **UnJS** ecosystem. It was really cool to share this knowledge and finally meet some community members like Romain Lanz, Michel, and Thomas. This was excellent preparation for my next talk at Devoxx France. I wrote an article about it, \u003CLinkToArticle id=\"ba562d1f-534c-4cef-9004-2fefc09829a6\" \u002F>.\n\n![StrasbourgJS Team and Speakers.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fstrasbourgjs.jpeg)\n\nFor the second time, I had the privilege of speaking at **Devoxx France**. This time, I discussed **UnJS** packages during a quickie, a highly challenging format because you must be very concise and clear. I was pleased with the outcome and received a lot of positive feedback. I wrote an article about it, \u003CLinkToArticle id=\"08852918-9829-4658-97b3-1aabede8e9fe\" \u002F>.\n\n![Me at Devoxx France.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fdevoxx-france.png)\n\nIn September, I spoke at the **Prague Vue Conference**. This was my first conference in English, so I prepared and trained extensively to be ready. I discussed the full-stack capabilities of **Nuxt** with NuxtHub. I also met some notable figures in the **Vue** community, like Daniel Roe and Alexander Lichter. I documented the experience in an article, \u003CLinkToArticle id=\"5665d6c9-f03c-4cbb-a69b-8cabd781e7a1\" \u002F>.\n\n![PragVue Team and Speakers.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fpragvue.jpg)\n\nIn November, I spoke at **NuxtNations**, once again, about the Nuxt full-stack capabilities. It was an online conference, but I was definitely a bit anxious before my talk.\n\nThen, I continued with a talk at the **TakiMeet meetup**. It's organized by a company in Paris, where I discussed user experience and optimistic **UI**. Between the two talks, I only had a week to prepare, finishing my slides just a couple of hours before the presentation. I wouldn't recommend this approach, but I was pleased with the result. Special thanks to Camille for the invite and the entire team for the organization.\n\n![The audience at TakiMeet during my talk.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Ftakimeet.jpg)\n\nFinally, I spoke at the **VueJS Paris meetup** about the Nuxt full-stack capabilities. This was similar to my previous talks in Prague and NuxtNations, but in French. People were very interested and curious about this emerging technology. Thanks to Elise and Eduardo for the invitation and organization.\n\n![The audience at VueJS Paris during my talk.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fvuejs-paris.jpg)\n\n## End of my studies and my new job\n\nI completed my engineering studies in September, culminating in an internship in Switzerland, with a diploma in **IT security**. Six years of studies, two internships, a gap year, and numerous projects, associations, and experiences. This was an incredible journey where I met a lot of amazing people. I wrote an article about this, \u003CLinkToArticle id=\"029ab8e8-401f-4728-adf9-4e0a2899fd05\" \u002F>.\n\nToday, I'm thrilled to have completed this chapter of my life and begin a new one. I'm now a full-time software engineer at **MaiaSpace**, a French company that designs, manufactures, operates, and commercializes competitive and sustainable solutions for **space launch and mobility**. I never imagined working in the space industry, especially as a web developer. This is a unique opportunity that I couldn't pass up, but also a significant challenge with much to learn. I can't wait for the launch in 2026 (though there is plenty of time, as another retrospective will be written in the meantime). 🧑‍🚀\n\n## Learning, always learning\n\nFor six years now, I've been learning **web development**. Initially, I learned a lot about **HTML**, **CSS**, and **JavaScript**, but in recent years, I stagnated somewhat and was not entirely satisfied with that. I wanted to build a full-stack application by myself and understand all its components.\n\nSo in June, I started learning **Laravel** with the excellent platform Laracasts. This changed my life. I learned so much in little time. I've never had so much fun coding and creating applications. This decision helped me secure my position at MaiaSpace.\n\nThis might be the biggest change in my life in 2024 and likely my best decision.\n\n## My blog\n\nI've mentioned it, but I created a new technical blog in March 2024 with a single idea in mind: writing articles that I would have loved to read when starting my web development journey. I wanted my articles to be clear, technical, and comprehensive. I'm tired of all those clickbait articles on Medium or Dev Community that lack structure and where the content is just as basic as the documentation.\n\nWith this guideline in mind, I wrote multiple articles about **Nuxt**, **Vue**, **UnJS**, and **Laravel**. For me, writing is a way to solidify my knowledge and ensure I truly understand the subject. I also love to share my knowledge with the community and help people learn new and useful things.\n\nIn October, I decided to write about **VitePress** and its customizability. The concept of a standalone post no longer fulfilled me, and I wanted to create a series of articles. I introduced the concept of series on my blog with the series \u003CLinkToArticle id=\"43c95b33-041c-4108-ab98-0d21ef60ed7f\" \u002F>. These series are perfect for delving deeply into a subject and explaining complex topics in a simple and actionable manner.\n\nIn December, I released a second series called \u003CLinkToArticle id=\"55d2cdcf-ba27-4bfe-9bc1-492e866ff3b1\" \u002F>. Since my first encounter with the **Vue** ecosystem six years ago, I have always wanted to find something that thoroughly explains how to build a component library professionally, with all the tools and techniques used in the industry. Now, it's done, and I'm very proud of it!\n\nAlong the way, I continually improved my blog. I reached a point where I was very satisfied, but something was still missing. I wanted to allow people to interact with my blog, ask questions, share thoughts, and provide feedback. So, I decided to create an **API** using Laravel to facilitate this. I wrote multiple articles about it, \u003CLinkToArticle id=\"0d4cb8be-230e-444e-9419-fce9adf0ef6b\" \u002F>, \u003CLinkToArticle id=\"c8faba17-5480-49fb-a201-d426ea583097\" \u002F>, and \u003CLinkToArticle id=\"600cf474-9e89-4045-9030-021332c2dfd6\" \u002F>.\n\nIt wasn't planned, but a dark theme was also added thanks to [Roman](https:\u002F\u002Fhrynevych.dev\u002F). He sent me a message on X and proposed to add a dark theme to my blog. I quickly accepted, and I must say that I find it absolutely stunning.\n\nFinally, I launched my sponsorship program on **GitHub** and through my blog. This first step is a way to support me and my work. I'm very grateful to those who already support me, and I'm excited to see how this will evolve in the future. Big plans for this program are in the works, and I can't wait to share them with you.\n\nMy goal for 2024 was to publish one article per week. I've published 40 articles since March, so this is a success. 🥳\n\n## What comes next\n\n2024 was a year of change, learning, and growth. I'm proud of my accomplishments and the progress I've made. But I'm not done yet. There's still so much to learn, explore, and create.\n\nIn 2025, I want to continue my journey with **Laravel** and to continue to dive deeper into it. I'm excited to explore more advanced topics and build more complex applications. With the knowledge I've gained, I'm confident I can tackle new challenges and create amazing projects. Building applications is a great start, but I also want to learn more about **infrastructure and deployment**. I aim to deploy my applications professionally by managing my **VPS** with **Ansible** and **Terraform**. I want to monitor my applications, troubleshoot if something goes wrong, and scale them as needed. This will take time, but that's okay. I have the year ahead of me to learn and grow.\n\nI will continue to explore the **Vue ecosystem**. I'm excited to delve deeper into the **3D** with Three.js and explore how it can be integrated into my projects. I buy the [Three.js Journey](https:\u002F\u002Fthreejs-journey.com\u002F) course, and I can't wait to dive into it. I would love to be able to share this journey with you on Twitch. This is one of the points for the second semester of 2025, being able to stream again.\n\nI have many ideas for new projects, but I would love to launch **Inalia**, my first SaaS, that makes your talks more engaging with real-time interactions. This is really important to me, and I can't wait to share more about it in the future, maybe during live coding sessions on Twitch.\n\nI will continue to speak at conferences and meetups. In December, I submitted 5 proposals for **Vue Amsterdam**, and I hope to be selected. I have submitted some talks to meetups in Paris, and in January, I will submit some talks to **Devoxx France** and **MiXit**. I really hope this year will be as rich as 2024. I love sharing my knowledge with the community and connecting with other developers. Feel free to reach out if you're organizing an event and would like me to speak.\n\nFinally, I will continue to write articles and create series on my blog. I'm excited to explore new topics and share insights with you. I have exciting changes planned for my blog to make it more interactive and engaging. I can't wait to share them with you.\n\n2024 was a year of change, growth, and learning. I'm proud of what I've accomplished and excited for what's to come. Thank you for being part of this journey with me.\n\n2025 is an ambitious year, but I've never been as motivated and confident as I am now. It will be a great year, and I can't wait to share it with you.\n\nTake care, and happy new year! 🫶\n\n![Me skiing in costume for a wedding.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fskiing.jpg)\n",{"title":30717,"description":31189},{"loc":8951,"images":31194},[31195,31196,31197,31198,31199,31200,31201],{"loc":30778},{"loc":30896},{"loc":30913},{"loc":30935},{"loc":30959},{"loc":30972},{"loc":31176},"posts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next","Up-Ms49IrZ7_gHGJJR_zb6lYcfxG1ACN49CgOk2wtv8",{"id":31205,"title":31206,"body":31207,"contentId":33468,"date":33469,"description":33470,"extension":18,"meta":33471,"navigation":20,"path":33472,"rawbody":33473,"robots":23,"schemaOrg":23,"seo":33474,"sitemap":33475,"stem":33476,"__hash__":33477},"posts\u002Fposts\u002Fthe-perfect-guide-to-setting-up-a-new-nuxt-project.md","The Perfect Guide to Setting Up a New Nuxt Project",{"type":7,"value":31208,"toc":33459},[31209,31218,31225,31229,31235,31261,31277,31289,31300,31304,31307,31322,31341,31344,31350,31397,31404,31413,31436,31445,33456],[35,31210,31211,31212,31214,31215,31217],{},"Every time I create a new ",[6850,31213,2918],{}," project, I follow specific steps to ensure an optimal setup. I've been working with ",[6850,31216,2918],{}," since 2019 and have developed specific habits when initiating new projects.",[35,31219,31220,31221,31224],{},"Through my experience, I've explored numerous modules and reviewed projects from both experts and beginners. This has given me a solid understanding of what works well in the ",[6850,31222,31223],{},"Nuxt ecosystem",". Setting up a new project can be daunting, especially for newcomers, so I've decided to share this guide to help you set up your Nuxt project as efficiently as possible.",[51,31226,31228],{"id":31227},"creating-a-new-nuxt-project","Creating a new Nuxt project",[35,31230,31231,31232,31234],{},"Let's begin with creating a new ",[6850,31233,2918],{}," project. This is the simplest step, requiring just one command:",[192,31236,31238],{"className":2151,"code":31237,"language":2153,"meta":13,"style":13},"npx nuxi@latest init \u003Cproject-name>\n",[156,31239,31240],{"__ignoreMap":13},[200,31241,31242,31245,31248,31251,31254,31257,31259],{"class":202,"line":203},[200,31243,31244],{"class":1978},"npx",[200,31246,31247],{"class":245}," nuxi@latest",[200,31249,31250],{"class":245}," init",[200,31252,31253],{"class":214}," \u003C",[200,31255,31256],{"class":245},"project-nam",[200,31258,29418],{"class":210},[200,31260,648],{"class":214},[939,31262,31263],{},[35,31264,31265,31268,31269,31272,31273,215],{},[200,31266,31267],{},"!TIP","\nYou can also create a v4 ready project with ",[156,31270,31271],{},"npx nuxi init nuxt-app -t v4-compat",", avoiding the need for ",[39,31274,31276],{"href":31275},"#prepare-for-nuxt-4","Prepare for Nuxt 4",[939,31278,31279],{},[35,31280,31281,31283,31284,31286,31287,215],{},[200,31282,945],{},"\nI always choose ",[6850,31285,2160],{}," as my package manager. You can select the one you prefer, but I recommend trying ",[6850,31288,2160],{},[35,31290,31291,31292,31295,31296,31299],{},"Running this command will scaffold a new Nuxt project within the ",[156,31293,31294],{},"\u003Cproject-name>"," directory. The app is minimalistic, no configuration, no modules, just Nuxt and an ",[156,31297,31298],{},"app.vue"," file.",[51,31301,31303],{"id":31302},"update-the-dependencies","Update the dependencies",[35,31305,31306],{},"Before proceeding, it's essential to update dependencies to their latest versions. This ensures you can leverage the newest features and bug fixes while avoiding issues that outdated dependencies might cause. Execute the following command to update them:",[192,31308,31310],{"className":2151,"code":31309,"language":2153,"meta":13,"style":13},"npx taze -w\n",[156,31311,31312],{"__ignoreMap":13},[200,31313,31314,31316,31319],{"class":202,"line":203},[200,31315,31244],{"class":1978},[200,31317,31318],{"class":245}," taze",[200,31320,31321],{"class":245}," -w\n",[35,31323,419,31324,31327,31328,31332,31333,31336,31337,31340],{},[6850,31325,31326],{},"taze"," package, developed by ",[39,31329,24113],{"href":31330,"rel":31331},"https:\u002F\u002Fantfu.me",[77],", updates all dependencies to their latest versions. It's similar to ",[156,31334,31335],{},"npm-check-updates"," but more powerful due to its adherence to ",[6850,31338,31339],{},"semver"," versioning, offering both speed and reliability.",[51,31342,31276],{"id":31343},"prepare-for-nuxt-4",[35,31345,31346,31347,239],{},"Currently, the latest stable version of Nuxt is version 3. However, version 4 is in development and promises numerous features and enhancements. Although not officially released, you can prepare your projects for this future update by adding the following configuration in your ",[156,31348,31349],{},"nuxt.config.js",[192,31351,31353],{"className":1370,"code":31352,"language":1373,"meta":13,"style":13},"export default defineNuxtConfig({\n  future: {\n    compatibilityVersion: 4,\n  }\n})\n",[156,31354,31355,31367,31376,31387,31391],{"__ignoreMap":13},[200,31356,31357,31359,31361,31363,31365],{"class":202,"line":203},[200,31358,1380],{"class":206},[200,31360,1383],{"class":206},[200,31362,1386],{"class":223},[200,31364,227],{"class":210},[200,31366,230],{"class":214},[200,31368,31369,31372,31374],{"class":202,"line":14},[200,31370,31371],{"class":235},"  future",[200,31373,239],{"class":214},[200,31375,280],{"class":214},[200,31377,31378,31381,31383,31385],{"class":202,"line":255},[200,31379,31380],{"class":235},"    compatibilityVersion",[200,31382,239],{"class":214},[200,31384,11018],{"class":2045},[200,31386,252],{"class":214},[200,31388,31389],{"class":202,"line":272},[200,31390,4490],{"class":214},[200,31392,31393,31395],{"class":202,"line":283},[200,31394,413],{"class":214},[200,31396,416],{"class":210},[35,31398,31399,31400,31403],{},"This setup enables you to start developing your projects as though you're already using ",[6850,31401,31402],{},"Nuxt 4",". This foresight not only allows you to benefit from upcoming features but also minimizes the work required to upgrade when Nuxt 4 is released.",[35,31405,31406,31407,31409,31410,31412],{},"To make the most of Nuxt 4, create a new ",[156,31408,26746],{}," folder and move the ",[156,31411,31298],{}," file into it:",[192,31414,31416],{"className":2151,"code":31415,"language":2153,"meta":13,"style":13},"mkdir app && mv app.vue app\n",[156,31417,31418],{"__ignoreMap":13},[200,31419,31420,31423,31425,31427,31430,31433],{"class":202,"line":203},[200,31421,31422],{"class":1978},"mkdir",[200,31424,14330],{"class":245},[200,31426,14928],{"class":214},[200,31428,31429],{"class":1978}," mv",[200,31431,31432],{"class":245}," app.vue",[200,31434,31435],{"class":245}," app\n",[939,31437,31438],{},[35,31439,31440,31442,31443,26684],{},[200,31441,31267],{},"\nYou can also create a v4 ready project by using the ",[156,31444,31271],{},[7905,31446,31449,31453,31456,31489,31504,31562,31569,31595,31601,31669,31680,31691,31695,31701,31705,31717],{"text":31447,"href":31448},"Upgrading to Nuxt 4","https:\u002F\u002Fnuxt.com\u002Fdocs\u002Fgetting-started\u002Fupgrade#opting-in-to-nuxt-4",[51,31450,31452],{"id":31451},"linting-and-formatting","Linting and formatting",[35,31454,31455],{},"Nuxt provides an excellent developer experience for linting and formatting. Install both the linter and the formatter with the following command:",[192,31457,31459],{"className":2151,"code":31458,"language":2153,"meta":13,"style":13},"npx nuxi module add eslint && npx @antfu\u002Fni -D typescript\n",[156,31460,31461],{"__ignoreMap":13},[200,31462,31463,31465,31468,31471,31473,31476,31478,31481,31484,31486],{"class":202,"line":203},[200,31464,31244],{"class":1978},[200,31466,31467],{"class":245}," nuxi",[200,31469,31470],{"class":245}," module",[200,31472,2163],{"class":245},[200,31474,31475],{"class":245}," eslint",[200,31477,14928],{"class":214},[200,31479,31480],{"class":1978}," npx",[200,31482,31483],{"class":245}," @antfu\u002Fni",[200,31485,12291],{"class":245},[200,31487,31488],{"class":245}," typescript\n",[35,31490,31491,31492,31495,31496,31501,31502,239],{},"This command installs the ",[156,31493,31494],{},"@nuxt\u002Feslint"," module, which offers a ",[39,31497,31500],{"href":31498,"rel":31499},"https:\u002F\u002Feslint.nuxt.com",[77],"0-config ESLint setup for Nuxt"," projects. Enable formatting, and it will automatically fix issues upon saving a file. I always configure it in my ",[156,31503,1372],{},[192,31505,31507],{"className":9741,"code":31506,"language":1373,"meta":13,"style":13},"export default defineNuxtConfig({\n  eslint: {\n    config: {\n      stylistic: true \u002F\u002F [!code highlight]\n    }\n  }\n})\n",[156,31508,31509,31521,31530,31538,31548,31552,31556],{"__ignoreMap":13},[200,31510,31511,31513,31515,31517,31519],{"class":202,"line":203},[200,31512,1380],{"class":206},[200,31514,1383],{"class":206},[200,31516,1386],{"class":223},[200,31518,227],{"class":210},[200,31520,230],{"class":214},[200,31522,31523,31526,31528],{"class":202,"line":14},[200,31524,31525],{"class":235},"  eslint",[200,31527,239],{"class":214},[200,31529,280],{"class":214},[200,31531,31532,31534,31536],{"class":202,"line":255},[200,31533,24931],{"class":235},[200,31535,239],{"class":214},[200,31537,280],{"class":214},[200,31539,31541,31544,31546],{"class":31540,"line":272},[202,9871],[200,31542,31543],{"class":235},"      stylistic",[200,31545,239],{"class":214},[200,31547,2424],{"class":2423},[200,31549,31550],{"class":202,"line":283},[200,31551,4962],{"class":214},[200,31553,31554],{"class":202,"line":300},[200,31555,4490],{"class":214},[200,31557,31558,31560],{"class":202,"line":310},[200,31559,413],{"class":214},[200,31561,416],{"class":210},[35,31563,31564,31565,31568],{},"Additionally, create a ",[156,31566,31567],{},".vscode\u002Fsettings.json"," file to disable the prettier extension, preventing conflicts with the ESLint formatter:",[192,31570,31572],{"className":26986,"code":31571,"language":26988,"meta":13,"style":13},"{\n  \"prettier.enable\": false\n}\n",[156,31573,31574,31578,31591],{"__ignoreMap":13},[200,31575,31576],{"class":202,"line":203},[200,31577,230],{"class":214},[200,31579,31580,31582,31585,31587,31589],{"class":202,"line":14},[200,31581,26999],{"class":214},[200,31583,31584],{"class":379},"prettier.enable",[200,31586,611],{"class":214},[200,31588,239],{"class":214},[200,31590,18889],{"class":214},[200,31592,31593],{"class":202,"line":255},[200,31594,1130],{"class":214},[35,31596,31597,31598,13591],{},"Add two scripts to your ",[156,31599,31600],{},"package.json",[192,31602,31604],{"className":26986,"code":31603,"language":26988,"meta":13,"style":13},"{\n  \"scripts\": {\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint --fix .\"\n  }\n}\n",[156,31605,31606,31610,31623,31643,31661,31665],{"__ignoreMap":13},[200,31607,31608],{"class":202,"line":203},[200,31609,230],{"class":214},[200,31611,31612,31614,31617,31619,31621],{"class":202,"line":14},[200,31613,26999],{"class":214},[200,31615,31616],{"class":379},"scripts",[200,31618,611],{"class":214},[200,31620,239],{"class":214},[200,31622,280],{"class":214},[200,31624,31625,31627,31630,31632,31634,31636,31639,31641],{"class":202,"line":255},[200,31626,27031],{"class":214},[200,31628,31629],{"class":1978},"lint",[200,31631,611],{"class":214},[200,31633,239],{"class":214},[200,31635,27008],{"class":214},[200,31637,31638],{"class":245},"eslint .",[200,31640,611],{"class":214},[200,31642,252],{"class":214},[200,31644,31645,31647,31650,31652,31654,31656,31659],{"class":202,"line":272},[200,31646,27031],{"class":214},[200,31648,31649],{"class":1978},"lint:fix",[200,31651,611],{"class":214},[200,31653,239],{"class":214},[200,31655,27008],{"class":214},[200,31657,31658],{"class":245},"eslint --fix .",[200,31660,617],{"class":214},[200,31662,31663],{"class":202,"line":283},[200,31664,4490],{"class":214},[200,31666,31667],{"class":202,"line":300},[200,31668,1130],{"class":214},[35,31670,31671,31672,31675,31676,31679],{},"Now, you can run ",[156,31673,31674],{},"pnpm run lint"," to check for issues and ",[156,31677,31678],{},"pnpm run lint:fix"," to automatically fix them.",[35,31681,31682,31683,14177,31686,1638,31688,31690],{},"These predefined configurations make it straightforward to set up ",[6850,31684,31685],{},"ESLint",[6850,31687,26232],{},[6850,31689,17986],{}," support, ensuring consistent and clean code.",[51,31692,31694],{"id":31693},"ui-and-components","UI and components",[35,31696,31697,31698,31700],{},"With a clean project setup, the latest dependencies, preparation for ",[6850,31699,31402],{},", and robust linting and formatting, it's time to focus on UI and components. This step varies based on project objectives and design requirements.",[1913,31702,31704],{"id":31703},"nuxt-ui-and-nuxt-ui-pro","Nuxt UI and Nuxt UI Pro",[35,31706,31707,31708,31711,31712,31716],{},"If the project emphasizes business logic over UI, I use the ",[39,31709,9190],{"href":14203,"rel":31710},[77]," and Nuxt UI Pro component libraries. They are well-designed and easy to use. Reading the ",[39,31713,31715],{"href":9188,"rel":31714},[77],"Nuxt UI source code"," is insightful, and I also have an article on building a component library with Vue.",[7905,31718,31719,31726,31729,31746,31751,31791,31797,31830,31835,31876,31887,31968,31979,32026,32033,32037,32048,32051,32068,32074,32091,32096,32188,32197,32273,32276,32280,32289,32292,32295,32305,32323,32326,32337,32353,32360,32478,32483,32510,32516,32520,32532,32550,32553,32557,32563,32573,32804,32812,33036,33040,33049,33055,33094,33097,33101,33104,33108,33117,33177,33181,33194,33231,33242,33251,33265,33310,33327,33333,33340,33349,33371,33378,33383],{"id":31061},[939,31720,31721],{},[35,31722,31723,31725],{},[200,31724,945],{},"\nAs of writing, Nuxt UI 3 is not officially released, but it can be used in projects. If you're reading this later, let me know in the comments, and I'll update this guide.",[35,31727,31728],{},"First, install Nuxt UI and Tailwind CSS:",[192,31730,31732],{"className":2151,"code":31731,"language":2153,"meta":13,"style":13},"pnpm add @nuxt\u002Fui@next tailwindcss@next\n",[156,31733,31734],{"__ignoreMap":13},[200,31735,31736,31738,31740,31743],{"class":202,"line":203},[200,31737,2160],{"class":1978},[200,31739,2163],{"class":245},[200,31741,31742],{"class":245}," @nuxt\u002Fui@next",[200,31744,31745],{"class":245}," tailwindcss@next\n",[35,31747,31748,31749,13591],{},"Add the module to your ",[156,31750,1372],{},[192,31752,31754],{"className":1370,"code":31753,"language":1373,"meta":13,"style":13},"export default defineNuxtConfig({\n  modules: ['@nuxt\u002Fui']\n})\n",[156,31755,31756,31768,31785],{"__ignoreMap":13},[200,31757,31758,31760,31762,31764,31766],{"class":202,"line":203},[200,31759,1380],{"class":206},[200,31761,1383],{"class":206},[200,31763,1386],{"class":223},[200,31765,227],{"class":210},[200,31767,230],{"class":214},[200,31769,31770,31772,31774,31776,31778,31781,31783],{"class":202,"line":14},[200,31771,1395],{"class":235},[200,31773,239],{"class":214},[200,31775,350],{"class":210},[200,31777,249],{"class":214},[200,31779,31780],{"class":245},"@nuxt\u002Fui",[200,31782,249],{"class":214},[200,31784,4496],{"class":210},[200,31786,31787,31789],{"class":202,"line":255},[200,31788,413],{"class":214},[200,31790,416],{"class":210},[35,31792,31793,31794,13591],{},"Import necessary CSS in your ",[156,31795,31796],{},"app\u002Fassets\u002Fcss\u002Fmain.css",[192,31798,31802],{"className":31799,"code":31800,"language":31801,"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,31803,31804,31818],{"__ignoreMap":13},[200,31805,31806,31809,31811,31814,31816],{"class":202,"line":203},[200,31807,31808],{"class":206},"@import",[200,31810,27008],{"class":214},[200,31812,31813],{"class":245},"tailwindcss",[200,31815,611],{"class":214},[200,31817,5932],{"class":214},[200,31819,31820,31822,31824,31826,31828],{"class":202,"line":14},[200,31821,31808],{"class":206},[200,31823,27008],{"class":214},[200,31825,31780],{"class":245},[200,31827,611],{"class":214},[200,31829,5932],{"class":214},[35,31831,31832,31833,13591],{},"Ensure this CSS file is included in the ",[156,31834,1372],{},[192,31836,31838],{"className":1370,"code":31837,"language":1373,"meta":13,"style":13},"export default defineNuxtConfig({\n  css: ['~\u002Fassets\u002Fcss\u002Fmain.css']\n})\n",[156,31839,31840,31852,31870],{"__ignoreMap":13},[200,31841,31842,31844,31846,31848,31850],{"class":202,"line":203},[200,31843,1380],{"class":206},[200,31845,1383],{"class":206},[200,31847,1386],{"class":223},[200,31849,227],{"class":210},[200,31851,230],{"class":214},[200,31853,31854,31857,31859,31861,31863,31866,31868],{"class":202,"line":14},[200,31855,31856],{"class":235},"  css",[200,31858,239],{"class":214},[200,31860,350],{"class":210},[200,31862,249],{"class":214},[200,31864,31865],{"class":245},"~\u002Fassets\u002Fcss\u002Fmain.css",[200,31867,249],{"class":214},[200,31869,4496],{"class":210},[200,31871,31872,31874],{"class":202,"line":255},[200,31873,413],{"class":214},[200,31875,416],{"class":210},[35,31877,31878,31879,31881,31882,239],{},"Update your ",[156,31880,31567],{}," file to enhance experience with the ",[39,31883,31886],{"href":31884,"rel":31885},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=bradlc.vscode-tailwindcss",[77],"Tailwind CSS IntelliSense extension",[192,31888,31890],{"className":26986,"code":31889,"language":26988,"meta":13,"style":13},"{\n  \"files.associations\": {\n    \"*.css\": \"tailwindcss\"\n  },\n  \"editor.quickSuggestions\": {\n    \"strings\": \"on\"\n  }\n}\n",[156,31891,31892,31896,31909,31926,31930,31943,31960,31964],{"__ignoreMap":13},[200,31893,31894],{"class":202,"line":203},[200,31895,230],{"class":214},[200,31897,31898,31900,31903,31905,31907],{"class":202,"line":14},[200,31899,26999],{"class":214},[200,31901,31902],{"class":379},"files.associations",[200,31904,611],{"class":214},[200,31906,239],{"class":214},[200,31908,280],{"class":214},[200,31910,31911,31913,31916,31918,31920,31922,31924],{"class":202,"line":255},[200,31912,27031],{"class":214},[200,31914,31915],{"class":1978},"*.css",[200,31917,611],{"class":214},[200,31919,239],{"class":214},[200,31921,27008],{"class":214},[200,31923,31813],{"class":245},[200,31925,617],{"class":214},[200,31927,31928],{"class":202,"line":272},[200,31929,368],{"class":214},[200,31931,31932,31934,31937,31939,31941],{"class":202,"line":283},[200,31933,26999],{"class":214},[200,31935,31936],{"class":379},"editor.quickSuggestions",[200,31938,611],{"class":214},[200,31940,239],{"class":214},[200,31942,280],{"class":214},[200,31944,31945,31947,31950,31952,31954,31956,31958],{"class":202,"line":300},[200,31946,27031],{"class":214},[200,31948,31949],{"class":1978},"strings",[200,31951,611],{"class":214},[200,31953,239],{"class":214},[200,31955,27008],{"class":214},[200,31957,25370],{"class":245},[200,31959,617],{"class":214},[200,31961,31962],{"class":202,"line":310},[200,31963,4490],{"class":214},[200,31965,31966],{"class":202,"line":336},[200,31967,1130],{"class":214},[35,31969,31970,31971,31974,31975,31978],{},"Lastly, modify your ",[156,31972,31973],{},"app\u002Fapp.vue"," file to include the Nuxt UI component ",[156,31976,31977],{},"\u003CUApp>",", automatically injecting global components:",[192,31980,31982],{"className":21417,"code":31981,"language":12060,"meta":13,"style":13},"\u003Ctemplate>\n  \u003CUApp>\n    \u003CNuxtPage \u002F>\n  \u003C\u002FUApp>\n\u003C\u002Ftemplate>\n",[156,31983,31984,31992,32001,32010,32018],{"__ignoreMap":13},[200,31985,31986,31988,31990],{"class":202,"line":203},[200,31987,600],{"class":214},[200,31989,21428],{"class":235},[200,31991,648],{"class":214},[200,31993,31994,31996,31999],{"class":202,"line":14},[200,31995,657],{"class":214},[200,31997,31998],{"class":235},"UApp",[200,32000,648],{"class":214},[200,32002,32003,32005,32008],{"class":202,"line":255},[200,32004,804],{"class":214},[200,32006,32007],{"class":235},"NuxtPage",[200,32009,21487],{"class":214},[200,32011,32012,32014,32016],{"class":202,"line":272},[200,32013,893],{"class":214},[200,32015,31998],{"class":235},[200,32017,648],{"class":214},[200,32019,32020,32022,32024],{"class":202,"line":283},[200,32021,681],{"class":214},[200,32023,21428],{"class":235},[200,32025,648],{"class":214},[939,32027,32028],{},[35,32029,32030,32032],{},[200,32031,945],{},"\nNuxt UI Pro is a paid extension offering layout components for rapid app development. It's a worthwhile investment that saves time and effort, while supporting the Nuxt team and open-source development. 💚",[1913,32034,32036],{"id":32035},"tailwind-css","Tailwind CSS",[35,32038,32039,32040,32044,32045,32047],{},"For projects requiring specific designs or when collaborating with designers, I turn to ",[39,32041,32036],{"href":32042,"rel":32043},"https:\u002F\u002Ftailwindcss.com",[77],". It's a utility-first CSS framework that streamlines the design process to such an extent that working without it is unimaginable. The upcoming version, Tailwind CSS 4, includes an indispensable ",[6850,32046,24355],{}," plugin, eliminating the need for additional modules and simplifying the setup.",[35,32049,32050],{},"Install the latest Tailwind CSS version:",[192,32052,32054],{"className":2151,"code":32053,"language":2153,"meta":13,"style":13},"pnpm add tailwindcss@next @tailwindcss\u002Fvite@next\n",[156,32055,32056],{"__ignoreMap":13},[200,32057,32058,32060,32062,32065],{"class":202,"line":203},[200,32059,2160],{"class":1978},[200,32061,2163],{"class":245},[200,32063,32064],{"class":245}," tailwindcss@next",[200,32066,32067],{"class":245}," @tailwindcss\u002Fvite@next\n",[35,32069,32070,32071,32073],{},"Create an ",[156,32072,31796],{}," file and import Tailwind CSS:",[192,32075,32077],{"className":31799,"code":32076,"language":31801,"meta":13,"style":13},"@import \"tailwindcss\";\n",[156,32078,32079],{"__ignoreMap":13},[200,32080,32081,32083,32085,32087,32089],{"class":202,"line":203},[200,32082,31808],{"class":206},[200,32084,27008],{"class":214},[200,32086,31813],{"class":245},[200,32088,611],{"class":214},[200,32090,5932],{"class":214},[35,32092,31878,32093,32095],{},[156,32094,1372],{}," file to utilize the Vite plugin:",[192,32097,32099],{"className":1370,"code":32098,"language":1373,"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,32100,32101,32117,32121,32133,32151,32159,32167,32174,32178,32182],{"__ignoreMap":13},[200,32102,32103,32105,32108,32110,32112,32115],{"class":202,"line":203},[200,32104,973],{"class":206},[200,32106,32107],{"class":210}," Tailwind ",[200,32109,1472],{"class":206},[200,32111,242],{"class":214},[200,32113,32114],{"class":245},"@tailwindcss\u002Fvite",[200,32116,992],{"class":214},[200,32118,32119],{"class":202,"line":14},[200,32120,529],{"emptyLinePlaceholder":20},[200,32122,32123,32125,32127,32129,32131],{"class":202,"line":255},[200,32124,1380],{"class":206},[200,32126,1383],{"class":206},[200,32128,1386],{"class":223},[200,32130,227],{"class":210},[200,32132,230],{"class":214},[200,32134,32135,32137,32139,32141,32143,32145,32147,32149],{"class":202,"line":272},[200,32136,31856],{"class":235},[200,32138,239],{"class":214},[200,32140,350],{"class":210},[200,32142,249],{"class":214},[200,32144,31865],{"class":245},[200,32146,249],{"class":214},[200,32148,360],{"class":210},[200,32150,252],{"class":214},[200,32152,32153,32155,32157],{"class":202,"line":283},[200,32154,2590],{"class":235},[200,32156,239],{"class":214},[200,32158,280],{"class":214},[200,32160,32161,32163,32165],{"class":202,"line":300},[200,32162,2599],{"class":235},[200,32164,239],{"class":214},[200,32166,1400],{"class":210},[200,32168,32169,32172],{"class":202,"line":310},[200,32170,32171],{"class":223},"      Tailwind",[200,32173,466],{"class":210},[200,32175,32176],{"class":202,"line":336},[200,32177,4484],{"class":210},[200,32179,32180],{"class":202,"line":342},[200,32181,4490],{"class":214},[200,32183,32184,32186],{"class":202,"line":365},[200,32185,413],{"class":214},[200,32187,416],{"class":210},[35,32189,32190,32191,32193,32194,239],{},"Enhance your developer experience by updating your ",[156,32192,31567],{}," file for the ",[39,32195,31886],{"href":31884,"rel":32196},[77],[192,32198,32199],{"className":26986,"code":31889,"language":26988,"meta":13,"style":13},[156,32200,32201,32205,32217,32233,32237,32249,32265,32269],{"__ignoreMap":13},[200,32202,32203],{"class":202,"line":203},[200,32204,230],{"class":214},[200,32206,32207,32209,32211,32213,32215],{"class":202,"line":14},[200,32208,26999],{"class":214},[200,32210,31902],{"class":379},[200,32212,611],{"class":214},[200,32214,239],{"class":214},[200,32216,280],{"class":214},[200,32218,32219,32221,32223,32225,32227,32229,32231],{"class":202,"line":255},[200,32220,27031],{"class":214},[200,32222,31915],{"class":1978},[200,32224,611],{"class":214},[200,32226,239],{"class":214},[200,32228,27008],{"class":214},[200,32230,31813],{"class":245},[200,32232,617],{"class":214},[200,32234,32235],{"class":202,"line":272},[200,32236,368],{"class":214},[200,32238,32239,32241,32243,32245,32247],{"class":202,"line":283},[200,32240,26999],{"class":214},[200,32242,31936],{"class":379},[200,32244,611],{"class":214},[200,32246,239],{"class":214},[200,32248,280],{"class":214},[200,32250,32251,32253,32255,32257,32259,32261,32263],{"class":202,"line":300},[200,32252,27031],{"class":214},[200,32254,31949],{"class":1978},[200,32256,611],{"class":214},[200,32258,239],{"class":214},[200,32260,27008],{"class":214},[200,32262,25370],{"class":245},[200,32264,617],{"class":214},[200,32266,32267],{"class":202,"line":310},[200,32268,4490],{"class":214},[200,32270,32271],{"class":202,"line":336},[200,32272,1130],{"class":214},[35,32274,32275],{},"The setup is incredibly straightforward!",[51,32277,32279],{"id":32278},"icons","Icons",[35,32281,32282,32283,32288],{},"Incorporating icons into your project is essential, albeit deceptively complex. ",[39,32284,32287],{"href":32285,"rel":32286},"https:\u002F\u002Fnuxt.com\u002Fblog\u002Fnuxt-icon-v1-0",[77],"Anthony Fu's article"," elaborates on finding the perfect icon solution.",[35,32290,32291],{},"If utilizing Nuxt UI, icons are already included. You can safely skip this section.",[35,32293,32294],{},"For Tailwind CSS users, two options are available:",[68,32296,32297],{},[71,32298,12142,32299,32304],{},[39,32300,32303],{"href":32301,"rel":32302},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ficon",[77],"Nuxt Icons"," module provides access to over 200,000 icons from various icon sets:",[192,32306,32308],{"className":2151,"code":32307,"language":2153,"meta":13,"style":13},"npx nuxi module add icon\n",[156,32309,32310],{"__ignoreMap":13},[200,32311,32312,32314,32316,32318,32320],{"class":202,"line":203},[200,32313,31244],{"class":1978},[200,32315,31467],{"class":245},[200,32317,31470],{"class":245},[200,32319,2163],{"class":245},[200,32321,32322],{"class":245}," icon\n",[35,32324,32325],{},"This is the simplest solution and with the latest improvements from the v1 release, it's a powerful choice. I recommend starting with this option.",[68,32327,32328],{"start":14},[71,32329,32330,32331,32336],{},"Use the Tailwind CSS plugin, ",[39,32332,32335],{"href":32333,"rel":32334},"https:\u002F\u002Fgithub.com\u002Fegoist\u002Ftailwindcss-icons",[77],"Tailwind CSS Icons",", offers similar icons via a different approach:",[192,32338,32340],{"className":2151,"code":32339,"language":2153,"meta":13,"style":13},"npx @antfu\u002Fni -D @egoist\u002Ftailwindcss-icons\n",[156,32341,32342],{"__ignoreMap":13},[200,32343,32344,32346,32348,32350],{"class":202,"line":203},[200,32345,31244],{"class":1978},[200,32347,31483],{"class":245},[200,32349,12291],{"class":245},[200,32351,32352],{"class":245}," @egoist\u002Ftailwindcss-icons\n",[35,32354,32355,32356,32359],{},"Create a ",[156,32357,32358],{},"tailwind.config.js"," file and include the plugin:",[192,32361,32363],{"className":25029,"code":32362,"language":25031,"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,32364,32365,32390,32394,32415,32424,32432,32441,32460,32468,32474],{"__ignoreMap":13},[200,32366,32367,32369,32371,32374,32376,32379,32381,32383,32385,32388],{"class":202,"line":203},[200,32368,973],{"class":206},[200,32370,318],{"class":214},[200,32372,32373],{"class":210}," getIconCollections",[200,32375,550],{"class":214},[200,32377,32378],{"class":210}," iconsPlugin",[200,32380,981],{"class":214},[200,32382,984],{"class":206},[200,32384,242],{"class":214},[200,32386,32387],{"class":245},"@egoist\u002Ftailwindcss-icons",[200,32389,992],{"class":214},[200,32391,32392],{"class":202,"line":14},[200,32393,529],{"emptyLinePlaceholder":20},[200,32395,32396,32399,32402,32405,32407,32411,32413],{"class":202,"line":255},[200,32397,32398],{"class":401},"\u002F** ",[200,32400,32401],{"class":206},"@",[200,32403,3771],{"class":32404},"s6hCs",[200,32406,318],{"class":206},[200,32408,32410],{"class":32409},"sFweD","import('tailwindcss').Config",[200,32412,413],{"class":206},[200,32414,26093],{"class":401},[200,32416,32417,32420,32422],{"class":202,"line":272},[200,32418,32419],{"class":214},"module.exports",[200,32421,510],{"class":214},[200,32423,280],{"class":214},[200,32425,32426,32428,32430],{"class":202,"line":283},[200,32427,1520],{"class":235},[200,32429,239],{"class":214},[200,32431,1400],{"class":210},[200,32433,32434,32437,32439],{"class":202,"line":300},[200,32435,32436],{"class":223},"    iconsPlugin",[200,32438,227],{"class":210},[200,32440,230],{"class":214},[200,32442,32443,32446,32448,32450,32452,32455,32458],{"class":202,"line":310},[200,32444,32445],{"class":235},"      collections",[200,32447,239],{"class":214},[200,32449,32373],{"class":223},[200,32451,25183],{"class":210},[200,32453,32454],{"class":401},"\u002F** ... *\u002F",[200,32456,32457],{"class":210},"])",[200,32459,252],{"class":214},[200,32461,32462,32464,32466],{"class":202,"line":336},[200,32463,2096],{"class":214},[200,32465,2089],{"class":210},[200,32467,252],{"class":214},[200,32469,32470,32472],{"class":202,"line":342},[200,32471,1417],{"class":210},[200,32473,252],{"class":214},[200,32475,32476],{"class":202,"line":365},[200,32477,1130],{"class":214},[35,32479,32480,32481,13591],{},"Import the configuration in your ",[156,32482,31796],{},[192,32484,32486],{"className":31799,"code":32485,"language":31801,"meta":13,"style":13},"@import \"tailwindcss\";\n@config \"..\u002F..\u002F..\u002Ftailwind.config.js\";\n",[156,32487,32488,32500],{"__ignoreMap":13},[200,32489,32490,32492,32494,32496,32498],{"class":202,"line":203},[200,32491,31808],{"class":206},[200,32493,27008],{"class":214},[200,32495,31813],{"class":245},[200,32497,611],{"class":214},[200,32499,5932],{"class":214},[200,32501,32502,32505,32508],{"class":202,"line":14},[200,32503,32504],{"class":206},"@config",[200,32506,32507],{"class":210}," \"..\u002F..\u002F..\u002Ftailwind.config.js\"",[200,32509,5932],{"class":214},[35,32511,32512,32513,215],{},"I personally prefer the second solution due to its uniform setup across projects, including non-Nuxt ones, and at the time I made this choice, the Nuxt Icons module was suffering from many drawbacks. However, that's not the case anymore and I highly recommend you to start with ",[39,32514,32303],{"href":32301,"rel":32515},[77],[51,32517,32519],{"id":32518},"fonts","Fonts",[35,32521,32522,32523,32525,32526,32531],{},"Fonts define your project's personality and influence user perception. ",[6850,32524,2918],{}," provides a solution: ",[39,32527,32530],{"href":32528,"rel":32529},"https:\u002F\u002Ffonts.nuxt.com\u002F",[77],"Nuxt Fonts",". This zero-config module automatically loads required fonts by scanning CSS files. Installation is straightforward:",[192,32533,32535],{"className":2151,"code":32534,"language":2153,"meta":13,"style":13},"npx nuxi module add fonts\n",[156,32536,32537],{"__ignoreMap":13},[200,32538,32539,32541,32543,32545,32547],{"class":202,"line":203},[200,32540,31244],{"class":1978},[200,32542,31467],{"class":245},[200,32544,31470],{"class":245},[200,32546,2163],{"class":245},[200,32548,32549],{"class":245}," fonts\n",[35,32551,32552],{},"This module is part of my projects unless I'm using Nuxt UI, where it's included. But I have to say that I rarely need to install it since Nuxt UI suffices for most scenarios.",[51,32554,32556],{"id":32555},"ci","CI",[35,32558,32559,32560,32562],{},"For personal projects, I use ",[6850,32561,11622],{}," and always set up a CI to enforce linting and autofix errors. This approach ensures clean, consistent code. While linters and formatters are great, they're useless if unused.",[35,32564,32565,32566,32569,32570,13591],{},"I utilize ",[6850,32567,32568],{},"GitHub Actions",", detailed in a ",[156,32571,32572],{},".github\u002Fworkflows\u002Fci.yml",[192,32574,32578],{"className":32575,"code":32576,"language":32577,"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: Install dependencies\n        run: pnpm install\n\n      - name: Lint files\n        run: pnpm run lint\n","yaml",[156,32579,32580,32589,32593,32599,32606,32613,32621,32628,32634,32640,32644,32651,32658,32667,32677,32681,32688,32703,32715,32728,32735,32745,32755,32759,32770,32780,32784,32795],{"__ignoreMap":13},[200,32581,32582,32584,32586],{"class":202,"line":203},[200,32583,20089],{"class":235},[200,32585,239],{"class":214},[200,32587,32588],{"class":245}," CI\n",[200,32590,32591],{"class":202,"line":14},[200,32592,529],{"emptyLinePlaceholder":20},[200,32594,32595,32597],{"class":202,"line":255},[200,32596,25370],{"class":2423},[200,32598,30028],{"class":214},[200,32600,32601,32604],{"class":202,"line":272},[200,32602,32603],{"class":235},"  push",[200,32605,30028],{"class":214},[200,32607,32608,32611],{"class":202,"line":283},[200,32609,32610],{"class":235},"    branches",[200,32612,30028],{"class":214},[200,32614,32615,32618],{"class":202,"line":300},[200,32616,32617],{"class":214},"      -",[200,32619,32620],{"class":245}," main\n",[200,32622,32623,32626],{"class":202,"line":310},[200,32624,32625],{"class":235},"  pull_request",[200,32627,30028],{"class":214},[200,32629,32630,32632],{"class":202,"line":336},[200,32631,32610],{"class":235},[200,32633,30028],{"class":214},[200,32635,32636,32638],{"class":202,"line":342},[200,32637,32617],{"class":214},[200,32639,32620],{"class":245},[200,32641,32642],{"class":202,"line":365},[200,32643,529],{"emptyLinePlaceholder":20},[200,32645,32646,32649],{"class":202,"line":371},[200,32647,32648],{"class":235},"jobs",[200,32650,30028],{"class":214},[200,32652,32653,32656],{"class":202,"line":398},[200,32654,32655],{"class":235},"  lint",[200,32657,30028],{"class":214},[200,32659,32660,32662,32664],{"class":202,"line":405},[200,32661,11797],{"class":235},[200,32663,239],{"class":214},[200,32665,32666],{"class":245}," Lint\n",[200,32668,32669,32672,32674],{"class":202,"line":410},[200,32670,32671],{"class":235},"    runs-on",[200,32673,239],{"class":214},[200,32675,32676],{"class":245}," ubuntu-latest\n",[200,32678,32679],{"class":202,"line":861},[200,32680,529],{"emptyLinePlaceholder":20},[200,32682,32683,32686],{"class":202,"line":890},[200,32684,32685],{"class":235},"    steps",[200,32687,30028],{"class":214},[200,32689,32690,32692,32695,32697,32700],{"class":202,"line":900},[200,32691,32617],{"class":214},[200,32693,32694],{"class":235}," uses",[200,32696,239],{"class":214},[200,32698,32699],{"class":245}," actions\u002Fcheckout@b4ffde65f46336ab88eb53be808477a3936bae11",[200,32701,32702],{"class":401}," # v4\n",[200,32704,32705,32707,32710,32712],{"class":202,"line":905},[200,32706,32617],{"class":214},[200,32708,32709],{"class":235}," run",[200,32711,239],{"class":214},[200,32713,32714],{"class":245}," corepack enable\n",[200,32716,32717,32719,32721,32723,32726],{"class":202,"line":931},[200,32718,32617],{"class":214},[200,32720,32694],{"class":235},[200,32722,239],{"class":214},[200,32724,32725],{"class":245}," actions\u002Fsetup-node@60edb5dd545a775178f52524783378180af0d1f8",[200,32727,32702],{"class":401},[200,32729,32730,32733],{"class":202,"line":3640},[200,32731,32732],{"class":235},"        with",[200,32734,30028],{"class":214},[200,32736,32737,32740,32742],{"class":202,"line":3645},[200,32738,32739],{"class":235},"          node-version",[200,32741,239],{"class":214},[200,32743,32744],{"class":2045}," 22\n",[200,32746,32747,32750,32752],{"class":202,"line":4020},[200,32748,32749],{"class":235},"          cache",[200,32751,239],{"class":214},[200,32753,32754],{"class":245}," pnpm\n",[200,32756,32757],{"class":202,"line":4475},[200,32758,529],{"emptyLinePlaceholder":20},[200,32760,32761,32763,32765,32767],{"class":202,"line":4481},[200,32762,32617],{"class":214},[200,32764,701],{"class":235},[200,32766,239],{"class":214},[200,32768,32769],{"class":245}," Install dependencies\n",[200,32771,32772,32775,32777],{"class":202,"line":4487},[200,32773,32774],{"class":235},"        run",[200,32776,239],{"class":214},[200,32778,32779],{"class":245}," pnpm install\n",[200,32781,32782],{"class":202,"line":4493},[200,32783,529],{"emptyLinePlaceholder":20},[200,32785,32786,32788,32790,32792],{"class":202,"line":5454},[200,32787,32617],{"class":214},[200,32789,701],{"class":235},[200,32791,239],{"class":214},[200,32793,32794],{"class":245}," Lint files\n",[200,32796,32797,32799,32801],{"class":202,"line":5459},[200,32798,32774],{"class":235},[200,32800,239],{"class":214},[200,32802,32803],{"class":245}," pnpm run lint\n",[35,32805,32806,32807,239],{},"To automate code formatting and commits, I use ",[39,32808,32811],{"href":32809,"rel":32810},"https:\u002F\u002Fautofix.ci\u002F",[77],"autofix.ci",[192,32813,32815],{"className":32575,"code":32814,"language":32577,"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: Install dependencies\n        run: pnpm install\n\n      - name: Lint & Fix\n        run: pnpm run lint:fix\n\n      - uses: autofix-ci\u002Faction@ff86a557419858bb967097bfc916833f5647fa8c\n",[156,32816,32817,32829,32833,32839,32845,32851,32864,32868,32875,32885,32889,32895,32902,32910,32916,32929,32939,32952,32958,32967,32975,32979,32989,32997,33001,33012,33021,33025],{"__ignoreMap":13},[200,32818,32819,32821,32823,32826],{"class":202,"line":203},[200,32820,20089],{"class":235},[200,32822,239],{"class":214},[200,32824,32825],{"class":245}," autofix.ci",[200,32827,32828],{"class":401}," # needed to securely identify the workflow\n",[200,32830,32831],{"class":202,"line":14},[200,32832,529],{"emptyLinePlaceholder":20},[200,32834,32835,32837],{"class":202,"line":255},[200,32836,25370],{"class":2423},[200,32838,30028],{"class":214},[200,32840,32841,32843],{"class":202,"line":272},[200,32842,32625],{"class":235},[200,32844,30028],{"class":214},[200,32846,32847,32849],{"class":202,"line":283},[200,32848,32603],{"class":235},[200,32850,30028],{"class":214},[200,32852,32853,32855,32857,32859,32862],{"class":202,"line":300},[200,32854,32610],{"class":235},[200,32856,239],{"class":214},[200,32858,350],{"class":214},[200,32860,32861],{"class":245},"main",[200,32863,4496],{"class":214},[200,32865,32866],{"class":202,"line":310},[200,32867,529],{"emptyLinePlaceholder":20},[200,32869,32870,32873],{"class":202,"line":336},[200,32871,32872],{"class":235},"permissions",[200,32874,30028],{"class":214},[200,32876,32877,32880,32882],{"class":202,"line":342},[200,32878,32879],{"class":235},"  contents",[200,32881,239],{"class":214},[200,32883,32884],{"class":245}," read\n",[200,32886,32887],{"class":202,"line":365},[200,32888,529],{"emptyLinePlaceholder":20},[200,32890,32891,32893],{"class":202,"line":371},[200,32892,32648],{"class":235},[200,32894,30028],{"class":214},[200,32896,32897,32900],{"class":202,"line":398},[200,32898,32899],{"class":235},"  autofix",[200,32901,30028],{"class":214},[200,32903,32904,32906,32908],{"class":202,"line":405},[200,32905,32671],{"class":235},[200,32907,239],{"class":214},[200,32909,32676],{"class":245},[200,32911,32912,32914],{"class":202,"line":410},[200,32913,32685],{"class":235},[200,32915,30028],{"class":214},[200,32917,32918,32920,32922,32924,32927],{"class":202,"line":861},[200,32919,32617],{"class":214},[200,32921,32694],{"class":235},[200,32923,239],{"class":214},[200,32925,32926],{"class":245}," actions\u002Fcheckout@11bd71901bbe5b1630ceea73d27597364c9af683",[200,32928,32702],{"class":401},[200,32930,32931,32933,32935,32937],{"class":202,"line":890},[200,32932,32617],{"class":214},[200,32934,32709],{"class":235},[200,32936,239],{"class":214},[200,32938,32714],{"class":245},[200,32940,32941,32943,32945,32947,32950],{"class":202,"line":900},[200,32942,32617],{"class":214},[200,32944,32694],{"class":235},[200,32946,239],{"class":214},[200,32948,32949],{"class":245}," actions\u002Fsetup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af",[200,32951,32702],{"class":401},[200,32953,32954,32956],{"class":202,"line":905},[200,32955,32732],{"class":235},[200,32957,30028],{"class":214},[200,32959,32960,32962,32964],{"class":202,"line":931},[200,32961,32739],{"class":235},[200,32963,239],{"class":214},[200,32965,32966],{"class":2045}," 20.5\n",[200,32968,32969,32971,32973],{"class":202,"line":3640},[200,32970,32749],{"class":235},[200,32972,239],{"class":214},[200,32974,32754],{"class":245},[200,32976,32977],{"class":202,"line":3645},[200,32978,529],{"emptyLinePlaceholder":20},[200,32980,32981,32983,32985,32987],{"class":202,"line":4020},[200,32982,32617],{"class":214},[200,32984,701],{"class":235},[200,32986,239],{"class":214},[200,32988,32769],{"class":245},[200,32990,32991,32993,32995],{"class":202,"line":4475},[200,32992,32774],{"class":235},[200,32994,239],{"class":214},[200,32996,32779],{"class":245},[200,32998,32999],{"class":202,"line":4481},[200,33000,529],{"emptyLinePlaceholder":20},[200,33002,33003,33005,33007,33009],{"class":202,"line":4487},[200,33004,32617],{"class":214},[200,33006,701],{"class":235},[200,33008,239],{"class":214},[200,33010,33011],{"class":245}," Lint & Fix\n",[200,33013,33014,33016,33018],{"class":202,"line":4493},[200,33015,32774],{"class":235},[200,33017,239],{"class":214},[200,33019,33020],{"class":245}," pnpm run lint:fix\n",[200,33022,33023],{"class":202,"line":5454},[200,33024,529],{"emptyLinePlaceholder":20},[200,33026,33027,33029,33031,33033],{"class":202,"line":5459},[200,33028,32617],{"class":214},[200,33030,32694],{"class":235},[200,33032,239],{"class":214},[200,33034,33035],{"class":245}," autofix-ci\u002Faction@ff86a557419858bb967097bfc916833f5647fa8c\n",[51,33037,33039],{"id":33038},"keep-dependencies-up-to-date","Keep dependencies up-to-date",[35,33041,33042,33043,33048],{},"Maintaining up-to-date dependencies is crucial. With numerous projects, this can be time-consuming. I use ",[39,33044,33047],{"href":33045,"rel":33046},"https:\u002F\u002Fdocs.renovatebot.com\u002F",[77],"Renovate"," in each project to automate dependency updates:",[35,33050,32355,33051,33054],{},[156,33052,33053],{},"renovate.json"," file with:",[192,33056,33058],{"className":26986,"code":33057,"language":26988,"meta":13,"style":13},"{\n  \"extends\": [\n    \"github>nuxt\u002Frenovate-config-nuxt\"\n  ]\n}\n",[156,33059,33060,33064,33077,33086,33090],{"__ignoreMap":13},[200,33061,33062],{"class":202,"line":203},[200,33063,230],{"class":214},[200,33065,33066,33068,33071,33073,33075],{"class":202,"line":14},[200,33067,26999],{"class":214},[200,33069,33070],{"class":379},"extends",[200,33072,611],{"class":214},[200,33074,239],{"class":214},[200,33076,1400],{"class":214},[200,33078,33079,33081,33084],{"class":202,"line":255},[200,33080,27031],{"class":214},[200,33082,33083],{"class":245},"github>nuxt\u002Frenovate-config-nuxt",[200,33085,617],{"class":214},[200,33087,33088],{"class":202,"line":272},[200,33089,13674],{"class":214},[200,33091,33092],{"class":202,"line":283},[200,33093,1130],{"class":214},[35,33095,33096],{},"This tool is indispensable, and I find it challenging to work on projects lacking Renovate (like some work projects).",[51,33098,33100],{"id":33099},"tips-tricks-and-additional-considerations","Tips, tricks, and additional considerations",[35,33102,33103],{},"Here are some extra tips and tricks I find valuable, even if not directly related to the setup:",[1913,33105,33107],{"id":33106},"always-place-scripts-first","Always place scripts first",[35,33109,33110,33111,33113,33114,33116],{},"Organize ",[156,33112,12060],{}," files by placing the ",[156,33115,14892],{}," section first. This offers clarity by presenting component logic before the template:",[192,33118,33120],{"className":21417,"code":33119,"language":12060,"meta":13,"style":13},"\u003Cscript lang=\"ts\">\n\u002F\u002F ...\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u002F\u002F ...\n\u003C\u002Ftemplate>\n",[156,33121,33122,33140,33145,33153,33157,33165,33169],{"__ignoreMap":13},[200,33123,33124,33126,33128,33130,33132,33134,33136,33138],{"class":202,"line":203},[200,33125,600],{"class":214},[200,33127,14892],{"class":235},[200,33129,14590],{"class":379},[200,33131,449],{"class":214},[200,33133,611],{"class":214},[200,33135,1373],{"class":245},[200,33137,611],{"class":214},[200,33139,648],{"class":214},[200,33141,33142],{"class":202,"line":14},[200,33143,33144],{"class":401},"\u002F\u002F ...\n",[200,33146,33147,33149,33151],{"class":202,"line":255},[200,33148,681],{"class":214},[200,33150,14892],{"class":235},[200,33152,648],{"class":214},[200,33154,33155],{"class":202,"line":272},[200,33156,529],{"emptyLinePlaceholder":20},[200,33158,33159,33161,33163],{"class":202,"line":283},[200,33160,600],{"class":214},[200,33162,21428],{"class":235},[200,33164,648],{"class":214},[200,33166,33167],{"class":202,"line":300},[200,33168,10634],{"class":210},[200,33170,33171,33173,33175],{"class":202,"line":310},[200,33172,681],{"class":214},[200,33174,21428],{"class":235},[200,33176,648],{"class":214},[1913,33178,33180],{"id":33179},"embrace-typescript","Embrace TypeScript",[35,33182,33183,33184,33186,33187,33189,33190,33193],{},"I advocate using ",[6850,33185,26232],{}," across all ",[156,33188,12060],{}," files and utility or composable scripts. Adding ",[156,33191,33192],{},"lang=\"ts\""," even without TypeScript code enhances the developer experience:",[192,33195,33197],{"className":21417,"code":33196,"language":12060,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\n\u002F\u002F ...\n\u003C\u002Fscript>\n",[156,33198,33199,33219,33223],{"__ignoreMap":13},[200,33200,33201,33203,33205,33207,33209,33211,33213,33215,33217],{"class":202,"line":203},[200,33202,600],{"class":214},[200,33204,14892],{"class":235},[200,33206,14590],{"class":379},[200,33208,449],{"class":214},[200,33210,611],{"class":214},[200,33212,1373],{"class":245},[200,33214,611],{"class":214},[200,33216,21781],{"class":379},[200,33218,648],{"class":214},[200,33220,33221],{"class":202,"line":14},[200,33222,33144],{"class":401},[200,33224,33225,33227,33229],{"class":202,"line":255},[200,33226,681],{"class":214},[200,33228,14892],{"class":235},[200,33230,648],{"class":214},[939,33232,33233],{},[35,33234,33235,33237,33238,33241],{},[200,33236,945],{},"\nAlways opt for the ",[156,33239,33240],{},"setup"," syntax in components.",[1913,33243,33245,33246,24572,33249],{"id":33244},"avoid-using-processenv-in-nuxtconfigts","Avoid using ",[156,33247,33248],{},"process.env",[156,33250,1372],{},[35,33252,33253,33254,24572,33256,33258,33259,33262,33263,239],{},"Refrain from directly accessing ",[156,33255,33248],{},[156,33257,1372],{},". For instance, assigning defaults for ",[156,33260,33261],{},"runtimeConfig"," shouldn't use ",[156,33264,33248],{},[192,33266,33268],{"className":1370,"code":33267,"language":1373,"meta":13,"style":13},"export default defineNuxtConfig({\n  runtimeConfig: {\n    apiUrl: ''\n  }\n})\n",[156,33269,33270,33282,33291,33300,33304],{"__ignoreMap":13},[200,33271,33272,33274,33276,33278,33280],{"class":202,"line":203},[200,33273,1380],{"class":206},[200,33275,1383],{"class":206},[200,33277,1386],{"class":223},[200,33279,227],{"class":210},[200,33281,230],{"class":214},[200,33283,33284,33287,33289],{"class":202,"line":14},[200,33285,33286],{"class":235},"  runtimeConfig",[200,33288,239],{"class":214},[200,33290,280],{"class":214},[200,33292,33293,33296,33298],{"class":202,"line":255},[200,33294,33295],{"class":235},"    apiUrl",[200,33297,239],{"class":214},[200,33299,18875],{"class":214},[200,33301,33302],{"class":202,"line":272},[200,33303,4490],{"class":214},[200,33305,33306,33308],{"class":202,"line":283},[200,33307,413],{"class":214},[200,33309,416],{"class":210},[939,33311,33312],{},[35,33313,33314,33316,33317,33320,33321,33323,33324,215],{},[200,33315,31267],{},"\nHover over the ",[156,33318,33319],{},"apiUrl"," key in your IDE for environment variable hints. ",[6850,33322,2918],{}," provides complex typing, such as ",[156,33325,33326],{},"NUXT_API_URL",[35,33328,32070,33329,33332],{},[156,33330,33331],{},".env"," file at the project's root to define environment variables:",[192,33334,33338],{"className":33335,"code":33337,"language":698},[33336],"language-text","NUXT_API_URL=https:\u002F\u002Fapi.example.com\n",[156,33339,33337],{"__ignoreMap":13},[35,33341,33342,33343,33345,33346,239],{},"Exclude the ",[156,33344,33331],{}," file using ",[156,33347,33348],{},".gitignore",[192,33350,33352],{"className":2151,"code":33351,"language":2153,"meta":13,"style":13},"echo \".env\" >> .gitignore\n",[156,33353,33354],{"__ignoreMap":13},[200,33355,33356,33359,33361,33363,33365,33368],{"class":202,"line":203},[200,33357,33358],{"class":223},"echo",[200,33360,27008],{"class":214},[200,33362,33331],{"class":245},[200,33364,611],{"class":214},[200,33366,33367],{"class":214}," >>",[200,33369,33370],{"class":245}," .gitignore\n",[35,33372,33373,33374,33377],{},"Provide a ",[156,33375,33376],{},".env.example"," file to guide other developers:",[192,33379,33381],{"className":33380,"code":33337,"language":698},[33336],[156,33382,33337],{"__ignoreMap":13},[7905,33384,33387,33391,33397,33407,33423,33436,33440,33446,33453],{"text":33385,"href":33386},"Runtime Config: The Common Mistakes","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=_FYV5WfiWvs",[1913,33388,33390],{"id":33389},"audit-before-installing-modules","Audit before installing modules",[35,33392,33393,33394,33396],{},"Conduct an audit before adding new modules to evaluate necessity and functionality. Sometimes, it's possible to proceed without them or to choose a superior alternative. Avoid basing decisions on stars or downloads. Instead, peruse documentation, source code, issues, and PRs for a comprehensive quality assessment. The ",[6850,33395,2918],{}," ecosystem evolves rapidly, and some modules may become outdated or incompatible with newer Nuxt versions.",[1913,33398,33400,33401,1638,33404],{"id":33399},"choosing-between-usefetch-and-fetch","Choosing between ",[156,33402,33403],{},"useFetch",[156,33405,33406],{},"$fetch",[35,33408,33245,33409,33412,33413,33415,33416,33422],{},[156,33410,33411],{},"fetch"," directly; opt for ",[156,33414,33406],{},", an enhanced instance of ",[39,33417,33420],{"href":33418,"rel":33419},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fofetch",[77],[6850,33421,19771],{},". Distinguish usage as follows:",[68,33424,33425,33431],{},[71,33426,33427,33428,33430],{},"Use ",[156,33429,33406],{}," for client-side operations, such as form data submissions.",[71,33432,33427,33433,33435],{},[156,33434,33403],{}," where server-side rendering is feasible, like fetching API data to display on pages.",[51,33437,33439],{"id":33438},"the-perfect-setup-is-yours","The perfect setup is yours",[35,33441,33442,33443,33445],{},"This guide embodies everything I do to initialize a new ",[6850,33444,2918],{}," project. This comprehensive setup takes around 10 minutes, resulting in a clean, powerful, and future-ready project that allows focusing on business logic without distractions.",[939,33447,33448],{},[35,33449,33450,33452],{},[200,33451,29700],{},"\nAvoid creating a template project from this guide. The ecosystem is dynamic, and new modules swiftly emerge. Each project might necessitate nuanced adjustments. This guide is a starting point, a checklist for initial setup. However, always adapt to the specific project needs. Maintaining a template demands effort and is generally not worthwhile.",[35,33454,33455],{},"I hope this guide enhances your Nuxt project setup. If you have questions or want to share your configuration, please leave a comment below.",[1171,33457,33458],{},"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":33460},[33461,33462,33463,33464,33465],{"id":31227,"depth":14,"text":31228},{"id":31302,"depth":14,"text":31303},{"id":31343,"depth":14,"text":31276},{"id":31451,"depth":14,"text":31452},{"id":31693,"depth":14,"text":31694,"children":33466},[33467],{"id":31703,"depth":255,"text":31704},"17995d3c-aadc-4dc8-a2f0-278bc35157f0","2024-12-21","A comprehensive guide to efficiently setting up a Nuxt project, ensuring a clean, powerful, and future-proof project.",{},"\u002Fposts\u002Fthe-perfect-guide-to-setting-up-a-new-nuxt-project","---\ncontentId: 17995d3c-aadc-4dc8-a2f0-278bc35157f0\ntitle: \"The Perfect Guide to Setting Up a New Nuxt Project\"\ndescription: \"A comprehensive guide to efficiently setting up a Nuxt project, ensuring a clean, powerful, and future-proof project.\"\ndate: 2024-12-21\n---\n\nEvery time I create a new **Nuxt** project, I follow specific steps to ensure an optimal setup. I've been working with **Nuxt** since 2019 and have developed specific habits when initiating new projects.\n\nThrough my experience, I've explored numerous modules and reviewed projects from both experts and beginners. This has given me a solid understanding of what works well in the **Nuxt ecosystem**. Setting up a new project can be daunting, especially for newcomers, so I've decided to share this guide to help you set up your Nuxt project as efficiently as possible.\n\n## Creating a new Nuxt project\n\nLet's begin with creating a new **Nuxt** project. This is the simplest step, requiring just one command:\n\n```bash\nnpx nuxi@latest init \u003Cproject-name>\n```\n\n> [!TIP]\n> You can also create a v4 ready project with `npx nuxi init nuxt-app -t v4-compat`, avoiding the need for [Prepare for Nuxt 4](#prepare-for-nuxt-4).\n\n> [!NOTE]\n> I always choose **pnpm** as my package manager. You can select the one you prefer, but I recommend trying **pnpm**.\n\nRunning this command will scaffold a new Nuxt project within the `\u003Cproject-name>` directory. The app is minimalistic, no configuration, no modules, just Nuxt and an `app.vue` file.\n\n## Update the dependencies\n\nBefore proceeding, it's essential to update dependencies to their latest versions. This ensures you can leverage the newest features and bug fixes while avoiding issues that outdated dependencies might cause. Execute the following command to update them:\n\n```bash\nnpx taze -w\n```\n\nThe **taze** package, developed by [Anthony Fu](https:\u002F\u002Fantfu.me), updates all dependencies to their latest versions. It's similar to `npm-check-updates` but more powerful due to its adherence to **semver** versioning, offering both speed and reliability.\n\n## Prepare for Nuxt 4\n\nCurrently, the latest stable version of Nuxt is version 3. However, version 4 is in development and promises numerous features and enhancements. Although not officially released, you can prepare your projects for this future update by adding the following configuration in your `nuxt.config.js`:\n\n```ts\nexport default defineNuxtConfig({\n  future: {\n    compatibilityVersion: 4,\n  }\n})\n```\n\nThis setup enables you to start developing your projects as though you're already using **Nuxt 4**. This foresight not only allows you to benefit from upcoming features but also minimizes the work required to upgrade when Nuxt 4 is released.\n\nTo make the most of Nuxt 4, create a new `app` folder and move the `app.vue` file into it:\n\n```bash\nmkdir app && mv app.vue app\n```\n\n> [!TIP]\n> You can also create a v4 ready project by using the `npx nuxi init nuxt-app -t v4-compat` command.\n\n\u003CCalloutReadMore text=\"Upgrading to Nuxt 4\" href=\"https:\u002F\u002Fnuxt.com\u002Fdocs\u002Fgetting-started\u002Fupgrade#opting-in-to-nuxt-4\" \u002F>\n\n## Linting and formatting\n\nNuxt provides an excellent developer experience for linting and formatting. Install both the linter and the formatter with the following command:\n\n```bash\nnpx nuxi module add eslint && npx @antfu\u002Fni -D typescript\n```\n\nThis command installs the `@nuxt\u002Feslint` module, which offers a [0-config ESLint setup for Nuxt](https:\u002F\u002Feslint.nuxt.com) projects. Enable formatting, and it will automatically fix issues upon saving a file. I always configure it in my `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\nAdditionally, create a `.vscode\u002Fsettings.json` file to disable the prettier extension, preventing conflicts with the ESLint formatter:\n\n```json\n{\n  \"prettier.enable\": false\n}\n```\n\nAdd two scripts to your `package.json` file:\n\n```json\n{\n  \"scripts\": {\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint --fix .\"\n  }\n}\n```\n\nNow, you can run `pnpm run lint` to check for issues and `pnpm run lint:fix` to automatically fix them.\n\nThese predefined configurations make it straightforward to set up **ESLint** with **TypeScript** and **Vue** support, ensuring consistent and clean code.\n\n## UI and components\n\nWith a clean project setup, the latest dependencies, preparation for **Nuxt 4**, and robust linting and formatting, it's time to focus on UI and components. This step varies based on project objectives and design requirements.\n\n### Nuxt UI and Nuxt UI Pro\n\nIf the project emphasizes business logic over UI, I use the [Nuxt UI](https:\u002F\u002Fui.nuxt.com) and Nuxt UI Pro component libraries. They are well-designed and easy to use. Reading the [Nuxt UI source code](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui) is insightful, and I also have an article on building a component library with Vue.\n\n\u003CCalloutReadMore id=\"55d2cdcf-ba27-4bfe-9bc1-492e866ff3b1\" \u002F>\n\n> [!NOTE]\n> As of writing, Nuxt UI 3 is not officially released, but it can be used in projects. If you're reading this later, let me know in the comments, and I'll update this guide.\n\nFirst, install Nuxt UI and Tailwind CSS:\n\n```bash\npnpm add @nuxt\u002Fui@next tailwindcss@next\n```\n\nAdd the module to your `nuxt.config.ts` file:\n\n```ts\nexport default defineNuxtConfig({\n  modules: ['@nuxt\u002Fui']\n})\n```\n\nImport necessary CSS in your `app\u002Fassets\u002Fcss\u002Fmain.css` file:\n\n```css\n@import \"tailwindcss\";\n@import \"@nuxt\u002Fui\";\n```\n\nEnsure this CSS file is included in the `nuxt.config.ts` file:\n\n```ts\nexport default defineNuxtConfig({\n  css: ['~\u002Fassets\u002Fcss\u002Fmain.css']\n})\n```\n\nUpdate your `.vscode\u002Fsettings.json` file to enhance experience with the [Tailwind CSS IntelliSense extension](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\nLastly, modify your `app\u002Fapp.vue` file to include the Nuxt UI component `\u003CUApp>`, automatically injecting global components:\n\n```vue\n\u003Ctemplate>\n  \u003CUApp>\n    \u003CNuxtPage \u002F>\n  \u003C\u002FUApp>\n\u003C\u002Ftemplate>\n```\n\n> [!NOTE]\n> Nuxt UI Pro is a paid extension offering layout components for rapid app development. It's a worthwhile investment that saves time and effort, while supporting the Nuxt team and open-source development. 💚\n\n### Tailwind CSS\n\nFor projects requiring specific designs or when collaborating with designers, I turn to [Tailwind CSS](https:\u002F\u002Ftailwindcss.com). It's a utility-first CSS framework that streamlines the design process to such an extent that working without it is unimaginable. The upcoming version, Tailwind CSS 4, includes an indispensable **Vite** plugin, eliminating the need for additional modules and simplifying the setup.\n\nInstall the latest Tailwind CSS version:\n\n```bash\npnpm add tailwindcss@next @tailwindcss\u002Fvite@next\n```\n\nCreate an `app\u002Fassets\u002Fcss\u002Fmain.css` file and import Tailwind CSS:\n\n```css\n@import \"tailwindcss\";\n```\n\nUpdate your `nuxt.config.ts` file to utilize the Vite plugin:\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\nEnhance your developer experience by updating your `.vscode\u002Fsettings.json` file for the [Tailwind CSS IntelliSense extension](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\nThe setup is incredibly straightforward!\n\n## Icons\n\nIncorporating icons into your project is essential, albeit deceptively complex. [Anthony Fu's article](https:\u002F\u002Fnuxt.com\u002Fblog\u002Fnuxt-icon-v1-0) elaborates on finding the perfect icon solution.\n\nIf utilizing Nuxt UI, icons are already included. You can safely skip this section.\n\nFor Tailwind CSS users, two options are available:\n\n1. Use the [Nuxt Icons](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ficon) module provides access to over 200,000 icons from various icon sets:\n\n```bash\nnpx nuxi module add icon\n```\n\nThis is the simplest solution and with the latest improvements from the v1 release, it's a powerful choice. I recommend starting with this option.\n\n2. Use the Tailwind CSS plugin, [Tailwind CSS Icons](https:\u002F\u002Fgithub.com\u002Fegoist\u002Ftailwindcss-icons), offers similar icons via a different approach:\n\n```bash\nnpx @antfu\u002Fni -D @egoist\u002Ftailwindcss-icons\n```\n\nCreate a `tailwind.config.js` file and include the 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\nImport the configuration in your `app\u002Fassets\u002Fcss\u002Fmain.css` file:\n\n```css\n@import \"tailwindcss\";\n@config \"..\u002F..\u002F..\u002Ftailwind.config.js\";\n```\n\nI personally prefer the second solution due to its uniform setup across projects, including non-Nuxt ones, and at the time I made this choice, the Nuxt Icons module was suffering from many drawbacks. However, that's not the case anymore and I highly recommend you to start with [Nuxt Icons](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ficon).\n\n## Fonts\n\nFonts define your project's personality and influence user perception. **Nuxt** provides a solution: [Nuxt Fonts](https:\u002F\u002Ffonts.nuxt.com\u002F). This zero-config module automatically loads required fonts by scanning CSS files. Installation is straightforward:\n\n```bash\nnpx nuxi module add fonts\n```\n\nThis module is part of my projects unless I'm using Nuxt UI, where it's included. But I have to say that I rarely need to install it since Nuxt UI suffices for most scenarios.\n\n## CI\n\nFor personal projects, I use **GitHub** and always set up a CI to enforce linting and autofix errors. This approach ensures clean, consistent code. While linters and formatters are great, they're useless if unused.\n\nI utilize **GitHub Actions**, detailed in a `.github\u002Fworkflows\u002Fci.yml` file:\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: Install dependencies\n        run: pnpm install\n\n      - name: Lint files\n        run: pnpm run lint\n```\n\nTo automate code formatting and commits, I use [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: Install dependencies\n        run: pnpm install\n\n      - name: Lint & Fix\n        run: pnpm run lint:fix\n\n      - uses: autofix-ci\u002Faction@ff86a557419858bb967097bfc916833f5647fa8c\n```\n\n## Keep dependencies up-to-date\n\nMaintaining up-to-date dependencies is crucial. With numerous projects, this can be time-consuming. I use [Renovate](https:\u002F\u002Fdocs.renovatebot.com\u002F) in each project to automate dependency updates:\n\nCreate a `renovate.json` file with:\n\n```json\n{\n  \"extends\": [\n    \"github>nuxt\u002Frenovate-config-nuxt\"\n  ]\n}\n```\n\nThis tool is indispensable, and I find it challenging to work on projects lacking Renovate (like some work projects).\n\n## Tips, tricks, and additional considerations\n\nHere are some extra tips and tricks I find valuable, even if not directly related to the setup:\n\n### Always place scripts first\n\nOrganize `vue` files by placing the `script` section first. This offers clarity by presenting component logic before the 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### Embrace TypeScript\n\nI advocate using **TypeScript** across all `vue` files and utility or composable scripts. Adding `lang=\"ts\"` even without TypeScript code enhances the developer experience:\n\n```vue\n\u003Cscript lang=\"ts\" setup>\n\u002F\u002F ...\n\u003C\u002Fscript>\n```\n\n> [!NOTE]\n> Always opt for the `setup` syntax in components.\n\n### Avoid using `process.env` in `nuxt.config.ts`\n\nRefrain from directly accessing `process.env` in `nuxt.config.ts`. For instance, assigning defaults for `runtimeConfig` shouldn't use `process.env`:\n\n```ts\nexport default defineNuxtConfig({\n  runtimeConfig: {\n    apiUrl: ''\n  }\n})\n```\n\n> [!TIP]\n> Hover over the `apiUrl` key in your IDE for environment variable hints. **Nuxt** provides complex typing, such as `NUXT_API_URL`.\n\nCreate an `.env` file at the project's root to define environment variables:\n\n```\nNUXT_API_URL=https:\u002F\u002Fapi.example.com\n```\n\nExclude the `.env` file using `.gitignore`:\n\n```bash\necho \".env\" >> .gitignore\n```\n\nProvide a `.env.example` file to guide other developers:\n\n```\nNUXT_API_URL=https:\u002F\u002Fapi.example.com\n```\n\n\u003CCalloutReadMore text=\"Runtime Config: The Common Mistakes\" href=\"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=_FYV5WfiWvs\" \u002F>\n\n### Audit before installing modules\n\nConduct an audit before adding new modules to evaluate necessity and functionality. Sometimes, it's possible to proceed without them or to choose a superior alternative. Avoid basing decisions on stars or downloads. Instead, peruse documentation, source code, issues, and PRs for a comprehensive quality assessment. The **Nuxt** ecosystem evolves rapidly, and some modules may become outdated or incompatible with newer Nuxt versions.\n\n### Choosing between `useFetch` and `$fetch`\n\nAvoid using `fetch` directly; opt for `$fetch`, an enhanced instance of [**ofetch**](https:\u002F\u002Fgithub.com\u002Funjs\u002Fofetch). Distinguish usage as follows:\n\n1. Use `$fetch` for client-side operations, such as form data submissions.\n2. Use `useFetch` where server-side rendering is feasible, like fetching API data to display on pages.\n\n## The perfect setup is yours\n\nThis guide embodies everything I do to initialize a new **Nuxt** project. This comprehensive setup takes around 10 minutes, resulting in a clean, powerful, and future-ready project that allows focusing on business logic without distractions.\n\n> [!WARNING]\n> Avoid creating a template project from this guide. The ecosystem is dynamic, and new modules swiftly emerge. Each project might necessitate nuanced adjustments. This guide is a starting point, a checklist for initial setup. However, always adapt to the specific project needs. Maintaining a template demands effort and is generally not worthwhile.\n\nI hope this guide enhances your Nuxt project setup. If you have questions or want to share your configuration, please leave a comment below.\n",{"title":31206,"description":33470},{"loc":33472},"posts\u002Fthe-perfect-guide-to-setting-up-a-new-nuxt-project","zb7ZdLf4LjP8pVi8z-gPXJICn-OLTME18s2mz_17XDs",{"id":33479,"title":33480,"body":33481,"contentId":33873,"date":33874,"description":33875,"extension":18,"meta":33876,"navigation":20,"path":33877,"rawbody":33878,"robots":23,"schemaOrg":23,"seo":33879,"sitemap":33880,"stem":33881,"__hash__":33882},"posts\u002Fposts\u002Ffor-a-friend-from-infrastructure-to-web-development-in-2025.md","For a Friend: From Infrastructure to Web Development in 2025",{"type":7,"value":33482,"toc":33865},[33483,33502,33512,33520,33523,33537,33540,33543,33546,33550,33560,33606,33613,33617,33636,33644,33651,33654,33657,33685],[35,33484,33485,33486,33489,33490,3076,33493,3080,33495,33498,33499,33501],{},"After a meetup where I delivered a talk on ",[6850,33487,33488],{},"optimistic UI",", a friend approached me with an interesting query. He primarily works in infrastructure, dealing with ",[6850,33491,33492],{},"Linux",[6850,33494,31123],{},[6850,33496,33497],{},"Kubernetes",", and dabbled in ",[6850,33500,21348],{}," years ago. He asked, \"I'd like to get back into web development. Where should I start?\"",[35,33503,33504,33505,33507,33508,33511],{},"The question struck me as both interesting and highly relevant. Web development is a vast and sometimes perplexing field from the outside. It evolves rapidly, with new tools, libraries, frameworks, and best practices emerging constantly. It can seem daunting, especially the ",[6850,33506,31016],{}," world, where ",[6850,33509,33510],{},"JS fatigue"," is a common challenge.",[939,33513,33514],{},[35,33515,33516,33519],{},[200,33517,33518],{},"!IMPORTANT","\nRegardless of your background, this advice applies universally. Remember, though, this is based on my experience, vision, and opinion, not an absolute truth.",[35,33521,33522],{},"Before I offer any advice, it's crucial to understand the context. How my friend intends to use web development will influence the recommendations. I see two main paths:",[68,33524,33525,33531],{},[71,33526,33527,33530],{},[6850,33528,33529],{},"Web development as a hobby",": Creating personal projects, learning, and having fun.",[71,33532,33533,33536],{},[6850,33534,33535],{},"Web development as a career",": Working in a company to build products and collaborate with a team.",[35,33538,33539],{},"The approach also varies by location, as different countries have distinct markets and favored tools. Here, we're focusing on creating websites or web applications, not developing tools or libraries.",[35,33541,33542],{},"In recent years, I've experimented with numerous tools, libraries, frameworks, and languages. Finding the right stack can be challenging and time-consuming. Guidance at the start of your journey can save significant time and prevent spreading yourself too thin amid the constant influx of new tools.",[35,33544,33545],{},"Let's start with the essentials.",[51,33547,33549],{"id":33548},"the-basics","The basics",[35,33551,33552,33553,3076,33555,3080,33557,33559],{},"The foundations of web development are ",[6850,33554,31010],{},[6850,33556,31013],{},[6850,33558,31016],{},". Here are some resources to learn them:",[1576,33561,33562,33570,33577,33584,33592,33599],{},[71,33563,33564,33569],{},[39,33565,33568],{"href":33566,"rel":33567},"https:\u002F\u002Fyoutu.be\u002FoEAuNzWXRjM?si=CtsGceAM_z3WNfGz",[77],"Apprendre l'HTML"," with Grafikart.",[71,33571,33572,33569],{},[39,33573,33576],{"href":33574,"rel":33575},"https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PLjwdMgw5TTLVjTZQocrMwKicV5wsZlRpj&si=J4vZKiGb3LBjiDks",[77],"Découverte du CSS",[71,33578,33579,33569],{},[39,33580,33583],{"href":33581,"rel":33582},"https:\u002F\u002Fwww.youtube.com\u002Fplaylist?list=PLjwdMgw5TTLXgsTQE_1PpRkC_yX47ZcGV",[77],"Apprendre le JavaScript",[71,33585,33586,33591],{},[39,33587,33590],{"href":33588,"rel":33589},"https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PL4cUxeGkcC9ivBf_eKCPIAYXWzLlPAm6G&si=QTwFntwwlQ3pbkQ-",[77],"HTML & CSS Crash Course Tutorial"," with the Net Ninja.",[71,33593,33594,33591],{},[39,33595,33598],{"href":33596,"rel":33597},"https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PL4cUxeGkcC9haFPT7J25Q9GRB_ZkFrQAc&si=RzCATnLejIdUzMgf",[77],"Modern JavaScript Tutorial",[71,33600,33601,33591],{},[39,33602,33605],{"href":33603,"rel":33604},"https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PL4cUxeGkcC9gfoKa5la9dsdCNpuey2s-V&si=kX5SKcV3x4Tc3BpD",[77],"JavaScript DOM Tutorial",[35,33607,33608,33609,33612],{},"These resources will provide a solid foundation. Don't spend excessive time on them, while fundamentals are essential, they are just the starting point. Keep the ",[6850,33610,33611],{},"Mozilla Developer Network (MDN)"," as your go-to reference guide.",[51,33614,33616],{"id":33615},"backend-or-frontend","Backend or frontend",[35,33618,33619,33620,3076,33622,33624,33625,33627,33628,33631,33632,33635],{},"Many newcomers prefer to explore frontend frameworks like ",[6850,33621,17991],{},[6850,33623,17986],{},", or ",[6850,33626,22878],{},". Others may opt for a ",[6850,33629,33630],{},"Node.js"," backend with ",[6850,33633,33634],{},"Express",", leveraging their comfort with JavaScript.",[35,33637,33638,33639,1638,33642,215],{},"However, neither path is ideal at the start. Using a frontend framework too early or diving into the Node.js ecosystem can be overwhelming. My recommendation is to start with ",[6850,33640,33641],{},"PHP",[6850,33643,19447],{},[939,33645,33646],{},[35,33647,33648,33650],{},[200,33649,945],{},"\nLaravel is a web application framework with an expressive, elegant syntax, allowing you to create apps effortlessly.",[35,33652,33653],{},"Forget any preconceived notions about PHP being outdated or cumbersome. The language has evolved significantly, making PHP development comfortable and efficient.",[35,33655,33656],{},"Here's why PHP and Laravel are recommended:",[1576,33658,33659,33670,33676],{},[71,33660,33661,17865,33664,33669],{},[6850,33662,33663],{},"Laravel's documentation",[39,33665,33668],{"href":33666,"rel":33667},"https:\u002F\u002Flaravel.com\u002F",[77],"laravel.com"," is one of the most comprehensive.",[71,33671,419,33672,33675],{},[6850,33673,33674],{},"Laravel package ecosystem"," is vast, well-maintained, and documented, providing resources even for niche needs.",[71,33677,33678,33679,33684],{},"Laravel's community is supportive and resource-rich, with platforms like ",[39,33680,33683],{"href":33681,"rel":33682},"https:\u002F\u002Flaracasts.com\u002F",[77],"Laracasts"," offering extensive learning materials across web development topics. You can't find a better resource for learning web development.",[7139,33686,33690,33694,33703,33727,33731,33734,33742,33758,33761,33765,33768,33779,33791,33795,33803,33811],{"icon":33687,"text":33688,"to":33689},"i-ph-ticket-duotone","Subscribe to Laracasts with my referral link","https:\u002F\u002Flaracasts.com\u002Freferral\u002FBarbapapazes",[51,33691,33693],{"id":33692},"learn-php-and-laravel","Learn PHP and Laravel",[35,33695,33696,33697,33702],{},"On Laracasts, I recommend following ",[39,33698,33701],{"href":33699,"rel":33700},"https:\u002F\u002Flaracasts.com\u002Fpath",[77],"the path",", which guides you from PHP basics to advanced Laravel concepts. Here are some courses to consider:",[1576,33704,33705,33712,33719],{},[71,33706,33707,215],{},[39,33708,33711],{"href":33709,"rel":33710},"https:\u002F\u002Flaracasts.com\u002Fseries\u002Fphp-for-beginners-2023-edition",[77],"PHP for Beginners",[71,33713,33714,215],{},[39,33715,33718],{"href":33716,"rel":33717},"https:\u002F\u002Flaracasts.com\u002Fseries\u002F30-days-to-learn-laravel-11",[77],"30 Days to Learn Laravel",[71,33720,33721,33726],{},[39,33722,33725],{"href":33723,"rel":33724},"https:\u002F\u002Flaracasts.com\u002Fseries\u002Fbuild-a-forum-with-laravel",[77],"Build a Forum With Laravel",". This course is challenging, but find related courses for concept clarifications when needed.",[51,33728,33730],{"id":33729},"things-start-to-get-messy","Things start to get messy",[35,33732,33733],{},"Once comfortable with PHP and Laravel, begin building with Blade, pure CSS, and JavaScript. Recognize why more powerful tools are necessary before adopting them.",[35,33735,33736,33737,1638,33739,33741],{},"When CSS and JavaScript management become challenging, learn ",[6850,33738,32036],{},[6850,33740,18624],{}," for enhanced maintainability and interactivity:",[1576,33743,33744,33751],{},[71,33745,33746,33750],{},[39,33747,32036],{"href":33748,"rel":33749},"https:\u002F\u002Ftailwindcss.com\u002F",[77]," is a utility-first CSS framework, making you more productive by eliminating direct CSS file edits, bloated stylesheets, and repetitive classes.",[71,33752,33753,33757],{},[39,33754,18624],{"href":33755,"rel":33756},"https:\u002F\u002Fvuejs.org\u002F",[77]," is a progressive JavaScript framework, easy for beginners, with technical advantages over React and Angular.",[35,33759,33760],{},"Explore Laracasts for courses on these technologies and thoroughly read their documentation to understand capabilities and limitations before usage.",[51,33762,33764],{"id":33763},"become-a-full-stack-developer","Become a full-stack developer",[35,33766,33767],{},"Achieving full-stack development proficiency is possible with the right approach and tools.",[35,33769,33770,33771,33774,33775,33778],{},"Avoid building a ",[6850,33772,33773],{},"Single Page Application (SPA)"," with a ",[6850,33776,33777],{},"REST API"," unless necessary. Over-engineering complicates maintenance without proper justification.",[35,33780,33781,33782,33784,33785,33790],{},"Once familiar with Laravel and Vue.js, learn ",[6850,33783,24209],{},", a protocol that combines server-driven simplicity with SPA power. ",[39,33786,33789],{"href":33787,"rel":33788},"https:\u002F\u002Flaracasts.com\u002Fseries\u002Fbuild-modern-laravel-apps-using-inertia-js",[77],"Laracasts offers a series on it",", helping you become a full-stack developer capable of building various web applications.",[51,33792,33794],{"id":33793},"some-more-tools","Some more tools",[35,33796,33797,33798,33802],{},"To avoid JavaScript package management fatigue, use ",[39,33799,2160],{"href":33800,"rel":33801},"https:\u002F\u002Fpnpm.io\u002F",[77],", a fast, space-efficient package manager suited for all needs, from mono-repos to local dependency patching.",[35,33804,33805,33806,33810],{},"For documentation or content-centric websites, leverage ",[39,33807,2913],{"href":33808,"rel":33809},"https:\u002F\u002Fvitepress.dev\u002F",[77],", a static site generator powered by Vue and Vite. Familiarity with Vue.js makes it easy to adopt.",[7905,33812,33813,33820,33823,33826,33843,33847,33859,33862],{"id":30815},[35,33814,33815,33816,33819],{},"For applications requiring API integration, consider ",[39,33817,2918],{"href":13818,"rel":33818},[77],", a Vue.js meta-framework supporting various build styles, offering a powerful and well-documented solution.",[35,33821,33822],{},"You might find other tools appealing, but consistency leads to mastery. Focus on these recommended tools and deepen your knowledge.",[35,33824,33825],{},"Additionally, keep an eye on:",[1576,33827,33828,33835],{},[71,33829,33830,33834],{},[39,33831,30791],{"href":33832,"rel":33833},"https:\u002F\u002Funjs.io\u002F",[77],", a growing ecosystem of well-crafted JavaScript libraries.",[71,33836,33837,33842],{},[39,33838,33841],{"href":33839,"rel":33840},"https:\u002F\u002Fspatie.be\u002F",[77],"Spatie",", a web development agency offering valuable PHP and Laravel packages.",[51,33844,33846],{"id":33845},"for-the-work","For the work",[35,33848,33849,33850,14177,33853,1638,33856,33858],{},"While these recommendations suit hobbyists and provide in-depth web development knowledge, adapt to market trends for professional work. In France, for example, ",[6850,33851,33852],{},"Java",[6850,33854,33855],{},"Spring Boot",[6850,33857,22878],{}," are in demand. Focus on core concepts for flexibility in tool or company transitions.",[35,33860,33861],{},"Stay motivated amidst the learning curve. The journey is long but rewarding. Embrace the learning process, connect with peers, and enjoy building creations. Web development is gratifying and offers boundless possibilities.",[35,33863,33864],{},"I hope this post assists you in embarking on your web development journey. Feel free to leave comments or share your experiences with us. 💜",{"title":13,"searchDepth":14,"depth":14,"links":33866},[33867,33868,33869,33870,33871,33872],{"id":33548,"depth":14,"text":33549},{"id":33615,"depth":14,"text":33616},{"id":33692,"depth":14,"text":33693},{"id":33729,"depth":14,"text":33730},{"id":33763,"depth":14,"text":33764},{"id":33793,"depth":14,"text":33794},"da0b1718-8c8c-4c99-bacc-72ec11d55317","2024-11-28","Guidance for transitioning from infrastructure to web development with practical steps and resources. Everything you need to know to start your journey.",{},"\u002Fposts\u002Ffor-a-friend-from-infrastructure-to-web-development-in-2025","---\ncontentId: da0b1718-8c8c-4c99-bacc-72ec11d55317\ntitle: \"For a Friend: From Infrastructure to Web Development in 2025\"\ndescription: \"Guidance for transitioning from infrastructure to web development with practical steps and resources. Everything you need to know to start your journey.\"\ndate: 2024-11-28\n---\n\nAfter a meetup where I delivered a talk on **optimistic UI**, a friend approached me with an interesting query. He primarily works in infrastructure, dealing with **Linux**, **Terraform**, and **Kubernetes**, and dabbled in **web development** years ago. He asked, \"I'd like to get back into web development. Where should I start?\"\n\nThe question struck me as both interesting and highly relevant. Web development is a vast and sometimes perplexing field from the outside. It evolves rapidly, with new tools, libraries, frameworks, and best practices emerging constantly. It can seem daunting, especially the **JavaScript** world, where **JS fatigue** is a common challenge.\n\n> [!IMPORTANT]\n> Regardless of your background, this advice applies universally. Remember, though, this is based on my experience, vision, and opinion, not an absolute truth.\n\nBefore I offer any advice, it's crucial to understand the context. How my friend intends to use web development will influence the recommendations. I see two main paths:\n\n1. **Web development as a hobby**: Creating personal projects, learning, and having fun.\n2. **Web development as a career**: Working in a company to build products and collaborate with a team.\n\nThe approach also varies by location, as different countries have distinct markets and favored tools. Here, we're focusing on creating websites or web applications, not developing tools or libraries.\n\nIn recent years, I've experimented with numerous tools, libraries, frameworks, and languages. Finding the right stack can be challenging and time-consuming. Guidance at the start of your journey can save significant time and prevent spreading yourself too thin amid the constant influx of new tools.\n\nLet's start with the essentials.\n\n## The basics\n\nThe foundations of web development are **HTML**, **CSS**, and **JavaScript**. Here are some resources to learn them:\n\n- [Apprendre l'HTML](https:\u002F\u002Fyoutu.be\u002FoEAuNzWXRjM?si=CtsGceAM_z3WNfGz) with Grafikart.\n- [Découverte du CSS](https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PLjwdMgw5TTLVjTZQocrMwKicV5wsZlRpj&si=J4vZKiGb3LBjiDks) with Grafikart.\n- [Apprendre le JavaScript](https:\u002F\u002Fwww.youtube.com\u002Fplaylist?list=PLjwdMgw5TTLXgsTQE_1PpRkC_yX47ZcGV) with Grafikart.\n- [HTML & CSS Crash Course Tutorial](https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PL4cUxeGkcC9ivBf_eKCPIAYXWzLlPAm6G&si=QTwFntwwlQ3pbkQ-) with the Net Ninja.\n- [Modern JavaScript Tutorial](https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PL4cUxeGkcC9haFPT7J25Q9GRB_ZkFrQAc&si=RzCATnLejIdUzMgf) with the Net Ninja.\n- [JavaScript DOM Tutorial](https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PL4cUxeGkcC9gfoKa5la9dsdCNpuey2s-V&si=kX5SKcV3x4Tc3BpD) with the Net Ninja.\n\nThese resources will provide a solid foundation. Don't spend excessive time on them, while fundamentals are essential, they are just the starting point. Keep the **Mozilla Developer Network (MDN)** as your go-to reference guide.\n\n## Backend or frontend\n\nMany newcomers prefer to explore frontend frameworks like **React**, **Vue**, or **Angular**. Others may opt for a **Node.js** backend with **Express**, leveraging their comfort with JavaScript.\n\nHowever, neither path is ideal at the start. Using a frontend framework too early or diving into the Node.js ecosystem can be overwhelming. My recommendation is to start with **PHP** and **Laravel**.\n\n> [!NOTE]\n> Laravel is a web application framework with an expressive, elegant syntax, allowing you to create apps effortlessly.\n\nForget any preconceived notions about PHP being outdated or cumbersome. The language has evolved significantly, making PHP development comfortable and efficient.\n\nHere's why PHP and Laravel are recommended:\n\n- **Laravel's documentation** at [laravel.com](https:\u002F\u002Flaravel.com\u002F) is one of the most comprehensive.\n- The **Laravel package ecosystem** is vast, well-maintained, and documented, providing resources even for niche needs.\n- Laravel's community is supportive and resource-rich, with platforms like [Laracasts](https:\u002F\u002Flaracasts.com\u002F) offering extensive learning materials across web development topics. You can't find a better resource for learning web development.\n\n\u003CCallout\n  icon=\"i-ph-ticket-duotone\"\n  text=\"Subscribe to Laracasts with my referral link\"\n  to=\"https:\u002F\u002Flaracasts.com\u002Freferral\u002FBarbapapazes\"\n\u002F>\n\n## Learn PHP and Laravel\n\nOn Laracasts, I recommend following [the path](https:\u002F\u002Flaracasts.com\u002Fpath), which guides you from PHP basics to advanced Laravel concepts. Here are some courses to consider:\n\n- [PHP for Beginners](https:\u002F\u002Flaracasts.com\u002Fseries\u002Fphp-for-beginners-2023-edition).\n- [30 Days to Learn Laravel](https:\u002F\u002Flaracasts.com\u002Fseries\u002F30-days-to-learn-laravel-11).\n- [Build a Forum With Laravel](https:\u002F\u002Flaracasts.com\u002Fseries\u002Fbuild-a-forum-with-laravel). This course is challenging, but find related courses for concept clarifications when needed.\n\n## Things start to get messy\n\nOnce comfortable with PHP and Laravel, begin building with Blade, pure CSS, and JavaScript. Recognize why more powerful tools are necessary before adopting them.\n\nWhen CSS and JavaScript management become challenging, learn **Tailwind CSS** and **Vue.js** for enhanced maintainability and interactivity:\n\n- [Tailwind CSS](https:\u002F\u002Ftailwindcss.com\u002F) is a utility-first CSS framework, making you more productive by eliminating direct CSS file edits, bloated stylesheets, and repetitive classes.\n- [Vue.js](https:\u002F\u002Fvuejs.org\u002F) is a progressive JavaScript framework, easy for beginners, with technical advantages over React and Angular.\n\nExplore Laracasts for courses on these technologies and thoroughly read their documentation to understand capabilities and limitations before usage.\n\n## Become a full-stack developer\n\nAchieving full-stack development proficiency is possible with the right approach and tools.\n\nAvoid building a **Single Page Application (SPA)** with a **REST API** unless necessary. Over-engineering complicates maintenance without proper justification.\n\nOnce familiar with Laravel and Vue.js, learn **Inertia**, a protocol that combines server-driven simplicity with SPA power. [Laracasts offers a series on it](https:\u002F\u002Flaracasts.com\u002Fseries\u002Fbuild-modern-laravel-apps-using-inertia-js), helping you become a full-stack developer capable of building various web applications.\n\n## Some more tools\n\nTo avoid JavaScript package management fatigue, use [pnpm](https:\u002F\u002Fpnpm.io\u002F), a fast, space-efficient package manager suited for all needs, from mono-repos to local dependency patching.\n\nFor documentation or content-centric websites, leverage [VitePress](https:\u002F\u002Fvitepress.dev\u002F), a static site generator powered by Vue and Vite. Familiarity with Vue.js makes it easy to adopt.\n\n\u003CCalloutReadMore id=\"674a3932-aaba-445c-82bb-0896ce3a8e7e\" \u002F>\n\nFor applications requiring API integration, consider [Nuxt](https:\u002F\u002Fnuxt.com), a Vue.js meta-framework supporting various build styles, offering a powerful and well-documented solution.\n\nYou might find other tools appealing, but consistency leads to mastery. Focus on these recommended tools and deepen your knowledge.\n\nAdditionally, keep an eye on:\n\n- [UnJS](https:\u002F\u002Funjs.io\u002F), a growing ecosystem of well-crafted JavaScript libraries.\n- [Spatie](https:\u002F\u002Fspatie.be\u002F), a web development agency offering valuable PHP and Laravel packages.\n\n## For the work\n\nWhile these recommendations suit hobbyists and provide in-depth web development knowledge, adapt to market trends for professional work. In France, for example, **Java** with **Spring Boot** and **Angular** are in demand. Focus on core concepts for flexibility in tool or company transitions.\n\nStay motivated amidst the learning curve. The journey is long but rewarding. Embrace the learning process, connect with peers, and enjoy building creations. Web development is gratifying and offers boundless possibilities.\n\nI hope this post assists you in embarking on your web development journey. Feel free to leave comments or share your experiences with us. 💜\n",{"title":33480,"description":33875},{"loc":33877},"posts\u002Ffor-a-friend-from-infrastructure-to-web-development-in-2025","JhsfD21A0zMVe8ufFpgcaqKk_yf-3pUkw0GJvs6_Auw",{"id":33884,"title":33885,"body":33886,"contentId":31081,"date":34072,"description":34073,"extension":18,"meta":34074,"navigation":20,"path":34075,"rawbody":34076,"robots":23,"schemaOrg":23,"seo":34077,"sitemap":34078,"stem":34079,"__hash__":34080},"posts\u002Fposts\u002Fenhancing-engagement-notifications-and-other-enhancements.md","Enhancing Engagement: Notifications and Other Enhancements",{"type":7,"value":33887,"toc":34070},[33888,33899,33909],[35,33889,33890,33891,33894,33895,33898],{},"For the past two weeks, I have been focused on enhancing the ",[6850,33892,33893],{},"interactivity"," of my website. I believe it's vital to enable readers to ",[6850,33896,33897],{},"engage"," with the content I share and interact with one another. This can foster meaningful conversations and discussions, which are essential for growth and learning, both for you and me.",[35,33900,33901,33902,33905,33906,33908],{},"I began by incorporating ",[6850,33903,33904],{},"reactions"," to my articles, followed by enabling ",[6850,33907,18831],{},". You can explore more about these features in the articles below:",[7905,33910,33911],{"id":31075},[7905,33912,33913,33935,33939,33945,33950],{"id":31078},[35,33914,33915,33916,1638,33919,33923,33924,1638,33927,33930,33931,33934],{},"At the conclusion of each article on the ",[39,33917,33918],{"href":21},"blog",[39,33920,33922],{"href":33921},"\u002Fseries","series"," pages, you'll find two new sections: ",[6850,33925,33926],{},"Reactions",[6850,33928,33929],{},"Comments",". With the former, you can express your feelings about the article using predefined emojis. This feedback is important as it provides immediate insights into the content I share. Through the latter, you can share your thoughts, ask ",[6850,33932,33933],{},"questions",", and engage in discussions with me and other readers. I'm eager to see how these interactions will develop over time.",[51,33936,33938],{"id":33937},"notifications-to-keep-you-updated","Notifications to keep you updated",[35,33940,33941,33942,215],{},"Today, I'm delighted to unveil a new feature: ",[6850,33943,33944],{},"notifications",[134,33946,132,33947],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,33948],{"src":33949,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fenhancing-engagement-notifications-and-other-enhancements\u002Fnotifications.mp4",[33951,33952,33953,33964,33971,33974,33978,33981,33995,34006,34022,34028,34033,34037,34046,34049,34051],"callout-support-my-work",{},[35,33954,33955,33956,33959,33960,33963],{},"Previously, if you posted a ",[6850,33957,33958],{},"comment"," on one of my articles, there was no way to know if I or someone else had replied. You had to periodically return to check for new ",[6850,33961,33962],{},"activity",", which is both impractical and frustrating. You invest time in writing, and you expect a response. However, if you're unaware when it happens, engaging in a conversation is challenging.",[35,33965,33966,33967,33970],{},"This has changed. I have implemented a ",[6850,33968,33969],{},"notification system"," that alerts you when someone responds to your comment. In the header, a new bell icon will display a badge when there are unread notifications. Clicking on it will open a panel listing all notifications. You can click on one to navigate directly to the article and the comment. Moreover, you can mark all as read or delete them.",[35,33972,33973],{},"If you'd like to try it, leave a comment below. I will reply, and you will receive a notification. I am enthusiastic about how this will enhance interactions on my website.",[51,33975,33977],{"id":33976},"additional-enhancements","Additional enhancements",[35,33979,33980],{},"Alongside notifications, I have made several other improvements to my website.",[35,33982,33983,33984,33987,33988,33991,33992,215],{},"I developed a ",[6850,33985,33986],{},"profile"," page. You can access it by clicking the link in the footer. It contains all the information regarding your account. For now, it's quite basic, but I plan to add more features in the future, like a ",[6850,33989,33990],{},"dashboard"," or a ",[6850,33993,33994],{},"settings page",[35,33996,33997,33998,34001,34002,34005],{},"I included your ",[6850,33999,34000],{},"profile picture"," above the \"Add a Comment\" header in the comment ",[6850,34003,34004],{},"section",". It's a small detail, but now, you know which account you're using for interaction.",[35,34007,34008,34009,34012,34013,34017,34018,34021],{},"You can ",[6850,34010,34011],{},"submit a comment"," using the shortcut ",[34014,34015,34016],"kbd",{},"Ctrl"," + ",[34014,34019,34020],{},"Enter",". This is a common shortcut for form submission, and I feel it's crucial to provide it.",[35,34023,34008,34024,34027],{},[6850,34025,34026],{},"like a comment"," to show your appreciation without replying. It's a simple gesture, but it can brighten someone's day. This feature is also integrated with the notification system. If someone likes your comment, you will receive a notification.",[134,34029,132,34030],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,34031],{"src":34032,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fenhancing-engagement-notifications-and-other-enhancements\u002Fcomment-likes.mp4",[51,34034,34036],{"id":34035},"looking-forward","Looking forward",[35,34038,34039,34040,33991,34043,34045],{},"I sincerely hope you will enjoy these new features. Please share your thoughts about them through a ",[6850,34041,34042],{},"reaction",[6850,34044,33958],{},". 💫",[35,34047,34048],{},"I'm eager to continue enhancing my website and adding more features, but I need your feedback. If you have any suggestions or ideas, please share them with me. I'm open to any input that can help me provide a better user experience.",[1159,34050],{},[35,34052,34053,34054,34056,34057,34060,34061,34066,34067,34069],{},"All these new functionalities are built on a custom ",[6850,34055,31071],{}," developed with ",[39,34058,19447],{"href":19443,"rel":34059},[77],". I primarily use ",[39,34062,34065],{"href":34063,"rel":34064},"https:\u002F\u002Flaravel.com\u002Fdocs\u002F11.x\u002Fnotifications",[77],"Laravel Notifications"," to construct the ",[6850,34068,33969],{},". I'm impressed by Laravel's flexibility and power. With just a few lines of code, I can create a robust system that significantly enriches my website.",{"title":13,"searchDepth":14,"depth":14,"links":34071},[],"2024-10-24","In this third update, discover the new notification system and other enhancements I've made to my website to help you to engage with others and me.",{},"\u002Fposts\u002Fenhancing-engagement-notifications-and-other-enhancements","---\ncontentId: 600cf474-9e89-4045-9030-021332c2dfd6\ntitle: \"Enhancing Engagement: Notifications and Other Enhancements\"\ndescription: In this third update, discover the new notification system and other enhancements I've made to my website to help you to engage with others and me.\ndate: 2024-10-24\n---\n\nFor the past two weeks, I have been focused on enhancing the **interactivity** of my website. I believe it's vital to enable readers to **engage** with the content I share and interact with one another. This can foster meaningful conversations and discussions, which are essential for growth and learning, both for you and me.\n\nI began by incorporating **reactions** to my articles, followed by enabling **comments**. You can explore more about these features in the articles below:\n\n\u003CCalloutReadMore id=\"0d4cb8be-230e-444e-9419-fce9adf0ef6b\" \u002F>\n\n\u003CCalloutReadMore id=\"c8faba17-5480-49fb-a201-d426ea583097\" \u002F>\n\nAt the conclusion of each article on the [blog](\u002Fposts) and [series](\u002Fseries) pages, you'll find two new sections: **Reactions** and **Comments**. With the former, you can express your feelings about the article using predefined emojis. This feedback is important as it provides immediate insights into the content I share. Through the latter, you can share your thoughts, ask **questions**, and engage in discussions with me and other readers. I'm eager to see how these interactions will develop over time.\n\n## Notifications to keep you updated\n\nToday, I'm delighted to unveil a new feature: **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\nPreviously, if you posted a **comment** on one of my articles, there was no way to know if I or someone else had replied. You had to periodically return to check for new **activity**, which is both impractical and frustrating. You invest time in writing, and you expect a response. However, if you're unaware when it happens, engaging in a conversation is challenging.\n\nThis has changed. I have implemented a **notification system** that alerts you when someone responds to your comment. In the header, a new bell icon will display a badge when there are unread notifications. Clicking on it will open a panel listing all notifications. You can click on one to navigate directly to the article and the comment. Moreover, you can mark all as read or delete them.\n\nIf you'd like to try it, leave a comment below. I will reply, and you will receive a notification. I am enthusiastic about how this will enhance interactions on my website.\n\n## Additional enhancements\n\nAlongside notifications, I have made several other improvements to my website.\n\nI developed a **profile** page. You can access it by clicking the link in the footer. It contains all the information regarding your account. For now, it's quite basic, but I plan to add more features in the future, like a **dashboard** or a **settings page**.\n\nI included your **profile picture** above the \"Add a Comment\" header in the comment **section**. It's a small detail, but now, you know which account you're using for interaction.\n\nYou can **submit a comment** using the shortcut \u003Ckbd>Ctrl\u003C\u002Fkbd> + \u003Ckbd>Enter\u003C\u002Fkbd>. This is a common shortcut for form submission, and I feel it's crucial to provide it.\n\nYou can **like a comment** to show your appreciation without replying. It's a simple gesture, but it can brighten someone's day. This feature is also integrated with the notification system. If someone likes your comment, you will receive a 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## Looking forward\n\nI sincerely hope you will enjoy these new features. Please share your thoughts about them through a **reaction** or a **comment**. 💫\n\nI'm eager to continue enhancing my website and adding more features, but I need your feedback. If you have any suggestions or ideas, please share them with me. I'm open to any input that can help me provide a better user experience.\n\n---\n\nAll these new functionalities are built on a custom **API** developed with [Laravel](https:\u002F\u002Flaravel.com). I primarily use [Laravel Notifications](https:\u002F\u002Flaravel.com\u002Fdocs\u002F11.x\u002Fnotifications) to construct the **notification system**. I'm impressed by Laravel's flexibility and power. With just a few lines of code, I can create a robust system that significantly enriches my website.\n",{"title":33885,"description":34073},{"loc":34075},"posts\u002Fenhancing-engagement-notifications-and-other-enhancements","FIhnP3VRPt7_J6YQufqLiGStl02h4nUAEO1pYwSiSFg",{"id":34082,"title":34083,"body":34084,"contentId":31078,"date":34294,"description":34295,"extension":18,"meta":34296,"navigation":20,"path":34297,"rawbody":34298,"robots":23,"schemaOrg":23,"seo":34299,"sitemap":34300,"stem":34301,"__hash__":34302},"posts\u002Fposts\u002Fenhancing-engagement-enabling-comments-on-my-articles.md","Enhancing Engagement: Enabling Comments on My Articles",{"type":7,"value":34085,"toc":34290},[34086,34097],[35,34087,34088,34089,34093,34094,215],{},"Last week, I introduced the first phase of a major project: ",[39,34090,34092],{"href":34091},".\u002Fenhancing-engagement-adding-reactions-to-my-articles","incorporating reactions to my articles",". Today, I'm excited to unveil the second phase: ",[6850,34095,34096],{},"enabling comments on my articles",[7905,34098,34099,34103,34106,34116,34120,34133,34136],{"id":31075},[51,34100,34102],{"id":34101},"a-second-phase","A second phase",[35,34104,34105],{},"In the previous article, I detailed the progressive approach I'm pursuing to enhance my personal website. Step by step, I aim to introduce features to engage with you, the reader, and create a platform for meaningful interactions around all the knowledge I share.",[35,34107,34108,34109,34112,34113],{},"It's a progressive process because I believe this is the most effective way to deliver, rather than waiting to complete everything at once. At the same time, it keeps me motivated and focused by achieving small victories and receiving feedback. And since I'm learning ",[39,34110,19447],{"href":19443,"rel":34111},[77],", it prevents me from feeling overwhelmed by the project's complexity. ",[1217,34114,34115],{},"In essence, it's a win-win scenario.",[51,34117,34119],{"id":34118},"the-importance-of-comments","The importance of comments",[35,34121,34122,34123,34128,34129,34132],{},"Recently, I posted on X ",[39,34124,34127],{"href":34125,"rel":34126},"https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1842987889216672223",[77],"a thread about \"singleton composables\"",". Swiftly, I received comments, suggestions, and insights that made me realize that I could be mistaken. ",[1217,34130,34131],{},"I was mistaken."," And I'm grateful that I had the opportunity to learn from others' feedback.",[35,34134,34135],{},"Two weeks ago, I also published an article on the simplest method to create a Vue.js component library. At one juncture, there were two approaches to constructing the library. I chose one, deeming it the simplest, but through comments, people could have shared their thoughts and experiences, which could have assisted others in making better decisions and learning from one another.",[7905,34137,34138,34141,34145,34148,34153,34159,34209,34215,34218,34260,34263,34277,34279,34285],{"id":21352},[35,34139,34140],{},"Comments are valuable as they allow us to express our thoughts, ask questions, and engage in discussions. They enable us to learn from each other, broaden our knowledge, and grow collectively. With this in mind, I decided to implement the comments feature on my personal website.",[51,34142,34144],{"id":34143},"taking-the-second-step","Taking the second step",[35,34146,34147],{},"And here we are, the comments feature has been implemented and launched.",[134,34149,132,34150],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,34151],{"src":34152,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fenhancing-engagement-enabling-comments-on-my-articles\u002Fdemo.mp4",[35,34154,34155,34156],{},"This phase posed more challenges than the first. I had to consider various factors and make decisions throughout the process. ",[1217,34157,34158],{},"It's fascinating to observe how the same feature can be implemented in different ways. Ultimately, it's about striking a balance, aiming to choose the best solution for your specific scenario based on your needs and constraints like time, resources, and expertise.",[1576,34160,34161,34167,34173,34182,34188,34194,34200],{},[71,34162,34163,34166],{},[6850,34164,34165],{},"Which editor to use?"," Notion-like, Markdown, or WYSIWYG?",[71,34168,34169,34172],{},[6850,34170,34171],{},"How to render the editor?"," EasyMDE, CKEditor, Quill, or a simple textarea?",[71,34174,34175,34178,34179],{},[6850,34176,34177],{},"How to highlight the code blocks?"," Prism.js, Highlight.js, or Shiki? ",[1217,34180,34181],{},"This is crucial for my technical articles.",[71,34183,34184,34187],{},[6850,34185,34186],{},"How to preview the comments?"," Real-time, on-demand, or in browser?",[71,34189,34190,34193],{},[6850,34191,34192],{},"Is a reply a comment?"," Comments can have replies, but should replies be considered comments?",[71,34195,34196,34199],{},[6850,34197,34198],{},"Does a comment deletion delete the replies?"," If a comment is deleted, should the replies be removed as well?",[71,34201,34202,34205,34206],{},[6850,34203,34204],{},"Who can comment?"," Everyone, authenticated users, or only me? ",[1217,34207,34208],{},"It would be odd if only I could comment on my articles.",[35,34210,34211,34212],{},"The benefit of building the project incrementally is that these were the only decisions I had to make. ",[1217,34213,34214],{},"I didn't have to worry about the entire project, just the comments feature.",[35,34216,34217],{},"To address these questions, I conducted research, experimented, and made trade-offs.",[1576,34219,34220,34237,34254],{},[71,34221,34222,34225,34226,1638,34231,34236],{},[6850,34223,34224],{},"Research",": I tried to find articles on best practices but didn't find anything especially useful. So I delved into ",[39,34227,34230],{"href":34228,"rel":34229},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Fdiscussions",[77],"GitHub Discussions",[39,34232,34235],{"href":34233,"rel":34234},"https:\u002F\u002Flaracasts.com\u002Fdiscuss",[77],"Laracasts Forum",". I discovered interesting patterns and ideas that I could leverage.",[71,34238,34239,34242,34243,1638,34248,34253],{},[6850,34240,34241],{},"Experiment",": In previous projects, I used ",[39,34244,34247],{"href":34245,"rel":34246},"https:\u002F\u002Fgithub.com\u002FIonaru\u002Feasy-markdown-editor",[77],"EasyMDE",[39,34249,34252],{"href":34250,"rel":34251},"https:\u002F\u002Fquilljs.com\u002F",[77],"Quill",". I also examined editors from the platforms mentioned earlier, and my final choice may surprise you. 🫣",[71,34255,34256,34259],{},[6850,34257,34258],{},"Trade-offs",": Choosing simplicity over complexity. This is my guiding principle, and all my decisions are informed by it. However, simplicity can be quite complex and doesn't mean compromising on features or user experience.",[35,34261,34262],{},"All these decisions were made with the user in mind. I aim to make the commenting experience as seamless and enjoyable as possible, encouraging you to share your thoughts, ask questions, and engage in discussions.",[35,34264,34265,34266,106,34269,34272,34273,34276],{},"Sometimes I'm wrong, and sometimes there are better solutions, and these are the moments when ",[6850,34267,34268],{},"I need your feedback",[1217,34270,34271],{},"I acknowledge I'm not perfect and I'm always open to learning and improving."," With this new comments feature, it's now easier than ever to share your thoughts and ",[6850,34274,34275],{},"help everyone learn and grow",". 💜",[1159,34278],{},[35,34280,34281,34284],{},[6850,34282,34283],{},"Would you like to delve deeper into how I implemented the comments feature?"," Let me know in the comments below. ⬇️",[35,34286,34287],{},[1217,34288,34289],{},"I'm contemplating writing a series of articles about this project, from ideation to real-world implementation, complete with numerous code snippets and explanations.",{"title":13,"searchDepth":14,"depth":14,"links":34291},[34292,34293],{"id":34101,"depth":14,"text":34102},{"id":34118,"depth":14,"text":34119},"2024-10-09","The second phase of a key project. Let's proceed by allowing readers to comment on my articles. It's time to foster meaningful conversations and discussions.",{},"\u002Fposts\u002Fenhancing-engagement-enabling-comments-on-my-articles","---\ncontentId: c8faba17-5480-49fb-a201-d426ea583097\ntitle: \"Enhancing Engagement: Enabling Comments on My Articles\"\ndescription: The second phase of a key project. Let's proceed by allowing readers to comment on my articles. It's time to foster meaningful conversations and discussions.\ndate: 2024-10-09\n---\n\nLast week, I introduced the first phase of a major project: [incorporating reactions to my articles](.\u002Fenhancing-engagement-adding-reactions-to-my-articles.md). Today, I'm excited to unveil the second phase: **enabling comments on my articles**.\n\n\u003CCalloutReadMore id=\"0d4cb8be-230e-444e-9419-fce9adf0ef6b\" \u002F>\n\n## A second phase\n\nIn the previous article, I detailed the progressive approach I'm pursuing to enhance my personal website. Step by step, I aim to introduce features to engage with you, the reader, and create a platform for meaningful interactions around all the knowledge I share.\n\nIt's a progressive process because I believe this is the most effective way to deliver, rather than waiting to complete everything at once. At the same time, it keeps me motivated and focused by achieving small victories and receiving feedback. And since I'm learning [Laravel](https:\u002F\u002Flaravel.com), it prevents me from feeling overwhelmed by the project's complexity. _In essence, it's a win-win scenario._\n\n## The importance of comments\n\nRecently, I posted on X [a thread about \"singleton composables\"](https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1842987889216672223). Swiftly, I received comments, suggestions, and insights that made me realize that I could be mistaken. _I was mistaken._ And I'm grateful that I had the opportunity to learn from others' feedback.\n\nTwo weeks ago, I also published an article on the simplest method to create a Vue.js component library. At one juncture, there were two approaches to constructing the library. I chose one, deeming it the simplest, but through comments, people could have shared their thoughts and experiences, which could have assisted others in making better decisions and learning from one another.\n\n\u003CCalloutReadMore id=\"f9754148-40bd-4df2-a144-f686e894b200\" \u002F>\n\nComments are valuable as they allow us to express our thoughts, ask questions, and engage in discussions. They enable us to learn from each other, broaden our knowledge, and grow collectively. With this in mind, I decided to implement the comments feature on my personal website.\n\n## Taking the second step\n\nAnd here we are, the comments feature has been implemented and launched.\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\nThis phase posed more challenges than the first. I had to consider various factors and make decisions throughout the process. _It's fascinating to observe how the same feature can be implemented in different ways. Ultimately, it's about striking a balance, aiming to choose the best solution for your specific scenario based on your needs and constraints like time, resources, and expertise._\n\n- **Which editor to use?** Notion-like, Markdown, or WYSIWYG?\n- **How to render the editor?** EasyMDE, CKEditor, Quill, or a simple textarea?\n- **How to highlight the code blocks?** Prism.js, Highlight.js, or Shiki? _This is crucial for my technical articles._\n- **How to preview the comments?** Real-time, on-demand, or in browser?\n- **Is a reply a comment?** Comments can have replies, but should replies be considered comments?\n- **Does a comment deletion delete the replies?** If a comment is deleted, should the replies be removed as well?\n- **Who can comment?** Everyone, authenticated users, or only me? _It would be odd if only I could comment on my articles._\n\nThe benefit of building the project incrementally is that these were the only decisions I had to make. _I didn't have to worry about the entire project, just the comments feature._\n\nTo address these questions, I conducted research, experimented, and made trade-offs.\n\n- **Research**: I tried to find articles on best practices but didn't find anything especially useful. So I delved into [GitHub Discussions](https:\u002F\u002Fdocs.github.com\u002Fen\u002Fdiscussions) and [Laracasts Forum](https:\u002F\u002Flaracasts.com\u002Fdiscuss). I discovered interesting patterns and ideas that I could leverage.\n- **Experiment**: In previous projects, I used [EasyMDE](https:\u002F\u002Fgithub.com\u002FIonaru\u002Feasy-markdown-editor) and [Quill](https:\u002F\u002Fquilljs.com\u002F). I also examined editors from the platforms mentioned earlier, and my final choice may surprise you. 🫣\n- **Trade-offs**: Choosing simplicity over complexity. This is my guiding principle, and all my decisions are informed by it. However, simplicity can be quite complex and doesn't mean compromising on features or user experience.\n\nAll these decisions were made with the user in mind. I aim to make the commenting experience as seamless and enjoyable as possible, encouraging you to share your thoughts, ask questions, and engage in discussions.\n\nSometimes I'm wrong, and sometimes there are better solutions, and these are the moments when **I need your feedback**. _I acknowledge I'm not perfect and I'm always open to learning and improving._ With this new comments feature, it's now easier than ever to share your thoughts and **help everyone learn and grow**. 💜\n\n---\n\n**Would you like to delve deeper into how I implemented the comments feature?** Let me know in the comments below. ⬇️\n\n_I'm contemplating writing a series of articles about this project, from ideation to real-world implementation, complete with numerous code snippets and explanations._\n",{"title":34083,"description":34295},{"loc":34297},"posts\u002Fenhancing-engagement-enabling-comments-on-my-articles","MDt3jBX3K_2x2WObJstlDQxjyMZd38Vrs9yAPTnE48w",{"id":34304,"title":34305,"body":34306,"contentId":31075,"date":34527,"description":34528,"extension":18,"meta":34529,"navigation":20,"path":34530,"rawbody":34531,"robots":23,"schemaOrg":23,"seo":34532,"sitemap":34533,"stem":34534,"__hash__":34535},"posts\u002Fposts\u002Fenhancing-engagement-adding-reactions-to-my-articles.md","Enhancing Engagement: Adding Reactions to My Articles",{"type":7,"value":34307,"toc":34521},[34308,34311,34315,34325],[35,34309,34310],{},"Hey there! I hope you're doing well. I'm excited to share with you the beginning of a significant project that will accompany me over the coming months.",[51,34312,34314],{"id":34313},"introduction","Introduction",[35,34316,34317,34318,34321,34322],{},"In March 2024, I announced the overhaul of my personal website for the fourth time, this time using ",[39,34319,2913],{"href":33808,"rel":34320},[77],". This decision was guided by the desire for a streamlined yet robust tool that allows me to write easily and rapidly while maintaining control over both design and functionality. ",[1217,34323,34324],{},"VitePress is truly exceptional in this regard.",[7905,34326,34327,34331,34338,34344,34352,34367,34371,34382,34385,34391,34395,34411,34416,34419,34499,34505,34513,34518],{"id":30815},[51,34328,34330],{"id":34329},"exploring-new-horizons","Exploring new horizons",[35,34332,34333,34334,34337],{},"In June 2024, I embarked on learning ",[39,34335,19447],{"href":19443,"rel":34336},[77]," for two primary reasons.",[35,34339,34340,34341],{},"Firstly, I aimed to broaden my expertise and skills in web development. I want to create applications from the ground up, turning ideas into reality without heavily relying on external services. ",[1217,34342,34343],{},"And I'm already working on my first SaaS 🤫.",[35,34345,34346,34347,34351],{},"Secondly, I sought more flexibility in my career options. Since the outset of my professional journey, I have predominantly used ",[39,34348,22878],{"href":34349,"rel":34350},"https:\u002F\u002Fangular.dev\u002F",[77]," as my main tool. I am eager to explore new avenues as Angular no longer resonates with me.",[35,34353,34354],{},[1217,34355,34356,34357,34362,34363,34366],{},"I considered ",[39,34358,34361],{"href":34359,"rel":34360},"https:\u002F\u002Frubyonrails.org\u002F",[77],"Rails",", but once you've experienced the Laravel Documentation and ",[39,34364,33683],{"href":33689,"rel":34365},[77],", the choice becomes apparent.",[51,34368,34370],{"id":34369},"progressing-incrementally","Progressing incrementally",[35,34372,34373,34374],{},"Addressing the first reason demands substantial time and effort. It is a long-term initiative that I perceive as a significant challenge. Although I may not precisely know where to begin or how the journey will unfold, I am clear about my ultimate goal. Like all challenges, the best approach is to break them into smaller, manageable tasks, solve them individually, and then integrate the solutions. ",[1217,34375,34376,34377,215],{},"This is the essence of ",[39,34378,34381],{"href":34379,"rel":34380},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FDivide-and-conquer_algorithm",[77],"divide and conquer",[35,34383,34384],{},"I firmly believe that the most effective way to learn is through practical projects. Not just a simple to-do list but something functional with a real purpose.",[35,34386,34387,34388],{},"Meanwhile, I aim to enhance engagement with others. It's much more enjoyable to share and converse, to have interactions. For months, I've been contemplating additions to my personal website but struggled to find a practical, engaging, and useful idea. ",[1217,34389,34390],{},"Until now.",[51,34392,34394],{"id":34393},"taking-the-first-step","Taking the first step",[35,34396,34397,34398,34401,34402,106,34405],{},"Here we are. I decided to implement the feature to ",[6850,34399,34400],{},"react to my articles using predefined emojis",", supported by an API developed with ",[39,34403,19447],{"href":19443,"rel":34404},[77],[1217,34406,34407,34408,215],{},"And the front-end continues to be powered by ",[39,34409,2913],{"href":19430,"rel":34410},[77],[134,34412,132,34413],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,34414],{"src":34415,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fenhancing-engagement-adding-reactions-to-my-articles\u002Fdemo.mp4",[35,34417,34418],{},"Initially, it might appear as a minor feature, and that's true; it isn't vast, yet for a first step, it suffices. In this process, I engaged with numerous different concepts:",[1576,34420,34421,34429,34437,34455,34463,34471,34480,34488],{},[71,34422,34423,34428],{},[39,34424,34427],{"href":34425,"rel":34426},"https:\u002F\u002Flaravel.com\u002Fdocs\u002Fsocialite",[77],"Laravel Socialite"," for GitHub authentication.",[71,34430,34431,34436],{},[39,34432,34435],{"href":34433,"rel":34434},"https:\u002F\u002Flaravel.com\u002Fdocs\u002Fsanctum",[77],"Laravel Sanctum"," for API authentication.",[71,34438,34439,3076,34444,3076,34449,34454],{},[39,34440,34443],{"href":34441,"rel":34442},"https:\u002F\u002Flaravel.com\u002Fdocs\u002Fpint",[77],"Laravel Pint",[39,34445,34448],{"href":34446,"rel":34447},"https:\u002F\u002Fphpstan.org\u002F",[77],"PHPStan",[39,34450,34453],{"href":34451,"rel":34452},"https:\u002F\u002Fgetrector.org\u002F",[77],"Rector"," for code quality assurance.",[71,34456,34457,34462],{},[39,34458,34461],{"href":34459,"rel":34460},"https:\u002F\u002Fforge.laravel.com\u002F",[77],"Laravel Forge"," for deployment.",[71,34464,34465,34470],{},[39,34466,34469],{"href":34467,"rel":34468},"https:\u002F\u002Fwww.hetzner.com\u002F",[77],"Hetzner"," for the server infrastructure.",[71,34472,34473,34474,34479],{},"Custom Command for database backups and ",[39,34475,34478],{"href":34476,"rel":34477},"https:\u002F\u002Fwww.cloudflare.com\u002Fdeveloper-platform\u002Fr2\u002F",[77],"R2"," for their storage.",[71,34481,34482,34487],{},[39,34483,34486],{"href":34484,"rel":34485},"https:\u002F\u002Ffilamentphp.com\u002F",[77],"Filament"," for the dashboard management.",[71,34489,34490,34491],{},"100% test coverage. ",[1217,34492,34493,34494,34045],{},"Achievable with ease using Laravel and ",[39,34495,34498],{"href":34496,"rel":34497},"https:\u002F\u002Fpestphp.com\u002F",[77],"Pest",[35,34500,34501,34502],{},"And that's about it. ",[1217,34503,34504],{},"Not too shabby for five days of work!",[35,34506,34507,34508],{},"Reviewing all the source code of ",[1582,34509,34512],{"repo":34510,"name":34511},"pinkary-project\u002Fpinkary.com","Pinkary Project"," greatly enhanced my understanding of these tools and the process of building a Laravel application.",[35,34514,34515],{},[1217,34516,34517],{},"One of the significant aspects of this step is laying the groundwork for more features on my website and numerous articles to share what I've learned.",[35,34519,34520],{},"So, what are your thoughts? ⬇️",{"title":13,"searchDepth":14,"depth":14,"links":34522},[34523,34524,34525,34526],{"id":34313,"depth":14,"text":34314},{"id":34329,"depth":14,"text":34330},{"id":34369,"depth":14,"text":34370},{"id":34393,"depth":14,"text":34394},"2024-10-01","The initial yet crucial phase of a significant project that will accompany me in the upcoming months. Let's start small by adding reactions to my articles.",{},"\u002Fposts\u002Fenhancing-engagement-adding-reactions-to-my-articles","---\ncontentId: 0d4cb8be-230e-444e-9419-fce9adf0ef6b\ntitle: \"Enhancing Engagement: Adding Reactions to My Articles\"\ndescription: The initial yet crucial phase of a significant project that will accompany me in the upcoming months. Let's start small by adding reactions to my articles.\ndate: 2024-10-01\n---\n\nHey there! I hope you're doing well. I'm excited to share with you the beginning of a significant project that will accompany me over the coming months.\n\n## Introduction\n\nIn March 2024, I announced the overhaul of my personal website for the fourth time, this time using [VitePress](https:\u002F\u002Fvitepress.dev\u002F). This decision was guided by the desire for a streamlined yet robust tool that allows me to write easily and rapidly while maintaining control over both design and functionality. _VitePress is truly exceptional in this regard._\n\n\u003CCalloutReadMore id=\"674a3932-aaba-445c-82bb-0896ce3a8e7e\"\u002F>\n\n## Exploring new horizons\n\nIn June 2024, I embarked on learning [Laravel](https:\u002F\u002Flaravel.com) for two primary reasons.\n\nFirstly, I aimed to broaden my expertise and skills in web development. I want to create applications from the ground up, turning ideas into reality without heavily relying on external services. _And I'm already working on my first SaaS 🤫._\n\nSecondly, I sought more flexibility in my career options. Since the outset of my professional journey, I have predominantly used [Angular](https:\u002F\u002Fangular.dev\u002F) as my main tool. I am eager to explore new avenues as Angular no longer resonates with me.\n\n_I considered [Rails](https:\u002F\u002Frubyonrails.org\u002F), but once you've experienced the Laravel Documentation and [Laracasts](https:\u002F\u002Flaracasts.com\u002Freferral\u002FBarbapapazes), the choice becomes apparent._\n\n## Progressing incrementally\n\nAddressing the first reason demands substantial time and effort. It is a long-term initiative that I perceive as a significant challenge. Although I may not precisely know where to begin or how the journey will unfold, I am clear about my ultimate goal. Like all challenges, the best approach is to break them into smaller, manageable tasks, solve them individually, and then integrate the solutions. _This is the essence of [divide and conquer](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FDivide-and-conquer_algorithm)._\n\nI firmly believe that the most effective way to learn is through practical projects. Not just a simple to-do list but something functional with a real purpose.\n\nMeanwhile, I aim to enhance engagement with others. It's much more enjoyable to share and converse, to have interactions. For months, I've been contemplating additions to my personal website but struggled to find a practical, engaging, and useful idea. _Until now._\n\n## Taking the first step\n\nHere we are. I decided to implement the feature to **react to my articles using predefined emojis**, supported by an API developed with [Laravel](https:\u002F\u002Flaravel.com). _And the front-end continues to be powered by [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\nInitially, it might appear as a minor feature, and that's true; it isn't vast, yet for a first step, it suffices. In this process, I engaged with numerous different concepts:\n\n- [Laravel Socialite](https:\u002F\u002Flaravel.com\u002Fdocs\u002Fsocialite) for GitHub authentication.\n- [Laravel Sanctum](https:\u002F\u002Flaravel.com\u002Fdocs\u002Fsanctum) for API authentication.\n- [Laravel Pint](https:\u002F\u002Flaravel.com\u002Fdocs\u002Fpint), [PHPStan](https:\u002F\u002Fphpstan.org\u002F), [Rector](https:\u002F\u002Fgetrector.org\u002F) for code quality assurance.\n- [Laravel Forge](https:\u002F\u002Fforge.laravel.com\u002F) for deployment.\n- [Hetzner](https:\u002F\u002Fwww.hetzner.com\u002F) for the server infrastructure.\n- Custom Command for database backups and [R2](https:\u002F\u002Fwww.cloudflare.com\u002Fdeveloper-platform\u002Fr2\u002F) for their storage.\n- [Filament](https:\u002F\u002Ffilamentphp.com\u002F) for the dashboard management.\n- 100% test coverage. _Achievable with ease using Laravel and [Pest](https:\u002F\u002Fpestphp.com\u002F). 💫_\n\nAnd that's about it. _Not too shabby for five days of work!_\n\nReviewing all the source code of \u003CGitHubLink repo=\"pinkary-project\u002Fpinkary.com\" name=\"Pinkary Project\" \u002F> greatly enhanced my understanding of these tools and the process of building a Laravel application.\n\n_One of the significant aspects of this step is laying the groundwork for more features on my website and numerous articles to share what I've learned._\n\nSo, what are your thoughts? ⬇️\n",{"title":34305,"description":34528},{"loc":34530},"posts\u002Fenhancing-engagement-adding-reactions-to-my-articles","LD0HcKhXb60MUxL3IW43GYJzYnvIjjtinUYforzYsiA",{"id":34537,"title":34538,"body":34539,"contentId":21352,"date":36541,"description":36542,"extension":18,"meta":36543,"navigation":20,"path":36544,"rawbody":36545,"robots":23,"schemaOrg":23,"seo":36546,"sitemap":36547,"stem":36548,"__hash__":36549},"posts\u002Fposts\u002Fthe-simplest-method-to-create-a-vue-js-component-library.md","The Simplest Method to Create a Vue.js Component Library",{"type":7,"value":34540,"toc":36530},[34541,34551,34557,34580,36527],[35,34542,34543,34544],{},"TL;DR: ",[1582,34545,34547,34548],{"repo":34546},"barbapapazes\u002Fvue-library"," ",[1217,34549,34550],{},"Continue reading to grasp the process behind it and understand the reasons for my choices.",[35,34552,34553,34554],{},"A component library is a collection of reusable pieces that can be employed in various projects. It facilitates sharing resources among different projects and teams. These components may be low-level and generic like buttons, inputs, and modals, or more business-specific modules. ",[1217,34555,34556],{},"Ultimately, it's a method to share code across projects, a crucial skill that can significantly save time.",[35,34558,34559,34560,34564,34565,34570,34571,13731,34576,215],{},"However, building a component library with ",[39,34561,18624],{"href":34562,"rel":34563},"https:\u002F\u002Fvuejs.org",[77]," is harder than it seems because of the ",[39,34566,34569],{"href":34567,"rel":34568},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fscaling-up\u002Fsfc.html",[77],"Single File Components"," (SFC). In a standard TypeScript project, you usually transpile to JavaScript and bundle your files with a tool like ",[39,34572,34575],{"href":34573,"rel":34574},"https:\u002F\u002Ftsup.egoist.dev\u002F",[77],"tsup",[39,34577,24355],{"href":34578,"rel":34579},"https:\u002F\u002Fvitejs.dev\u002F",[77],[7905,34581,34582,34596,34599,34602,34728,34731,34823,34829,34832,34838,34844,34904,34913,35049,35053,35060,35066,35072,35101,35108,35120,35134,35138,35155,35158,35161,35173,35176,35185,35190,35196,35219,35226,35368,35377,35381,35397,35415,35424,35589,35596,35600,35606,35657,35663,35669,35672,35683,35687,35690,35715,35718,35731,35756,35760,35763,35773,35850,35880,35890,35896,36047,36053,36059,36072,36076,36083,36086,36089,36096,36103,36130,36137,36158,36161,36173,36184,36190,36200,36280,36283,36302,36311,36315,36318,36325,36337,36346,36363,36368,36434,36445,36456,36459,36473,36476,36480,36483,36503,36512,36520,36525],{"id":31061},[939,34583,34584],{},[35,34585,34586,34588,34589,34592,34593],{},[200,34587,945],{},"\nRemember to ",[6850,34590,34591],{},"deliver package code as native as possible"," and let userland tools handle transpiling and optimization. It's a principle I always keep in mind when crafting libraries, streamlining the process significantly. ",[1217,34594,34595],{},"Each case is unique, but this is a solid guideline to follow.",[35,34597,34598],{},"The use of Vue.js SFC complicates this process. Let's examine why by exploring the challenges you'll encounter when constructing a Vue.js component library.",[35,34600,34601],{},"Consider the following component:",[192,34603,34605],{"className":21417,"code":34604,"language":12060,"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,34606,34607,34627,34647,34651,34668,34676,34680,34688,34707,34712,34720],{"__ignoreMap":13},[200,34608,34609,34611,34613,34615,34617,34619,34621,34623,34625],{"class":202,"line":203},[200,34610,600],{"class":214},[200,34612,14892],{"class":235},[200,34614,14590],{"class":379},[200,34616,449],{"class":214},[200,34618,611],{"class":214},[200,34620,1373],{"class":245},[200,34622,611],{"class":214},[200,34624,21781],{"class":379},[200,34626,648],{"class":214},[200,34628,34629,34631,34633,34636,34638,34640,34642,34645],{"class":202,"line":14},[200,34630,973],{"class":206},[200,34632,318],{"class":214},[200,34634,34635],{"class":210}," useUser",[200,34637,981],{"class":214},[200,34639,984],{"class":206},[200,34641,242],{"class":214},[200,34643,34644],{"class":245},"..\u002Fcomposables",[200,34646,992],{"class":214},[200,34648,34649],{"class":202,"line":255},[200,34650,529],{"emptyLinePlaceholder":20},[200,34652,34653,34655,34657,34660,34662,34664,34666],{"class":202,"line":272},[200,34654,443],{"class":379},[200,34656,318],{"class":214},[200,34658,34659],{"class":210}," user ",[200,34661,413],{"class":214},[200,34663,510],{"class":214},[200,34665,34635],{"class":223},[200,34667,466],{"class":210},[200,34669,34670,34672,34674],{"class":202,"line":283},[200,34671,681],{"class":214},[200,34673,14892],{"class":235},[200,34675,648],{"class":214},[200,34677,34678],{"class":202,"line":300},[200,34679,529],{"emptyLinePlaceholder":20},[200,34681,34682,34684,34686],{"class":202,"line":310},[200,34683,600],{"class":214},[200,34685,21428],{"class":235},[200,34687,648],{"class":214},[200,34689,34690,34692,34694,34697,34699,34701,34703,34705],{"class":202,"line":336},[200,34691,657],{"class":214},[200,34693,11386],{"class":235},[200,34695,34696],{"class":379}," v-if",[200,34698,449],{"class":214},[200,34700,611],{"class":214},[200,34702,26808],{"class":245},[200,34704,611],{"class":214},[200,34706,648],{"class":214},[200,34708,34709],{"class":202,"line":342},[200,34710,34711],{"class":210},"    {{ user.name }}\n",[200,34713,34714,34716,34718],{"class":202,"line":365},[200,34715,893],{"class":214},[200,34717,11386],{"class":235},[200,34719,648],{"class":214},[200,34721,34722,34724,34726],{"class":202,"line":371},[200,34723,681],{"class":214},[200,34725,21428],{"class":235},[200,34727,648],{"class":214},[35,34729,34730],{},"The composable is as follows:",[192,34732,34734],{"className":1370,"code":34733,"language":1373,"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,34735,34736,34754,34758,34770,34800,34804,34810,34815,34819],{"__ignoreMap":13},[200,34737,34738,34740,34742,34744,34746,34748,34750,34752],{"class":202,"line":203},[200,34739,973],{"class":206},[200,34741,318],{"class":214},[200,34743,18685],{"class":210},[200,34745,981],{"class":214},[200,34747,984],{"class":206},[200,34749,242],{"class":214},[200,34751,12060],{"class":245},[200,34753,992],{"class":214},[200,34755,34756],{"class":202,"line":14},[200,34757,529],{"emptyLinePlaceholder":20},[200,34759,34760,34762,34764,34766,34768],{"class":202,"line":255},[200,34761,1380],{"class":206},[200,34763,4987],{"class":379},[200,34765,34635],{"class":223},[200,34767,1532],{"class":214},[200,34769,280],{"class":214},[200,34771,34772,34774,34777,34779,34781,34783,34785,34787,34789,34791,34794,34796,34798],{"class":202,"line":272},[200,34773,3723],{"class":379},[200,34775,34776],{"class":210}," user",[200,34778,510],{"class":214},[200,34780,18685],{"class":223},[200,34782,227],{"class":235},[200,34784,2752],{"class":214},[200,34786,701],{"class":235},[200,34788,239],{"class":214},[200,34790,242],{"class":214},[200,34792,34793],{"class":245},"John Doe",[200,34795,249],{"class":214},[200,34797,981],{"class":214},[200,34799,416],{"class":235},[200,34801,34802],{"class":202,"line":283},[200,34803,529],{"emptyLinePlaceholder":20},[200,34805,34806,34808],{"class":202,"line":300},[200,34807,3743],{"class":206},[200,34809,280],{"class":214},[200,34811,34812],{"class":202,"line":310},[200,34813,34814],{"class":210},"    user\n",[200,34816,34817],{"class":202,"line":336},[200,34818,4490],{"class":214},[200,34820,34821],{"class":202,"line":342},[200,34822,1130],{"class":214},[35,34824,34825,34826],{},"The component employs a composable imported from another file. ",[1217,34827,34828],{},"It may seem trivial, but this poses a significant issue.",[35,34830,34831],{},"All of this follows the architecture below:",[192,34833,34836],{"className":34834,"code":34835,"language":698},[33336],"src\u002F\n  components\u002F\n    User.vue\n  composables\u002F\n    useUser.ts\n  index.ts\n",[156,34837,34835],{"__ignoreMap":13},[35,34839,419,34840,34843],{},[156,34841,34842],{},"index.ts"," file acts as the library's entry point and it looks like this:",[192,34845,34847],{"className":1370,"code":34846,"language":1373,"meta":13,"style":13},"import User from '.\u002Fcomponents\u002FUser.vue'\n\nexport { User }\n\nexport { useUser } from '.\u002Fcomposables\u002FuseUser'\n",[156,34848,34849,34865,34869,34881,34885],{"__ignoreMap":13},[200,34850,34851,34853,34856,34858,34860,34863],{"class":202,"line":203},[200,34852,973],{"class":206},[200,34854,34855],{"class":210}," User ",[200,34857,1472],{"class":206},[200,34859,242],{"class":214},[200,34861,34862],{"class":245},".\u002Fcomponents\u002FUser.vue",[200,34864,992],{"class":214},[200,34866,34867],{"class":202,"line":14},[200,34868,529],{"emptyLinePlaceholder":20},[200,34870,34871,34873,34875,34878],{"class":202,"line":255},[200,34872,1380],{"class":206},[200,34874,318],{"class":214},[200,34876,34877],{"class":210}," User",[200,34879,34880],{"class":214}," }\n",[200,34882,34883],{"class":202,"line":272},[200,34884,529],{"emptyLinePlaceholder":20},[200,34886,34887,34889,34891,34893,34895,34897,34899,34902],{"class":202,"line":283},[200,34888,1380],{"class":206},[200,34890,318],{"class":214},[200,34892,34635],{"class":210},[200,34894,981],{"class":214},[200,34896,984],{"class":206},[200,34898,242],{"class":214},[200,34900,34901],{"class":245},".\u002Fcomposables\u002FuseUser",[200,34903,992],{"class":214},[35,34905,34906,34907,34909,34910,239],{},"Plus, the ",[156,34908,31600],{}," includes some ",[156,34911,34912],{},"exports",[192,34914,34916],{"className":26986,"code":34915,"language":26988,"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,34917,34918,34922,34940,34952,34964,34984,35001,35005,35009,35028,35045],{"__ignoreMap":13},[200,34919,34920],{"class":202,"line":203},[200,34921,230],{"class":214},[200,34923,34924,34926,34928,34930,34932,34934,34936,34938],{"class":202,"line":14},[200,34925,26999],{"class":214},[200,34927,3771],{"class":379},[200,34929,611],{"class":214},[200,34931,239],{"class":214},[200,34933,27008],{"class":214},[200,34935,15093],{"class":245},[200,34937,611],{"class":214},[200,34939,252],{"class":214},[200,34941,34942,34944,34946,34948,34950],{"class":202,"line":255},[200,34943,26999],{"class":214},[200,34945,34912],{"class":379},[200,34947,611],{"class":214},[200,34949,239],{"class":214},[200,34951,280],{"class":214},[200,34953,34954,34956,34958,34960,34962],{"class":202,"line":272},[200,34955,27031],{"class":214},[200,34957,215],{"class":1978},[200,34959,611],{"class":214},[200,34961,239],{"class":214},[200,34963,280],{"class":214},[200,34965,34966,34968,34971,34973,34975,34977,34980,34982],{"class":202,"line":283},[200,34967,27056],{"class":214},[200,34969,34970],{"class":2045},"types",[200,34972,611],{"class":214},[200,34974,239],{"class":214},[200,34976,27008],{"class":214},[200,34978,34979],{"class":245},".\u002Fdist\u002Findex.d.ts",[200,34981,611],{"class":214},[200,34983,252],{"class":214},[200,34985,34986,34988,34990,34992,34994,34996,34999],{"class":202,"line":300},[200,34987,27056],{"class":214},[200,34989,973],{"class":2045},[200,34991,611],{"class":214},[200,34993,239],{"class":214},[200,34995,27008],{"class":214},[200,34997,34998],{"class":245},".\u002Fdist\u002Findex.mjs",[200,35000,617],{"class":214},[200,35002,35003],{"class":202,"line":310},[200,35004,4962],{"class":214},[200,35006,35007],{"class":202,"line":336},[200,35008,368],{"class":214},[200,35010,35011,35013,35015,35017,35019,35021,35024,35026],{"class":202,"line":342},[200,35012,26999],{"class":214},[200,35014,32861],{"class":379},[200,35016,611],{"class":214},[200,35018,239],{"class":214},[200,35020,27008],{"class":214},[200,35022,35023],{"class":245},"dist\u002Findex.mjs",[200,35025,611],{"class":214},[200,35027,252],{"class":214},[200,35029,35030,35032,35034,35036,35038,35040,35043],{"class":202,"line":365},[200,35031,26999],{"class":214},[200,35033,34970],{"class":379},[200,35035,611],{"class":214},[200,35037,239],{"class":214},[200,35039,27008],{"class":214},[200,35041,35042],{"class":245},"dist\u002Findex.d.ts",[200,35044,617],{"class":214},[200,35046,35047],{"class":202,"line":371},[200,35048,1130],{"class":214},[51,35050,35052],{"id":35051},"similar-to-a-standard-typescript-project","Similar to a standard TypeScript project",[939,35054,35055],{},[35,35056,35057,35059],{},[200,35058,945],{},"\nA bundle is a single file encapsulating all your project's code. It optimizes project loading by minimizing the number of files to load. This bundle isn't transpiled (except TypeScript to JavaScript) or minified when creating an npm package.",[35,35061,35062,35063,35065],{},"Now, if we attempt to bundle this project, our ",[156,35064,5139],{}," folder will appear like this:",[192,35067,35070],{"className":35068,"code":35069,"language":698},[33336],"dist\u002F\n  index.cjs\n",[156,35071,35069],{"__ignoreMap":13},[35,35073,35074,35075,35078,35079,35082,35083],{},"Upon inspecting ",[156,35076,35077],{},"index.cjs",", you'll find a surprise: the ",[156,35080,35081],{},"useUser"," composable is inlined, but the component is entirely ignored. ",[1217,35084,35085,35086,35089,35090,13731,35095,35100],{},"If it functions, the bundler might throw an error like ",[156,35087,35088],{},"No loader is configured for \".vue\" files",". Essentially, tools like ",[39,35091,35094],{"href":35092,"rel":35093},"https:\u002F\u002Fesbuild.github.io",[77],"esbuild",[39,35096,35099],{"href":35097,"rel":35098},"https:\u002F\u002Fwww.typescriptlang.org\u002Fdocs\u002Fhandbook\u002Fcompiler-options.html",[77],"tsc"," don't know how to handle Vue files. It's logical since Vue files aren't JavaScript files.",[35,35102,35103,35104,35107],{},"The problem becomes evident. We need some configuration to handle Vue files. Let's quickly browse the internet for something like ",[156,35105,35106],{},"vue loader",", as we're utilizing Vue files and need to transpile them to JavaScript.",[35,35109,35110,35111,35116,35117],{},"The top result is ",[39,35112,35115],{"href":35113,"rel":35114},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fvue-loader",[77],"Vue Loader",", which is a Webpack loader. ",[1217,35118,35119],{},"No. I refuse to use Webpack. Vite is now a standard, and there's no justification for employing Webpack in this context.",[35,35121,35122,35123,34547,35125,35133],{},"But before delving further, let's revisit our mantra: ",[6850,35124,34591],{},[1217,35126,35127,35128],{},"and ",[39,35129,35132],{"href":35130,"rel":35131},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FKISS_principle",[77],"keep it simple, stupid!"," Transpiling Vue SFC files using Webpack seems contrary to this principle.",[51,35135,35137],{"id":35136},"ignoring-vue-files","Ignoring Vue files",[35,35139,35140,35141,35143,35144,35149,35150,35154],{},"To deliver ",[156,35142,11851],{}," files without transpiling them, we instruct our bundler to disregard them. This is achieved using a tool called ",[39,35145,35148],{"href":35146,"rel":35147},"https:\u002F\u002Funbuild.unjs.io",[77],"unbuild"," from the ",[39,35151,30791],{"href":35152,"rel":35153},"https:\u002F\u002Funjs.io",[77]," ecosystem.",[35,35156,35157],{},"Unbuild is a straightforward tool yet highly configurable since it's constructed atop rollup.",[35,35159,35160],{},"For our small project, we can directly experiment with it:",[192,35162,35164],{"className":1355,"code":35163,"language":1358,"meta":13,"style":13},"npx unbuild\n",[156,35165,35166],{"__ignoreMap":13},[200,35167,35168,35170],{"class":202,"line":203},[200,35169,31244],{"class":1978},[200,35171,35172],{"class":245}," unbuild\n",[35,35174,35175],{},"Unfortunately, it fails for the same reasons as before:",[192,35177,35179],{"className":4277,"code":35178,"language":4279,"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,35180,35181],{"__ignoreMap":13},[200,35182,35183],{"class":202,"line":203},[200,35184,35178],{},[35,35186,35187],{},[1217,35188,35189],{},"But unbuild has the potential for much more.",[35,35191,35192,35193,35195],{},"To tackle this problem, let's attempt the obvious solution. In the ",[156,35194,34842],{}," file, remove the component export and solely export TypeScript files:",[192,35197,35199],{"className":1370,"code":35198,"language":1373,"meta":13,"style":13},"export { useUser } from '.\u002Fcomposables\u002FuseUser'\n",[156,35200,35201],{"__ignoreMap":13},[200,35202,35203,35205,35207,35209,35211,35213,35215,35217],{"class":202,"line":203},[200,35204,1380],{"class":206},[200,35206,318],{"class":214},[200,35208,34635],{"class":210},[200,35210,981],{"class":214},[200,35212,984],{"class":206},[200,35214,242],{"class":214},[200,35216,34901],{"class":245},[200,35218,992],{"class":214},[35,35220,35221,35222,35225],{},"Now, the command ",[156,35223,35224],{},"npx unbuild"," functions properly.",[192,35227,35229],{"className":1355,"code":35228,"language":1358,"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,35230,35231,35240,35267,35277,35293,35308,35342,35346],{"__ignoreMap":13},[200,35232,35233,35236,35238],{"class":202,"line":203},[200,35234,35235],{"class":1978},"➜",[200,35237,31480],{"class":245},[200,35239,35172],{"class":245},[200,35241,35242,35245,35248,35251,35254,35257,35260,35262,35265],{"class":202,"line":14},[200,35243,35244],{"class":1978},"ℹ",[200,35246,35247],{"class":245}," Automatically",[200,35249,35250],{"class":245}," detected",[200,35252,35253],{"class":245}," entries:",[200,35255,35256],{"class":245}," src\u002Findex",[200,35258,35259],{"class":210}," [esm] ",[200,35261,9858],{"class":214},[200,35263,35264],{"class":210},"dts",[200,35266,4496],{"class":214},[200,35268,35269,35271,35274],{"class":202,"line":255},[200,35270,35244],{"class":1978},[200,35272,35273],{"class":245}," Building",[200,35275,35276],{"class":245}," vue-library\n",[200,35278,35279,35281,35284,35287,35290],{"class":202,"line":272},[200,35280,35244],{"class":1978},[200,35282,35283],{"class":245}," Cleaning",[200,35285,35286],{"class":245}," dist",[200,35288,35289],{"class":245}," directory:",[200,35291,35292],{"class":245}," .\u002Fdist\n",[200,35294,35295,35298,35301,35304,35306],{"class":202,"line":283},[200,35296,35297],{"class":1978},"✔",[200,35299,35300],{"class":245}," Build",[200,35302,35303],{"class":245}," succeeded",[200,35305,663],{"class":245},[200,35307,35276],{"class":245},[200,35309,35310,35313,35316,35319,35322,35325,35328,35331,35333,35335,35338,35340],{"class":202,"line":300},[200,35311,35312],{"class":1978},"  dist\u002Findex.mjs",[200,35314,35315],{"class":210}," (total ",[200,35317,35318],{"class":245},"size:",[200,35320,35321],{"class":2045}," 139",[200,35323,35324],{"class":245}," B,",[200,35326,35327],{"class":245}," chunk",[200,35329,35330],{"class":245}," size:",[200,35332,35321],{"class":2045},[200,35334,35324],{"class":245},[200,35336,35337],{"class":245}," exports:",[200,35339,34635],{"class":245},[200,35341,416],{"class":210},[200,35343,35344],{"class":202,"line":310},[200,35345,529],{"emptyLinePlaceholder":20},[200,35347,35348,35351,35354,35356,35359,35362,35365],{"class":202,"line":336},[200,35349,35350],{"class":1978},"Σ",[200,35352,35353],{"class":245}," Total",[200,35355,35286],{"class":245},[200,35357,35358],{"class":245}," size",[200,35360,35361],{"class":210}," (byte ",[200,35363,35364],{"class":245},"size",[200,35366,35367],{"class":210},"): 531 B\n",[35,35369,35370,35371,35373,35374,35376],{},"This is a good start, but our components remain in the ",[156,35372,11165],{}," folder and no matter how diligently you search, they're missing from the ",[156,35375,5139],{}," folder.",[51,35378,35380],{"id":35379},"copying-vue-files","Copying Vue files",[35,35382,35383,35384,35387,35388,35390,35391,35393,35394,26684],{},"Now that ",[156,35385,35386],{},".ts"," files are processed correctly, we can attempt to copy the ",[156,35389,11851],{}," files to the ",[156,35392,5139],{}," folder using a simple ",[156,35395,35396],{},"cp",[192,35398,35400],{"className":1355,"code":35399,"language":1358,"meta":13,"style":13},"cp -r src\u002Fcomponents\u002F dist\u002Fcomponents\u002F\n",[156,35401,35402],{"__ignoreMap":13},[200,35403,35404,35406,35409,35412],{"class":202,"line":203},[200,35405,35396],{"class":1978},[200,35407,35408],{"class":245}," -r",[200,35410,35411],{"class":245}," src\u002Fcomponents\u002F",[200,35413,35414],{"class":245}," dist\u002Fcomponents\u002F\n",[35,35416,35417,35418,35420,35421,35423],{},"Subsequently, we add an ",[156,35419,34912],{}," field in the ",[156,35422,31600],{}," to inform the user where to locate the components:",[192,35425,35427],{"className":26986,"code":35426,"language":26988,"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,35428,35429,35433,35451,35463,35475,35493,35509,35513,35526,35543,35547,35551,35569,35585],{"__ignoreMap":13},[200,35430,35431],{"class":202,"line":203},[200,35432,230],{"class":214},[200,35434,35435,35437,35439,35441,35443,35445,35447,35449],{"class":202,"line":14},[200,35436,26999],{"class":214},[200,35438,3771],{"class":379},[200,35440,611],{"class":214},[200,35442,239],{"class":214},[200,35444,27008],{"class":214},[200,35446,15093],{"class":245},[200,35448,611],{"class":214},[200,35450,252],{"class":214},[200,35452,35453,35455,35457,35459,35461],{"class":202,"line":255},[200,35454,26999],{"class":214},[200,35456,34912],{"class":379},[200,35458,611],{"class":214},[200,35460,239],{"class":214},[200,35462,280],{"class":214},[200,35464,35465,35467,35469,35471,35473],{"class":202,"line":272},[200,35466,27031],{"class":214},[200,35468,215],{"class":1978},[200,35470,611],{"class":214},[200,35472,239],{"class":214},[200,35474,280],{"class":214},[200,35476,35477,35479,35481,35483,35485,35487,35489,35491],{"class":202,"line":283},[200,35478,27056],{"class":214},[200,35480,34970],{"class":2045},[200,35482,611],{"class":214},[200,35484,239],{"class":214},[200,35486,27008],{"class":214},[200,35488,34979],{"class":245},[200,35490,611],{"class":214},[200,35492,252],{"class":214},[200,35494,35495,35497,35499,35501,35503,35505,35507],{"class":202,"line":300},[200,35496,27056],{"class":214},[200,35498,973],{"class":2045},[200,35500,611],{"class":214},[200,35502,239],{"class":214},[200,35504,27008],{"class":214},[200,35506,34998],{"class":245},[200,35508,617],{"class":214},[200,35510,35511],{"class":202,"line":310},[200,35512,339],{"class":214},[200,35514,35515,35517,35520,35522,35524],{"class":202,"line":336},[200,35516,27031],{"class":214},[200,35518,35519],{"class":1978},".\u002Fcomponents\u002F*",[200,35521,611],{"class":214},[200,35523,239],{"class":214},[200,35525,280],{"class":214},[200,35527,35528,35530,35532,35534,35536,35538,35541],{"class":202,"line":342},[200,35529,27056],{"class":214},[200,35531,973],{"class":2045},[200,35533,611],{"class":214},[200,35535,239],{"class":214},[200,35537,27008],{"class":214},[200,35539,35540],{"class":245},".\u002Fdist\u002Fcomponents\u002F*.vue",[200,35542,617],{"class":214},[200,35544,35545],{"class":202,"line":365},[200,35546,4962],{"class":214},[200,35548,35549],{"class":202,"line":371},[200,35550,368],{"class":214},[200,35552,35553,35555,35557,35559,35561,35563,35565,35567],{"class":202,"line":398},[200,35554,26999],{"class":214},[200,35556,32861],{"class":379},[200,35558,611],{"class":214},[200,35560,239],{"class":214},[200,35562,27008],{"class":214},[200,35564,35023],{"class":245},[200,35566,611],{"class":214},[200,35568,252],{"class":214},[200,35570,35571,35573,35575,35577,35579,35581,35583],{"class":202,"line":405},[200,35572,26999],{"class":214},[200,35574,34970],{"class":379},[200,35576,611],{"class":214},[200,35578,239],{"class":214},[200,35580,27008],{"class":214},[200,35582,35042],{"class":245},[200,35584,617],{"class":214},[200,35586,35587],{"class":202,"line":410},[200,35588,1130],{"class":214},[35,35590,35591,35592,35595],{},"Looks promising, doesn't it? ",[1217,35593,35594],{},"But it isn't."," 😔",[51,35597,35599],{"id":35598},"the-import-path-dilemma","The import path dilemma",[35,35601,35602,35603,35605],{},"In our Vue component, the ",[156,35604,35081],{}," composable is imported like this:",[192,35607,35609],{"className":21417,"code":35608,"language":12060,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nimport { useUser } from '..\u002Fcomposables'\n\u003C\u002Fscript>\n",[156,35610,35611,35631,35649],{"__ignoreMap":13},[200,35612,35613,35615,35617,35619,35621,35623,35625,35627,35629],{"class":202,"line":203},[200,35614,600],{"class":214},[200,35616,14892],{"class":235},[200,35618,14590],{"class":379},[200,35620,449],{"class":214},[200,35622,611],{"class":214},[200,35624,1373],{"class":245},[200,35626,611],{"class":214},[200,35628,21781],{"class":379},[200,35630,648],{"class":214},[200,35632,35633,35635,35637,35639,35641,35643,35645,35647],{"class":202,"line":14},[200,35634,973],{"class":206},[200,35636,318],{"class":214},[200,35638,34635],{"class":210},[200,35640,981],{"class":214},[200,35642,984],{"class":206},[200,35644,242],{"class":214},[200,35646,34644],{"class":245},[200,35648,992],{"class":214},[200,35650,35651,35653,35655],{"class":202,"line":255},[200,35652,681],{"class":214},[200,35654,14892],{"class":235},[200,35656,648],{"class":214},[35,35658,35659,35660,35662],{},"Meanwhile, our ",[156,35661,5139],{}," folder is structured as follows:",[192,35664,35667],{"className":35665,"code":35666,"language":698},[33336],"dist\u002F\n  components\u002F\n    User.vue\n  index.d.mts\n  index.d.ts\n  index.mjs\n",[156,35668,35666],{"__ignoreMap":13},[35,35670,35671],{},"Can you identify the problem? 👀",[35,35673,35674,35675,35678,35679,35682],{},"The component is unusable and will produce an error like ",[156,35676,35677],{},"Cannot find module '..\u002Fcomposables' or its corresponding type declarations",", because the path leads nowhere. We've bundled all of our TypeScript files into a single file ",[156,35680,35681],{},"index.mjs",", losing the project's structure entirely.",[51,35684,35686],{"id":35685},"to-bundle-or-not-to-bundle","To bundle or not to bundle?",[35,35688,35689],{},"From here, we have two choices:",[68,35691,35692,35703],{},[71,35693,35694,35697,35698,35702],{},[6850,35695,35696],{},"Bundling the Vue files"," using ",[39,35699,24355],{"href":35700,"rel":35701},"https:\u002F\u002Fvitejs.dev",[77],", plugins, and extensive configuration.",[71,35704,35705,35708,35709,35714],{},[6850,35706,35707],{},"Maintaining the directory structure"," and using a tool like ",[39,35710,35713],{"href":35711,"rel":35712},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fmkdist",[77],"mkdist"," for file-to-file transpiling (bundleless build) on TypeScript files.",[35,35716,35717],{},"The decision largely hinges on your specific needs, but I'm convinced that simplicity is the best approach. So, let's explore the second option.",[35,35719,35720,35721,35723,35724,35726,35727,35730],{},"The goal is to transform the TypeScript files into JavaScript files, preserving the original structure of the ",[156,35722,11165],{}," folder in the ",[156,35725,5139],{}," folder and ",[1217,35728,35729],{},"ignoring"," the Vue files.",[939,35732,35733],{},[35,35734,35735,35737,35738,35741,35742,35745,35746,35750,35751,215],{},[200,35736,945],{},"\nUsing a ",[156,35739,35740],{},"\u003Cscript setup lang=\"ts\">"," block, mkdist will ignore Vue files to enable the Vue compiler to generate runtime props. If not, it will generate a ",[156,35743,35744],{},".vue.d.ts"," file to offer types for the Vue component. Remember that the ",[39,35747,11646],{"href":35748,"rel":35749},"https:\u002F\u002Fgithub.com\u002Fvitejs\u002Fvite-plugin-vue\u002Ftree\u002Fmain\u002Fpackages\u002Fplugin-vue",[77]," and Vite inherently understand TypeScript script blocks. For more details, refer to ",[39,35752,35755],{"href":35753,"rel":35754},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fmkdist\u002Fissues\u002F14",[77],"issue mkdist#14",[51,35757,35759],{"id":35758},"configuring-unbuild","Configuring Unbuild",[35,35761,35762],{},"Yes, we will configure unbuild since it incorporates mkdist, making it exceptionally user-friendly.",[35,35764,35765,35766,35769,35770,35772],{},"First, create a ",[156,35767,35768],{},"build.config.ts"," file at the project's root. Unbuild will read this configuration file to understand how to process the project. It assumes defaults and infers many aspects from the ",[156,35771,31600],{},", but we need to tell it to utilize mkdist.",[192,35774,35776],{"className":1370,"code":35775,"language":1373,"meta":13,"style":13},"import { defineBuildConfig } from 'unbuild'\n\nexport default defineBuildConfig({\n  entries: ['.\u002Fsrc\u002F'],\n  declaration: true,\n})\n",[156,35777,35778,35797,35801,35813,35833,35844],{"__ignoreMap":13},[200,35779,35780,35782,35784,35787,35789,35791,35793,35795],{"class":202,"line":203},[200,35781,973],{"class":206},[200,35783,318],{"class":214},[200,35785,35786],{"class":210}," defineBuildConfig",[200,35788,981],{"class":214},[200,35790,984],{"class":206},[200,35792,242],{"class":214},[200,35794,35148],{"class":245},[200,35796,992],{"class":214},[200,35798,35799],{"class":202,"line":14},[200,35800,529],{"emptyLinePlaceholder":20},[200,35802,35803,35805,35807,35809,35811],{"class":202,"line":255},[200,35804,1380],{"class":206},[200,35806,1383],{"class":206},[200,35808,35786],{"class":223},[200,35810,227],{"class":210},[200,35812,230],{"class":214},[200,35814,35815,35818,35820,35822,35824,35827,35829,35831],{"class":202,"line":272},[200,35816,35817],{"class":235},"  entries",[200,35819,239],{"class":214},[200,35821,350],{"class":210},[200,35823,249],{"class":214},[200,35825,35826],{"class":245},".\u002Fsrc\u002F",[200,35828,249],{"class":214},[200,35830,360],{"class":210},[200,35832,252],{"class":214},[200,35834,35835,35838,35840,35842],{"class":202,"line":283},[200,35836,35837],{"class":235},"  declaration",[200,35839,239],{"class":214},[200,35841,2424],{"class":2423},[200,35843,252],{"class":214},[200,35845,35846,35848],{"class":202,"line":300},[200,35847,413],{"class":214},[200,35849,416],{"class":210},[35,35851,35852,35853,35856,35857,35859,35860,35862,35863,35865,35866,35869,35870,35872,35873,35876,35877,1552],{},"It's a very straightforward configuration file. ",[1217,35854,35855],{},"And I adore it."," The ",[156,35858,6453],{}," key informs unbuild of the initial file to begin transpiling. However, ",[156,35861,35826],{}," is a directory, indicated by the ",[156,35864,2882],{}," at the end. With this hint, unbuild bypasses the default bundler ",[156,35867,35868],{},"rollup"," in favor of ",[156,35871,35713],{},". The ",[156,35874,35875],{},"declaration"," key prompts unbuild to create TypeScript declaration files (",[156,35878,35879],{},".d.ts",[35,35881,35882,35883,35420,35885,35887,35888,31299],{},"We can also remove the ",[156,35884,34912],{},[156,35886,31600],{}," since we won't be bundling files anymore and restore the component export in the ",[156,35889,34842],{},[35,35891,35892,35893,35895],{},"Let's rerun the ",[156,35894,35224],{}," command and witness the magic.",[192,35897,35899],{"className":1355,"code":35898,"language":1358,"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,35900,35901,35909,35917,35929,35941,35958,35974,35988,36002,36016,36030],{"__ignoreMap":13},[200,35902,35903,35905,35907],{"class":202,"line":203},[200,35904,35235],{"class":1978},[200,35906,31480],{"class":245},[200,35908,35172],{"class":245},[200,35910,35911,35913,35915],{"class":202,"line":14},[200,35912,35244],{"class":1978},[200,35914,35273],{"class":245},[200,35916,35276],{"class":245},[200,35918,35919,35921,35923,35925,35927],{"class":202,"line":255},[200,35920,35244],{"class":1978},[200,35922,35283],{"class":245},[200,35924,35286],{"class":245},[200,35926,35289],{"class":245},[200,35928,35292],{"class":245},[200,35930,35931,35933,35935,35937,35939],{"class":202,"line":272},[200,35932,35297],{"class":1978},[200,35934,35300],{"class":245},[200,35936,35303],{"class":245},[200,35938,663],{"class":245},[200,35940,35276],{"class":245},[200,35942,35943,35946,35948,35950,35953,35956],{"class":202,"line":283},[200,35944,35945],{"class":1978},"  dist",[200,35947,35315],{"class":210},[200,35949,35318],{"class":245},[200,35951,35952],{"class":2045}," 600",[200,35954,35955],{"class":245}," B",[200,35957,416],{"class":210},[200,35959,35960,35963,35966,35969,35972],{"class":202,"line":300},[200,35961,35962],{"class":1978},"  └─",[200,35964,35965],{"class":245}," dist\u002Findex.d.ts",[200,35967,35968],{"class":210}," (108 ",[200,35970,35971],{"class":245},"B",[200,35973,416],{"class":210},[200,35975,35976,35978,35981,35984,35986],{"class":202,"line":310},[200,35977,35962],{"class":1978},[200,35979,35980],{"class":245}," dist\u002Findex.mjs",[200,35982,35983],{"class":210}," (112 ",[200,35985,35971],{"class":245},[200,35987,416],{"class":210},[200,35989,35990,35992,35995,35998,36000],{"class":202,"line":336},[200,35991,35962],{"class":1978},[200,35993,35994],{"class":245}," dist\u002Fcomposables\u002FuseUser.d.ts",[200,35996,35997],{"class":210}," (79 ",[200,35999,35971],{"class":245},[200,36001,416],{"class":210},[200,36003,36004,36006,36009,36012,36014],{"class":202,"line":342},[200,36005,35962],{"class":1978},[200,36007,36008],{"class":245}," dist\u002Fcomposables\u002FuseUser.mjs",[200,36010,36011],{"class":210}," (124 ",[200,36013,35971],{"class":245},[200,36015,416],{"class":210},[200,36017,36018,36020,36023,36026,36028],{"class":202,"line":365},[200,36019,35962],{"class":1978},[200,36021,36022],{"class":245}," dist\u002Fcomponents\u002FUser.vue",[200,36024,36025],{"class":210}," (177 ",[200,36027,35971],{"class":245},[200,36029,416],{"class":210},[200,36031,36032,36034,36036,36038,36040,36042,36044],{"class":202,"line":371},[200,36033,35350],{"class":1978},[200,36035,35353],{"class":245},[200,36037,35286],{"class":245},[200,36039,35358],{"class":245},[200,36041,35361],{"class":210},[200,36043,35364],{"class":245},[200,36045,36046],{"class":210},"): 600 B\n",[35,36048,36049,36050,36052],{},"Now, the ",[156,36051,5139],{}," folder appears like this:",[192,36054,36057],{"className":36055,"code":36056,"language":698},[33336],"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,36058,36056],{"__ignoreMap":13},[35,36060,36061,36062,36064,36065,36068,36069,36071],{},"This mirrors the structure of the ",[156,36063,11165],{}," folder, and the ",[156,36066,36067],{},"User.vue"," component is now usable in the ",[156,36070,5139],{}," folder. The relative import path remains intact, and the component can be utilized in any Vue project. :partying_face:",[51,36073,36075],{"id":36074},"local-development","Local development",[35,36077,36078,36079,36082],{},"Many tutorials halt here, suggesting you're ready to publish your package to npm. But how can you create a complex library if you can't ",[6850,36080,36081],{},"see the outcome of your work","? If you can't test it while building it?",[35,36084,36085],{},"Basically, you can't.",[35,36087,36088],{},"Let's examine how to use the library in a local project, within the same repository for simplicity.",[35,36090,36091,36092,215],{},"I'll demonstrate the simplest method using a ",[39,36093,36095],{"href":33800,"rel":36094},[77],"pnpm workspace",[35,36097,36098,36099,36102],{},"Start by creating a ",[156,36100,36101],{},"pnpm-workspace.yaml"," file at the project's root:",[192,36104,36106],{"className":32575,"code":36105,"language":32577,"meta":13,"style":13},"packages:\n  - .\n  - playground\n",[156,36107,36108,36115,36123],{"__ignoreMap":13},[200,36109,36110,36113],{"class":202,"line":203},[200,36111,36112],{"class":235},"packages",[200,36114,30028],{"class":214},[200,36116,36117,36120],{"class":202,"line":14},[200,36118,36119],{"class":214},"  -",[200,36121,36122],{"class":2045}," .\n",[200,36124,36125,36127],{"class":202,"line":255},[200,36126,36119],{"class":214},[200,36128,36129],{"class":245}," playground\n",[35,36131,36132,36133,36136],{},"Then, create a new Vite project in a ",[156,36134,36135],{},"playground"," folder:",[192,36138,36140],{"className":1355,"code":36139,"language":1358,"meta":13,"style":13},"npx create-vite playground --template vue-ts\n",[156,36141,36142],{"__ignoreMap":13},[200,36143,36144,36146,36149,36152,36155],{"class":202,"line":203},[200,36145,31244],{"class":1978},[200,36147,36148],{"class":245}," create-vite",[200,36150,36151],{"class":245}," playground",[200,36153,36154],{"class":245}," --template",[200,36156,36157],{"class":245}," vue-ts\n",[35,36159,36160],{},"Since we're using a pnpm workspace, install the dependencies from the project's root:",[192,36162,36164],{"className":1355,"code":36163,"language":1358,"meta":13,"style":13},"pnpm install\n",[156,36165,36166],{"__ignoreMap":13},[200,36167,36168,36170],{"class":202,"line":203},[200,36169,2160],{"class":1978},[200,36171,36172],{"class":245}," install\n",[939,36174,36175],{},[35,36176,36177,36179,36180,36183],{},[200,36178,945],{},"\nThis step isn't mandatory. You can create a new Vite project and install dependencies within it. Pnpm simplifies this by installing dependencies for all workspaces in the root. A simple ",[156,36181,36182],{},"pnpm install"," installs dependencies from all workspaces.",[35,36185,36186,36187,36189],{},"Now, we can use this ",[156,36188,36135],{}," library within a \"real\" project to test our library.",[35,36191,36192,36193,36195,36196,36199],{},"For instance, open the ",[156,36194,21995],{}," file and import the ",[156,36197,36198],{},"User"," component:",[192,36201,36203],{"className":21417,"code":36202,"language":12060,"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,36204,36205,36225,36244,36252,36256,36264,36272],{"__ignoreMap":13},[200,36206,36207,36209,36211,36213,36215,36217,36219,36221,36223],{"class":202,"line":203},[200,36208,600],{"class":214},[200,36210,14892],{"class":235},[200,36212,21781],{"class":379},[200,36214,14590],{"class":379},[200,36216,449],{"class":214},[200,36218,611],{"class":214},[200,36220,1373],{"class":245},[200,36222,611],{"class":214},[200,36224,648],{"class":214},[200,36226,36227,36229,36231,36233,36235,36237,36239,36242],{"class":202,"line":14},[200,36228,973],{"class":206},[200,36230,318],{"class":214},[200,36232,34877],{"class":210},[200,36234,981],{"class":214},[200,36236,984],{"class":206},[200,36238,242],{"class":214},[200,36240,36241],{"class":245},"..\u002F..\u002Fsrc",[200,36243,992],{"class":214},[200,36245,36246,36248,36250],{"class":202,"line":255},[200,36247,681],{"class":214},[200,36249,14892],{"class":235},[200,36251,648],{"class":214},[200,36253,36254],{"class":202,"line":272},[200,36255,529],{"emptyLinePlaceholder":20},[200,36257,36258,36260,36262],{"class":202,"line":283},[200,36259,600],{"class":214},[200,36261,21428],{"class":235},[200,36263,648],{"class":214},[200,36265,36266,36268,36270],{"class":202,"line":300},[200,36267,657],{"class":214},[200,36269,36198],{"class":235},[200,36271,21487],{"class":214},[200,36273,36274,36276,36278],{"class":202,"line":310},[200,36275,681],{"class":214},[200,36277,21428],{"class":235},[200,36279,648],{"class":214},[35,36281,36282],{},"And run the project:",[192,36284,36286],{"className":1355,"code":36285,"language":1358,"meta":13,"style":13},"cd playground && pnpm dev\n",[156,36287,36288],{"__ignoreMap":13},[200,36289,36290,36293,36295,36297,36300],{"class":202,"line":203},[200,36291,36292],{"class":223},"cd",[200,36294,36151],{"class":245},[200,36296,14928],{"class":214},[200,36298,36299],{"class":1978}," pnpm",[200,36301,13695],{"class":245},[35,36303,36304,36305,36307,36308],{},"You'll see the ",[156,36306,36198],{}," component displayed in the browser. 🎉 So simple, yet so powerful. ",[1217,36309,36310],{},"I use this method for virtually every library I build, and it works brilliantly.",[51,36312,36314],{"id":36313},"publishing-to-npm","Publishing to npm",[35,36316,36317],{},"This part is straightforward once you grasp the workflow.",[35,36319,36320,36321,36324],{},"Begin by naming your library. I'll use ",[156,36322,36323],{},"@barbapapazes\u002Fvue-library"," for this example.",[35,36326,36327,36328,36333,36334,26684],{},"Next, create an account on ",[39,36329,36332],{"href":36330,"rel":36331},"https:\u002F\u002Fwww.npmjs.com",[77],"npm"," and log in using the ",[156,36335,36336],{},"npm login",[35,36338,36339,36340,36345],{},"Then, install ",[39,36341,36344],{"href":36342,"rel":36343},"https:\u002F\u002Fchangelogen.unjs.io",[77],"changelogen"," to generate a changelog, update the version number following semantic versioning and commit convention, and to pre-fill the GitHub release.",[192,36347,36349],{"className":1355,"code":36348,"language":1358,"meta":13,"style":13},"pnpm i -D changelogen\n",[156,36350,36351],{"__ignoreMap":13},[200,36352,36353,36355,36358,36360],{"class":202,"line":203},[200,36354,2160],{"class":1978},[200,36356,36357],{"class":245}," i",[200,36359,12291],{"class":245},[200,36361,36362],{"class":245}," changelogen\n",[35,36364,36365,36366,239],{},"Next, add two scripts to your ",[156,36367,31600],{},[192,36369,36371],{"className":26986,"code":36370,"language":26988,"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,36372,36373,36377,36389,36408,36426,36430],{"__ignoreMap":13},[200,36374,36375],{"class":202,"line":203},[200,36376,230],{"class":214},[200,36378,36379,36381,36383,36385,36387],{"class":202,"line":14},[200,36380,26999],{"class":214},[200,36382,31616],{"class":379},[200,36384,611],{"class":214},[200,36386,239],{"class":214},[200,36388,280],{"class":214},[200,36390,36391,36393,36396,36398,36400,36402,36404,36406],{"class":202,"line":255},[200,36392,27031],{"class":214},[200,36394,36395],{"class":1978},"prepack",[200,36397,611],{"class":214},[200,36399,239],{"class":214},[200,36401,27008],{"class":214},[200,36403,35148],{"class":245},[200,36405,611],{"class":214},[200,36407,252],{"class":214},[200,36409,36410,36412,36415,36417,36419,36421,36424],{"class":202,"line":272},[200,36411,27031],{"class":214},[200,36413,36414],{"class":1978},"release",[200,36416,611],{"class":214},[200,36418,239],{"class":214},[200,36420,27008],{"class":214},[200,36422,36423],{"class":245},"changelogen --release && npm publish --access public && git push --follow-tags",[200,36425,617],{"class":214},[200,36427,36428],{"class":202,"line":283},[200,36429,4490],{"class":214},[200,36431,36432],{"class":202,"line":300},[200,36433,1130],{"class":214},[35,36435,419,36436,36438,36439,36441,36442,36444],{},[156,36437,36395],{}," script runs the ",[156,36440,35148],{}," command before publishing the package to npm. The ",[156,36443,36414],{}," script generates a changelog, publishes the package to npm, and pushes the tags to GitHub.",[939,36446,36447],{},[35,36448,36449,36451,36452,36455],{},[200,36450,945],{},"\nYour project should be on GitHub for the ",[156,36453,36454],{},"--release"," option to work. If not, omit it and create the release manually on your repository.",[35,36457,36458],{},"Now, publish your package to npm using the following command:",[192,36460,36462],{"className":1355,"code":36461,"language":1358,"meta":13,"style":13},"npm run release\n",[156,36463,36464],{"__ignoreMap":13},[200,36465,36466,36468,36470],{"class":202,"line":203},[200,36467,36332],{"class":1978},[200,36469,32709],{"class":245},[200,36471,36472],{"class":245}," release\n",[35,36474,36475],{},"And that's it! You've just published your first Vue.js component library to npm. 🚀",[51,36477,36479],{"id":36478},"everything-is-ready","Everything is ready",[35,36481,36482],{},"We're done for today. We've covered numerous topics:",[1576,36484,36485,36488,36491,36494,36497,36500],{},[71,36486,36487],{},"How to build a Vue.js component library with TypeScript.",[71,36489,36490],{},"How to manage Vue files in a TypeScript project.",[71,36492,36493],{},"How to use unbuild to transpile TypeScript files without bundling Vue files.",[71,36495,36496],{},"How to leverage mkdist to maintain the project structure.",[71,36498,36499],{},"How to utilize a pnpm workspace to test the library in a local project.",[71,36501,36502],{},"How to publish the library to npm.",[35,36504,36505,36506],{},"For a more detailed and complex example, check GitHub: ",[1582,36507,36508,36509,215],{"repo":34546}," but everything explained here stems from ",[6850,36510,36511],{},"my real-world experience",[35,36513,36514,36515,215],{},"I hope you enjoyed this tutorial and that it helps you build your own Vue.js component library. If you have questions, feel free to reach out to me on ",[39,36516,36519],{"href":36517,"rel":36518},"https:\u002F\u002Fx.com\u002Fsoubiran_",[77],"X (Twitter)",[35,36521,36522],{},[1217,36523,36524],{},"You can go even further by using Vite to build your component library:",[7905,36526],{"id":31061},[1171,36528,36529],{},"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":36531},[36532,36533,36534,36535,36536,36537,36538,36539,36540],{"id":35051,"depth":14,"text":35052},{"id":35136,"depth":14,"text":35137},{"id":35379,"depth":14,"text":35380},{"id":35598,"depth":14,"text":35599},{"id":35685,"depth":14,"text":35686},{"id":35758,"depth":14,"text":35759},{"id":36074,"depth":14,"text":36075},{"id":36313,"depth":14,"text":36314},{"id":36478,"depth":14,"text":36479},"2024-09-24","Manage the complexities of Vue SFCs with powerful tools like unbuild and mkdist to create a component library, saving you time and effort.",{},"\u002Fposts\u002Fthe-simplest-method-to-create-a-vue-js-component-library","---\ncontentId: f9754148-40bd-4df2-a144-f686e894b200\ntitle: The Simplest Method to Create a Vue.js Component Library\ndescription: Manage the complexities of Vue SFCs with powerful tools like unbuild and mkdist to create a component library, saving you time and effort.\ndate: 2024-09-24\n---\n\nTL;DR: \u003CGitHubLink repo=\"barbapapazes\u002Fvue-library\" \u002F> _Continue reading to grasp the process behind it and understand the reasons for my choices._\n\nA component library is a collection of reusable pieces that can be employed in various projects. It facilitates sharing resources among different projects and teams. These components may be low-level and generic like buttons, inputs, and modals, or more business-specific modules. _Ultimately, it's a method to share code across projects, a crucial skill that can significantly save time._\n\nHowever, building a component library with [Vue.js](https:\u002F\u002Fvuejs.org) is harder than it seems because of the [Single File Components](https:\u002F\u002Fvuejs.org\u002Fguide\u002Fscaling-up\u002Fsfc.html) (SFC). In a standard TypeScript project, you usually transpile to JavaScript and bundle your files with a tool like [tsup](https:\u002F\u002Ftsup.egoist.dev\u002F) or [Vite](https:\u002F\u002Fvitejs.dev\u002F).\n\n\u003CCalloutReadMore id=\"55d2cdcf-ba27-4bfe-9bc1-492e866ff3b1\" \u002F>\n\n> [!NOTE]\n> Remember to **deliver package code as native as possible** and let userland tools handle transpiling and optimization. It's a principle I always keep in mind when crafting libraries, streamlining the process significantly. _Each case is unique, but this is a solid guideline to follow._\n\nThe use of Vue.js SFC complicates this process. Let's examine why by exploring the challenges you'll encounter when constructing a Vue.js component library.\n\nConsider the following component:\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\nThe composable is as follows:\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\nThe component employs a composable imported from another file. _It may seem trivial, but this poses a significant issue._\n\nAll of this follows the architecture below:\n\n```\nsrc\u002F\n  components\u002F\n    User.vue\n  composables\u002F\n    useUser.ts\n  index.ts\n```\n\nThe `index.ts` file acts as the library's entry point and it looks like this:\n\n```ts\nimport User from '.\u002Fcomponents\u002FUser.vue'\n\nexport { User }\n\nexport { useUser } from '.\u002Fcomposables\u002FuseUser'\n```\n\nPlus, the `package.json` includes some `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## Similar to a standard TypeScript project\n\n> [!NOTE]\n> A bundle is a single file encapsulating all your project's code. It optimizes project loading by minimizing the number of files to load. This bundle isn't transpiled (except TypeScript to JavaScript) or minified when creating an npm package.\n\nNow, if we attempt to bundle this project, our `dist` folder will appear like this:\n\n```\ndist\u002F\n  index.cjs\n```\n\nUpon inspecting `index.cjs`, you'll find a surprise: the `useUser` composable is inlined, but the component is entirely ignored. _If it functions, the bundler might throw an error like `No loader is configured for \".vue\" files`. Essentially, tools like [esbuild](https:\u002F\u002Fesbuild.github.io) or [tsc](https:\u002F\u002Fwww.typescriptlang.org\u002Fdocs\u002Fhandbook\u002Fcompiler-options.html) don't know how to handle Vue files. It's logical since Vue files aren't JavaScript files._\n\nThe problem becomes evident. We need some configuration to handle Vue files. Let's quickly browse the internet for something like `vue loader`, as we're utilizing Vue files and need to transpile them to JavaScript.\n\nThe top result is [Vue Loader](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fvue-loader), which is a Webpack loader. _No. I refuse to use Webpack. Vite is now a standard, and there's no justification for employing Webpack in this context._\n\nBut before delving further, let's revisit our mantra: **deliver package code as native as possible** _and [keep it simple, stupid!](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FKISS_principle)_ Transpiling Vue SFC files using Webpack seems contrary to this principle.\n\n## Ignoring Vue files\n\nTo deliver `.vue` files without transpiling them, we instruct our bundler to disregard them. This is achieved using a tool called [unbuild](https:\u002F\u002Funbuild.unjs.io) from the [UnJS](https:\u002F\u002Funjs.io) ecosystem.\n\nUnbuild is a straightforward tool yet highly configurable since it's constructed atop rollup.\n\nFor our small project, we can directly experiment with it:\n\n```sh\nnpx unbuild\n```\n\nUnfortunately, it fails for the same reasons as before:\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_But unbuild has the potential for much more._\n\nTo tackle this problem, let's attempt the obvious solution. In the `index.ts` file, remove the component export and solely export TypeScript files:\n\n```ts\nexport { useUser } from '.\u002Fcomposables\u002FuseUser'\n```\n\nNow, the command `npx unbuild` functions properly.\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\nThis is a good start, but our components remain in the `src` folder and no matter how diligently you search, they're missing from the `dist` folder.\n\n## Copying Vue files\n\nNow that `.ts` files are processed correctly, we can attempt to copy the `.vue` files to the `dist` folder using a simple `cp` command.\n\n```sh\ncp -r src\u002Fcomponents\u002F dist\u002Fcomponents\u002F\n```\n\nSubsequently, we add an `exports` field in the `package.json` to inform the user where to locate the components:\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\nLooks promising, doesn't it? _But it isn't._ :pensive:\n\n## The import path dilemma\n\nIn our Vue component, the `useUser` composable is imported like this:\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nimport { useUser } from '..\u002Fcomposables'\n\u003C\u002Fscript>\n```\n\nMeanwhile, our `dist` folder is structured as follows:\n\n```\ndist\u002F\n  components\u002F\n    User.vue\n  index.d.mts\n  index.d.ts\n  index.mjs\n```\n\nCan you identify the problem? :eyes:\n\nThe component is unusable and will produce an error like `Cannot find module '..\u002Fcomposables' or its corresponding type declarations`, because the path leads nowhere. We've bundled all of our TypeScript files into a single file `index.mjs`, losing the project's structure entirely.\n\n## To bundle or not to bundle?\n\nFrom here, we have two choices:\n\n1. **Bundling the Vue files** using [Vite](https:\u002F\u002Fvitejs.dev), plugins, and extensive configuration.\n2. **Maintaining the directory structure** and using a tool like [mkdist](https:\u002F\u002Fgithub.com\u002Funjs\u002Fmkdist) for file-to-file transpiling (bundleless build) on TypeScript files.\n\nThe decision largely hinges on your specific needs, but I'm convinced that simplicity is the best approach. So, let's explore the second option.\n\nThe goal is to transform the TypeScript files into JavaScript files, preserving the original structure of the `src` folder in the `dist` folder and _ignoring_ the Vue files.\n\n> [!NOTE]\n> Using a `\u003Cscript setup lang=\"ts\">` block, mkdist will ignore Vue files to enable the Vue compiler to generate runtime props. If not, it will generate a `.vue.d.ts` file to offer types for the Vue component. Remember that the [@vitejs\u002Fplugin-vue](https:\u002F\u002Fgithub.com\u002Fvitejs\u002Fvite-plugin-vue\u002Ftree\u002Fmain\u002Fpackages\u002Fplugin-vue) and Vite inherently understand TypeScript script blocks. For more details, refer to [issue mkdist#14](https:\u002F\u002Fgithub.com\u002Funjs\u002Fmkdist\u002Fissues\u002F14).\n\n## Configuring Unbuild\n\nYes, we will configure unbuild since it incorporates mkdist, making it exceptionally user-friendly.\n\nFirst, create a `build.config.ts` file at the project's root. Unbuild will read this configuration file to understand how to process the project. It assumes defaults and infers many aspects from the `package.json`, but we need to tell it to utilize mkdist.\n\n```ts\nimport { defineBuildConfig } from 'unbuild'\n\nexport default defineBuildConfig({\n  entries: ['.\u002Fsrc\u002F'],\n  declaration: true,\n})\n```\n\nIt's a very straightforward configuration file. _And I adore it._ The `entries` key informs unbuild of the initial file to begin transpiling. However, `.\u002Fsrc\u002F` is a directory, indicated by the `\u002F` at the end. With this hint, unbuild bypasses the default bundler `rollup` in favor of `mkdist`. The `declaration` key prompts unbuild to create TypeScript declaration files (`.d.ts`).\n\nWe can also remove the `exports` field in the `package.json` since we won't be bundling files anymore and restore the component export in the `index.ts` file.\n\nLet's rerun the `npx unbuild` command and witness the magic.\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\nNow, the `dist` folder appears like this:\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\nThis mirrors the structure of the `src` folder, and the `User.vue` component is now usable in the `dist` folder. The relative import path remains intact, and the component can be utilized in any Vue project. :partying_face:\n\n## Local development\n\nMany tutorials halt here, suggesting you're ready to publish your package to npm. But how can you create a complex library if you can't **see the outcome of your work**? If you can't test it while building it?\n\nBasically, you can't.\n\nLet's examine how to use the library in a local project, within the same repository for simplicity.\n\nI'll demonstrate the simplest method using a [pnpm workspace](https:\u002F\u002Fpnpm.io\u002F).\n\nStart by creating a `pnpm-workspace.yaml` file at the project's root:\n\n```yaml\npackages:\n  - .\n  - playground\n```\n\nThen, create a new Vite project in a `playground` folder:\n\n```sh\nnpx create-vite playground --template vue-ts\n```\n\nSince we're using a pnpm workspace, install the dependencies from the project's root:\n\n```sh\npnpm install\n```\n\n> [!NOTE]\n> This step isn't mandatory. You can create a new Vite project and install dependencies within it. Pnpm simplifies this by installing dependencies for all workspaces in the root. A simple `pnpm install` installs dependencies from all workspaces.\n\nNow, we can use this `playground` library within a \"real\" project to test our library.\n\nFor instance, open the `src\u002FApp.vue` file and import the `User` component:\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\nAnd run the project:\n\n```sh\ncd playground && pnpm dev\n```\n\nYou'll see the `User` component displayed in the browser. :tada: So simple, yet so powerful. _I use this method for virtually every library I build, and it works brilliantly._\n\n## Publishing to npm\n\nThis part is straightforward once you grasp the workflow.\n\nBegin by naming your library. I'll use `@barbapapazes\u002Fvue-library` for this example.\n\nNext, create an account on [npm](https:\u002F\u002Fwww.npmjs.com) and log in using the `npm login` command.\n\nThen, install [changelogen](https:\u002F\u002Fchangelogen.unjs.io) to generate a changelog, update the version number following semantic versioning and commit convention, and to pre-fill the GitHub release.\n\n```sh\npnpm i -D changelogen\n```\n\nNext, add two scripts to your `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\nThe `prepack` script runs the `unbuild` command before publishing the package to npm. The `release` script generates a changelog, publishes the package to npm, and pushes the tags to GitHub.\n\n> [!NOTE]\n> Your project should be on GitHub for the `--release` option to work. If not, omit it and create the release manually on your repository.\n\nNow, publish your package to npm using the following command:\n\n```sh\nnpm run release\n```\n\nAnd that's it! You've just published your first Vue.js component library to npm. :rocket:\n\n## Everything is ready\n\nWe're done for today. We've covered numerous topics:\n\n- How to build a Vue.js component library with TypeScript.\n- How to manage Vue files in a TypeScript project.\n- How to use unbuild to transpile TypeScript files without bundling Vue files.\n- How to leverage mkdist to maintain the project structure.\n- How to utilize a pnpm workspace to test the library in a local project.\n- How to publish the library to npm.\n\nFor a more detailed and complex example, check GitHub: \u003CGitHubLink repo=\"barbapapazes\u002Fvue-library\" \u002F> but everything explained here stems from **my real-world experience**.\n\nI hope you enjoyed this tutorial and that it helps you build your own Vue.js component library. If you have questions, feel free to reach out to me on [X (Twitter)](https:\u002F\u002Fx.com\u002Fsoubiran_).\n\n_You can go even further by using Vite to build your component library:_\n\n\u003CCalloutReadMore id=\"55d2cdcf-ba27-4bfe-9bc1-492e866ff3b1\" \u002F>\n",{"title":34538,"description":36542},{"loc":36544},"posts\u002Fthe-simplest-method-to-create-a-vue-js-component-library","54sSthPedhwWu7jxhjUQZlFD1TvUKk6X4GktWQA3ikA",{"id":36551,"title":36552,"body":36553,"contentId":30929,"date":36913,"description":36914,"extension":18,"meta":36915,"navigation":20,"path":36916,"rawbody":36917,"robots":23,"schemaOrg":23,"seo":36918,"sitemap":36919,"stem":36939,"__hash__":36940},"posts\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js.md","PragVue 2024: Travel, Food, People, and a lot of Vue.js",{"type":7,"value":36554,"toc":36906},[36555,36568,36574,36578,36581,36587,36593,36597,36600,36621,36627,36633,36642,36648,36654,36659,36662,36668,36671,36686,36692,36695,36707,36713,36716,36719,36735,36741,36747,36750,36756,36759,36763,36768,36774,36777,36806,36809,36827,36833,36839,36842,36848,36852,36866,36872,36878,36884,36888,36894,36897,36900],[35,36556,36557,36558,36563,36564,36567],{},"Before I delve into my journey to Prague, let me elaborate how I got there. In March 2024, ",[39,36559,36562],{"href":36560,"rel":36561},"https:\u002F\u002Fx.com\u002Fkissu_io",[77],"Konstantin"," reached out to me on X, inviting me to speak at a new ",[39,36565,17986],{"href":34562,"rel":36566},[77]," conference in Prague.",[35,36569,36570,36571],{},"After gathering more details, I eagerly accepted the invitation. ",[1217,36572,36573],{},"Even though I accepted promptly, I was slightly nervous as it would be my first time presenting in English at a conference. But everything requires a first attempt, so I decided to embrace the opportunity.",[51,36575,36577],{"id":36576},"en-route-to-prague","En route to Prague",[35,36579,36580],{},"On Saturday, September 14, I flew from Paris to Prague. The conference was scheduled for the 17th, but I opted to arrive a few days early to explore the city.",[35,36582,36583],{},[1690,36584],{"alt":36585,"src":36586},"The view from my window seat during the flight.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fcdg-to-pra-flight.jpeg",[35,36588,36589,36590],{},"I reached my hotel, centrally located in Prague, at 22:30. With heavy rain outside and feeling exhausted, I opted to rest. ",[1217,36591,36592],{},"Luckily, I have two days to explore before the conference.",[51,36594,36596],{"id":36595},"exploring-the-city","Exploring the city",[35,36598,36599],{},"The following day, Sunday, the weather remained as dismal as the previous day, with strong winds and rain. Nonetheless, I was determined to explore, so I had breakfast before heading out.",[35,36601,36602,36603,36608,36609,36614,36615,36620],{},"Without a concrete plan, I decided to visit a few notable spots. I began at the ",[39,36604,36607],{"href":36605,"rel":36606},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FOld_Town_Square",[77],"Old Town Square",", a stunning yet tourist-heavy location. My exploration led me to the ",[39,36610,36613],{"href":36611,"rel":36612},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrague_astronomical_clock",[77],"Astronomical Clock",", followed by crossing the ",[39,36616,36619],{"href":36617,"rel":36618},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCharles_Bridge",[77],"Charles Bridge"," over the Vltava River.",[35,36622,36623],{},[1690,36624],{"alt":36625,"src":36626},"Old Town Square.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fold-town-square.jpeg",[35,36628,36629],{},[1690,36630],{"alt":36631,"src":36632},"Charles Bridge and the Prague Castle in the background.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fcharles-bridge.jpeg",[35,36634,36635,36636,36641],{},"Upon crossing the bridge, I noticed a tower atop a hill. Curious, I headed there and discovered the mini Eiffel Tower, the ",[39,36637,36640],{"href":36638,"rel":36639},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPet%C5%99%C3%ADn_Lookout_Tower",[77],"Petrin Tower",". It's possible to ascend, so I did. The panoramic view from the top was breathtaking.",[35,36643,36644],{},[1690,36645],{"alt":36646,"src":36647},"The Petrin Tower.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fpetrin-tower.jpeg",[35,36649,36650],{},[1690,36651],{"alt":36652,"src":36653},"The view from the Petrin Tower.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fview-from-petrin-tower.jpeg",[35,36655,36656],{},[1217,36657,36658],{},"Despite the storm causing the tower to sway, it was a thrilling experience for the amazing view.",[35,36660,36661],{},"With the rain intensifying, I sought out a nearby restaurant. I found one and enjoyed a traditional goulash paired with a local beer. The meal was delightful and warmed me up considerably.",[35,36663,36664],{},[1690,36665],{"alt":36666,"src":36667},"The traditional Czech Goulash.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fgoulash.jpeg",[35,36669,36670],{},"Even after dining, the rain persisted, leaving me quite drenched, so I returned to the hotel. I rehearsed my talk for several hours, hoping the weather would improve later.",[35,36672,36673,36674,36679,36680,36685],{},"By late afternoon, the rain ceased, allowing me to resume exploring. I wandered to ",[39,36675,36678],{"href":36676,"rel":36677},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FLetn%C3%A1_Park",[77],"Letná Park"," and continued to ",[39,36681,36684],{"href":36682,"rel":36683},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FStromovka",[77],"Stromovka Park",". It was a pleasant and tranquil stroll, offering a more natural perspective of the city, away from the typical tourist spots.",[35,36687,36688],{},[1690,36689],{"alt":36690,"src":36691},"Some buildings in Prague,","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fprague-buildings.jpeg",[35,36693,36694],{},"Prague is adorned with vibrant buildings, making city walks quite enjoyable.",[35,36696,36697,36698,36703,36704],{},"For dinner, I found an authentic local restaurant where I savored ",[39,36699,36702],{"href":36700,"rel":36701},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSv%C3%AD%C4%8Dkov%C3%A1",[77],"Svíčková"," with another local beer. ",[1217,36705,36706],{},"Again! The beer in Prague is excellent and even more affordable than water.",[35,36708,36709],{},[1690,36710],{"alt":36711,"src":36712},"A Svíčková, the traditional Czech dish.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fsvickova.jpeg",[35,36714,36715],{},"After this scrumptious meal, it was time to head back to the hotel for a restful night's sleep.",[35,36717,36718],{},"By Monday morning, the weather hadn't improved, still characterized by substantial rain and wind. The day's goal was to be at the conference hotel by 14:00 to meet fellow speakers and prepare before the speaker dinner at 19:00.",[35,36720,36721,36722,36727,36728,36731,36732],{},"This hotel was a one-hour walk from my accommodation. I opted to walk, seizing the opportunity to see more of the city. I didn't have specific plans aside from wandering aimlessly. Serendipitously, I stumbled upon the ",[39,36723,36726],{"href":36724,"rel":36725},"https:\u002F\u002Fmuseumofbricks.cz\u002Fen",[77],"Museum of Bricks",", the world's largest private LEGO collection. ",[1217,36729,36730],{},"As a die-hard LEGO enthusiast, I couldn't pass up the chance to visit."," This museum, one of five in the Czech Republic, spans an area of 420m² and boasts 3,000 unique models made from over a million bricks. ",[1217,36733,36734],{},"It was nostalgic to see some LEGO from my childhood.",[35,36736,36737],{},[1690,36738],{"alt":36739,"src":36740},"The museum of Bricks.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fmuseum-of-bricks.jpeg",[35,36742,36743],{},[1690,36744],{"alt":36745,"src":36746},"Some Star Wars LEGO. A very little part of the collection.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fstar-wars-lego.jpeg",[35,36748,36749],{},"This visit was wonderful, marking the start of my journey to the conference hotel. Time to stroll again and avoid any delays.",[35,36751,36752],{},[1690,36753],{"alt":36754,"src":36755},"Another view from Prague during the walk","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fprague-view.jpeg",[35,36757,36758],{},"After the long walk, I arrived at the hotel. I met the speakers and attended the dinner. It was a fantastic opportunity to meet everyone and establish rapport before the conference.",[51,36760,36762],{"id":36761},"pragvue","PragVue",[939,36764,36765],{},[35,36766,36767],{},"PragVue is a new conference in Prague, Czech Republic, focused on Vue.js and its ecosystem. It's a single-day event featuring 8 speakers from the European Vue.js community.",[35,36769,36770,36771],{},"It's the main event day, the conference day. I was slightly nervous but fully prepared. ",[1217,36772,36773],{},"I had meticulously prepared my talk and knew my content well. As an anecdote, many speakers finished their slides the day before or even on the morning of the conference. But I won't name any names. 🤫",[35,36775,36776],{},"The conference took place at the Eviden company with the following agenda:",[1576,36778,36779,36782,36785,36788,36791,36794,36797,36800,36803],{},[71,36780,36781],{},"Vue App in 15 mins by Alois Seckár",[71,36783,36784],{},"What's New in Nuxt 4 by Daniel Roe",[71,36786,36787],{},"Fullstack Application with Nuxt by Estéban Soubiran",[71,36789,36790],{},"Vue Practical Tips and Tricks by Alexander Lichter",[71,36792,36793],{},"Plants vs Thieves: Automated Tests in the World of Web Security by Ramona Schwering",[71,36795,36796],{},"Playing with Vue in 3D by Thorsten Seyschab",[71,36798,36799],{},"Implementing Nuxt frontend in real projects by Alois Seckár",[71,36801,36802],{},"Ship Vuejs across 5+ platforms by Konstantin Bifert",[71,36804,36805],{},"Vue Realtime with Firebase by Juan Andrés Núñez",[35,36807,36808],{},"All the presentations were captivating, with the audience engrossed to catch every detail.",[35,36810,36811,36812,36817,36818,36823,36824],{},"My session revolved around Nuxt's full-stack capabilities, where I showcased ",[39,36813,36816],{"href":36814,"rel":36815},"https:\u002F\u002Fnitro.unjs.io\u002F",[77],"Nitro"," and the ",[39,36819,36822],{"href":36820,"rel":36821},"https:\u002F\u002Fhub.nuxt.com\u002F",[77],"NuxtHub"," project. These tools empower developers to create and deploy full-stack applications using Nuxt on the Cloudflare platform. Everything unfolded seamlessly, and I felt immense joy presenting this project. ",[1217,36825,36826],{},"This experience was truly rewarding, and I hope to replicate it in the future.",[35,36828,36829],{},[1690,36830],{"alt":36831,"src":36832},"Me presenting Nuxt's full-stack capabilities (Photo from Daniel Roe).","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fme-presenting-nuxt.jpeg",[35,36834,36835],{},[1690,36836],{"alt":36837,"src":36838},"All the speakers at PragVue.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fpragvue-speakers.jpeg",[35,36840,36841],{},"After the conference, we celebrated the conclusion of PragVue's first edition at a bar. It was a delightful opportunity to converse with the speakers and attendees.",[35,36843,36844],{},[1690,36845],{"alt":36846,"src":36847},"Going to the bar with Daniel, Alexander, and Thorsten.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fpragvue-bar-walk.jpg",[51,36849,36851],{"id":36850},"bouldering-before-departure","Bouldering before departure",[35,36853,36854,36855,1638,36858,36862,36863],{},"On Wednesday, the day after the conference, most participants had already departed. Along with ",[39,36856,36562],{"href":36560,"rel":36857},[77],[39,36859,17423],{"href":36860,"rel":36861},"https:\u002F\u002Fx.com\u002Fdanielcroe",[77],", we went to a bouldering gym. It had been years since my last climb, and I relished reconnecting with the activity. ",[1217,36864,36865],{},"It was an excellent way to strengthen connections, have fun, and conclude the trip on a high note. Thank you, Daniel, for the suggestion.",[35,36867,36868],{},[1690,36869],{"alt":36870,"src":36871},"Daniel climbing with ease.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fdaniel-bouldering.jpeg",[35,36873,36874],{},[1690,36875],{"alt":36876,"src":36877},"Konstantin trying bouldering for the first time.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fkonstantin-bouldering.jpeg",[35,36879,36880],{},[1690,36881],{"alt":36882,"src":36883},"Me, enjoying bouldering (Photo from Daniel Roe).","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fme-bouldering.jpeg",[51,36885,36887],{"id":36886},"returning-to-paris","Returning to Paris",[35,36889,36890,36891],{},"This adventure in Prague has been a remarkable journey. I met wonderful people, finally in person, rather than just profile pictures on GitHub or X, savored local cuisine, and explored the city. I also delivered my fist English talk at a conference. ",[1217,36892,36893],{},"These four days were intense yet timeless. 💜",[35,36895,36896],{},"Knowing that all of this is credited to open-source and the tech industry is astounding. I'm grateful for all the opportunities, and I hope to continue meeting people, traveling, and sharing my insights.",[35,36898,36899],{},"See you at the next adventure!",[35,36901,36902],{},[1690,36903],{"alt":36904,"src":36905},"The view from the Letna Park (Photo from Thorsten Seyschab).","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fletna-park.jpg",{"title":13,"searchDepth":14,"depth":14,"links":36907},[36908,36909,36910,36911,36912],{"id":36576,"depth":14,"text":36577},{"id":36595,"depth":14,"text":36596},{"id":36761,"depth":14,"text":36762},{"id":36850,"depth":14,"text":36851},{"id":36886,"depth":14,"text":36887},"2024-09-18","I was in Prague for the PragVue 2024 conference. As a speaker, I had the opportunity to present Nuxt's full-stack capabilities.",{},"\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js","---\ncontentId: 5665d6c9-f03c-4cbb-a69b-8cabd781e7a1\ntitle: \"PragVue 2024: Travel, Food, People, and a lot of Vue.js\"\ndescription: I was in Prague for the PragVue 2024 conference. As a speaker, I had the opportunity to present Nuxt's full-stack capabilities.\ndate: 2024-09-18\n---\n\nBefore I delve into my journey to Prague, let me elaborate how I got there. In March 2024, [Konstantin](https:\u002F\u002Fx.com\u002Fkissu_io) reached out to me on X, inviting me to speak at a new [Vue](https:\u002F\u002Fvuejs.org) conference in Prague.\n\nAfter gathering more details, I eagerly accepted the invitation. _Even though I accepted promptly, I was slightly nervous as it would be my first time presenting in English at a conference. But everything requires a first attempt, so I decided to embrace the opportunity._\n\n## En route to Prague\n\nOn Saturday, September 14, I flew from Paris to Prague. The conference was scheduled for the 17th, but I opted to arrive a few days early to explore the city.\n\n![The view from my window seat during the flight.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fcdg-to-pra-flight.jpeg)\n\nI reached my hotel, centrally located in Prague, at 22:30. With heavy rain outside and feeling exhausted, I opted to rest. _Luckily, I have two days to explore before the conference._\n\n## Exploring the city\n\nThe following day, Sunday, the weather remained as dismal as the previous day, with strong winds and rain. Nonetheless, I was determined to explore, so I had breakfast before heading out.\n\nWithout a concrete plan, I decided to visit a few notable spots. I began at the [Old Town Square](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FOld_Town_Square), a stunning yet tourist-heavy location. My exploration led me to the [Astronomical Clock](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrague_astronomical_clock), followed by crossing the [Charles Bridge](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCharles_Bridge) over the Vltava River.\n\n![Old Town Square.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fold-town-square.jpeg)\n\n![Charles Bridge and the Prague Castle in the background.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fcharles-bridge.jpeg)\n\nUpon crossing the bridge, I noticed a tower atop a hill. Curious, I headed there and discovered the mini Eiffel Tower, the [Petrin Tower](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPet%C5%99%C3%ADn_Lookout_Tower). It's possible to ascend, so I did. The panoramic view from the top was breathtaking.\n\n![The Petrin Tower.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fpetrin-tower.jpeg)\n\n![The view from the Petrin Tower.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fview-from-petrin-tower.jpeg)\n\n_Despite the storm causing the tower to sway, it was a thrilling experience for the amazing view._\n\nWith the rain intensifying, I sought out a nearby restaurant. I found one and enjoyed a traditional goulash paired with a local beer. The meal was delightful and warmed me up considerably.\n\n![The traditional Czech Goulash.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fgoulash.jpeg)\n\nEven after dining, the rain persisted, leaving me quite drenched, so I returned to the hotel. I rehearsed my talk for several hours, hoping the weather would improve later.\n\nBy late afternoon, the rain ceased, allowing me to resume exploring. I wandered to [Letná Park](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FLetn%C3%A1_Park) and continued to [Stromovka Park](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FStromovka). It was a pleasant and tranquil stroll, offering a more natural perspective of the city, away from the typical tourist spots.\n\n![Some buildings in Prague,](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fprague-buildings.jpeg)\n\nPrague is adorned with vibrant buildings, making city walks quite enjoyable.\n\nFor dinner, I found an authentic local restaurant where I savored [Svíčková](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSv%C3%AD%C4%8Dkov%C3%A1) with another local beer. _Again! The beer in Prague is excellent and even more affordable than water._\n\n![A Svíčková, the traditional Czech dish.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fsvickova.jpeg)\n\nAfter this scrumptious meal, it was time to head back to the hotel for a restful night's sleep.\n\nBy Monday morning, the weather hadn't improved, still characterized by substantial rain and wind. The day's goal was to be at the conference hotel by 14:00 to meet fellow speakers and prepare before the speaker dinner at 19:00.\n\nThis hotel was a one-hour walk from my accommodation. I opted to walk, seizing the opportunity to see more of the city. I didn't have specific plans aside from wandering aimlessly. Serendipitously, I stumbled upon the [Museum of Bricks](https:\u002F\u002Fmuseumofbricks.cz\u002Fen), the world's largest private LEGO collection. _As a die-hard LEGO enthusiast, I couldn't pass up the chance to visit._ This museum, one of five in the Czech Republic, spans an area of 420m² and boasts 3,000 unique models made from over a million bricks. _It was nostalgic to see some LEGO from my childhood._\n\n![The museum of Bricks.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fmuseum-of-bricks.jpeg)\n\n![Some Star Wars LEGO. A very little part of the collection.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fstar-wars-lego.jpeg)\n\nThis visit was wonderful, marking the start of my journey to the conference hotel. Time to stroll again and avoid any delays.\n\n![Another view from Prague during the walk](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fprague-view.jpeg)\n\nAfter the long walk, I arrived at the hotel. I met the speakers and attended the dinner. It was a fantastic opportunity to meet everyone and establish rapport before the conference.\n\n## PragVue\n\n> PragVue is a new conference in Prague, Czech Republic, focused on Vue.js and its ecosystem. It's a single-day event featuring 8 speakers from the European Vue.js community.\n\nIt's the main event day, the conference day. I was slightly nervous but fully prepared. _I had meticulously prepared my talk and knew my content well. As an anecdote, many speakers finished their slides the day before or even on the morning of the conference. But I won't name any names. 🤫_\n\nThe conference took place at the Eviden company with the following agenda:\n\n- Vue App in 15 mins by Alois Seckár\n- What's New in Nuxt 4 by Daniel Roe\n- Fullstack Application with Nuxt by Estéban Soubiran\n- Vue Practical Tips and Tricks by Alexander Lichter\n- Plants vs Thieves: Automated Tests in the World of Web Security by Ramona Schwering\n- Playing with Vue in 3D by Thorsten Seyschab\n- Implementing Nuxt frontend in real projects by Alois Seckár\n- Ship Vuejs across 5+ platforms by Konstantin Bifert\n- Vue Realtime with Firebase by Juan Andrés Núñez\n\nAll the presentations were captivating, with the audience engrossed to catch every detail.\n\nMy session revolved around Nuxt's full-stack capabilities, where I showcased [Nitro](https:\u002F\u002Fnitro.unjs.io\u002F) and the [NuxtHub](https:\u002F\u002Fhub.nuxt.com\u002F) project. These tools empower developers to create and deploy full-stack applications using Nuxt on the Cloudflare platform. Everything unfolded seamlessly, and I felt immense joy presenting this project. _This experience was truly rewarding, and I hope to replicate it in the future._\n\n![Me presenting Nuxt's full-stack capabilities (Photo from Daniel Roe).](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fme-presenting-nuxt.jpeg)\n\n![All the speakers at PragVue.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fpragvue-speakers.jpeg)\n\nAfter the conference, we celebrated the conclusion of PragVue's first edition at a bar. It was a delightful opportunity to converse with the speakers and attendees.\n\n![Going to the bar with Daniel, Alexander, and Thorsten.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fpragvue-bar-walk.jpg)\n\n## Bouldering before departure\n\nOn Wednesday, the day after the conference, most participants had already departed. Along with [Konstantin](https:\u002F\u002Fx.com\u002Fkissu_io) and [Daniel](https:\u002F\u002Fx.com\u002Fdanielcroe), we went to a bouldering gym. It had been years since my last climb, and I relished reconnecting with the activity. _It was an excellent way to strengthen connections, have fun, and conclude the trip on a high note. Thank you, Daniel, for the suggestion._\n\n![Daniel climbing with ease.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fdaniel-bouldering.jpeg)\n\n![Konstantin trying bouldering for the first time.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fkonstantin-bouldering.jpeg)\n\n![Me, enjoying bouldering (Photo from Daniel Roe).](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fme-bouldering.jpeg)\n\n## Returning to Paris\n\nThis adventure in Prague has been a remarkable journey. I met wonderful people, finally in person, rather than just profile pictures on GitHub or X, savored local cuisine, and explored the city. I also delivered my fist English talk at a conference. _These four days were intense yet timeless. 💜_\n\nKnowing that all of this is credited to open-source and the tech industry is astounding. I'm grateful for all the opportunities, and I hope to continue meeting people, traveling, and sharing my insights.\n\nSee you at the next adventure!\n\n![The view from the Letna Park (Photo from Thorsten Seyschab).](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fletna-park.jpg)\n",{"title":36552,"description":36914},{"loc":36916,"images":36920},[36921,36922,36923,36924,36925,36926,36927,36928,36929,36930,36931,36932,36933,36934,36935,36936,36937,36938],{"loc":36586},{"loc":36626},{"loc":36632},{"loc":36647},{"loc":36653},{"loc":36667},{"loc":36691},{"loc":36712},{"loc":36740},{"loc":36746},{"loc":36755},{"loc":36832},{"loc":36838},{"loc":36847},{"loc":36871},{"loc":36877},{"loc":36883},{"loc":36905},"posts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js","ExFacZw2N8ik6QOC9Eg65FL5jPh84HylZkbYBIZSEQ4",{"id":36942,"title":36943,"body":36944,"contentId":38085,"date":38086,"description":38087,"extension":18,"meta":38088,"navigation":20,"path":38089,"rawbody":38090,"robots":23,"schemaOrg":23,"seo":38091,"sitemap":38092,"stem":38097,"__hash__":38098},"posts\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests.md","Testing is More Larger Than Just Writing Some Tests",{"type":7,"value":36945,"toc":38082},[36946,36953,36956,36959,37018,37025,37028,37032,37035,37041,37063,37540,37547,37806,37812,37818,37827,37830,37842,37848,37851,37865,37871,37877,37883,37889,37892,37899,37910,37913,37916,37919,38079],[939,36947,36948],{},[35,36949,36950,36952],{},[200,36951,945],{},"\nI am not a testing expert. I am merely sharing my thoughts on testing as a developer, in an article I would have appreciated when I began coding.",[35,36954,36955],{},"I started coding six years ago. Since then, I've heard a lot about testing, but I have seen few real and practical examples.",[35,36957,36958],{},"In many articles I read, tests look like this:",[192,36960,36962],{"className":25029,"code":36961,"language":25031,"meta":13,"style":13},"it('should return 2 when 1 + 1', () => {\n  expect(1 + 1).toBe(2)\n})\n",[156,36963,36964,36986,37012],{"__ignoreMap":13},[200,36965,36966,36969,36971,36973,36976,36978,36980,36982,36984],{"class":202,"line":203},[200,36967,36968],{"class":223},"it",[200,36970,227],{"class":210},[200,36972,249],{"class":214},[200,36974,36975],{"class":245},"should return 2 when 1 + 1",[200,36977,249],{"class":214},[200,36979,550],{"class":214},[200,36981,3714],{"class":214},[200,36983,393],{"class":379},[200,36985,280],{"class":214},[200,36987,36988,36991,36993,36995,36997,36999,37001,37003,37006,37008,37010],{"class":202,"line":14},[200,36989,36990],{"class":223},"  expect",[200,36992,227],{"class":235},[200,36994,3188],{"class":2045},[200,36996,13205],{"class":214},[200,36998,3938],{"class":2045},[200,37000,2089],{"class":235},[200,37002,215],{"class":214},[200,37004,37005],{"class":223},"toBe",[200,37007,227],{"class":235},[200,37009,4782],{"class":2045},[200,37011,416],{"class":235},[200,37013,37014,37016],{"class":202,"line":255},[200,37015,413],{"class":214},[200,37017,416],{"class":210},[35,37019,37020,37021,37024],{},"This is ",[6850,37022,37023],{},"far from real-world scenarios"," and does not help in understanding the true value of testing.",[35,37026,37027],{},"I have to admit, when I don't comprehend something, I avoid using it. Nevertheless, since I first encountered the concept of testing, it has lingered in my mind, prompting me to gather knowledge about it.",[51,37029,37031],{"id":37030},"a-long-journey","A long journey",[35,37033,37034],{},"I first heard about testing when I learned Vue 2 in 2019. In the tooling section, under the \"Single File Component\" page, I found a section dedicated to \"Testing.\"",[35,37036,37037,37038],{},"I think I read the beginning of the page but never went further. There were too many new concepts to grasp, too many tools to learn, and too many choices to make. ",[1217,37039,37040],{},"At that time, I understood that front-end testing is by far the most challenging part of testing.",[35,37042,37043,37044,37049,37050,37055,37056,37059,37060],{},"In 2020, with some friends, we developed a tutoring platform. The API server was built with ",[39,37045,37048],{"href":37046,"rel":37047},"https:\u002F\u002Fcrow.docs.feathersjs.com\u002F",[77],"Feathers.js 4",". At that time, I was still grappling with testing. I remember looking for ",[39,37051,37054],{"href":37052,"rel":37053},"https:\u002F\u002Fcrow.docs.feathersjs.com\u002Fguides\u002Fbasics\u002Ftesting.html#writing-tests",[77],"the testing page in the Feathers.js documentation"," and even reading it. Despite not fully understanding it, I wrote many tests. It was a real struggle because the distinction between unit tests and integration tests was unclear to me, and running them in a CI was very difficult. ",[1217,37057,37058],{},"I was using MongoDB and Azure CI. Looking back, I think I wanted to endure pain."," I also remember trying to achieve 100% coverage. ",[1217,37061,37062],{},"I was so naive.",[192,37064,37066],{"className":1370,"code":37065,"language":1373,"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,37067,37068,37091,37107,37111,37129,37143,37166,37170,37188,37213,37229,37239,37254,37258,37283,37308,37314,37318,37341,37355,37359,37376,37388,37405,37409,37426,37450,37486,37510,37534],{"__ignoreMap":13},[200,37069,37070,37072,37074,37076,37079,37081,37083,37085,37087,37089],{"class":202,"line":203},[200,37071,36968],{"class":223},[200,37073,227],{"class":210},[200,37075,249],{"class":214},[200,37077,37078],{"class":245},"should patch",[200,37080,249],{"class":214},[200,37082,550],{"class":214},[200,37084,380],{"class":379},[200,37086,3714],{"class":214},[200,37088,393],{"class":379},[200,37090,280],{"class":214},[200,37092,37093,37095,37097,37100,37102,37105],{"class":202,"line":14},[200,37094,36990],{"class":210},[200,37096,215],{"class":214},[200,37098,37099],{"class":223},"assertions",[200,37101,227],{"class":235},[200,37103,37104],{"class":2045},"5",[200,37106,416],{"class":235},[200,37108,37109],{"class":202,"line":255},[200,37110,529],{"emptyLinePlaceholder":20},[200,37112,37113,37115,37118,37120,37123,37125,37127],{"class":202,"line":272},[200,37114,3723],{"class":379},[200,37116,37117],{"class":210}," patchedResult",[200,37119,239],{"class":214},[200,37121,37122],{"class":1978}," Department",[200,37124,510],{"class":214},[200,37126,452],{"class":206},[200,37128,31435],{"class":210},[200,37130,37131,37133,37136,37138,37141],{"class":202,"line":283},[200,37132,3755],{"class":214},[200,37134,37135],{"class":223},"service",[200,37137,227],{"class":235},[200,37139,37140],{"class":210},"serviceName",[200,37142,416],{"class":235},[200,37144,37145,37147,37150,37152,37154,37156,37159,37161,37164],{"class":202,"line":300},[200,37146,3755],{"class":214},[200,37148,37149],{"class":223},"patch",[200,37151,227],{"class":235},[200,37153,3531],{"class":210},[200,37155,215],{"class":214},[200,37157,37158],{"class":210},"_id",[200,37160,550],{"class":214},[200,37162,37163],{"class":210}," anotherDepartment",[200,37165,416],{"class":235},[200,37167,37168],{"class":202,"line":310},[200,37169,529],{"emptyLinePlaceholder":20},[200,37171,37172,37174,37176,37179,37181,37183,37186],{"class":202,"line":336},[200,37173,36990],{"class":223},[200,37175,227],{"class":235},[200,37177,37178],{"class":210},"patchedResult",[200,37180,2089],{"class":235},[200,37182,215],{"class":214},[200,37184,37185],{"class":223},"toBeDefined",[200,37187,466],{"class":235},[200,37189,37190,37192,37194,37196,37198,37200,37203,37205,37207,37209,37211],{"class":202,"line":342},[200,37191,36990],{"class":223},[200,37193,227],{"class":235},[200,37195,37178],{"class":210},[200,37197,2089],{"class":235},[200,37199,215],{"class":214},[200,37201,37202],{"class":223},"toHaveProperty",[200,37204,227],{"class":235},[200,37206,249],{"class":214},[200,37208,37158],{"class":245},[200,37210,249],{"class":214},[200,37212,416],{"class":235},[200,37214,37215,37217,37219,37221,37223,37225,37227],{"class":202,"line":365},[200,37216,36990],{"class":223},[200,37218,227],{"class":235},[200,37220,37178],{"class":210},[200,37222,2089],{"class":235},[200,37224,215],{"class":214},[200,37226,37202],{"class":223},[200,37228,3115],{"class":235},[200,37230,37231,37233,37235,37237],{"class":202,"line":371},[200,37232,1405],{"class":214},[200,37234,20089],{"class":245},[200,37236,249],{"class":214},[200,37238,252],{"class":214},[200,37240,37241,37244,37246,37248,37250,37252],{"class":202,"line":398},[200,37242,37243],{"class":210},"    anotherDepartment",[200,37245,215],{"class":214},[200,37247,20089],{"class":210},[200,37249,215],{"class":214},[200,37251,3832],{"class":223},[200,37253,466],{"class":235},[200,37255,37256],{"class":202,"line":405},[200,37257,30218],{"class":235},[200,37259,37260,37262,37264,37266,37268,37270,37272,37274,37276,37279,37281],{"class":202,"line":410},[200,37261,36990],{"class":223},[200,37263,227],{"class":235},[200,37265,37178],{"class":210},[200,37267,2089],{"class":235},[200,37269,215],{"class":214},[200,37271,37202],{"class":223},[200,37273,227],{"class":235},[200,37275,249],{"class":214},[200,37277,37278],{"class":245},"createdAt",[200,37280,249],{"class":214},[200,37282,416],{"class":235},[200,37284,37285,37287,37289,37291,37293,37295,37297,37299,37301,37304,37306],{"class":202,"line":861},[200,37286,36990],{"class":223},[200,37288,227],{"class":235},[200,37290,37178],{"class":210},[200,37292,2089],{"class":235},[200,37294,215],{"class":214},[200,37296,37202],{"class":223},[200,37298,227],{"class":235},[200,37300,249],{"class":214},[200,37302,37303],{"class":245},"updatedAt",[200,37305,249],{"class":214},[200,37307,416],{"class":235},[200,37309,37310,37312],{"class":202,"line":890},[200,37311,413],{"class":214},[200,37313,416],{"class":210},[200,37315,37316],{"class":202,"line":900},[200,37317,529],{"emptyLinePlaceholder":20},[200,37319,37320,37322,37324,37326,37329,37331,37333,37335,37337,37339],{"class":202,"line":905},[200,37321,36968],{"class":223},[200,37323,227],{"class":210},[200,37325,249],{"class":214},[200,37327,37328],{"class":245},"should delete",[200,37330,249],{"class":214},[200,37332,550],{"class":214},[200,37334,380],{"class":379},[200,37336,3714],{"class":214},[200,37338,393],{"class":379},[200,37340,280],{"class":214},[200,37342,37343,37345,37347,37349,37351,37353],{"class":202,"line":931},[200,37344,36990],{"class":210},[200,37346,215],{"class":214},[200,37348,37099],{"class":223},[200,37350,227],{"class":235},[200,37352,37104],{"class":2045},[200,37354,416],{"class":235},[200,37356,37357],{"class":202,"line":3640},[200,37358,529],{"emptyLinePlaceholder":20},[200,37360,37361,37363,37366,37368,37370,37372,37374],{"class":202,"line":3645},[200,37362,3723],{"class":379},[200,37364,37365],{"class":210}," deleteResult",[200,37367,239],{"class":214},[200,37369,37122],{"class":1978},[200,37371,510],{"class":214},[200,37373,452],{"class":206},[200,37375,31435],{"class":210},[200,37377,37378,37380,37382,37384,37386],{"class":202,"line":4020},[200,37379,3755],{"class":214},[200,37381,37135],{"class":223},[200,37383,227],{"class":235},[200,37385,37140],{"class":210},[200,37387,416],{"class":235},[200,37389,37390,37392,37395,37397,37399,37401,37403],{"class":202,"line":4475},[200,37391,3755],{"class":214},[200,37393,37394],{"class":223},"remove",[200,37396,227],{"class":235},[200,37398,3531],{"class":210},[200,37400,215],{"class":214},[200,37402,37158],{"class":210},[200,37404,416],{"class":235},[200,37406,37407],{"class":202,"line":4481},[200,37408,529],{"emptyLinePlaceholder":20},[200,37410,37411,37413,37415,37418,37420,37422,37424],{"class":202,"line":4487},[200,37412,36990],{"class":223},[200,37414,227],{"class":235},[200,37416,37417],{"class":210},"deleteResult",[200,37419,2089],{"class":235},[200,37421,215],{"class":214},[200,37423,37185],{"class":223},[200,37425,466],{"class":235},[200,37427,37428,37430,37432,37434,37436,37438,37440,37442,37444,37446,37448],{"class":202,"line":4493},[200,37429,36990],{"class":223},[200,37431,227],{"class":235},[200,37433,37417],{"class":210},[200,37435,2089],{"class":235},[200,37437,215],{"class":214},[200,37439,37202],{"class":223},[200,37441,227],{"class":235},[200,37443,249],{"class":214},[200,37445,37158],{"class":245},[200,37447,249],{"class":214},[200,37449,416],{"class":235},[200,37451,37452,37454,37456,37458,37460,37462,37464,37466,37468,37470,37472,37474,37476,37478,37480,37482,37484],{"class":202,"line":5454},[200,37453,36990],{"class":223},[200,37455,227],{"class":235},[200,37457,37417],{"class":210},[200,37459,2089],{"class":235},[200,37461,215],{"class":214},[200,37463,37202],{"class":223},[200,37465,227],{"class":235},[200,37467,249],{"class":214},[200,37469,20089],{"class":245},[200,37471,249],{"class":214},[200,37473,550],{"class":214},[200,37475,3546],{"class":210},[200,37477,215],{"class":214},[200,37479,20089],{"class":210},[200,37481,215],{"class":214},[200,37483,3832],{"class":223},[200,37485,10947],{"class":235},[200,37487,37488,37490,37492,37494,37496,37498,37500,37502,37504,37506,37508],{"class":202,"line":5459},[200,37489,36990],{"class":223},[200,37491,227],{"class":235},[200,37493,37417],{"class":210},[200,37495,2089],{"class":235},[200,37497,215],{"class":214},[200,37499,37202],{"class":223},[200,37501,227],{"class":235},[200,37503,249],{"class":214},[200,37505,37278],{"class":245},[200,37507,249],{"class":214},[200,37509,416],{"class":235},[200,37511,37512,37514,37516,37518,37520,37522,37524,37526,37528,37530,37532],{"class":202,"line":13227},[200,37513,36990],{"class":223},[200,37515,227],{"class":235},[200,37517,37417],{"class":210},[200,37519,2089],{"class":235},[200,37521,215],{"class":214},[200,37523,37202],{"class":223},[200,37525,227],{"class":235},[200,37527,249],{"class":214},[200,37529,37303],{"class":245},[200,37531,249],{"class":214},[200,37533,416],{"class":235},[200,37535,37536,37538],{"class":202,"line":13253},[200,37537,413],{"class":214},[200,37539,416],{"class":210},[35,37541,37542,37543,37546],{},"But the more I added features to the platform, the more I realized the value of testing. It can ",[6850,37544,37545],{},"ensure that a new feature does not break an existing one"," without the hassle of manually testing everything. This was particularly evident because Feathers.js has many intricate parts and services that can easily break when one part is modified.",[192,37548,37550],{"className":1370,"code":37549,"language":1373,"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,37551,37552,37560,37569,37580,37591,37602,37619,37636,37651,37662,37666,37670,37679,37689,37726,37758,37768,37778,37788,37798,37802],{"__ignoreMap":13},[200,37553,37554,37556,37558],{"class":202,"line":203},[200,37555,1380],{"class":206},[200,37557,1383],{"class":206},[200,37559,280],{"class":214},[200,37561,37562,37565,37567],{"class":202,"line":14},[200,37563,37564],{"class":235},"  before",[200,37566,239],{"class":214},[200,37568,280],{"class":214},[200,37570,37571,37574,37576,37578],{"class":202,"line":255},[200,37572,37573],{"class":235},"    all",[200,37575,239],{"class":214},[200,37577,27960],{"class":210},[200,37579,252],{"class":214},[200,37581,37582,37585,37587,37589],{"class":202,"line":272},[200,37583,37584],{"class":235},"    find",[200,37586,239],{"class":214},[200,37588,27960],{"class":210},[200,37590,252],{"class":214},[200,37592,37593,37596,37598,37600],{"class":202,"line":283},[200,37594,37595],{"class":235},"    get",[200,37597,239],{"class":214},[200,37599,27960],{"class":210},[200,37601,252],{"class":214},[200,37603,37604,37607,37609,37611,37614,37617],{"class":202,"line":300},[200,37605,37606],{"class":235},"    create",[200,37608,239],{"class":214},[200,37610,350],{"class":210},[200,37612,37613],{"class":223},"checkData",[200,37615,37616],{"class":210},"(checkDataOptions)]",[200,37618,252],{"class":214},[200,37620,37621,37624,37626,37628,37631,37634],{"class":202,"line":310},[200,37622,37623],{"class":235},"    update",[200,37625,239],{"class":214},[200,37627,350],{"class":210},[200,37629,37630],{"class":223},"disallow",[200,37632,37633],{"class":210},"()]",[200,37635,252],{"class":214},[200,37637,37638,37641,37643,37645,37647,37649],{"class":202,"line":336},[200,37639,37640],{"class":235},"    patch",[200,37642,239],{"class":214},[200,37644,350],{"class":210},[200,37646,37613],{"class":223},[200,37648,37616],{"class":210},[200,37650,252],{"class":214},[200,37652,37653,37656,37658,37660],{"class":202,"line":342},[200,37654,37655],{"class":235},"    remove",[200,37657,239],{"class":214},[200,37659,27960],{"class":210},[200,37661,252],{"class":214},[200,37663,37664],{"class":202,"line":365},[200,37665,368],{"class":214},[200,37667,37668],{"class":202,"line":371},[200,37669,529],{"emptyLinePlaceholder":20},[200,37671,37672,37675,37677],{"class":202,"line":398},[200,37673,37674],{"class":235},"  after",[200,37676,239],{"class":214},[200,37678,280],{"class":214},[200,37680,37681,37683,37685,37687],{"class":202,"line":405},[200,37682,37573],{"class":235},[200,37684,239],{"class":214},[200,37686,27960],{"class":210},[200,37688,252],{"class":214},[200,37690,37691,37693,37695,37697,37700,37702,37705,37707,37709,37712,37714,37716,37718,37721,37724],{"class":202,"line":410},[200,37692,37584],{"class":235},[200,37694,239],{"class":214},[200,37696,350],{"class":210},[200,37698,37699],{"class":223},"iff",[200,37701,227],{"class":210},[200,37703,37704],{"class":223},"isProvider",[200,37706,227],{"class":210},[200,37708,249],{"class":214},[200,37710,37711],{"class":245},"external",[200,37713,249],{"class":214},[200,37715,2089],{"class":210},[200,37717,550],{"class":214},[200,37719,37720],{"class":223}," pickResult",[200,37722,37723],{"class":210},"())]",[200,37725,252],{"class":214},[200,37727,37728,37730,37732,37734,37736,37738,37740,37742,37744,37746,37748,37750,37752,37754,37756],{"class":202,"line":861},[200,37729,37595],{"class":235},[200,37731,239],{"class":214},[200,37733,350],{"class":210},[200,37735,37699],{"class":223},[200,37737,227],{"class":210},[200,37739,37704],{"class":223},[200,37741,227],{"class":210},[200,37743,249],{"class":214},[200,37745,37711],{"class":245},[200,37747,249],{"class":214},[200,37749,2089],{"class":210},[200,37751,550],{"class":214},[200,37753,37720],{"class":223},[200,37755,37723],{"class":210},[200,37757,252],{"class":214},[200,37759,37760,37762,37764,37766],{"class":202,"line":890},[200,37761,37606],{"class":235},[200,37763,239],{"class":214},[200,37765,27960],{"class":210},[200,37767,252],{"class":214},[200,37769,37770,37772,37774,37776],{"class":202,"line":900},[200,37771,37623],{"class":235},[200,37773,239],{"class":214},[200,37775,27960],{"class":210},[200,37777,252],{"class":214},[200,37779,37780,37782,37784,37786],{"class":202,"line":905},[200,37781,37640],{"class":235},[200,37783,239],{"class":214},[200,37785,27960],{"class":210},[200,37787,252],{"class":214},[200,37789,37790,37792,37794,37796],{"class":202,"line":931},[200,37791,37655],{"class":235},[200,37793,239],{"class":214},[200,37795,27960],{"class":210},[200,37797,252],{"class":214},[200,37799,37800],{"class":202,"line":3640},[200,37801,368],{"class":214},[200,37803,37804],{"class":202,"line":3645},[200,37805,1130],{"class":214},[35,37807,37808,37809],{},"The complexity of the app grew exponentially, feature after feature. This was more related to the architecture of Feathers.js, but combined with the challenges of writing efficient tests, it was a nightmare to maintain. ",[1217,37810,37811],{},"Each change broke so many tests that I was unsure if the app was working or not.",[35,37813,37814,37815],{},"From that point on, I decided the ability to test the app was a requirement for choosing a new stack. ",[1217,37816,37817],{},"And this will be a long journey because in 2024, I'm still learning a lot but writing more tests than ever.",[35,37819,37820,37821,37826],{},"At the beginning of 2021, Strapi was gaining popularity, and I looked at it as a potential replacement for Feathers.js, still to build the tutoring platform. I was pleased to see that Strapi has a ",[39,37822,37825],{"href":37823,"rel":37824},"https:\u002F\u002Fdocs.strapi.io\u002Fdev-docs\u002Ftesting",[77],"dedicated page for testing"," in the documentation.",[35,37828,37829],{},"So, I jumped in and created a new project with Strapi to get started and see if testing was effective.",[35,37831,37832,37833,37838,37839],{},"Sadly, I immediately ",[39,37834,37837],{"href":37835,"rel":37836},"https:\u002F\u002Fgithub.com\u002Fstrapi\u002Fstrapi\u002Fissues\u002F9376",[77],"encountered a problem that never got resolved",". Unable to run tests, I gave up and the tutoring platform was never migrated to Strapi, nor deployed to production. ",[1217,37840,37841],{},"I learned a lot from this side project. Often, the journey is more important than the destination.",[35,37843,37844],{},[1690,37845],{"alt":37846,"src":37847},"Screenshot of this issue 9376","\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests\u002Fstrapi-testing-issue.png",[35,37849,37850],{},"So let's recap:",[1576,37852,37853,37856,37859,37862],{},[71,37854,37855],{},"I only know JavaScript.",[71,37857,37858],{},"Feathers.js has a complex architecture and poor documentation on testing.",[71,37860,37861],{},"Strapi has good documentation on testing, but I was unable to run tests.",[71,37863,37864],{},"I need to find a new side project to learn testing.",[35,37866,37867,37868],{},"Some months later, I heard about AdonisJS. I don't remember how, but at that time, version 5 was in beta. It was a breath of fresh air because the documentation looked very good, and the framework was rich in features. Full of features, yes, but the testing part was not there yet. ",[1217,37869,37870],{},"The core team released Japa a year later. It's a pleasant testing framework for Node.js.",[35,37872,37873,37874],{},"Regardless, I learned so much about backend development and architecture with AdonisJS that testing was clearly not a priority. This was the first time I could split features and avoid internal dependencies. Much cleaner and easier to maintain than Feathers.js or NestJS. ",[1217,37875,37876],{},"Yes, I also tried NestJS, but this framework is a real joke to me.",[35,37878,37879,37880],{},"By the end of August 2021, I embarked on a new side project called Insamee, a student ecosystem. That was the successor of the tutoring platform. The architecture is important to clarify. Initially, it was an API server with AdonisJS and four front-end apps using Nuxt. ",[1217,37881,37882],{},"Overkill and overly complicated, as I realize now.",[35,37884,37885],{},[1690,37886],{"alt":37887,"src":37888},"Screenshot of Insamee","\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests\u002Finsameeapp.png",[35,37890,37891],{},"By the end of 2021, I rewrote the project from scratch using only Adonis and a single server. No more tests.",[35,37893,37894,37895,37898],{},"I managed to pull myself away from school classes to focus on my project. ",[1217,37896,37897],{},"A real opportunity."," To validate my semester, I had to write a report and make a live presentation. During the presentation, I didn't talk about testing (there were none). However, the jury questioned me about it. I just said the runner was not ready yet, so I hadn't written any tests. They took a few minutes to explain that testing is crucial, and I clearly understood that a real and professional project must have tests, it's non-negotiable. In my mind, something clicked.",[35,37900,37901,37902,37905,37906,37909],{},"Subsequently, from April to August 2022, I did a four-month internship at a consulting company in Paris. I was responsible for building, from scratch, an administration panel for a high-performance computing cluster project using Angular 14. This was a first for me. I had never used Angular before. It took me months to grasp the concept of ",[156,37903,37904],{},"TestBed"," and the dependency injection (DI) system. ",[1217,37907,37908],{},"I was so lost, but later, I discovered that dependency injection is important."," During that period, I learned concepts but had no real practice.",[35,37911,37912],{},"In 2024, I did my six-month internship at a Swiss cloud company. I was part of a team developing components for other projects. This is where I truly began to appreciate testing. Firstly, the coverage was at 90%. This means if the coverage drops below 90%, you have no other choice than to write tests to submit an MR. The project was eight months old, so there were plenty of tests to reference. I also wrote many end-to-end tests using Playwright.",[35,37914,37915],{},"Simultaneously, I watched a live stream by Romain Lanz discussing \"writing testable code\" and explaining the DI in Adonis. With these explanations and the concrete examples I encountered during my internship, things started to come together and make sense.",[35,37917,37918],{},"This year, at Devoxx 2024, I tried to attend as many talks about TDD and DDD as possible. Hearing experts discuss real-world examples and how they architect their apps to make them testable and future-proof was truly enlightening.",[7905,37920,37921,37924,37941,37954,37960,37966],{"id":20826},[35,37922,37923],{},"Despite all this, I still struggle to find genuine and practical examples of testing. It's so frustrating to know the theory but not be able to apply it.",[35,37925,37926,37927,37932,37933],{},"But I believe that to learn, there is nothing more effective than taking action. So I took the framework I know best, Nuxt, and started experimenting with the ",[39,37928,37931],{"href":37929,"rel":37930},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ftest-utils",[77],"available testing tools",". I used the end-to-end integration with Playwright, but it was not a pleasant experience. Tests took so long to run. ",[1217,37934,37935,37936,215],{},"Strangely, the experience during my internship felt the opposite. I discovered that the Nuxt integration rebuilds the application before every test. I made ",[39,37937,37940],{"href":37938,"rel":37939},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ftest-utils\u002Fpull\u002F881",[77],"a PR to allow the runner to reuse an existing server",[35,37942,37943,37944,37949,37950,37953],{},"Simultaneously, I received feedback about ",[39,37945,37948],{"href":37946,"rel":37947},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fplausible-tracker",[77],"my fork of the Plausible tracker",". Some bugs were reported, and new features were requested. The problem was the project had no tests, and I clearly remembered how painful it was on the tutoring platform. I also noticed I hadn't touched the project for a while, and manual testing wouldn't be enough to ensure the quality of the project in the long term. ",[1217,37951,37952],{},"When you haven't touched a project for some time, you have to relearn everything. With reliable tests, you can be confident that the project works as expected, even after refactoring, bug fixes, and new features."," I decided to write tests before touching anything else. It took me three days, but now, I'm confident that the project works as expected. This is satisfying, and there is no more fear of touching the project.",[35,37955,37956],{},[1690,37957],{"alt":37958,"src":37959},"Passing tests of Plausible Tracker","\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests\u002Fplausible-tracker-passing-tests.png",[35,37961,37962,37963],{},"Finally, around the same time, I discovered Laracasts. I learned PHP and encountered testing in Laravel. It was eye-opening. The testing is well explained, writing tests, even with a database, is easy, and it runs so fast. Writing tests with Pest is truly enjoyable. ",[1217,37964,37965],{},"If you have never tried it, you should.",[7139,37967,37970,37974,37977,37980,38061,38067,38070],{"icon":37968,"text":37969,"to":33689},"i-ph-book-duotone","Try Laracats",[51,37971,37973],{"id":37972},"understanding-the-value-of-testing","Understanding the value of testing",[35,37975,37976],{},"It took me six years to grasp the value of testing and write meaningful tests. Is that long? I'm not sure. It demonstrates that learning is an iterative process, with failures and retries. With perseverance, you finally get there.",[35,37978,37979],{},"Throughout this journey, I asked myself many questions. Here are some answers I would have loved to find earlier.",[68,37981,37982,37985,37991,37997,38003,38009,38015,38021,38027,38043,38049,38055],{},[71,37983,37984],{},"Testing does not have to be difficult.",[71,37986,37987,37988,215],{},"However, testing is more challenging than ",[156,37989,37990],{},"expect(1 + 1).toBe(2)",[71,37992,37993,37994],{},"The difficulty of testing often relates to the application's architecture and the ability to separate features. ",[1217,37995,37996],{},"The more modular the app is, the easier it is to test.",[71,37998,37999,38000],{},"Before choosing a tool, consider the ability to test your app. ",[1217,38001,38002],{},"If you can't test it, it's not worth it.",[71,38004,38005,38006],{},"No tests are acceptable. ",[1217,38007,38008],{},"It's a trade-off. Use it wisely.",[71,38010,38011,38012],{},"Rule 5 does not invalidate rule 4 or rule 3. ",[1217,38013,38014],{},"If you end up writing tests, you will thank yourself.",[71,38016,38017,38018],{},"Aiming for 100% coverage is meaningless by itself. ",[1217,38019,38020],{},"It's a trap that can lead to poor practices.",[71,38022,38023,38024],{},"Both unit, integration, and end-to-end tests are valuable. ",[1217,38025,38026],{},"Choose the one that best fits your needs.",[71,38028,38029,38030,3076,38033,3080,38036,38039,38040],{},"Structure your test with the ",[156,38031,38032],{},"arrange",[156,38034,38035],{},"act",[156,38037,38038],{},"assert"," method. ",[1217,38041,38042],{},"It's a good method for organizing your test and starting to write them.",[71,38044,38045,38046,38048],{},"Write the ",[156,38047,38038],{}," part first.",[71,38050,38051,38052],{},"Think of a test as a description of a specification of the application. ",[1217,38053,38054],{},"My app should display\u002Fdo this when this happens.",[71,38056,38057,38058],{},"And more importantly, writing testable code before writing tests. ",[1217,38059,38060],{},"This approach will make your code more readable, maintainable, and testable.",[35,38062,38063,38064],{},"Finally, do not attempt to test everything from day one in this journey. Start small, learn, write simple unit tests, understand how to modularize your code, and progress from there. ",[1217,38065,38066],{},"It's a long journey, but it's worth it.",[35,38068,38069],{},"I'm still learning a lot, but I'm confident that I'm on the right path. See you in the next article!",[35,38071,38072],{},[1217,38073,38074,38075,215],{},"Laracasts is the best resource I've found for learning testing. PHP is great, and learning general concepts is even better. ",[39,38076,38078],{"href":33689,"rel":38077},[77],"Try it now",[1171,38080,38081],{},"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":38083},[38084],{"id":37030,"depth":14,"text":37031},"a4721c3e-e9c6-4025-ae82-9c44edd68d75","2024-08-30","Testing is a journey, a lengthy expedition to uncover how the architecture and dependencies of an application can directly impact its quality and testability.",{},"\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests","---\ncontentId: a4721c3e-e9c6-4025-ae82-9c44edd68d75\ntitle: Testing is More Larger Than Just Writing Some Tests\ndescription: Testing is a journey, a lengthy expedition to uncover how the architecture and dependencies of an application can directly impact its quality and testability.\ndate: 2024-08-30\n---\n\n> [!NOTE]\n> I am not a testing expert. I am merely sharing my thoughts on testing as a developer, in an article I would have appreciated when I began coding.\n\nI started coding six years ago. Since then, I've heard a lot about testing, but I have seen few real and practical examples.\n\nIn many articles I read, tests look like this:\n\n```js\nit('should return 2 when 1 + 1', () => {\n  expect(1 + 1).toBe(2)\n})\n```\n\nThis is **far from real-world scenarios** and does not help in understanding the true value of testing.\n\nI have to admit, when I don't comprehend something, I avoid using it. Nevertheless, since I first encountered the concept of testing, it has lingered in my mind, prompting me to gather knowledge about it.\n\n## A long journey\n\nI first heard about testing when I learned Vue 2 in 2019. In the tooling section, under the \"Single File Component\" page, I found a section dedicated to \"Testing.\"\n\nI think I read the beginning of the page but never went further. There were too many new concepts to grasp, too many tools to learn, and too many choices to make. _At that time, I understood that front-end testing is by far the most challenging part of testing._\n\nIn 2020, with some friends, we developed a tutoring platform. The API server was built with [Feathers.js 4](https:\u002F\u002Fcrow.docs.feathersjs.com\u002F). At that time, I was still grappling with testing. I remember looking for [the testing page in the Feathers.js documentation](https:\u002F\u002Fcrow.docs.feathersjs.com\u002Fguides\u002Fbasics\u002Ftesting.html#writing-tests) and even reading it. Despite not fully understanding it, I wrote many tests. It was a real struggle because the distinction between unit tests and integration tests was unclear to me, and running them in a CI was very difficult. _I was using MongoDB and Azure CI. Looking back, I think I wanted to endure pain._ I also remember trying to achieve 100% coverage. _I was so naive._\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\nBut the more I added features to the platform, the more I realized the value of testing. It can **ensure that a new feature does not break an existing one** without the hassle of manually testing everything. This was particularly evident because Feathers.js has many intricate parts and services that can easily break when one part is modified.\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\nThe complexity of the app grew exponentially, feature after feature. This was more related to the architecture of Feathers.js, but combined with the challenges of writing efficient tests, it was a nightmare to maintain. _Each change broke so many tests that I was unsure if the app was working or not._\n\nFrom that point on, I decided the ability to test the app was a requirement for choosing a new stack. _And this will be a long journey because in 2024, I'm still learning a lot but writing more tests than ever._\n\nAt the beginning of 2021, Strapi was gaining popularity, and I looked at it as a potential replacement for Feathers.js, still to build the tutoring platform. I was pleased to see that Strapi has a [dedicated page for testing](https:\u002F\u002Fdocs.strapi.io\u002Fdev-docs\u002Ftesting) in the documentation.\n\nSo, I jumped in and created a new project with Strapi to get started and see if testing was effective.\n\nSadly, I immediately [encountered a problem that never got resolved](https:\u002F\u002Fgithub.com\u002Fstrapi\u002Fstrapi\u002Fissues\u002F9376). Unable to run tests, I gave up and the tutoring platform was never migrated to Strapi, nor deployed to production. _I learned a lot from this side project. Often, the journey is more important than the destination._\n\n![Screenshot of this issue 9376](\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests\u002Fstrapi-testing-issue.png)\n\nSo let's recap:\n\n- I only know JavaScript.\n- Feathers.js has a complex architecture and poor documentation on testing.\n- Strapi has good documentation on testing, but I was unable to run tests.\n- I need to find a new side project to learn testing.\n\nSome months later, I heard about AdonisJS. I don't remember how, but at that time, version 5 was in beta. It was a breath of fresh air because the documentation looked very good, and the framework was rich in features. Full of features, yes, but the testing part was not there yet. _The core team released Japa a year later. It's a pleasant testing framework for Node.js._\n\nRegardless, I learned so much about backend development and architecture with AdonisJS that testing was clearly not a priority. This was the first time I could split features and avoid internal dependencies. Much cleaner and easier to maintain than Feathers.js or NestJS. _Yes, I also tried NestJS, but this framework is a real joke to me._\n\nBy the end of August 2021, I embarked on a new side project called Insamee, a student ecosystem. That was the successor of the tutoring platform. The architecture is important to clarify. Initially, it was an API server with AdonisJS and four front-end apps using Nuxt. _Overkill and overly complicated, as I realize now._\n\n![Screenshot of Insamee](\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests\u002Finsameeapp.png)\n\nBy the end of 2021, I rewrote the project from scratch using only Adonis and a single server. No more tests.\n\nI managed to pull myself away from school classes to focus on my project. _A real opportunity._ To validate my semester, I had to write a report and make a live presentation. During the presentation, I didn't talk about testing (there were none). However, the jury questioned me about it. I just said the runner was not ready yet, so I hadn't written any tests. They took a few minutes to explain that testing is crucial, and I clearly understood that a real and professional project must have tests, it's non-negotiable. In my mind, something clicked.\n\nSubsequently, from April to August 2022, I did a four-month internship at a consulting company in Paris. I was responsible for building, from scratch, an administration panel for a high-performance computing cluster project using Angular 14. This was a first for me. I had never used Angular before. It took me months to grasp the concept of `TestBed` and the dependency injection (DI) system. _I was so lost, but later, I discovered that dependency injection is important._ During that period, I learned concepts but had no real practice.\n\nIn 2024, I did my six-month internship at a Swiss cloud company. I was part of a team developing components for other projects. This is where I truly began to appreciate testing. Firstly, the coverage was at 90%. This means if the coverage drops below 90%, you have no other choice than to write tests to submit an MR. The project was eight months old, so there were plenty of tests to reference. I also wrote many end-to-end tests using Playwright.\n\nSimultaneously, I watched a live stream by Romain Lanz discussing \"writing testable code\" and explaining the DI in Adonis. With these explanations and the concrete examples I encountered during my internship, things started to come together and make sense.\n\nThis year, at Devoxx 2024, I tried to attend as many talks about TDD and DDD as possible. Hearing experts discuss real-world examples and how they architect their apps to make them testable and future-proof was truly enlightening.\n\n\u003CCalloutReadMore id=\"08852918-9829-4658-97b3-1aabede8e9fe\" \u002F>\n\nDespite all this, I still struggle to find genuine and practical examples of testing. It's so frustrating to know the theory but not be able to apply it.\n\nBut I believe that to learn, there is nothing more effective than taking action. So I took the framework I know best, Nuxt, and started experimenting with the [available testing tools](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ftest-utils). I used the end-to-end integration with Playwright, but it was not a pleasant experience. Tests took so long to run. _Strangely, the experience during my internship felt the opposite. I discovered that the Nuxt integration rebuilds the application before every test. I made [a PR to allow the runner to reuse an existing server](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ftest-utils\u002Fpull\u002F881)._\n\nSimultaneously, I received feedback about [my fork of the Plausible tracker](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fplausible-tracker). Some bugs were reported, and new features were requested. The problem was the project had no tests, and I clearly remembered how painful it was on the tutoring platform. I also noticed I hadn't touched the project for a while, and manual testing wouldn't be enough to ensure the quality of the project in the long term. _When you haven't touched a project for some time, you have to relearn everything. With reliable tests, you can be confident that the project works as expected, even after refactoring, bug fixes, and new features._ I decided to write tests before touching anything else. It took me three days, but now, I'm confident that the project works as expected. This is satisfying, and there is no more fear of touching the project.\n\n![Passing tests of Plausible Tracker](\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests\u002Fplausible-tracker-passing-tests.png)\n\nFinally, around the same time, I discovered Laracasts. I learned PHP and encountered testing in Laravel. It was eye-opening. The testing is well explained, writing tests, even with a database, is easy, and it runs so fast. Writing tests with Pest is truly enjoyable. _If you have never tried it, you should._\n\n\u003CCallout icon=\"i-ph-book-duotone\" text=\"Try Laracats\" to=\"https:\u002F\u002Flaracasts.com\u002Freferral\u002FBarbapapazes\" \u002F>\n\n## Understanding the value of testing\n\nIt took me six years to grasp the value of testing and write meaningful tests. Is that long? I'm not sure. It demonstrates that learning is an iterative process, with failures and retries. With perseverance, you finally get there.\n\nThroughout this journey, I asked myself many questions. Here are some answers I would have loved to find earlier.\n\n1. Testing does not have to be difficult.\n2. However, testing is more challenging than `expect(1 + 1).toBe(2)`.\n3. The difficulty of testing often relates to the application's architecture and the ability to separate features. _The more modular the app is, the easier it is to test._\n4. Before choosing a tool, consider the ability to test your app. _If you can't test it, it's not worth it._\n5. No tests are acceptable. _It's a trade-off. Use it wisely._\n6. Rule 5 does not invalidate rule 4 or rule 3. _If you end up writing tests, you will thank yourself._\n7. Aiming for 100% coverage is meaningless by itself. _It's a trap that can lead to poor practices._\n8. Both unit, integration, and end-to-end tests are valuable. _Choose the one that best fits your needs._\n9. Structure your test with the `arrange`, `act`, and `assert` method. _It's a good method for organizing your test and starting to write them._\n10. Write the `assert` part first.\n11. Think of a test as a description of a specification of the application. _My app should display\u002Fdo this when this happens._\n12. And more importantly, writing testable code before writing tests. _This approach will make your code more readable, maintainable, and testable._\n\nFinally, do not attempt to test everything from day one in this journey. Start small, learn, write simple unit tests, understand how to modularize your code, and progress from there. _It's a long journey, but it's worth it._\n\nI'm still learning a lot, but I'm confident that I'm on the right path. See you in the next article!\n\n_Laracasts is the best resource I've found for learning testing. PHP is great, and learning general concepts is even better. [Try it now](https:\u002F\u002Flaracasts.com\u002Freferral\u002FBarbapapazes)._\n",{"title":36943,"description":38087},{"loc":38089,"images":38093},[38094,38095,38096],{"loc":37847},{"loc":37888},{"loc":37959},"posts\u002Ftesting-is-more-larger-than-just-writing-some-tests","K6dOUjkk13_dbFi3PuV_3hEGcwAOJ2o-l67flRAXLYs",{"id":38100,"title":38101,"body":38102,"contentId":38335,"date":38336,"description":38337,"extension":18,"meta":38338,"navigation":20,"path":38339,"rawbody":38340,"robots":23,"schemaOrg":23,"seo":38341,"sitemap":38342,"stem":38346,"__hash__":38347},"posts\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie.md","Build SaaS Apps With Confidence on NuxtHub with Gavarnie",{"type":7,"value":38103,"toc":38331},[38104,38115,38122,38128,38131,38154,38167,38177,38182,38185,38188,38194,38201,38217,38233,38238,38242,38245,38252,38261,38269,38278,38291,38294],[939,38105,38106],{},[35,38107,38108,38110,38111,215],{},[200,38109,945],{},"\nThis starter was crafted during my free time. If you find it useful, please consider ",[39,38112,38114],{"href":38113},"\u002Fsponsorship","supporting my work",[35,38116,34543,38117],{},[39,38118,38121],{"href":38119,"rel":38120},"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fgavarnie",[77],"Gavarnie",[35,38123,38124],{},[1690,38125],{"alt":38126,"src":38127},"OpenGraph image of Gavarnie project","\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie\u002Fog.png",[35,38129,38130],{},"Before exploring this starter, let me share the motivation behind its creation.",[35,38132,38133,38134,38139,38140,38144,38145,38150,38151],{},"Several months ago, ",[39,38135,38138],{"href":38136,"rel":38137},"https:\u002F\u002Fgithub.com\u002Fatinux",[77],"Sébastien Chopin",", the creator of Nuxt, introduced ",[39,38141,36822],{"href":38142,"rel":38143},"https:\u002F\u002Fhub.nuxt.com",[77],", a platform to deploy and manage Nuxt applications, powered by ",[39,38146,38149],{"href":38147,"rel":38148},"https:\u002F\u002Fcloudflare.com",[77],"Cloudflare",". Briefly, you can create applications by leveraging Cloudflare infrastructure like D1 (database) and R2 (storage), enabling easy deployment and management. ",[1217,38152,38153],{},"It's akin to Vercel or Netlify but for Nuxt applications, and I find it exceptional.",[35,38155,38156,38157,38160,38161,38166],{},"However, this operates on what's known as ",[6850,38158,38159],{},"the edge",", which differs from traditional application deployment methods like VPS. The edge uses workers, similar to web workers in your browser, but on the server side. This system allows for cost-effective and speedy deployment, though it comes with several constraints such as ",[39,38162,38165],{"href":38163,"rel":38164},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fworkers\u002Fruntime-apis\u002F",[77],"available APIs"," and limited compute time. It's a trade-off.",[35,38168,38169,38170,38173,38174],{},"The edge introduces a new conceptual approach where limitations necessitate innovative solutions. ",[1217,38171,38172],{},"While challenges exist, rest assured, you can still achieve your desired outcomes."," This new landscape requires building a comprehensive ecosystem around it. ",[1217,38175,38176],{},"Do not expect ready-to-use solutions for everything; I'll expand on this in another post.",[939,38178,38179],{},[35,38180,38181],{},"Gradually, the edge should be mastered.",[35,38183,38184],{},"Here is where Gavarnie makes its entrance.",[51,38186,38121],{"id":38187},"gavarnie",[35,38189,38190],{},[1690,38191],{"alt":38192,"src":38193},"Cirque de Gavarnie by 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,38195,38196,38197,38200],{},"Gavarnie is a starter intended for use with NuxtHub, specifically on the Cloudflare infrastructure. ",[1217,38198,38199],{},"If you require compatibility with Supabase, Turso, or Vercel, this may not be the right fit."," It features pre-configured social authentication with multiple providers, email verification, and prioritizes architectural and security best practices.",[35,38202,38203,38204,1638,38207,38210,38211,1638,38214,215],{},"The most important, it's ",[6850,38205,38206],{},"free",[6850,38208,38209],{},"open-source",", allowing you to commence your SaaS project now or leverage it ",[6850,38212,38213],{},"for learning",[6850,38215,38216],{},"to foster ecosystem development",[35,38218,38219,38220,3076,38223,38226,38227,106,38230],{},"Indeed, one of my objectives with Gavarnie is not to create just another starter but to further the initiative to advance Nuxt and NuxtHub. I am convinced that by establishing common, robust foundations, we can collectively strengthen the ecosystem surrounding NuxtHub and the edge. Thus, Gavarnie serves as ",[6850,38221,38222],{},"a starting point",[6850,38224,38225],{},"not an end",", and it's up ",[6850,38228,38229],{},"to us to nurture its growth",[1217,38231,38232],{},"Please, join me in enhancing it.",[939,38234,38235],{},[35,38236,38237],{},"Gavarnie is a beginning, not a conclusion.",[51,38239,38241],{"id":38240},"features","Features",[35,38243,38244],{},"Gavarnie is developed on Nuxt and NuxtHub. These two serve as the backbone of this project, with all elements constructed around them.",[35,38246,38247,38248,38251],{},"In its current form, ",[39,38249,38121],{"href":38119,"rel":38250},[77]," enables users to create an account using their social profiles (GitHub and Twitch), link social accounts, change their email address using a verification mechanism, update their profile and display picture, and delete their account.",[35,38253,38254,38255,38260],{},"The email service is facilitated by ",[39,38256,38259],{"href":38257,"rel":38258},"https:\u002F\u002Fresend.io",[77],"Resend",", simplifying email transactions.",[35,38262,38263,38264,38268],{},"The user interface is powered by ",[39,38265,38267],{"href":14203,"rel":38266},[77],"Nuxt UI Pro",", a UI toolkit provided by the Nuxt team for Nuxt applications.",[35,38270,38271,38272,38277],{},"Social authentication is offered via the ",[39,38273,38276],{"href":38274,"rel":38275},"https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-auth-utils",[77],"nuxt-auth-utils"," module, which simplifies social authentication in Nuxt. It supports several providers such as Google, Facebook, Twitter, GitHub, among others.",[35,38279,38280,38281,38286,38287,38290],{},"Moreover, I incorporated the ",[39,38282,38285],{"href":38283,"rel":38284},"https:\u002F\u002Fnuxt.com\u002Fmodules\u002Fsecurity",[77],"Nuxt Security"," module to enhance application security. This starter includes pre-configured CSP, a CSRF ",[1217,38288,38289],{},"which I plan to improve",", and a rate limiter to prevent abuse of sensitive endpoints.",[35,38292,38293],{},"Gavarnie is a continuation to bring full-stack capabilities to Nuxt applications.",[7905,38295,38296],{"id":30855},[7905,38297,38298,38305,38309,38316,38322,38328],{"id":30850},[35,38299,38300,38301],{},"Explore the ",[1582,38302,38304],{"repo":38303},"barbapapazes\u002Fgavarnie"," repository to discover more about its features and how to use them.",[51,38306,38308],{"id":38307},"step-by-step","Step by step",[35,38310,38311,38312,38315],{},"This marks just the ",[6850,38313,38314],{},"beginning"," of the journey.",[35,38317,38318,38319],{},"Gavarnie is a dynamic project that will evolve. We can envision incorporating better session management, an admin dashboard, an API token system, a billing solution, and more. ",[1217,38320,38321],{},"The opportunities are vast, and it's up to us to drive its development.",[35,38323,38324,38325],{},"Having acquainted yourself with ",[1582,38326,38327],{"repo":38303},", feel free to explore it, extend it, experiment, and create practical applications with it.",[35,38329,38330],{},"Do not hesitate to give a star, start a discussion on the GitHub repository for any questions or suggestions to enhance the starter! 💛",{"title":13,"searchDepth":14,"depth":14,"links":38332},[38333,38334],{"id":38187,"depth":14,"text":38121},{"id":38240,"depth":14,"text":38241},"882ebfc2-25f7-46f3-b829-2d44a25b6f86","2024-08-20","Build and deploy NuxtHub SaaS apps easily with Gavarnie. Offers social login, email verification, is free, and open-source!",{},"\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie","---\ncontentId: 882ebfc2-25f7-46f3-b829-2d44a25b6f86\ntitle: Build SaaS Apps With Confidence on NuxtHub with Gavarnie\ndescription: Build and deploy NuxtHub SaaS apps easily with Gavarnie. Offers social login, email verification, is free, and open-source!\ndate: 2024-08-20\n---\n\n> [!NOTE]\n> This starter was crafted during my free time. If you find it useful, please consider [supporting my work](\u002Fsponsorship).\n\nTL;DR: [Gavarnie](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fgavarnie)\n\n![OpenGraph image of Gavarnie project](\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie\u002Fog.png)\n\nBefore exploring this starter, let me share the motivation behind its creation.\n\nSeveral months ago, [Sébastien Chopin](https:\u002F\u002Fgithub.com\u002Fatinux), the creator of Nuxt, introduced [NuxtHub](https:\u002F\u002Fhub.nuxt.com), a platform to deploy and manage Nuxt applications, powered by [Cloudflare](https:\u002F\u002Fcloudflare.com). Briefly, you can create applications by leveraging Cloudflare infrastructure like D1 (database) and R2 (storage), enabling easy deployment and management. _It's akin to Vercel or Netlify but for Nuxt applications, and I find it exceptional._\n\nHowever, this operates on what's known as **the edge**, which differs from traditional application deployment methods like VPS. The edge uses workers, similar to web workers in your browser, but on the server side. This system allows for cost-effective and speedy deployment, though it comes with several constraints such as [available APIs](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fworkers\u002Fruntime-apis\u002F) and limited compute time. It's a trade-off.\n\nThe edge introduces a new conceptual approach where limitations necessitate innovative solutions. _While challenges exist, rest assured, you can still achieve your desired outcomes._ This new landscape requires building a comprehensive ecosystem around it. _Do not expect ready-to-use solutions for everything; I'll expand on this in another post._\n\n> Gradually, the edge should be mastered.\n\nHere is where Gavarnie makes its entrance.\n\n## Gavarnie\n\n![Cirque de Gavarnie by 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 is a starter intended for use with NuxtHub, specifically on the Cloudflare infrastructure. _If you require compatibility with Supabase, Turso, or Vercel, this may not be the right fit._ It features pre-configured social authentication with multiple providers, email verification, and prioritizes architectural and security best practices.\n\nThe most important, it's **free** and **open-source**, allowing you to commence your SaaS project now or leverage it **for learning** and **to foster ecosystem development**.\n\nIndeed, one of my objectives with Gavarnie is not to create just another starter but to further the initiative to advance Nuxt and NuxtHub. I am convinced that by establishing common, robust foundations, we can collectively strengthen the ecosystem surrounding NuxtHub and the edge. Thus, Gavarnie serves as **a starting point**, **not an end**, and it's up **to us to nurture its growth**. _Please, join me in enhancing it._\n\n> Gavarnie is a beginning, not a conclusion.\n\n## Features\n\nGavarnie is developed on Nuxt and NuxtHub. These two serve as the backbone of this project, with all elements constructed around them.\n\nIn its current form, [Gavarnie](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fgavarnie) enables users to create an account using their social profiles (GitHub and Twitch), link social accounts, change their email address using a verification mechanism, update their profile and display picture, and delete their account.\n\nThe email service is facilitated by [Resend](https:\u002F\u002Fresend.io), simplifying email transactions.\n\nThe user interface is powered by [Nuxt UI Pro](https:\u002F\u002Fui.nuxt.com), a UI toolkit provided by the Nuxt team for Nuxt applications.\n\nSocial authentication is offered via the [nuxt-auth-utils](https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-auth-utils) module, which simplifies social authentication in Nuxt. It supports several providers such as Google, Facebook, Twitter, GitHub, among others.\n\nMoreover, I incorporated the [Nuxt Security](https:\u002F\u002Fnuxt.com\u002Fmodules\u002Fsecurity) module to enhance application security. This starter includes pre-configured CSP, a CSRF _which I plan to improve_, and a rate limiter to prevent abuse of sensitive endpoints.\n\nGavarnie is a continuation to bring full-stack capabilities to Nuxt applications.\n\n\u003CCalloutReadMore id=\"f47ef865-e311-48e6-ab6e-6f7ee6bde6e8\" \u002F>\n\n\u003CCalloutReadMore id=\"6df6b96b-58fc-4294-8a38-d209f2b36d19\" \u002F>\n\nExplore the \u003CGitHubLink repo=\"barbapapazes\u002Fgavarnie\" \u002F> repository to discover more about its features and how to use them.\n\n## Step by step\n\nThis marks just the **beginning** of the journey.\n\nGavarnie is a dynamic project that will evolve. We can envision incorporating better session management, an admin dashboard, an API token system, a billing solution, and more. _The opportunities are vast, and it's up to us to drive its development._\n\nHaving acquainted yourself with \u003CGitHubLink repo=\"barbapapazes\u002Fgavarnie\" \u002F>, feel free to explore it, extend it, experiment, and create practical applications with it.\n\nDo not hesitate to give a star, start a discussion on the GitHub repository for any questions or suggestions to enhance the starter! 💛\n",{"title":38101,"description":38337},{"loc":38339,"images":38343},[38344,38345],{"loc":38127},{"loc":38193},"posts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie","_9-PMLU3hSeuv4S2XM46UGDRRIl6I5vqe3UIx5BqsCA",{"id":38349,"title":38350,"body":38351,"contentId":30986,"date":38503,"description":38504,"extension":18,"meta":38505,"navigation":20,"path":38506,"rawbody":38507,"robots":23,"schemaOrg":23,"seo":38508,"sitemap":38509,"stem":38515,"__hash__":38516},"posts\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter.md","The End of My Studies And The Beginning of a New Chapter",{"type":7,"value":38352,"toc":38500},[38353,38369,38375,38383,38386,38389,38392,38395,38405,38411,38424,38430,38433,38444,38447,38453,38456,38460,38463,38469,38489,38494],[939,38354,38355],{},[35,38356,38357,38359,38360,3076,38363,13731,38366,215],{},[200,38358,33518],{},"\nI'm open to new opportunities, near Paris or remote! If you think I can contribute to your company or your project, feel free to contact me on ",[39,38361,36519],{"href":36517,"rel":38362},[77],[39,38364,7243],{"href":7031,"rel":38365},[77],[39,38367,38368],{"href":20728},"Email",[35,38370,38371,38372],{},"Six years ago, I embarked on my studies at INSA Centre Val de Loire to specialize in cybersecurity. When I selected this school on the Parcoursup platform, I was uncertain. At that time, my sole aspiration was to work in IT. ",[1217,38373,38374],{},"Yes, even the web had not crossed my mind.",[35,38376,38377,38378,38382],{},"I discovered my passion for web development during a contest called ",[39,38379,38381],{"href":8204,"rel":38380},[77],"Nuit de l'info"," in early December 2018. The freedom and creativity that web development offers were a revelation for me. I learned a lot, and one of my teammates added 3D objects to our project. It was amazing!",[35,38384,38385],{},"After this incredible experience that profoundly transformed my future, I've participated annually ever since. In my leisure time, I continued to educate myself, from HTML to JavaScript to TypeScript. Every moment spent on the web was a delight. I was enthusiastic to acquire more and more knowledge.",[35,38387,38388],{},"I met many brilliant individuals at school. We could rely on each other during the two years of preparatory classes. Together, we grew and learned a lot. I'm grateful for that and happy to have met them.",[35,38390,38391],{},"With these friends, we created numerous projects ranging from IoT to a tutoring platform and the school campus website. We gained invaluable knowledge and had a lot of fun. Some of these projects were successful, while others were not. But in the end, we gained experience.",[35,38393,38394],{},"I invested a significant amount of time in the school's associations from day one. I initiated an association for sustainable development and served as the vice-president of the national engineering student association in France. I learned extensively about management, communication, and teamwork. These experiences have left a lasting impact on me and have truly transformed me for the better.",[35,38396,38397,38398,38401,38402],{},"After this intense beginning, I did a 4-month internship at a consulting company in Paris. I studied Angular. Within the same company, and to solidify my interest in web development and better understand myself, I took a gap year. I continued to work at this company as well as volunteering for a project called ",[39,38399,18045],{"href":18043,"rel":38400},[77],". Project management, social media management, event organization, and contacting associations, companies, and schools were part of my daily tasks. ",[1217,38403,38404],{},"We privatized the Opera Bastille for a day! What a day!",[35,38406,38407],{},[1690,38408],{"alt":38409,"src":38410},"At the Opera Bastille during Le Classement","\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Flcda.jpg",[35,38412,38413,38414,1638,38417,38420,38421],{},"This gap year gave me a lot of time to do open-source and contribute to projects like ",[39,38415,2918],{"href":13818,"rel":38416},[77],[39,38418,30791],{"href":35152,"rel":38419},[77],". I participated in many meetups and gave my first talk at Devoxx France. ",[1217,38422,38423],{},"Incredible experience!",[35,38425,38426],{},[1690,38427],{"alt":38428,"src":38429},"My talk at Devoxx France","\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Fdevoxxfr.jpeg",[35,38431,38432],{},"2023 was an intense year with a turning point in my web development comprehension.",[35,38434,38435,38436,38439,38440,38443],{},"I returned to school to finish my studies and did a 6-month internship at ",[39,38437,21117],{"href":21115,"rel":38438},[77],". I applied, without much hope, but I was accepted. My mission was to create components for the design system using Angular. I wrote numerous tests, both unit and end-to-end, and documentation. ",[1217,38441,38442],{},"I love when things are clear and well-documented!"," I also set up an internal ecosystem CI to test projects against each merge request. I met a lot of incredible people in a company that respects its values. I'm grateful for this experience.",[35,38445,38446],{},"I took some time to walk in Switzerland, build this new blog, do live coding, write articles, learn PHP and Laravel, code, start a SaaS, contribute to open-source, give two talks, and do sports.",[35,38448,38449],{},[1690,38450],{"alt":38451,"src":38452},"My talk at StrasbourgJS","\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Fstrasbourgjs.jpg",[35,38454,38455],{},"Now we're here, and I'm writing these lines before my last day at Infomaniak.",[51,38457,38459],{"id":38458},"a-new-chapter","A new chapter",[35,38461,38462],{},"My studies are over. I'm closing a significant chapter of my life. But this also signifies the beginning of a new one.",[35,38464,38465,38466],{},"I've defined some objectives for the coming months and the next year to guide myself on a new journey. I made this mistake in the past and I've spread myself thinly over too many projects and activities. I want to focus on a few things and do them well. ",[1217,38467,38468],{},"Don't ask me my objectives.",[35,38470,38471,38472,38475,38476,38479,38480,3076,38484,13731,38487,215],{},"To start this new chapter, I'm looking for a job in Paris or remote. ",[6850,38473,38474],{},"I'm open to new opportunities",", from backend to frontend. I adapt quickly, am eager to learn new things, and love meeting new people through conferences and meetups. ",[1217,38477,38478],{},"I don't want to sit behind a desk all the time."," If you think I can contribute to your company or your project, feel free to contact me on ",[39,38481,38483],{"href":36517,"rel":38482},[77],"X",[39,38485,7243],{"href":7031,"rel":38486},[77],[39,38488,38368],{"href":20728},[35,38490,38491],{},[1217,38492,38493],{},"Hope the next 6 years are as rich as the last ones.",[35,38495,38496],{},[1690,38497],{"alt":38498,"src":38499},"Mountains from the Salanfe lake","\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Fmountain-salanfe.jpg",{"title":13,"searchDepth":14,"depth":14,"links":38501},[38502],{"id":38458,"depth":14,"text":38459},"2024-08-09","Life is a journey. Like every journey, it is divided into parts. Today, I'm concluding my studies, closing a significant chapter but I'm opening a new one.",{},"\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter","---\ncontentId: 029ab8e8-401f-4728-adf9-4e0a2899fd05\ntitle: \"The End of My Studies And The Beginning of a New Chapter\"\ndescription: Life is a journey. Like every journey, it is divided into parts. Today, I'm concluding my studies, closing a significant chapter but I'm opening a new one.\ndate: 2024-08-09\n---\n\n> [!IMPORTANT]\n> I'm open to new opportunities, near Paris or remote! If you think I can contribute to your company or your project, feel free to contact me on [X (Twitter)](https:\u002F\u002Fx.com\u002Fsoubiran_), [LinkedIn](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F) or [Email](mailto:esteban@soubiran.dev).\n\nSix years ago, I embarked on my studies at INSA Centre Val de Loire to specialize in cybersecurity. When I selected this school on the Parcoursup platform, I was uncertain. At that time, my sole aspiration was to work in IT. _Yes, even the web had not crossed my mind._\n\nI discovered my passion for web development during a contest called [Nuit de l'info](https:\u002F\u002Fwww.nuitdelinfo.com\u002F) in early December 2018. The freedom and creativity that web development offers were a revelation for me. I learned a lot, and one of my teammates added 3D objects to our project. It was amazing!\n\nAfter this incredible experience that profoundly transformed my future, I've participated annually ever since. In my leisure time, I continued to educate myself, from HTML to JavaScript to TypeScript. Every moment spent on the web was a delight. I was enthusiastic to acquire more and more knowledge.\n\nI met many brilliant individuals at school. We could rely on each other during the two years of preparatory classes. Together, we grew and learned a lot. I'm grateful for that and happy to have met them.\n\nWith these friends, we created numerous projects ranging from IoT to a tutoring platform and the school campus website. We gained invaluable knowledge and had a lot of fun. Some of these projects were successful, while others were not. But in the end, we gained experience.\n\nI invested a significant amount of time in the school's associations from day one. I initiated an association for sustainable development and served as the vice-president of the national engineering student association in France. I learned extensively about management, communication, and teamwork. These experiences have left a lasting impact on me and have truly transformed me for the better.\n\nAfter this intense beginning, I did a 4-month internship at a consulting company in Paris. I studied Angular. Within the same company, and to solidify my interest in web development and better understand myself, I took a gap year. I continued to work at this company as well as volunteering for a project called [Le Classement des Associations](https:\u002F\u002Fle-classement.fr\u002F). Project management, social media management, event organization, and contacting associations, companies, and schools were part of my daily tasks. _We privatized the Opera Bastille for a day! What a day!_\n\n![At the Opera Bastille during Le Classement](\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Flcda.jpg)\n\nThis gap year gave me a lot of time to do open-source and contribute to projects like [Nuxt](https:\u002F\u002Fnuxt.com) and [UnJS](https:\u002F\u002Funjs.io). I participated in many meetups and gave my first talk at Devoxx France. _Incredible experience!_\n\n![My talk at Devoxx France](\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Fdevoxxfr.jpeg)\n\n2023 was an intense year with a turning point in my web development comprehension.\n\nI returned to school to finish my studies and did a 6-month internship at [Infomaniak](https:\u002F\u002Fwww.infomaniak.com\u002F). I applied, without much hope, but I was accepted. My mission was to create components for the design system using Angular. I wrote numerous tests, both unit and end-to-end, and documentation. _I love when things are clear and well-documented!_ I also set up an internal ecosystem CI to test projects against each merge request. I met a lot of incredible people in a company that respects its values. I'm grateful for this experience.\n\nI took some time to walk in Switzerland, build this new blog, do live coding, write articles, learn PHP and Laravel, code, start a SaaS, contribute to open-source, give two talks, and do sports.\n\n![My talk at StrasbourgJS](\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Fstrasbourgjs.jpg)\n\nNow we're here, and I'm writing these lines before my last day at Infomaniak.\n\n## A new chapter\n\nMy studies are over. I'm closing a significant chapter of my life. But this also signifies the beginning of a new one.\n\nI've defined some objectives for the coming months and the next year to guide myself on a new journey. I made this mistake in the past and I've spread myself thinly over too many projects and activities. I want to focus on a few things and do them well. _Don't ask me my objectives._\n\nTo start this new chapter, I'm looking for a job in Paris or remote. **I'm open to new opportunities**, from backend to frontend. I adapt quickly, am eager to learn new things, and love meeting new people through conferences and meetups. _I don't want to sit behind a desk all the time._ If you think I can contribute to your company or your project, feel free to contact me on [X](https:\u002F\u002Fx.com\u002Fsoubiran_), [LinkedIn](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F) or [Email](mailto:esteban@soubiran.dev).\n\n_Hope the next 6 years are as rich as the last ones._\n\n![Mountains from the Salanfe lake](\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Fmountain-salanfe.jpg)\n",{"title":38350,"description":38504},{"loc":38506,"images":38510},[38511,38512,38513,38514],{"loc":38410},{"loc":38429},{"loc":38452},{"loc":38499},"posts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter","C2VqF5xvLCVY3ipXa6S5FM3PKFE0c4keBApVU2aQ1k0",{"id":38518,"title":38519,"body":38520,"contentId":38872,"date":38873,"description":38874,"extension":18,"meta":38875,"navigation":20,"path":38876,"rawbody":38877,"robots":23,"schemaOrg":23,"seo":38878,"sitemap":38879,"stem":38886,"__hash__":38887},"posts\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt.md","Orion, A Community-Driven Collection of Templates for Nuxt",{"type":7,"value":38521,"toc":38863},[38522,38532,38534,38541,38544,38549,38561,38564,38568,38580,38583,38589,38596,38600,38607,38619,38631,38634,38643,38646,38657,38666,38668,38673,38676,38681,38687,38690,38696,38700,38706,38709,38712,38716,38722,38725,38729,38732,38800,38804,38816,38827,38834,38842,38860],[939,38523,38524],{},[35,38525,38526,38527,215],{},"TL;DR: I'm excited to announce Orion, a community-driven collection of templates for your next Nuxt projects, from landing pages to complete web applications. Orion is a place where you can discover, search, find, and learn from a collection of templates built by the Nuxt community for the Nuxt ecosystem. Available at ",[39,38528,38531],{"href":38529,"rel":38530},"https:\u002F\u002Forion.barbapapazes.dev",[77],"orion.barbapapazes.dev",[51,38533,17286],{"id":17285},[35,38535,38536,38537,38540],{},"At the end of February 2024, during Vue Amsterdam, Sébastien Chopin unveiled ",[39,38538,36822],{"href":38142,"rel":38539},[77],", a platform and a module to build and deploy full-stack Nuxt applications on the edge.",[35,38542,38543],{},"This announcement was huge because since version 3, the tagline of Nuxt contains the word 'full-stack' but until now, it was more a dream than a reality.",[939,38545,38546],{},[35,38547,38548],{},"Create performant and production-grade full-stack web apps and websites with confidence.",[35,38550,38551,38552],{},"With this new platform, the full-stack becomes a reality. ",[1217,38553,38554,38555,38560],{},"And I'm really pumped about it! In December 2023, I wrote about ",[39,38556,38559],{"href":38557,"rel":38558},"https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Fnuxt-3-et-le-full-stack-sur-ledge",[77],"Nuxt 3 and full-stack on the edge"," (French), and there was still a lot of work to do. 6 months later, the full-stack is here! Progress is impressive! Great job Nuxt team!",[35,38562,38563],{},"With that in mind, I wanted to give it a try, a real try, to NuxtHub. This tool can shape the future of Nuxt, and I think it's important to understand it to not be overtaken.",[51,38565,38567],{"id":38566},"finding-an-idea","Finding an idea",[35,38569,38570,38571,38579],{},"In order to give a real try to NuxtHub, I can't just create a simple project like a todo list. ",[1217,38572,38573,38574,215],{},"And Sébastien already did it with ",[39,38575,38578],{"href":38576,"rel":38577},"https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-todos-edge",[77],"Atinotes"," I need to create something more complex that uses all the power of NuxtHub, from the database to the blob storage and the kv store.",[35,38581,38582],{},"During the month of March and April, I tried NuxtHub on different projects, and the more I used it, the more I liked it. It made both the local development and the production deployment easier. No more tweaks between the two environments. It was a real pleasure to use it.",[35,38584,38585,38586],{},"With all these experiments, I'm confident in building a bigger project with NuxtHub. But I need to find an idea, and that's not easy. ",[1217,38587,38588],{},"Not easy is an understatement!",[35,38590,38591,38592,38595],{},"Building something fake is not an option. I know I will spend a lot of time on this project, and this will ask me a lot of energy. A project without a real goal that nobody will (or can) use is, ",[1217,38593,38594],{},"in my opinion",", a waste of time. In the meantime, and because the project has no goal, it's difficult to push the ecosystem forward. One of the objectives of this project is to be a showcase, to allow me to write articles, to create new modules and overall, to help people to build their projects.",[51,38597,38599],{"id":38598},"building-orion","Building Orion",[35,38601,38602,38603,38606],{},"A good project to try NuxtHub must have authentication, use the database, the blob storage, the kv store, fit on Cloudflare, and be useful, at least, to me. ",[1217,38604,38605],{},"And if the project can be useful for the community, I've to admit, it's a plus."," If I don't use it every week, I will not be able to create content and maintain it.",[35,38608,38609,38610,38615,38616],{},"During the beginning of May, I found some interesting ideas but none of them convinced me, some were too light and others not useful enough. I was stuck. But one day, I came across the Astro article about the ",[39,38611,38614],{"href":38612,"rel":38613},"https:\u002F\u002Fastro.build\u002Fblog\u002Fdev-portal\u002F",[77],"Astro Developer Portal"," again. ",[1217,38617,38618],{},"This is a neat idea for a project!",[35,38620,38621,38622,38627,38628],{},"A central place for all the templates built by the community is one of the missing pieces in the Nuxt ecosystem. I know, Nuxt main website has a ",[39,38623,38626],{"href":38624,"rel":38625},"https:\u002F\u002Fnuxt.com\u002Ftemplates",[77],"section for templates",". But it's not enough. Available information is limited and there is no way to filter, search or sort the templates. And, the most important, it's not scalable. ",[1217,38629,38630],{},"Behind the scene, it's a simple YAML file that is updated manually.",[35,38632,38633],{},"Since the release of Nuxt Content and Nuxt Studio, a lot of templates have been created by the community. One of the ways to find them is to search for the correct keywords on GitHub. It's inefficient, does not allow to easily discover new templates and does not provide visibility to the authors.",[35,38635,38636,38637,106,38640],{},"With this observation, I decided to build a project that will allow the community to share their templates, to discover new templates, and to learn from them. This project will be called ",[6850,38638,38639],{},"Orion",[1217,38641,38642],{},"There is a story behind this name, but it's for another time!",[35,38644,38645],{},"Orion is, as I write these lines, a collection of templates for your Nuxt projects. From landing pages to complete web applications, you will find a template that fits your needs. You can discover, search, find, and learn from a collection of templates built by the Nuxt community for the Nuxt ecosystem.",[35,38647,38648,38649,38652,38653],{},"Orion is open-source and available at ",[39,38650,38531],{"href":38529,"rel":38651},[77],". You can find the code on ",[1582,38654,38656],{"repo":38655},"barbapapazes\u002Forion"," to learn from it or to contribute to it.",[35,38658,38659,38660,38665],{},"To showcase your template with beautiful cover images, I publish a ",[39,38661,38664],{"href":38662,"rel":38663},"https:\u002F\u002Fwww.figma.com\u002Fcommunity\u002Ffile\u002F1385718462370739769\u002Forion-cover-image-template",[77],"Figma template"," that you can use to create your cover image.",[1913,38667,38241],{"id":38240},[35,38669,38670],{},[1690,38671],{"alt":38639,"src":38672},"\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Forion.png",[35,38674,38675],{},"Now you know the story behind Orion and why I built it, it's time to talk about the features.",[38677,38678,38680],"h4",{"id":38679},"discover-templates","Discover templates",[35,38682,38683],{},[1690,38684],{"alt":38685,"src":38686},"Discover Templates","\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Fdiscover-templates.png",[35,38688,38689],{},"The homepage contains a section that lists templates. You can filter them by category, pricing, and modules. You can also search for a template by name. Once you find a template that interests you, you can click on it to see more details like the live demo and the source code.",[35,38691,38692],{},[1690,38693],{"alt":38694,"src":38695},"Template Details","\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Ftemplate-details.png",[38677,38697,38699],{"id":38698},"create-a-template","Create a template",[35,38701,38702],{},[1690,38703],{"alt":38704,"src":38705},"Create a Template","\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Fcreate-a-template.png",[35,38707,38708],{},"If you have a template that you want to share with the community, you can create a new entry. First, you need to authenticate with your GitHub account. Then, you can fill in the form with the details of your template and submit it.",[35,38710,38711],{},"Before being available on the platform, your template will be reviewed by the team. Once approved, it will be visible to everyone.",[38677,38713,38715],{"id":38714},"manage-your-templates","Manage your templates",[35,38717,38718],{},[1690,38719],{"alt":38720,"src":38721},"Manage Your Templates","\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Fmanage-your-templates.png",[35,38723,38724],{},"If you have created templates, you can manage them from your dashboard. You can see the status of each template, edit them, or delete them.",[1913,38726,38728],{"id":38727},"tech-stack","Tech stack",[35,38730,38731],{},"Orion is built on top of:",[1576,38733,38734,38743,38749,38755,38763,38769,38776,38784,38792],{},[71,38735,38736,34547,38740],{},[39,38737,38739],{"href":23044,"rel":38738},[77],"Nuxt 3",[1217,38741,38742],{},"(with compatibility 4 activated)",[71,38744,38745,38748],{},[39,38746,9190],{"href":23049,"rel":38747},[77]," without customizations. I wanted to stay in the Nuxt identity because it's a project for the Nuxt community.",[71,38750,38751,38754],{},[39,38752,36822],{"href":36820,"rel":38753},[77]," for the full-stack part.",[71,38756,38757,38762],{},[39,38758,38761],{"href":38759,"rel":38760},"https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-auth-utils",[77],"Nuxt Auth"," Utils for authentication.",[71,38764,38765,38768],{},[39,38766,9221],{"href":9219,"rel":38767},[77]," for authorization.",[71,38770,38771,34462],{},[39,38772,38775],{"href":38773,"rel":38774},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fpages\u002F",[77],"Cloudflare Pages",[71,38777,38778,38783],{},[39,38779,38782],{"href":38780,"rel":38781},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fd1\u002F",[77],"Cloudflare D1"," for the database.",[71,38785,38786,38791],{},[39,38787,38790],{"href":38788,"rel":38789},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fkv\u002F",[77],"Cloudflare KV"," for the key-value store.",[71,38793,38794,38799],{},[39,38795,38798],{"href":38796,"rel":38797},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fr2\u002F",[77],"Cloudflare R2"," for the blob storage.",[51,38801,38803],{"id":38802},"for-the-community","For the community",[35,38805,38806,38807,38810,38811,38815],{},"Orion has been built during live streams on ",[39,38808,18569],{"href":18567,"rel":38809},[77],". I also write ",[39,38812,38814],{"href":38813},".\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey","an article"," to share the process of building a project from scratch, to show the difficulties, the solutions, and the choices that have been made. I wanted to show that building a project is not just writing code but also a lot of thinking, a lot of research, and a lot of testing.",[35,38817,38818,38819,3080,38823,215],{},"During the development of Orion, I encountered some common problems that I documented in 2 articles: ",[39,38820,38822],{"href":38821},".\u002Fnuxt-going-full-stack-how-to-streamline-form-validation","Nuxt Going Full-Stack: How to Validate Forms?",[39,38824,38826],{"href":38825},".\u002Fnuxt-going-full-stack-how-to-handle-authorization","Nuxt Going Full-Stack: How to Handle Authorization?",[35,38828,38829,38830,38833],{},"I also created a module, ",[39,38831,9221],{"href":9219,"rel":38832},[77],", that allows you to handle the authorization in your Nuxt project. This module is used in Orion and now available for the community. I hope it will help you to build your next project.",[35,38835,38836,38837,11837],{},"The whole process made me do some PRs on the Nuxt and UnJS repositories. One of them is the writing of the ",[39,38838,38841],{"href":38839,"rel":38840},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fnitro\u002Fpull\u002F2508",[77],"Nitro module author guide",[35,38843,38844,38845,38851,38852],{},"Now the project is released, you can ",[39,38846,38848],{"href":38529,"rel":38847},[77],[6850,38849,38850],{},"publish and share your templates"," to the Nuxt community. ",[1217,38853,38854,38855,11837],{},"Feedback is welcome on ",[39,38856,38859],{"href":38857,"rel":38858},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Forion",[77],"the GitHub repository",[35,38861,38862],{},"Happy coding!",{"title":13,"searchDepth":14,"depth":14,"links":38864},[38865,38866,38867,38871],{"id":17285,"depth":14,"text":17286},{"id":38566,"depth":14,"text":38567},{"id":38598,"depth":14,"text":38599,"children":38868},[38869,38870],{"id":38240,"depth":255,"text":38241},{"id":38727,"depth":255,"text":38728},{"id":38802,"depth":14,"text":38803},"cfeeda70-0c55-4fe3-b086-7c461aa8fa4e","2024-06-18","I'm excited to announce Orion, a community-driven collection of templates for your next Nuxt projects, from landing pages to complete web applications.",{},"\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt","---\ncontentId: cfeeda70-0c55-4fe3-b086-7c461aa8fa4e\ntitle: \"Orion, A Community-Driven Collection of Templates for Nuxt\"\ndescription: I'm excited to announce Orion, a community-driven collection of templates for your next Nuxt projects, from landing pages to complete web applications.\ndate: 2024-06-18\n---\n\n> TL;DR: I'm excited to announce Orion, a community-driven collection of templates for your next Nuxt projects, from landing pages to complete web applications. Orion is a place where you can discover, search, find, and learn from a collection of templates built by the Nuxt community for the Nuxt ecosystem. Available at [orion.barbapapazes.dev](https:\u002F\u002Forion.barbapapazes.dev).\n\n## The beginning\n\nAt the end of February 2024, during Vue Amsterdam, Sébastien Chopin unveiled [NuxtHub](https:\u002F\u002Fhub.nuxt.com), a platform and a module to build and deploy full-stack Nuxt applications on the edge.\n\nThis announcement was huge because since version 3, the tagline of Nuxt contains the word 'full-stack' but until now, it was more a dream than a reality.\n\n> Create performant and production-grade full-stack web apps and websites with confidence.\n\nWith this new platform, the full-stack becomes a reality. _And I'm really pumped about it! In December 2023, I wrote about [Nuxt 3 and full-stack on the edge](https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Fnuxt-3-et-le-full-stack-sur-ledge) (French), and there was still a lot of work to do. 6 months later, the full-stack is here! Progress is impressive! Great job Nuxt team!_\n\nWith that in mind, I wanted to give it a try, a real try, to NuxtHub. This tool can shape the future of Nuxt, and I think it's important to understand it to not be overtaken.\n\n## Finding an idea\n\nIn order to give a real try to NuxtHub, I can't just create a simple project like a todo list. _And Sébastien already did it with [Atinotes](https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-todos-edge)._ I need to create something more complex that uses all the power of NuxtHub, from the database to the blob storage and the kv store.\n\nDuring the month of March and April, I tried NuxtHub on different projects, and the more I used it, the more I liked it. It made both the local development and the production deployment easier. No more tweaks between the two environments. It was a real pleasure to use it.\n\nWith all these experiments, I'm confident in building a bigger project with NuxtHub. But I need to find an idea, and that's not easy. _Not easy is an understatement!_\n\nBuilding something fake is not an option. I know I will spend a lot of time on this project, and this will ask me a lot of energy. A project without a real goal that nobody will (or can) use is, _in my opinion_, a waste of time. In the meantime, and because the project has no goal, it's difficult to push the ecosystem forward. One of the objectives of this project is to be a showcase, to allow me to write articles, to create new modules and overall, to help people to build their projects.\n\n## Building Orion\n\nA good project to try NuxtHub must have authentication, use the database, the blob storage, the kv store, fit on Cloudflare, and be useful, at least, to me. _And if the project can be useful for the community, I've to admit, it's a plus._ If I don't use it every week, I will not be able to create content and maintain it.\n\nDuring the beginning of May, I found some interesting ideas but none of them convinced me, some were too light and others not useful enough. I was stuck. But one day, I came across the Astro article about the [Astro Developer Portal](https:\u002F\u002Fastro.build\u002Fblog\u002Fdev-portal\u002F) again. _This is a neat idea for a project!_\n\nA central place for all the templates built by the community is one of the missing pieces in the Nuxt ecosystem. I know, Nuxt main website has a [section for templates](https:\u002F\u002Fnuxt.com\u002Ftemplates). But it's not enough. Available information is limited and there is no way to filter, search or sort the templates. And, the most important, it's not scalable. _Behind the scene, it's a simple YAML file that is updated manually._\n\nSince the release of Nuxt Content and Nuxt Studio, a lot of templates have been created by the community. One of the ways to find them is to search for the correct keywords on GitHub. It's inefficient, does not allow to easily discover new templates and does not provide visibility to the authors.\n\nWith this observation, I decided to build a project that will allow the community to share their templates, to discover new templates, and to learn from them. This project will be called **Orion**. _There is a story behind this name, but it's for another time!_\n\nOrion is, as I write these lines, a collection of templates for your Nuxt projects. From landing pages to complete web applications, you will find a template that fits your needs. You can discover, search, find, and learn from a collection of templates built by the Nuxt community for the Nuxt ecosystem.\n\nOrion is open-source and available at [orion.barbapapazes.dev](https:\u002F\u002Forion.barbapapazes.dev). You can find the code on \u003CGitHubLink repo=\"barbapapazes\u002Forion\" \u002F> to learn from it or to contribute to it.\n\nTo showcase your template with beautiful cover images, I publish a [Figma template](https:\u002F\u002Fwww.figma.com\u002Fcommunity\u002Ffile\u002F1385718462370739769\u002Forion-cover-image-template) that you can use to create your cover image.\n\n### Features\n\n![Orion](\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Forion.png)\n\nNow you know the story behind Orion and why I built it, it's time to talk about the features.\n\n#### Discover templates\n\n![Discover Templates](\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Fdiscover-templates.png)\n\nThe homepage contains a section that lists templates. You can filter them by category, pricing, and modules. You can also search for a template by name. Once you find a template that interests you, you can click on it to see more details like the live demo and the source code.\n\n![Template Details](\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Ftemplate-details.png)\n\n#### Create a template\n\n![Create a Template](\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Fcreate-a-template.png)\n\nIf you have a template that you want to share with the community, you can create a new entry. First, you need to authenticate with your GitHub account. Then, you can fill in the form with the details of your template and submit it.\n\nBefore being available on the platform, your template will be reviewed by the team. Once approved, it will be visible to everyone.\n\n#### Manage your templates\n\n![Manage Your Templates](\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Fmanage-your-templates.png)\n\nIf you have created templates, you can manage them from your dashboard. You can see the status of each template, edit them, or delete them.\n\n### Tech stack\n\nOrion is built on top of:\n\n- [Nuxt 3](https:\u002F\u002Fnuxt.com\u002F) _(with compatibility 4 activated)_\n- [Nuxt UI](https:\u002F\u002Fui.nuxt.com\u002F) without customizations. I wanted to stay in the Nuxt identity because it's a project for the Nuxt community.\n- [NuxtHub](https:\u002F\u002Fhub.nuxt.com\u002F) for the full-stack part.\n- [Nuxt Auth](https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-auth-utils) Utils for authentication.\n- [Nuxt Authorization](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fnuxt-authorization) for authorization.\n- [Cloudflare Pages](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fpages\u002F) for deployment.\n- [Cloudflare D1](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fd1\u002F) for the database.\n- [Cloudflare KV](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fkv\u002F) for the key-value store.\n- [Cloudflare R2](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fr2\u002F) for the blob storage.\n\n## For the community\n\nOrion has been built during live streams on [Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). I also write [an article](.\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey.md) to share the process of building a project from scratch, to show the difficulties, the solutions, and the choices that have been made. I wanted to show that building a project is not just writing code but also a lot of thinking, a lot of research, and a lot of testing.\n\nDuring the development of Orion, I encountered some common problems that I documented in 2 articles: [Nuxt Going Full-Stack: How to Validate Forms?](.\u002Fnuxt-going-full-stack-how-to-streamline-form-validation.md), and [Nuxt Going Full-Stack: How to Handle Authorization?](.\u002Fnuxt-going-full-stack-how-to-handle-authorization.md).\n\nI also created a module, [Nuxt Authorization](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fnuxt-authorization), that allows you to handle the authorization in your Nuxt project. This module is used in Orion and now available for the community. I hope it will help you to build your next project.\n\nThe whole process made me do some PRs on the Nuxt and UnJS repositories. One of them is the writing of the [Nitro module author guide](https:\u002F\u002Fgithub.com\u002Funjs\u002Fnitro\u002Fpull\u002F2508)!\n\nNow the project is released, you can [**publish and share your templates**](https:\u002F\u002Forion.barbapapazes.dev) to the Nuxt community. _Feedback is welcome on [the GitHub repository](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Forion)!_\n\nHappy coding!\n",{"title":38519,"description":38874},{"loc":38876,"images":38880},[38881,38882,38883,38884,38885],{"loc":38672},{"loc":38686},{"loc":38695},{"loc":38705},{"loc":38721},"posts\u002Forion-a-community-driven-collection-of-templates-for-nuxt","Txdch-yGAdj2LVHYlv1X_NyyHy5OCTEszj_4_XLD2s4",{"id":38889,"title":38826,"body":38890,"contentId":30855,"date":40794,"description":40795,"extension":18,"meta":40796,"navigation":20,"path":40797,"rawbody":40798,"robots":23,"schemaOrg":23,"seo":40799,"sitemap":40800,"stem":40801,"__hash__":40802},"posts\u002Fposts\u002Fnuxt-going-full-stack-how-to-handle-authorization.md",{"type":7,"value":38891,"toc":40783},[38892,38905,38920,38930,38933,38936,40780],[35,38893,38894,38895,13731,38898,38901,38902,215],{},"When developing an application, it is essential to ",[6850,38896,38897],{},"grant",[6850,38899,38900],{},"restrict"," access to specific areas or data based on a user's role or permissions. This concept is known as ",[6850,38903,38904],{},"authorization",[35,38906,38907,38908,38911,38912,38915,38916,38919],{},"Authorization is a critical aspect of any application for ",[6850,38909,38910],{},"security"," purposes. It is imperative to protect ",[6850,38913,38914],{},"sensitive data",", such as a user's email address or password, and prevent the disclosure of ",[6850,38917,38918],{},"private information"," that could compromise user privacy and your business integrity.",[35,38921,38922,38923,38926,38927,38929],{},"To clarify: ",[6850,38924,38925],{},"authentication"," involves verifying a user's identity, whereas ",[6850,38928,38904],{}," involves permitting or denying access to resources based on the user's role or permissions.",[35,38931,38932],{},"While building Orion, I confronted this challenge and experimented with various approaches. In this article, I'll share several strategies to manage authorization in a NuxtHub application, ranging from the simplest to the most sophisticated methods. It is crucial to understand that the most complex method is not necessarily the best for every application.",[35,38934,38935],{},"Orion serves as a community-driven collection of templates for your projects, from landing pages to complete web applications. NuxtHub functions as a deployment and management platform for Nuxt, powered by Cloudflare.",[7905,38937,38938,38940,38947,39127,39143,39153,39162,39166,39175,39189,39202,39276,39283,39287,39301,39520,39529,39600,39610,39613,39617,39624,39630,39980,39983,39999,40002,40020,40026,40032,40039,40043,40054,40061,40068,40072,40078,40081,40088,40104,40107,40111,40120,40126,40196,40199,40250,40253,40259,40265,40360,40375,40479,40482,40489,40496,40629,40639,40651,40663,40746,40751,40753,40762,40765,40768,40778],{"id":30840},[51,38939,29349],{"id":29348},[35,38941,38942,38943,38946],{},"Consider this ",[6850,38944,38945],{},"endpoint"," in your Nuxt application:",[192,38948,38950],{"className":1370,"code":38949,"language":1373,"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,38951,38952,38975,39000,39004,39036,39049,39058,39068,39078,39089,39100,39104,39110,39114,39121],{"__ignoreMap":13},[200,38953,38954,38956,38958,38961,38963,38965,38967,38969,38971,38973],{"class":202,"line":203},[200,38955,1380],{"class":206},[200,38957,1383],{"class":206},[200,38959,38960],{"class":223}," defineEventHandler",[200,38962,227],{"class":210},[200,38964,3711],{"class":379},[200,38966,1095],{"class":214},[200,38968,25386],{"class":386},[200,38970,2089],{"class":214},[200,38972,393],{"class":379},[200,38974,280],{"class":214},[200,38976,38977,38979,38981,38983,38986,38988,38990,38992,38994,38996,38998],{"class":202,"line":14},[200,38978,3723],{"class":379},[200,38980,3224],{"class":210},[200,38982,510],{"class":214},[200,38984,38985],{"class":223}," getRouteParam",[200,38987,227],{"class":235},[200,38989,25386],{"class":210},[200,38991,550],{"class":214},[200,38993,242],{"class":214},[200,38995,3280],{"class":245},[200,38997,249],{"class":214},[200,38999,416],{"class":235},[200,39001,39002],{"class":202,"line":255},[200,39003,529],{"emptyLinePlaceholder":20},[200,39005,39006,39008,39011,39013,39015,39018,39020,39022,39024,39027,39029,39032,39034],{"class":202,"line":272},[200,39007,3723],{"class":379},[200,39009,39010],{"class":210}," publication",[200,39012,510],{"class":214},[200,39014,452],{"class":206},[200,39016,39017],{"class":210}," db",[200,39019,215],{"class":214},[200,39021,3509],{"class":210},[200,39023,215],{"class":214},[200,39025,39026],{"class":210},"publications",[200,39028,215],{"class":214},[200,39030,39031],{"class":223},"findOne",[200,39033,227],{"class":235},[200,39035,230],{"class":214},[200,39037,39038,39041,39043,39045,39047],{"class":202,"line":283},[200,39039,39040],{"class":235},"    where",[200,39042,239],{"class":214},[200,39044,318],{"class":214},[200,39046,3224],{"class":210},[200,39048,333],{"class":214},[200,39050,39051,39054,39056],{"class":202,"line":300},[200,39052,39053],{"class":235},"    columns",[200,39055,239],{"class":214},[200,39057,280],{"class":214},[200,39059,39060,39062,39064,39066],{"class":202,"line":310},[200,39061,3557],{"class":235},[200,39063,239],{"class":214},[200,39065,2424],{"class":2423},[200,39067,252],{"class":214},[200,39069,39070,39072,39074,39076],{"class":202,"line":336},[200,39071,3589],{"class":235},[200,39073,239],{"class":214},[200,39075,2424],{"class":2423},[200,39077,252],{"class":214},[200,39079,39080,39083,39085,39087],{"class":202,"line":342},[200,39081,39082],{"class":235},"      status",[200,39084,239],{"class":214},[200,39086,2424],{"class":2423},[200,39088,252],{"class":214},[200,39090,39091,39094,39096,39098],{"class":202,"line":365},[200,39092,39093],{"class":235},"      authorId",[200,39095,239],{"class":214},[200,39097,2424],{"class":2423},[200,39099,252],{"class":214},[200,39101,39102],{"class":202,"line":371},[200,39103,339],{"class":214},[200,39105,39106,39108],{"class":202,"line":398},[200,39107,5080],{"class":214},[200,39109,416],{"class":235},[200,39111,39112],{"class":202,"line":405},[200,39113,529],{"emptyLinePlaceholder":20},[200,39115,39116,39118],{"class":202,"line":410},[200,39117,3743],{"class":206},[200,39119,39120],{"class":210}," publication\n",[200,39122,39123,39125],{"class":202,"line":861},[200,39124,413],{"class":214},[200,39126,416],{"class":210},[35,39128,39129,39130,39133,39134,3076,39137,33624,39140,215],{},"You can access this endpoint by making a ",[156,39131,39132],{},"GET \u002Fapi\u002Fpublication\u002F\u003Cid>"," request. This endpoint returns a publication. At first glance, there is no issue with this endpoint. However, a closer examination reveals that a publication has a status that can be ",[156,39135,39136],{},"draft",[156,39138,39139],{},"published",[156,39141,39142],{},"deleted",[35,39144,39145,39146,33991,39149,39152],{},"The concern is that anyone can access this endpoint and view a ",[6850,39147,39148],{},"draft publication",[6850,39150,39151],{},"deleted publication",". Some might be able to view the content of a publication before it is published, which could contain vital information such as a press release or a product launch. Such a scenario could significantly harm your business.",[35,39154,39155,39156,39161],{},"This is a security issue that necessitates restricting access to this endpoint, utilizing the ",[39,39157,39160],{"href":39158,"rel":39159},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrinciple_of_least_privilege",[77],"principle of least privilege",". Security is not merely a feature but should be ingrained in the system's design.",[51,39163,39165],{"id":39164},"authenticated-or-not","Authenticated or not",[35,39167,39168,39169,39174],{},"The following section is premised on the utilization of the Nuxt ",[39,39170,39172],{"href":38759,"rel":39171},[77],[6850,39173,38276],{}," package. Nevertheless, its usage is not essential.",[35,39176,39177,39178,39180,39181,39184,39185,39188],{},"The initial step in securing this ",[6850,39179,38945],{}," is verifying if the user is ",[6850,39182,39183],{},"authenticated",". If the user lacks authentication, it should return a ",[156,39186,39187],{},"401 Unauthorized"," error.",[35,39190,39191,39192,39194,39195,39198,39199,39201],{},"Upon exploring the ",[6850,39193,38276],{}," package, I discovered a server utility termed ",[156,39196,39197],{},"requireUserSession",". This utility can be employed at an endpoint's inception to attempt retrieving the user's session and triggering a ",[156,39200,39187],{}," error if there is no session, indicating the user is unauthenticated.",[192,39203,39205],{"className":1370,"code":39204,"language":1373,"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,39206,39207,39229,39242,39246,39260,39264,39270],{"__ignoreMap":13},[200,39208,39209,39211,39213,39215,39217,39219,39221,39223,39225,39227],{"class":202,"line":203},[200,39210,1380],{"class":206},[200,39212,1383],{"class":206},[200,39214,38960],{"class":223},[200,39216,227],{"class":210},[200,39218,3711],{"class":379},[200,39220,1095],{"class":214},[200,39222,25386],{"class":386},[200,39224,2089],{"class":214},[200,39226,393],{"class":379},[200,39228,280],{"class":214},[200,39230,39231,39233,39236,39238,39240],{"class":202,"line":14},[200,39232,6165],{"class":206},[200,39234,39235],{"class":223}," requireUserSession",[200,39237,227],{"class":235},[200,39239,25386],{"class":210},[200,39241,416],{"class":235},[200,39243,39244],{"class":202,"line":255},[200,39245,529],{"emptyLinePlaceholder":20},[200,39247,39248,39250,39252,39254,39257],{"class":202,"line":272},[200,39249,3723],{"class":379},[200,39251,39010],{"class":210},[200,39253,510],{"class":214},[200,39255,39256],{"class":214}," {}",[200,39258,39259],{"class":401}," \u002F\u002F ...\n",[200,39261,39262],{"class":202,"line":283},[200,39263,529],{"emptyLinePlaceholder":20},[200,39265,39266,39268],{"class":202,"line":300},[200,39267,3743],{"class":206},[200,39269,39120],{"class":210},[200,39271,39272,39274],{"class":202,"line":310},[200,39273,413],{"class":214},[200,39275,416],{"class":210},[35,39277,39278,39279,39282],{},"This is the preliminary step towards safeguarding the endpoint against ",[6850,39280,39281],{},"unauthenticated users",". However, if anyone can register an account, they may still access the content of a draft publication. Further action is necessary.",[51,39284,39286],{"id":39285},"only-admins","Only admins",[35,39288,39289,39290,39292,39293,39296,39297,39300],{},"Building on ",[156,39291,39197],{},", I can readily devise a new utility named ",[156,39294,39295],{},"requireAdminSession",". This session will verify if the user is authenticated and has admin rights. If the user is not an admin, a ",[156,39298,39299],{},"403 Forbidden"," error should be returned. Unauthorized relates to authentication, while Forbidden pertains to authorization.",[192,39302,39304],{"className":1370,"code":39303,"language":1373,"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 || 'Unauthorized',\n    })\n  }\n\n  return userSession as UserSessionRequired\n}\n",[156,39305,39306,39370,39389,39404,39408,39434,39446,39467,39491,39497,39501,39505,39516],{"__ignoreMap":13},[200,39307,39308,39310,39312,39314,39317,39319,39321,39323,39326,39328,39331,39333,39335,39338,39341,39343,39345,39348,39350,39352,39354,39356,39359,39361,39363,39366,39368],{"class":202,"line":203},[200,39309,1380],{"class":206},[200,39311,380],{"class":379},[200,39313,4987],{"class":379},[200,39315,39316],{"class":223}," requireAdminSession",[200,39318,227],{"class":214},[200,39320,25386],{"class":386},[200,39322,239],{"class":214},[200,39324,39325],{"class":1978}," H3Event",[200,39327,550],{"class":214},[200,39329,39330],{"class":386}," opts",[200,39332,239],{"class":214},[200,39334,318],{"class":214},[200,39336,39337],{"class":235}," statusCode",[200,39339,39340],{"class":214},"?:",[200,39342,20132],{"class":1978},[200,39344,550],{"class":214},[200,39346,39347],{"class":235}," message",[200,39349,39340],{"class":214},[200,39351,22708],{"class":1978},[200,39353,981],{"class":214},[200,39355,510],{"class":214},[200,39357,39358],{"class":214}," {}):",[200,39360,4996],{"class":1978},[200,39362,600],{"class":214},[200,39364,39365],{"class":1978},"UserSessionRequired",[200,39367,675],{"class":214},[200,39369,280],{"class":214},[200,39371,39372,39374,39377,39379,39381,39383,39385,39387],{"class":202,"line":14},[200,39373,3723],{"class":379},[200,39375,39376],{"class":210}," userSession",[200,39378,510],{"class":214},[200,39380,452],{"class":206},[200,39382,39235],{"class":223},[200,39384,227],{"class":235},[200,39386,25386],{"class":210},[200,39388,416],{"class":235},[200,39390,39391,39393,39395,39397,39399,39401],{"class":202,"line":255},[200,39392,3723],{"class":379},[200,39394,318],{"class":214},[200,39396,34776],{"class":210},[200,39398,981],{"class":214},[200,39400,510],{"class":214},[200,39402,39403],{"class":210}," userSession\n",[200,39405,39406],{"class":202,"line":272},[200,39407,529],{"emptyLinePlaceholder":20},[200,39409,39410,39412,39414,39416,39418,39421,39423,39425,39428,39430,39432],{"class":202,"line":283},[200,39411,20075],{"class":206},[200,39413,1095],{"class":235},[200,39415,26808],{"class":210},[200,39417,215],{"class":214},[200,39419,39420],{"class":210},"roleType",[200,39422,13388],{"class":214},[200,39424,242],{"class":214},[200,39426,39427],{"class":245},"admin",[200,39429,249],{"class":214},[200,39431,5267],{"class":235},[200,39433,230],{"class":214},[200,39435,39436,39439,39442,39444],{"class":202,"line":300},[200,39437,39438],{"class":206},"    throw",[200,39440,39441],{"class":223}," createError",[200,39443,227],{"class":235},[200,39445,230],{"class":214},[200,39447,39448,39451,39453,39455,39457,39460,39462,39465],{"class":202,"line":310},[200,39449,39450],{"class":235},"      statusCode",[200,39452,239],{"class":214},[200,39454,39330],{"class":210},[200,39456,215],{"class":214},[200,39458,39459],{"class":210},"statusCode",[200,39461,15009],{"class":214},[200,39463,39464],{"class":2045}," 403",[200,39466,252],{"class":214},[200,39468,39469,39472,39474,39476,39478,39480,39482,39484,39487,39489],{"class":202,"line":336},[200,39470,39471],{"class":235},"      message",[200,39473,239],{"class":214},[200,39475,39330],{"class":210},[200,39477,215],{"class":214},[200,39479,29574],{"class":210},[200,39481,15009],{"class":214},[200,39483,242],{"class":214},[200,39485,39486],{"class":245},"Unauthorized",[200,39488,249],{"class":214},[200,39490,252],{"class":214},[200,39492,39493,39495],{"class":202,"line":342},[200,39494,2096],{"class":214},[200,39496,416],{"class":235},[200,39498,39499],{"class":202,"line":365},[200,39500,4490],{"class":214},[200,39502,39503],{"class":202,"line":371},[200,39504,529],{"emptyLinePlaceholder":20},[200,39506,39507,39509,39511,39513],{"class":202,"line":398},[200,39508,3743],{"class":206},[200,39510,39376],{"class":210},[200,39512,14674],{"class":206},[200,39514,39515],{"class":1978}," UserSessionRequired\n",[200,39517,39518],{"class":202,"line":405},[200,39519,1130],{"class":214},[35,39521,39522,39523,39525,39526,39528],{},"Subsequently, ",[156,39524,39197],{}," can be replaced with ",[156,39527,39295],{}," in the endpoint.",[192,39530,39532],{"className":1370,"code":39531,"language":1373,"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,39533,39534,39556,39568,39572,39584,39588,39594],{"__ignoreMap":13},[200,39535,39536,39538,39540,39542,39544,39546,39548,39550,39552,39554],{"class":202,"line":203},[200,39537,1380],{"class":206},[200,39539,1383],{"class":206},[200,39541,38960],{"class":223},[200,39543,227],{"class":210},[200,39545,3711],{"class":379},[200,39547,1095],{"class":214},[200,39549,25386],{"class":386},[200,39551,2089],{"class":214},[200,39553,393],{"class":379},[200,39555,280],{"class":214},[200,39557,39558,39560,39562,39564,39566],{"class":202,"line":14},[200,39559,6165],{"class":206},[200,39561,39316],{"class":223},[200,39563,227],{"class":235},[200,39565,25386],{"class":210},[200,39567,416],{"class":235},[200,39569,39570],{"class":202,"line":255},[200,39571,529],{"emptyLinePlaceholder":20},[200,39573,39574,39576,39578,39580,39582],{"class":202,"line":272},[200,39575,3723],{"class":379},[200,39577,39010],{"class":210},[200,39579,510],{"class":214},[200,39581,39256],{"class":214},[200,39583,39259],{"class":401},[200,39585,39586],{"class":202,"line":283},[200,39587,529],{"emptyLinePlaceholder":20},[200,39589,39590,39592],{"class":202,"line":300},[200,39591,3743],{"class":206},[200,39593,39120],{"class":210},[200,39595,39596,39598],{"class":202,"line":310},[200,39597,413],{"class":214},[200,39599,416],{"class":210},[35,39601,39602,39603,39606,39607,39609],{},"Now, only ",[6850,39604,39605],{},"admins",", users assigned the ",[156,39608,39427],{}," role, can access a publication's content.",[35,39611,39612],{},"However, this approach is not entirely effective, as if the publication is published, I wish to permit all users to access it. Currently, only admins can view the content of a publication, irrespective of the publication's status.",[51,39614,39616],{"id":39615},"issues-with-these-approaches","Issues with these approaches",[35,39618,39619,39620,39623],{},"The aforementioned strategies lack sufficient flexibility for a detailed ",[6850,39621,39622],{},"authorization system",". They cannot accommodate situations where the author of the publication should have access, even if the publication is unpublished.",[35,39625,39626,39627,239],{},"To address this, I could draft another utility termed ",[156,39628,39629],{},"requirePublicationAccess",[192,39631,39633],{"className":1370,"code":39632,"language":1373,"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 || 'Unauthorized',\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 || 'Forbidden',\n  })\n}\n",[156,39634,39635,39701,39720,39734,39738,39762,39768,39772,39787,39797,39816,39838,39844,39848,39852,39875,39881,39885,39907,39913,39917,39927,39946,39970,39976],{"__ignoreMap":13},[200,39636,39637,39639,39641,39643,39646,39648,39650,39652,39654,39656,39658,39660,39663,39665,39667,39669,39671,39673,39675,39677,39679,39681,39683,39685,39687,39689,39691,39693,39695,39697,39699],{"class":202,"line":203},[200,39638,1380],{"class":206},[200,39640,380],{"class":379},[200,39642,4987],{"class":379},[200,39644,39645],{"class":223}," requirePublicationAccess",[200,39647,227],{"class":214},[200,39649,25386],{"class":386},[200,39651,239],{"class":214},[200,39653,39325],{"class":1978},[200,39655,550],{"class":214},[200,39657,39010],{"class":386},[200,39659,239],{"class":214},[200,39661,39662],{"class":1978}," Publication",[200,39664,550],{"class":214},[200,39666,39330],{"class":386},[200,39668,239],{"class":214},[200,39670,318],{"class":214},[200,39672,39337],{"class":235},[200,39674,39340],{"class":214},[200,39676,20132],{"class":1978},[200,39678,550],{"class":214},[200,39680,39347],{"class":235},[200,39682,39340],{"class":214},[200,39684,22708],{"class":1978},[200,39686,981],{"class":214},[200,39688,510],{"class":214},[200,39690,39358],{"class":214},[200,39692,4996],{"class":1978},[200,39694,600],{"class":214},[200,39696,39365],{"class":1978},[200,39698,675],{"class":214},[200,39700,280],{"class":214},[200,39702,39703,39705,39707,39709,39711,39714,39716,39718],{"class":202,"line":14},[200,39704,3723],{"class":379},[200,39706,39376],{"class":210},[200,39708,510],{"class":214},[200,39710,452],{"class":206},[200,39712,39713],{"class":223}," getUserSession",[200,39715,227],{"class":235},[200,39717,25386],{"class":210},[200,39719,416],{"class":235},[200,39721,39722,39724,39726,39728,39730,39732],{"class":202,"line":255},[200,39723,3723],{"class":379},[200,39725,318],{"class":214},[200,39727,34776],{"class":210},[200,39729,981],{"class":214},[200,39731,510],{"class":214},[200,39733,39403],{"class":210},[200,39735,39736],{"class":202,"line":272},[200,39737,529],{"emptyLinePlaceholder":20},[200,39739,39740,39742,39744,39747,39749,39752,39754,39756,39758,39760],{"class":202,"line":283},[200,39741,20075],{"class":206},[200,39743,1095],{"class":235},[200,39745,39746],{"class":210},"publication",[200,39748,215],{"class":214},[200,39750,39751],{"class":210},"status",[200,39753,3283],{"class":214},[200,39755,242],{"class":214},[200,39757,39139],{"class":245},[200,39759,249],{"class":214},[200,39761,416],{"class":235},[200,39763,39764,39766],{"class":202,"line":300},[200,39765,3296],{"class":206},[200,39767,39403],{"class":210},[200,39769,39770],{"class":202,"line":310},[200,39771,529],{"emptyLinePlaceholder":20},[200,39773,39774,39776,39778,39780,39783,39785],{"class":202,"line":336},[200,39775,20075],{"class":206},[200,39777,1095],{"class":235},[200,39779,11837],{"class":214},[200,39781,39782],{"class":210},"userSession",[200,39784,5267],{"class":235},[200,39786,230],{"class":214},[200,39788,39789,39791,39793,39795],{"class":202,"line":342},[200,39790,39438],{"class":206},[200,39792,39441],{"class":223},[200,39794,227],{"class":235},[200,39796,230],{"class":214},[200,39798,39799,39801,39803,39805,39807,39809,39811,39814],{"class":202,"line":365},[200,39800,39450],{"class":235},[200,39802,239],{"class":214},[200,39804,39330],{"class":210},[200,39806,215],{"class":214},[200,39808,39459],{"class":210},[200,39810,15009],{"class":214},[200,39812,39813],{"class":2045}," 401",[200,39815,252],{"class":214},[200,39817,39818,39820,39822,39824,39826,39828,39830,39832,39834,39836],{"class":202,"line":371},[200,39819,39471],{"class":235},[200,39821,239],{"class":214},[200,39823,39330],{"class":210},[200,39825,215],{"class":214},[200,39827,29574],{"class":210},[200,39829,15009],{"class":214},[200,39831,242],{"class":214},[200,39833,39486],{"class":245},[200,39835,249],{"class":214},[200,39837,252],{"class":214},[200,39839,39840,39842],{"class":202,"line":398},[200,39841,2096],{"class":214},[200,39843,416],{"class":235},[200,39845,39846],{"class":202,"line":405},[200,39847,4490],{"class":214},[200,39849,39850],{"class":202,"line":410},[200,39851,529],{"emptyLinePlaceholder":20},[200,39853,39854,39856,39858,39860,39862,39865,39867,39869,39871,39873],{"class":202,"line":861},[200,39855,20075],{"class":206},[200,39857,1095],{"class":235},[200,39859,39746],{"class":210},[200,39861,215],{"class":214},[200,39863,39864],{"class":210},"authorId",[200,39866,3283],{"class":214},[200,39868,34776],{"class":210},[200,39870,215],{"class":214},[200,39872,3280],{"class":210},[200,39874,416],{"class":235},[200,39876,39877,39879],{"class":202,"line":890},[200,39878,3296],{"class":206},[200,39880,39403],{"class":210},[200,39882,39883],{"class":202,"line":900},[200,39884,529],{"emptyLinePlaceholder":20},[200,39886,39887,39889,39891,39893,39895,39897,39899,39901,39903,39905],{"class":202,"line":905},[200,39888,20075],{"class":206},[200,39890,1095],{"class":235},[200,39892,26808],{"class":210},[200,39894,215],{"class":214},[200,39896,39420],{"class":210},[200,39898,3283],{"class":214},[200,39900,242],{"class":214},[200,39902,39427],{"class":245},[200,39904,249],{"class":214},[200,39906,416],{"class":235},[200,39908,39909,39911],{"class":202,"line":931},[200,39910,3296],{"class":206},[200,39912,39403],{"class":210},[200,39914,39915],{"class":202,"line":3640},[200,39916,529],{"emptyLinePlaceholder":20},[200,39918,39919,39921,39923,39925],{"class":202,"line":3645},[200,39920,20264],{"class":206},[200,39922,39441],{"class":223},[200,39924,227],{"class":235},[200,39926,230],{"class":214},[200,39928,39929,39932,39934,39936,39938,39940,39942,39944],{"class":202,"line":4020},[200,39930,39931],{"class":235},"    statusCode",[200,39933,239],{"class":214},[200,39935,39330],{"class":210},[200,39937,215],{"class":214},[200,39939,39459],{"class":210},[200,39941,15009],{"class":214},[200,39943,39464],{"class":2045},[200,39945,252],{"class":214},[200,39947,39948,39951,39953,39955,39957,39959,39961,39963,39966,39968],{"class":202,"line":4475},[200,39949,39950],{"class":235},"    message",[200,39952,239],{"class":214},[200,39954,39330],{"class":210},[200,39956,215],{"class":214},[200,39958,29574],{"class":210},[200,39960,15009],{"class":214},[200,39962,242],{"class":214},[200,39964,39965],{"class":245},"Forbidden",[200,39967,249],{"class":214},[200,39969,252],{"class":214},[200,39971,39972,39974],{"class":202,"line":4481},[200,39973,5080],{"class":214},[200,39975,416],{"class":235},[200,39977,39978],{"class":202,"line":4487},[200,39979,1130],{"class":214},[35,39981,39982],{},"This solution is adequate until I need to manage additional endpoints. This approach will result in excessive code duplication concerning error handling. Additionally, the code becomes more challenging to test due to the necessity of managing an entire H3 event.",[35,39984,39985,39986,39989,39990,39993,39994,1638,39996,215],{},"Without a superior approach, I continue developing Orion but soon encountered another challenge: I need to implement the same ",[6850,39987,39988],{},"authorization logic on the client side",". I do not want to show an edit button to a user who is neither the author of the publication nor an admin, and I do not wish to hide this edit button from the admin if I decide to permit publication editing on the server. Similar to ",[39,39991,39992],{"href":38821},"forms validation",", duplicating logic can result in substantial inconsistencies between the client and the server, leading to customer frustration. The utilities I have created thus far are solely server-side due to the use of ",[156,39995,39197],{},[156,39997,39998],{},"getUserSession",[35,40000,40001],{},"Let's take a step back to comprehend how this issue might be resolved. Authorization consists of three components:",[1576,40003,40004,40010,40015],{},[71,40005,40006,40009],{},[6850,40007,40008],{},"allow"," access to a resource",[71,40011,40012,40009],{},[6850,40013,40014],{},"deny",[71,40016,40017,40009],{},[6850,40018,40019],{},"authorize",[35,40021,40022,40023,40025],{},"The first two are straightforward conditions yielding a boolean response to questions like \"Can I access this publication? Yes or No\". The developer must manually handle this response. The third one grants access to the resource. For the question \"Can I access this publication?\", there is neither a \"yes\" nor \"no\"; nothing happens if the answer is \"yes\", but an error is raised if it is \"no\". The ",[156,40024,40019],{}," sends an error automatically.",[35,40027,40028,40029,215],{},"These elements hold no direct relation to the client, server, or the framework in use or the authentication system. Armed with this knowledge, I began working on a local module for Orion to manage authorization more flexibly. After several days of effort, I am proud to introduce ",[6850,40030,40031],{},"nuxt-authorization",[35,40033,40034,40035,215],{},"Initially, I aimed to develop both a Nitro and a Nuxt package, but Nitro modules are not ready yet. Nonetheless, I submitted some pull requests to the Nitro repository to enhance the module system. One of these is the ",[39,40036,40038],{"href":38839,"rel":40037},[77],"module author guide",[51,40040,40042],{"id":40041},"introducing-nuxt-authorization","Introducing nuxt-authorization",[35,40044,40045,40046,40048,40049,215],{},"This module offers a simple yet powerful method for managing ",[6850,40047,38904],{}," in a Nuxt application, applicable to both client and server. It is authentication system agnostic but can be seamlessly integrated with ",[39,40050,40052],{"href":38759,"rel":40051},[77],[6850,40053,38276],{},[35,40055,40056,40057],{},"For instructions on its usage, visit the GitHub repository: ",[1582,40058,40060],{"repo":40059},"barbapapazes\u002Fnuxt-authorization",". To delve into the design and implementation, continue reading.",[35,40062,40063,40064,215],{},"The module remains in its early stages of development, and if you have any feedback or suggestions for improvement, feel free to open an issue on the GitHub repository or contact me on ",[39,40065,38483],{"href":40066,"rel":40067},"https:\u002F\u002Fx.com\u002Fbarbapapazes",[77],[1913,40069,40071],{"id":40070},"problems-i-want-to-solve","Problems I want to solve",[35,40073,40074,40075,40077],{},"Before describing what has been accomplished with ",[6850,40076,40031],{},", let's examine the problems I intended to solve.",[35,40079,40080],{},"Much like forms validation, I aim to maintain a single source of truth and a single API for both the app and server. This is crucial to avoid discrepancies between the app and server.",[35,40082,40083,40084,40087],{},"I also seek the ability to define ",[6850,40085,40086],{},"authorization logic"," simply and grouped by resource. This approach enhances readability, maintainability, and consistency across various application aspects, such as forms validation.",[35,40089,40090,40091,13731,40093,40095,40096,40098,40099,40101,40102,215],{},"The authorization logic must be adaptable enough to accommodate a varied range of use cases, from ",[156,40092,40008],{},[156,40094,40014],{}," conditions to more complex ",[156,40097,40019],{}," conditions. The errors produced by the ",[156,40100,40019],{}," condition should be customizable to enhance user experience. Not all unauthorized errors need to be a ",[156,40103,39299],{},[35,40105,40106],{},"Finally, I desire components that ease integrating authorization logic into templates. For instance, I want the ability to show or hide a button based on authorization logic.",[1913,40108,40110],{"id":40109},"design","Design",[35,40112,40113,40114,36817,40117,215],{},"The module comprises two components: the ",[6850,40115,40116],{},"ability",[6850,40118,40119],{},"bouncer",[35,40121,40122,40123,40125],{},"Abilities constitute the rules that embody the ",[6850,40124,40086],{},". They take a user and one or more resources and yield a deny or an allow condition. While they can be grouped by resource, they remain independent of one another.",[192,40127,40129],{"className":1370,"code":40128,"language":1373,"meta":13,"style":13},"export const editBook = defineAbility((user: User, book: Book) => {\n  return user.id === book.authorId\n})\n",[156,40130,40131,40171,40190],{"__ignoreMap":13},[200,40132,40133,40135,40137,40140,40142,40145,40147,40149,40151,40153,40155,40157,40160,40162,40165,40167,40169],{"class":202,"line":203},[200,40134,1380],{"class":206},[200,40136,14301],{"class":379},[200,40138,40139],{"class":210}," editBook ",[200,40141,449],{"class":214},[200,40143,40144],{"class":223}," defineAbility",[200,40146,227],{"class":210},[200,40148,227],{"class":214},[200,40150,26808],{"class":386},[200,40152,239],{"class":214},[200,40154,34877],{"class":1978},[200,40156,550],{"class":214},[200,40158,40159],{"class":386}," book",[200,40161,239],{"class":214},[200,40163,40164],{"class":1978}," Book",[200,40166,2089],{"class":214},[200,40168,393],{"class":379},[200,40170,280],{"class":214},[200,40172,40173,40175,40177,40179,40181,40183,40185,40187],{"class":202,"line":14},[200,40174,3743],{"class":206},[200,40176,34776],{"class":210},[200,40178,215],{"class":214},[200,40180,3280],{"class":210},[200,40182,3283],{"class":214},[200,40184,40159],{"class":210},[200,40186,215],{"class":214},[200,40188,40189],{"class":210},"authorId\n",[200,40191,40192,40194],{"class":202,"line":255},[200,40193,413],{"class":214},[200,40195,416],{"class":210},[35,40197,40198],{},"In the above example, only the author of a book is allowed to edit it. By default, abilities are executed only if the user is authenticated, but it is possible to permit guest access to certain resources.",[192,40200,40202],{"className":1370,"code":40201,"language":1373,"meta":13,"style":13},"export const listBooks = defineAbility((user: User | null) => {\n  return true\n})\n",[156,40203,40204,40238,40244],{"__ignoreMap":13},[200,40205,40206,40208,40210,40213,40215,40217,40219,40221,40223,40225,40227,40230,40232,40234,40236],{"class":202,"line":203},[200,40207,1380],{"class":206},[200,40209,14301],{"class":379},[200,40211,40212],{"class":210}," listBooks ",[200,40214,449],{"class":214},[200,40216,40144],{"class":223},[200,40218,227],{"class":210},[200,40220,227],{"class":214},[200,40222,26808],{"class":386},[200,40224,239],{"class":214},[200,40226,34877],{"class":1978},[200,40228,40229],{"class":214}," |",[200,40231,12422],{"class":1978},[200,40233,2089],{"class":214},[200,40235,393],{"class":379},[200,40237,280],{"class":214},[200,40239,40240,40242],{"class":202,"line":14},[200,40241,3743],{"class":206},[200,40243,22192],{"class":2423},[200,40245,40246,40248],{"class":202,"line":255},[200,40247,413],{"class":214},[200,40249,416],{"class":210},[35,40251,40252],{},"In this instance, everyone, including unauthenticated users, is permitted to list books.",[35,40254,419,40255,40258],{},[156,40256,40257],{},"defineAbility"," function acts as a factory that creates an ability but nothing beyond that. It needs to be used with bouncer functions.",[35,40260,40261,40262,40264],{},"In a server endpoint, the ",[156,40263,40019],{}," function can be employed to grant access to a resource based on the abilities.",[192,40266,40268],{"className":1370,"code":40267,"language":1373,"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,40269,40270,40292,40310,40314,40343,40347,40354],{"__ignoreMap":13},[200,40271,40272,40274,40276,40278,40280,40282,40284,40286,40288,40290],{"class":202,"line":203},[200,40273,1380],{"class":206},[200,40275,1383],{"class":206},[200,40277,38960],{"class":223},[200,40279,227],{"class":210},[200,40281,3711],{"class":379},[200,40283,1095],{"class":214},[200,40285,25386],{"class":386},[200,40287,2089],{"class":214},[200,40289,393],{"class":379},[200,40291,280],{"class":214},[200,40293,40294,40296,40299,40301,40303,40305,40308],{"class":202,"line":14},[200,40295,6165],{"class":206},[200,40297,40298],{"class":223}," authorize",[200,40300,227],{"class":235},[200,40302,25386],{"class":210},[200,40304,550],{"class":214},[200,40306,40307],{"class":210}," listBooks",[200,40309,416],{"class":235},[200,40311,40312],{"class":202,"line":255},[200,40313,529],{"emptyLinePlaceholder":20},[200,40315,40316,40318,40321,40323,40325,40327,40329,40331,40333,40336,40338,40341],{"class":202,"line":272},[200,40317,3723],{"class":379},[200,40319,40320],{"class":210}," books",[200,40322,510],{"class":214},[200,40324,452],{"class":206},[200,40326,39017],{"class":210},[200,40328,215],{"class":214},[200,40330,3509],{"class":210},[200,40332,215],{"class":214},[200,40334,40335],{"class":210},"books",[200,40337,215],{"class":214},[200,40339,40340],{"class":223},"findMany",[200,40342,466],{"class":235},[200,40344,40345],{"class":202,"line":283},[200,40346,529],{"emptyLinePlaceholder":20},[200,40348,40349,40351],{"class":202,"line":300},[200,40350,3743],{"class":206},[200,40352,40353],{"class":210}," books\n",[200,40355,40356,40358],{"class":202,"line":310},[200,40357,413],{"class":214},[200,40359,416],{"class":210},[35,40361,419,40362,40364,40365,40367,40368,13731,40371,40374],{},[156,40363,40019],{}," function will trigger a ",[156,40366,39299],{}," error if the user is not permitted to list books. More granular control can be achieved using the ",[156,40369,40370],{},"allows",[156,40372,40373],{},"denies"," functions.",[192,40376,40378],{"className":1370,"code":40377,"language":1373,"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,40379,40380,40402,40427,40433,40437,40463,40467,40473],{"__ignoreMap":13},[200,40381,40382,40384,40386,40388,40390,40392,40394,40396,40398,40400],{"class":202,"line":203},[200,40383,1380],{"class":206},[200,40385,1383],{"class":206},[200,40387,38960],{"class":223},[200,40389,227],{"class":210},[200,40391,3711],{"class":379},[200,40393,1095],{"class":214},[200,40395,25386],{"class":386},[200,40397,2089],{"class":214},[200,40399,393],{"class":379},[200,40401,280],{"class":214},[200,40403,40404,40406,40408,40410,40413,40415,40417,40419,40421,40423,40425],{"class":202,"line":14},[200,40405,20075],{"class":206},[200,40407,1095],{"class":235},[200,40409,207],{"class":206},[200,40411,40412],{"class":223}," denies",[200,40414,227],{"class":235},[200,40416,25386],{"class":210},[200,40418,550],{"class":214},[200,40420,40307],{"class":210},[200,40422,550],{"class":214},[200,40424,40159],{"class":210},[200,40426,585],{"class":235},[200,40428,40429,40431],{"class":202,"line":255},[200,40430,3296],{"class":206},[200,40432,6421],{"class":235},[200,40434,40435],{"class":202,"line":272},[200,40436,529],{"emptyLinePlaceholder":20},[200,40438,40439,40441,40443,40445,40447,40449,40451,40453,40455,40457,40459,40461],{"class":202,"line":283},[200,40440,3723],{"class":379},[200,40442,40320],{"class":210},[200,40444,510],{"class":214},[200,40446,452],{"class":206},[200,40448,39017],{"class":210},[200,40450,215],{"class":214},[200,40452,3509],{"class":210},[200,40454,215],{"class":214},[200,40456,40335],{"class":210},[200,40458,215],{"class":214},[200,40460,40340],{"class":223},[200,40462,466],{"class":235},[200,40464,40465],{"class":202,"line":300},[200,40466,529],{"emptyLinePlaceholder":20},[200,40468,40469,40471],{"class":202,"line":310},[200,40470,3743],{"class":206},[200,40472,40353],{"class":210},[200,40474,40475,40477],{"class":202,"line":336},[200,40476,413],{"class":214},[200,40478,416],{"class":210},[35,40480,40481],{},"Instead of triggering an error, an empty array is returned if the user is not allowed to list books. The appropriate method for handling unauthorized access will depend on your business logic.",[35,40483,40484,40485,40488],{},"For example, returning a ",[156,40486,40487],{},"404 Not Found"," error if the user is not authorized to access a specific resource can preserve data privacy. Disclosing the existence of the resource could pose a security risk.",[35,40490,40491,40492,1638,40494,40374],{},"This customization is attainable within the abilities through the ",[156,40493,40008],{},[156,40495,40014],{},[192,40497,40499],{"className":1370,"code":40498,"language":1373,"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: 'Not Found',\n    })\n  }\n\n  return allow()\n})\n",[156,40500,40501,40538,40563,40574,40585,40600,40606,40610,40614,40623],{"__ignoreMap":13},[200,40502,40503,40505,40507,40510,40512,40514,40516,40518,40520,40522,40524,40526,40528,40530,40532,40534,40536],{"class":202,"line":203},[200,40504,1380],{"class":206},[200,40506,14301],{"class":379},[200,40508,40509],{"class":210}," viewBook ",[200,40511,449],{"class":214},[200,40513,40144],{"class":223},[200,40515,227],{"class":210},[200,40517,227],{"class":214},[200,40519,26808],{"class":386},[200,40521,239],{"class":214},[200,40523,34877],{"class":1978},[200,40525,550],{"class":214},[200,40527,40159],{"class":386},[200,40529,239],{"class":214},[200,40531,40164],{"class":1978},[200,40533,2089],{"class":214},[200,40535,393],{"class":379},[200,40537,280],{"class":214},[200,40539,40540,40542,40544,40547,40549,40551,40553,40555,40557,40559,40561],{"class":202,"line":14},[200,40541,20075],{"class":206},[200,40543,1095],{"class":235},[200,40545,40546],{"class":210},"book",[200,40548,215],{"class":214},[200,40550,39751],{"class":210},[200,40552,3283],{"class":214},[200,40554,242],{"class":214},[200,40556,39136],{"class":245},[200,40558,249],{"class":214},[200,40560,5267],{"class":235},[200,40562,230],{"class":214},[200,40564,40565,40567,40570,40572],{"class":202,"line":255},[200,40566,3296],{"class":206},[200,40568,40569],{"class":223}," deny",[200,40571,227],{"class":235},[200,40573,230],{"class":214},[200,40575,40576,40578,40580,40583],{"class":202,"line":272},[200,40577,39450],{"class":235},[200,40579,239],{"class":214},[200,40581,40582],{"class":2045}," 404",[200,40584,252],{"class":214},[200,40586,40587,40589,40591,40593,40596,40598],{"class":202,"line":283},[200,40588,39471],{"class":235},[200,40590,239],{"class":214},[200,40592,242],{"class":214},[200,40594,40595],{"class":245},"Not Found",[200,40597,249],{"class":214},[200,40599,252],{"class":214},[200,40601,40602,40604],{"class":202,"line":300},[200,40603,2096],{"class":214},[200,40605,416],{"class":235},[200,40607,40608],{"class":202,"line":310},[200,40609,4490],{"class":214},[200,40611,40612],{"class":202,"line":336},[200,40613,529],{"emptyLinePlaceholder":20},[200,40615,40616,40618,40621],{"class":202,"line":342},[200,40617,3743],{"class":206},[200,40619,40620],{"class":223}," allow",[200,40622,466],{"class":235},[200,40624,40625,40627],{"class":202,"line":365},[200,40626,413],{"class":214},[200,40628,416],{"class":210},[35,40630,36049,40631,40633,40634,40636,40637,215],{},[156,40632,40019],{}," function will throw a ",[156,40635,40487],{}," error if the user is not permitted to view a draft book, rather than the default ",[156,40638,39299],{},[35,40640,40641,40642,3076,40644,3080,40646,40648,40649,215],{},"On the client side, the ",[156,40643,40370],{},[156,40645,40373],{},[156,40647,40019],{}," functions can also be utilized to administer the ",[6850,40650,40086],{},[35,40652,40653,40654,1638,40657,40660,40661,215],{},"In addition, these functions offer two components: ",[156,40655,40656],{},"Can",[156,40658,40659],{},"Cannot",". These components enable portions of a template to be shown or hidden based on the ",[6850,40662,40086],{},[192,40664,40666],{"className":21417,"code":40665,"language":12060,"meta":13,"style":13},"\u003Ctemplate>\n  \u003CCan :ability=\"editBook\" :args=\"[book]\">\n    \u003Cbutton>Edit\u003C\u002Fbutton>\n  \u003C\u002FCan>\n\u003C\u002Ftemplate>\n",[156,40667,40668,40676,40713,40730,40738],{"__ignoreMap":13},[200,40669,40670,40672,40674],{"class":202,"line":203},[200,40671,600],{"class":214},[200,40673,21428],{"class":235},[200,40675,648],{"class":214},[200,40677,40678,40680,40682,40684,40686,40688,40690,40693,40695,40697,40699,40701,40703,40705,40707,40709,40711],{"class":202,"line":14},[200,40679,657],{"class":214},[200,40681,40656],{"class":235},[200,40683,28009],{"class":214},[200,40685,40116],{"class":379},[200,40687,449],{"class":214},[200,40689,611],{"class":214},[200,40691,40692],{"class":210},"editBook",[200,40694,611],{"class":214},[200,40696,28009],{"class":214},[200,40698,28625],{"class":379},[200,40700,449],{"class":214},[200,40702,611],{"class":214},[200,40704,9858],{"class":214},[200,40706,40546],{"class":210},[200,40708,360],{"class":214},[200,40710,611],{"class":214},[200,40712,648],{"class":214},[200,40714,40715,40717,40719,40721,40724,40726,40728],{"class":202,"line":255},[200,40716,804],{"class":214},[200,40718,910],{"class":235},[200,40720,675],{"class":214},[200,40722,40723],{"class":210},"Edit",[200,40725,681],{"class":214},[200,40727,910],{"class":235},[200,40729,648],{"class":214},[200,40731,40732,40734,40736],{"class":202,"line":272},[200,40733,893],{"class":214},[200,40735,40656],{"class":235},[200,40737,648],{"class":214},[200,40739,40740,40742,40744],{"class":202,"line":283},[200,40741,681],{"class":214},[200,40743,21428],{"class":235},[200,40745,648],{"class":214},[35,40747,40748,40749],{},"Consult the GitHub repository for more information: ",[1582,40750,215],{"repo":40059},[1913,40752,24102],{"id":24101},[35,40754,40755,40756,40761],{},"Both the code and the design of this package draw significant inspiration from the ",[39,40757,40760],{"href":40758,"rel":40759},"https:\u002F\u002Fdocs.adonisjs.com\u002Fguides\u002Fsecurity\u002Fauthorization",[77],"Adonis Bouncer",". It is well-crafted, and reinventing concepts unnecessarily is often futile.",[35,40763,40764],{},"I am already implementing this package in Orion, and the experience has been quite enjoyable. I am confident there is still potential for enhancement, but this module represents an initial step in empowering Nuxt developers and advancing Nuxt's full-stack capabilities. Contributions and suggestions for new features or improvements are welcome.",[35,40766,40767],{},"Orion is a community-driven collection of templates for your projects, spanning from landing pages to comprehensive web applications.",[35,40769,40770,40771,40773,40774,40777],{},"I hope this article and module assist you in managing ",[6850,40772,38904],{}," within your Nuxt application, thereby preventing and restricting ",[6850,40775,40776],{},"unauthorized access"," to your data. Security is imperative.",[35,40779,38862],{},[1171,40781,40782],{},"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":40784},[40785,40786,40787,40788,40789],{"id":29348,"depth":14,"text":29349},{"id":39164,"depth":14,"text":39165},{"id":39285,"depth":14,"text":39286},{"id":39615,"depth":14,"text":39616},{"id":40041,"depth":14,"text":40042,"children":40790},[40791,40792,40793],{"id":40070,"depth":255,"text":40071},{"id":40109,"depth":255,"text":40110},{"id":24101,"depth":255,"text":24102},"2024-06-09","Secure your Nuxt app with advanced authorization techniques. Learn to restrict unauthorized access and safeguard sensitive data seamlessly.",{},"\u002Fposts\u002Fnuxt-going-full-stack-how-to-handle-authorization","---\ncontentId: f47ef865-e311-48e6-ab6e-6f7ee6bde6e8\ntitle: \"Nuxt Going Full-Stack: How to Handle Authorization?\"\ndescription: Secure your Nuxt app with advanced authorization techniques. Learn to restrict unauthorized access and safeguard sensitive data seamlessly.\ndate: 2024-06-09\n---\n\nWhen developing an application, it is essential to **grant** or **restrict** access to specific areas or data based on a user's role or permissions. This concept is known as **authorization**.\n\nAuthorization is a critical aspect of any application for **security** purposes. It is imperative to protect **sensitive data**, such as a user's email address or password, and prevent the disclosure of **private information** that could compromise user privacy and your business integrity.\n\nTo clarify: **authentication** involves verifying a user's identity, whereas **authorization** involves permitting or denying access to resources based on the user's role or permissions.\n\nWhile building Orion, I confronted this challenge and experimented with various approaches. In this article, I'll share several strategies to manage authorization in a NuxtHub application, ranging from the simplest to the most sophisticated methods. It is crucial to understand that the most complex method is not necessarily the best for every application.\n\nOrion serves as a community-driven collection of templates for your projects, from landing pages to complete web applications. NuxtHub functions as a deployment and management platform for Nuxt, powered by Cloudflare.\n\n\u003CCalloutReadMore id=\"c21a371e-abdd-4325-b296-dadcb8deec25\" \u002F>\n\n## The context\n\nConsider this **endpoint** in your Nuxt application:\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\nYou can access this endpoint by making a `GET \u002Fapi\u002Fpublication\u002F\u003Cid>` request. This endpoint returns a publication. At first glance, there is no issue with this endpoint. However, a closer examination reveals that a publication has a status that can be `draft`, `published`, or `deleted`.\n\nThe concern is that anyone can access this endpoint and view a **draft publication** or a **deleted publication**. Some might be able to view the content of a publication before it is published, which could contain vital information such as a press release or a product launch. Such a scenario could significantly harm your business.\n\nThis is a security issue that necessitates restricting access to this endpoint, utilizing the [principle of least privilege](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrinciple_of_least_privilege). Security is not merely a feature but should be ingrained in the system's design.\n\n## Authenticated or not\n\nThe following section is premised on the utilization of the Nuxt [**nuxt-auth-utils**](https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-auth-utils) package. Nevertheless, its usage is not essential.\n\nThe initial step in securing this **endpoint** is verifying if the user is **authenticated**. If the user lacks authentication, it should return a `401 Unauthorized` error.\n\nUpon exploring the **nuxt-auth-utils** package, I discovered a server utility termed `requireUserSession`. This utility can be employed at an endpoint's inception to attempt retrieving the user's session and triggering a `401 Unauthorized` error if there is no session, indicating the user is unauthenticated.\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\nThis is the preliminary step towards safeguarding the endpoint against **unauthenticated users**. However, if anyone can register an account, they may still access the content of a draft publication. Further action is necessary.\n\n## Only admins\n\nBuilding on `requireUserSession`, I can readily devise a new utility named `requireAdminSession`. This session will verify if the user is authenticated and has admin rights. If the user is not an admin, a `403 Forbidden` error should be returned. Unauthorized relates to authentication, while Forbidden pertains to authorization.\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 || 'Unauthorized',\n    })\n  }\n\n  return userSession as UserSessionRequired\n}\n```\n\nSubsequently, `requireUserSession` can be replaced with `requireAdminSession` in the 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\nNow, only **admins**, users assigned the `admin` role, can access a publication's content.\n\nHowever, this approach is not entirely effective, as if the publication is published, I wish to permit all users to access it. Currently, only admins can view the content of a publication, irrespective of the publication's status.\n\n## Issues with these approaches\n\nThe aforementioned strategies lack sufficient flexibility for a detailed **authorization system**. They cannot accommodate situations where the author of the publication should have access, even if the publication is unpublished.\n\nTo address this, I could draft another utility termed `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 || 'Unauthorized',\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 || 'Forbidden',\n  })\n}\n```\n\nThis solution is adequate until I need to manage additional endpoints. This approach will result in excessive code duplication concerning error handling. Additionally, the code becomes more challenging to test due to the necessity of managing an entire H3 event.\n\nWithout a superior approach, I continue developing Orion but soon encountered another challenge: I need to implement the same **authorization logic on the client side**. I do not want to show an edit button to a user who is neither the author of the publication nor an admin, and I do not wish to hide this edit button from the admin if I decide to permit publication editing on the server. Similar to [forms validation](.\u002Fnuxt-going-full-stack-how-to-streamline-form-validation.md), duplicating logic can result in substantial inconsistencies between the client and the server, leading to customer frustration. The utilities I have created thus far are solely server-side due to the use of `requireUserSession` and `getUserSession`.\n\nLet's take a step back to comprehend how this issue might be resolved. Authorization consists of three components:\n\n- **allow** access to a resource\n- **deny** access to a resource\n- **authorize** access to a resource\n\nThe first two are straightforward conditions yielding a boolean response to questions like \"Can I access this publication? Yes or No\". The developer must manually handle this response. The third one grants access to the resource. For the question \"Can I access this publication?\", there is neither a \"yes\" nor \"no\"; nothing happens if the answer is \"yes\", but an error is raised if it is \"no\". The `authorize` sends an error automatically.\n\nThese elements hold no direct relation to the client, server, or the framework in use or the authentication system. Armed with this knowledge, I began working on a local module for Orion to manage authorization more flexibly. After several days of effort, I am proud to introduce **nuxt-authorization**.\n\nInitially, I aimed to develop both a Nitro and a Nuxt package, but Nitro modules are not ready yet. Nonetheless, I submitted some pull requests to the Nitro repository to enhance the module system. One of these is the [module author guide](https:\u002F\u002Fgithub.com\u002Funjs\u002Fnitro\u002Fpull\u002F2508).\n\n## Introducing nuxt-authorization\n\nThis module offers a simple yet powerful method for managing **authorization** in a Nuxt application, applicable to both client and server. It is authentication system agnostic but can be seamlessly integrated with [**nuxt-auth-utils**](https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-auth-utils).\n\nFor instructions on its usage, visit the GitHub repository: \u003CGitHubLink repo=\"barbapapazes\u002Fnuxt-authorization\" \u002F>. To delve into the design and implementation, continue reading.\n\nThe module remains in its early stages of development, and if you have any feedback or suggestions for improvement, feel free to open an issue on the GitHub repository or contact me on [X](https:\u002F\u002Fx.com\u002Fbarbapapazes).\n\n### Problems I want to solve\n\nBefore describing what has been accomplished with **nuxt-authorization**, let's examine the problems I intended to solve.\n\nMuch like forms validation, I aim to maintain a single source of truth and a single API for both the app and server. This is crucial to avoid discrepancies between the app and server.\n\nI also seek the ability to define **authorization logic** simply and grouped by resource. This approach enhances readability, maintainability, and consistency across various application aspects, such as forms validation.\n\nThe authorization logic must be adaptable enough to accommodate a varied range of use cases, from `allow` or `deny` conditions to more complex `authorize` conditions. The errors produced by the `authorize` condition should be customizable to enhance user experience. Not all unauthorized errors need to be a `403 Forbidden`.\n\nFinally, I desire components that ease integrating authorization logic into templates. For instance, I want the ability to show or hide a button based on authorization logic.\n\n### Design\n\nThe module comprises two components: the **ability** and the **bouncer**.\n\nAbilities constitute the rules that embody the **authorization logic**. They take a user and one or more resources and yield a deny or an allow condition. While they can be grouped by resource, they remain independent of one another.\n\n```ts\nexport const editBook = defineAbility((user: User, book: Book) => {\n  return user.id === book.authorId\n})\n```\n\nIn the above example, only the author of a book is allowed to edit it. By default, abilities are executed only if the user is authenticated, but it is possible to permit guest access to certain resources.\n\n```ts\nexport const listBooks = defineAbility((user: User | null) => {\n  return true\n})\n```\n\nIn this instance, everyone, including unauthenticated users, is permitted to list books.\n\nThe `defineAbility` function acts as a factory that creates an ability but nothing beyond that. It needs to be used with bouncer functions.\n\nIn a server endpoint, the `authorize` function can be employed to grant access to a resource based on the abilities.\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\nThe `authorize` function will trigger a `403 Forbidden` error if the user is not permitted to list books. More granular control can be achieved using the `allows` or `denies` functions.\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\nInstead of triggering an error, an empty array is returned if the user is not allowed to list books. The appropriate method for handling unauthorized access will depend on your business logic.\n\nFor example, returning a `404 Not Found` error if the user is not authorized to access a specific resource can preserve data privacy. Disclosing the existence of the resource could pose a security risk.\n\nThis customization is attainable within the abilities through the `allow` and `deny` functions.\n\n```ts\nexport const viewBook = defineAbility((user: User, book: Book) => {\n  if (book.status === 'draft') {\n    return deny({\n      statusCode: 404,\n      message: 'Not Found',\n    })\n  }\n\n  return allow()\n})\n```\n\nNow, the `authorize` function will throw a `404 Not Found` error if the user is not permitted to view a draft book, rather than the default `403 Forbidden`.\n\nOn the client side, the `allows`, `denies`, and `authorize` functions can also be utilized to administer the **authorization logic**.\n\nIn addition, these functions offer two components: `Can` and `Cannot`. These components enable portions of a template to be shown or hidden based on the **authorization logic**.\n\n```vue\n\u003Ctemplate>\n  \u003CCan :ability=\"editBook\" :args=\"[book]\">\n    \u003Cbutton>Edit\u003C\u002Fbutton>\n  \u003C\u002FCan>\n\u003C\u002Ftemplate>\n```\n\nConsult the GitHub repository for more information: \u003CGitHubLink repo=\"barbapapazes\u002Fnuxt-authorization\" \u002F>.\n\n### Final thoughts\n\nBoth the code and the design of this package draw significant inspiration from the [Adonis Bouncer](https:\u002F\u002Fdocs.adonisjs.com\u002Fguides\u002Fsecurity\u002Fauthorization). It is well-crafted, and reinventing concepts unnecessarily is often futile.\n\nI am already implementing this package in Orion, and the experience has been quite enjoyable. I am confident there is still potential for enhancement, but this module represents an initial step in empowering Nuxt developers and advancing Nuxt's full-stack capabilities. Contributions and suggestions for new features or improvements are welcome.\n\nOrion is a community-driven collection of templates for your projects, spanning from landing pages to comprehensive web applications.\n\nI hope this article and module assist you in managing **authorization** within your Nuxt application, thereby preventing and restricting **unauthorized access** to your data. Security is imperative.\n\nHappy coding!\n",{"title":38826,"description":40795},{"loc":40797},"posts\u002Fnuxt-going-full-stack-how-to-handle-authorization","Nb1IQ9J-6zpzK79Vmw92JpvkKoqjtukuLEsLIQ35_5Q",{"id":40804,"title":40805,"body":40806,"contentId":30850,"date":45981,"description":45982,"extension":18,"meta":45983,"navigation":20,"path":45984,"rawbody":45985,"robots":23,"schemaOrg":23,"seo":45986,"sitemap":45987,"stem":45988,"__hash__":45989},"posts\u002Fposts\u002Fnuxt-going-full-stack-how-to-streamline-form-validation.md","Nuxt Going Full-Stack: How to Streamline Form Validation?",{"type":7,"value":40807,"toc":45975},[40808,40819,40824,45972],[35,40809,40810,40811,40814,40815,40818],{},"Since weeks, I'm building a full-stack application called Orion with Nuxt and ",[39,40812,36822],{"href":38142,"rel":40813},[77],". Throughout this journey, I've been facing a lot of challenges, and one of them is forms validation. In this article, I'll show you how I've managed to ",[6850,40816,40817],{},"validate forms on both the client and server sides without duplicating"," the validation logic, from simple text fields to complex file uploads with NuxtHub.",[35,40820,40821],{},[1217,40822,40823],{},"Orion is a community-driven collection of templates for your next project,\nfrom landing pages to complete web applications. NuxtHub is deployment and administration platform for Nuxt, powered by Cloudflare.",[7905,40825,40826,40835,40841,40843,40861,40872,40876,40879,41002,41005,41089,41095,41100,41104,41119,41260,41266],{"id":30840},[35,40827,40828],{},[1217,40829,40830,40831,40834],{},"During this article, I'll talk about ",[39,40832,36822],{"href":38142,"rel":40833},[77]," because it provides the primitives to build full-stack application, but even if you don't use it, you can easily follow this article. Forms will be handled without any libraries.",[35,40836,40837,40838],{},"You can browse the example code in ",[1582,40839,215],{"repo":40840},"barbapapazes\u002Fvalidate-forms-in-nuxt",[51,40842,18635],{"id":18634},[35,40844,40845,40846,40849,40850,40853,40854,1638,40857,40860],{},"On the web, forms are everywhere. They are the primary way to interact and ask for user input. But on the web, there is an essential rule: ",[6850,40847,40848],{},"never trust the client"," (absolutely never and under no circumstances). This means that ",[6850,40851,40852],{},"every data"," that comes from the client must be ",[6850,40855,40856],{},"verified",[6850,40858,40859],{},"sanitized"," on the server before being processed or stored in a database. A client can be a browser, a mobile app, an HTTP request, or anything else that can send data to your server, built by you or not.",[35,40862,40863,40864,40867,40868,40871],{},"When you build a traditional MPA (Multi-Page Application), only the server is responsible for the validation. For a SPA (Single-Page Application), both the client and the server are responsible for the validation to provide a better user experience. ",[1217,40865,40866],{},"This is simplified because there is some way to provide a feeling of client-side validation with a traditional MPA, but it's not the subject of this article."," BUT, even if the data is validated on the client side, it is ",[6850,40869,40870],{},"mandatory"," to validate them on the server side.",[51,40873,40875],{"id":40874},"the-simplest-form","The simplest form",[35,40877,40878],{},"Let's start our journey with the simplest form: a single text input.",[192,40880,40882],{"className":21417,"code":40881,"language":12060,"meta":13,"style":13},"\u003Ctemplate>\n  \u003Cform method=\"post\" action=\"\u002Ftag\">\n    \u003Cinput type=\"text\" placeholder=\"Name\" required>\n    \u003Cbutton type=\"submit\">\n      Create Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[156,40883,40884,40892,40922,40955,40973,40978,40986,40994],{"__ignoreMap":13},[200,40885,40886,40888,40890],{"class":202,"line":203},[200,40887,600],{"class":214},[200,40889,21428],{"class":235},[200,40891,648],{"class":214},[200,40893,40894,40896,40898,40900,40902,40904,40906,40908,40911,40913,40915,40918,40920],{"class":202,"line":14},[200,40895,657],{"class":214},[200,40897,603],{"class":235},[200,40899,6577],{"class":379},[200,40901,449],{"class":214},[200,40903,611],{"class":214},[200,40905,3778],{"class":245},[200,40907,611],{"class":214},[200,40909,40910],{"class":379}," action",[200,40912,449],{"class":214},[200,40914,611],{"class":214},[200,40916,40917],{"class":245},"\u002Ftag",[200,40919,611],{"class":214},[200,40921,648],{"class":214},[200,40923,40924,40926,40928,40930,40932,40934,40936,40938,40941,40943,40945,40948,40950,40953],{"class":202,"line":255},[200,40925,804],{"class":214},[200,40927,355],{"class":235},[200,40929,321],{"class":379},[200,40931,449],{"class":214},[200,40933,611],{"class":214},[200,40935,698],{"class":245},[200,40937,611],{"class":214},[200,40939,40940],{"class":379}," placeholder",[200,40942,449],{"class":214},[200,40944,611],{"class":214},[200,40946,40947],{"class":245},"Name",[200,40949,611],{"class":214},[200,40951,40952],{"class":379}," required",[200,40954,648],{"class":214},[200,40956,40957,40959,40961,40963,40965,40967,40969,40971],{"class":202,"line":272},[200,40958,804],{"class":214},[200,40960,910],{"class":235},[200,40962,321],{"class":379},[200,40964,449],{"class":214},[200,40966,611],{"class":214},[200,40968,917],{"class":245},[200,40970,611],{"class":214},[200,40972,648],{"class":214},[200,40974,40975],{"class":202,"line":283},[200,40976,40977],{"class":210},"      Create Tag\n",[200,40979,40980,40982,40984],{"class":202,"line":300},[200,40981,15074],{"class":214},[200,40983,910],{"class":235},[200,40985,648],{"class":214},[200,40987,40988,40990,40992],{"class":202,"line":310},[200,40989,893],{"class":214},[200,40991,603],{"class":235},[200,40993,648],{"class":214},[200,40995,40996,40998,41000],{"class":202,"line":336},[200,40997,681],{"class":214},[200,40999,21428],{"class":235},[200,41001,648],{"class":214},[35,41003,41004],{},"Then, on the server side, I can create a route to handle the form submission:",[192,41006,41008],{"className":1370,"code":41007,"language":1373,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  const body = await readBody(event)\n\n  \u002F\u002F Save in the database...\n\n  return sendNoContent(event, 201)\n})\n",[156,41009,41010,41032,41052,41056,41061,41065,41083],{"__ignoreMap":13},[200,41011,41012,41014,41016,41018,41020,41022,41024,41026,41028,41030],{"class":202,"line":203},[200,41013,1380],{"class":206},[200,41015,1383],{"class":206},[200,41017,38960],{"class":223},[200,41019,227],{"class":210},[200,41021,3711],{"class":379},[200,41023,1095],{"class":214},[200,41025,25386],{"class":386},[200,41027,2089],{"class":214},[200,41029,393],{"class":379},[200,41031,280],{"class":214},[200,41033,41034,41036,41039,41041,41043,41046,41048,41050],{"class":202,"line":14},[200,41035,3723],{"class":379},[200,41037,41038],{"class":210}," body",[200,41040,510],{"class":214},[200,41042,452],{"class":206},[200,41044,41045],{"class":223}," readBody",[200,41047,227],{"class":235},[200,41049,25386],{"class":210},[200,41051,416],{"class":235},[200,41053,41054],{"class":202,"line":255},[200,41055,529],{"emptyLinePlaceholder":20},[200,41057,41058],{"class":202,"line":272},[200,41059,41060],{"class":401},"  \u002F\u002F Save in the database...\n",[200,41062,41063],{"class":202,"line":283},[200,41064,529],{"emptyLinePlaceholder":20},[200,41066,41067,41069,41072,41074,41076,41078,41081],{"class":202,"line":300},[200,41068,3743],{"class":206},[200,41070,41071],{"class":223}," sendNoContent",[200,41073,227],{"class":235},[200,41075,25386],{"class":210},[200,41077,550],{"class":214},[200,41079,41080],{"class":2045}," 201",[200,41082,416],{"class":235},[200,41084,41085,41087],{"class":202,"line":310},[200,41086,413],{"class":214},[200,41088,416],{"class":210},[35,41090,41091,41092],{},"Until now, I don't have any validation. I can send anything to the server, and it will accept it. ",[1217,41093,41094],{},"This is a huge security issue.",[35,41096,41097],{},[1217,41098,41099],{},"Remember that the way I'm handling the validation is specific to a SPA. A traditional MPA would have added errors to flash messages and redirect back the request.",[1913,41101,41103],{"id":41102},"validating-the-body","Validating the body",[35,41105,41106,41107,106,41112,41115,41116,215],{},"In order to validate the body on the server side, I'm using ",[39,41108,41111],{"href":41109,"rel":41110},"https:\u002F\u002Fzod.dev",[77],"Zod",[1217,41113,41114],{},"Zod is a TypeScript-first schema validation with static type inference."," The good part is that h3, the router and web framework that power Nitro, the server layer of Nuxt, provides a utility to validate the body: ",[156,41117,41118],{},"readValidatedBody",[192,41120,41122],{"className":1370,"code":41121,"language":1373,"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 Save in the database...\n\n  return sendNoContent(event, 201)\n})\n",[156,41123,41124,41142,41146,41168,41197,41213,41226,41230,41234,41238,41254],{"__ignoreMap":13},[200,41125,41126,41128,41130,41132,41134,41136,41138,41140],{"class":202,"line":203},[200,41127,973],{"class":206},[200,41129,318],{"class":214},[200,41131,3165],{"class":210},[200,41133,981],{"class":214},[200,41135,984],{"class":206},[200,41137,242],{"class":214},[200,41139,19790],{"class":245},[200,41141,992],{"class":214},[200,41143,41144],{"class":202,"line":14},[200,41145,529],{"emptyLinePlaceholder":20},[200,41147,41148,41150,41152,41154,41156,41158,41160,41162,41164,41166],{"class":202,"line":255},[200,41149,1380],{"class":206},[200,41151,1383],{"class":206},[200,41153,38960],{"class":223},[200,41155,227],{"class":210},[200,41157,3711],{"class":379},[200,41159,1095],{"class":214},[200,41161,25386],{"class":386},[200,41163,2089],{"class":214},[200,41165,393],{"class":379},[200,41167,280],{"class":214},[200,41169,41170,41172,41174,41176,41178,41181,41183,41185,41187,41189,41191,41193,41195],{"class":202,"line":272},[200,41171,3723],{"class":379},[200,41173,41038],{"class":210},[200,41175,510],{"class":214},[200,41177,452],{"class":206},[200,41179,41180],{"class":223}," readValidatedBody",[200,41182,227],{"class":235},[200,41184,25386],{"class":210},[200,41186,550],{"class":214},[200,41188,3165],{"class":210},[200,41190,215],{"class":214},[200,41192,293],{"class":223},[200,41194,227],{"class":235},[200,41196,230],{"class":214},[200,41198,41199,41201,41203,41205,41207,41209,41211],{"class":202,"line":283},[200,41200,11797],{"class":235},[200,41202,239],{"class":214},[200,41204,3165],{"class":210},[200,41206,215],{"class":214},[200,41208,328],{"class":223},[200,41210,1532],{"class":235},[200,41212,252],{"class":214},[200,41214,41215,41217,41219,41221,41224],{"class":202,"line":300},[200,41216,5080],{"class":214},[200,41218,2089],{"class":235},[200,41220,215],{"class":214},[200,41222,41223],{"class":210},"parse",[200,41225,416],{"class":235},[200,41227,41228],{"class":202,"line":310},[200,41229,529],{"emptyLinePlaceholder":20},[200,41231,41232],{"class":202,"line":336},[200,41233,41060],{"class":401},[200,41235,41236],{"class":202,"line":342},[200,41237,529],{"emptyLinePlaceholder":20},[200,41239,41240,41242,41244,41246,41248,41250,41252],{"class":202,"line":365},[200,41241,3743],{"class":206},[200,41243,41071],{"class":223},[200,41245,227],{"class":235},[200,41247,25386],{"class":210},[200,41249,550],{"class":214},[200,41251,41080],{"class":2045},[200,41253,416],{"class":235},[200,41255,41256,41258],{"class":202,"line":371},[200,41257,413],{"class":214},[200,41259,416],{"class":210},[35,41261,41262,41263,41265],{},"The utility ",[156,41264,41118],{}," will automatically throw an error if the body doesn't match the schema. For example, if the name is missing, the request will fail.",[7139,41267,41271,41274,41281,41536,41543,41822,41830,41834,41837,42322,42343,42347,42350,42353,42363,42449,42456,42459,42462,42555,42558,42669,42672,42676,42683,42688,42692,42695,42828,42839,42854,43332,43335,43341,43591,43594,44021,44028,44031,44123,44127,44136,44317,44321,44324,44330,44333,44511,44514,44806,44809,44815,45158,45165,45427,45437,45781,45786,45949,45951,45966,45969],{"icon":41268,"text":41269,"to":41270},"i-unjs-h3","Validate Data using H3","https:\u002F\u002Fh3.unjs.io\u002Fexamples\u002Fvalidate-data",[35,41272,41273],{},"By default, the error will be handled by the server and throw a 500 error, but I can catch the error to return a more user-friendly error message.",[35,41275,41276,41277,41280],{},"The first solution is by using the method ",[156,41278,41279],{},"safeParse"," that will not throw an error but return a result with the error if any.",[192,41282,41284],{"className":1370,"code":41283,"language":1373,"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: 'Invalid data',\n      data: result.error.errors[0].message,\n    })\n  }\n\n  const body = result.data\n\n  \u002F\u002F Save in the database...\n\n  return sendNoContent(event, 201)\n})\n",[156,41285,41286,41304,41308,41330,41358,41374,41386,41390,41409,41419,41430,41445,41474,41480,41484,41488,41502,41506,41510,41514,41530],{"__ignoreMap":13},[200,41287,41288,41290,41292,41294,41296,41298,41300,41302],{"class":202,"line":203},[200,41289,973],{"class":206},[200,41291,318],{"class":214},[200,41293,3165],{"class":210},[200,41295,981],{"class":214},[200,41297,984],{"class":206},[200,41299,242],{"class":214},[200,41301,19790],{"class":245},[200,41303,992],{"class":214},[200,41305,41306],{"class":202,"line":14},[200,41307,529],{"emptyLinePlaceholder":20},[200,41309,41310,41312,41314,41316,41318,41320,41322,41324,41326,41328],{"class":202,"line":255},[200,41311,1380],{"class":206},[200,41313,1383],{"class":206},[200,41315,38960],{"class":223},[200,41317,227],{"class":210},[200,41319,3711],{"class":379},[200,41321,1095],{"class":214},[200,41323,25386],{"class":386},[200,41325,2089],{"class":214},[200,41327,393],{"class":379},[200,41329,280],{"class":214},[200,41331,41332,41334,41336,41338,41340,41342,41344,41346,41348,41350,41352,41354,41356],{"class":202,"line":272},[200,41333,3723],{"class":379},[200,41335,3546],{"class":210},[200,41337,510],{"class":214},[200,41339,452],{"class":206},[200,41341,41180],{"class":223},[200,41343,227],{"class":235},[200,41345,25386],{"class":210},[200,41347,550],{"class":214},[200,41349,3165],{"class":210},[200,41351,215],{"class":214},[200,41353,293],{"class":223},[200,41355,227],{"class":235},[200,41357,230],{"class":214},[200,41359,41360,41362,41364,41366,41368,41370,41372],{"class":202,"line":283},[200,41361,11797],{"class":235},[200,41363,239],{"class":214},[200,41365,3165],{"class":210},[200,41367,215],{"class":214},[200,41369,328],{"class":223},[200,41371,1532],{"class":235},[200,41373,252],{"class":214},[200,41375,41376,41378,41380,41382,41384],{"class":202,"line":300},[200,41377,5080],{"class":214},[200,41379,2089],{"class":235},[200,41381,215],{"class":214},[200,41383,41279],{"class":210},[200,41385,416],{"class":235},[200,41387,41388],{"class":202,"line":310},[200,41389,529],{"emptyLinePlaceholder":20},[200,41391,41392,41394,41396,41398,41400,41402,41405,41407],{"class":202,"line":336},[200,41393,20075],{"class":206},[200,41395,1095],{"class":235},[200,41397,11837],{"class":214},[200,41399,3531],{"class":210},[200,41401,215],{"class":214},[200,41403,41404],{"class":210},"success",[200,41406,5267],{"class":235},[200,41408,230],{"class":214},[200,41410,41411,41413,41415,41417],{"class":202,"line":342},[200,41412,39438],{"class":206},[200,41414,39441],{"class":223},[200,41416,227],{"class":235},[200,41418,230],{"class":214},[200,41420,41421,41423,41425,41428],{"class":202,"line":365},[200,41422,39082],{"class":235},[200,41424,239],{"class":214},[200,41426,41427],{"class":2045}," 400",[200,41429,252],{"class":214},[200,41431,41432,41434,41436,41438,41441,41443],{"class":202,"line":371},[200,41433,39471],{"class":235},[200,41435,239],{"class":214},[200,41437,242],{"class":214},[200,41439,41440],{"class":245},"Invalid data",[200,41442,249],{"class":214},[200,41444,252],{"class":214},[200,41446,41447,41450,41452,41454,41456,41458,41460,41462,41464,41466,41468,41470,41472],{"class":202,"line":398},[200,41448,41449],{"class":235},"      data",[200,41451,239],{"class":214},[200,41453,3546],{"class":210},[200,41455,215],{"class":214},[200,41457,19260],{"class":210},[200,41459,215],{"class":214},[200,41461,26789],{"class":210},[200,41463,9858],{"class":235},[200,41465,3933],{"class":2045},[200,41467,360],{"class":235},[200,41469,215],{"class":214},[200,41471,29574],{"class":210},[200,41473,252],{"class":214},[200,41475,41476,41478],{"class":202,"line":405},[200,41477,2096],{"class":214},[200,41479,416],{"class":235},[200,41481,41482],{"class":202,"line":410},[200,41483,4490],{"class":214},[200,41485,41486],{"class":202,"line":861},[200,41487,529],{"emptyLinePlaceholder":20},[200,41489,41490,41492,41494,41496,41498,41500],{"class":202,"line":890},[200,41491,3723],{"class":379},[200,41493,41038],{"class":210},[200,41495,510],{"class":214},[200,41497,3546],{"class":210},[200,41499,215],{"class":214},[200,41501,3750],{"class":210},[200,41503,41504],{"class":202,"line":900},[200,41505,529],{"emptyLinePlaceholder":20},[200,41507,41508],{"class":202,"line":905},[200,41509,41060],{"class":401},[200,41511,41512],{"class":202,"line":931},[200,41513,529],{"emptyLinePlaceholder":20},[200,41515,41516,41518,41520,41522,41524,41526,41528],{"class":202,"line":3640},[200,41517,3743],{"class":206},[200,41519,41071],{"class":223},[200,41521,227],{"class":235},[200,41523,25386],{"class":210},[200,41525,550],{"class":214},[200,41527,41080],{"class":2045},[200,41529,416],{"class":235},[200,41531,41532,41534],{"class":202,"line":3645},[200,41533,413],{"class":214},[200,41535,416],{"class":210},[35,41537,41538,41539,41542],{},"The second solution is by using the ",[156,41540,41541],{},"parseAsync"," method to attach a catch to the promise and handle the error manually.",[192,41544,41546],{"className":1370,"code":41545,"language":1373,"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: 'Invalid data',\n        data: error.errors[0].message,\n      })\n    }\n  }))\n\n  \u002F\u002F Save in the database...\n\n  return sendNoContent(event, 201)\n})\n",[156,41547,41548,41569,41587,41591,41613,41645,41661,41694,41711,41721,41732,41747,41772,41778,41782,41788,41792,41796,41800,41816],{"__ignoreMap":13},[200,41549,41550,41552,41554,41556,41559,41561,41563,41565,41567],{"class":202,"line":203},[200,41551,973],{"class":206},[200,41553,321],{"class":206},[200,41555,318],{"class":214},[200,41557,41558],{"class":210}," ZodError",[200,41560,981],{"class":214},[200,41562,984],{"class":206},[200,41564,242],{"class":214},[200,41566,19790],{"class":245},[200,41568,992],{"class":214},[200,41570,41571,41573,41575,41577,41579,41581,41583,41585],{"class":202,"line":14},[200,41572,973],{"class":206},[200,41574,318],{"class":214},[200,41576,3165],{"class":210},[200,41578,981],{"class":214},[200,41580,984],{"class":206},[200,41582,242],{"class":214},[200,41584,19790],{"class":245},[200,41586,992],{"class":214},[200,41588,41589],{"class":202,"line":255},[200,41590,529],{"emptyLinePlaceholder":20},[200,41592,41593,41595,41597,41599,41601,41603,41605,41607,41609,41611],{"class":202,"line":272},[200,41594,1380],{"class":206},[200,41596,1383],{"class":206},[200,41598,38960],{"class":223},[200,41600,227],{"class":210},[200,41602,3711],{"class":379},[200,41604,1095],{"class":214},[200,41606,25386],{"class":386},[200,41608,2089],{"class":214},[200,41610,393],{"class":379},[200,41612,280],{"class":214},[200,41614,41615,41617,41619,41621,41623,41625,41627,41629,41631,41633,41635,41637,41639,41641,41643],{"class":202,"line":283},[200,41616,3723],{"class":379},[200,41618,41038],{"class":210},[200,41620,510],{"class":214},[200,41622,452],{"class":206},[200,41624,41180],{"class":223},[200,41626,227],{"class":235},[200,41628,25386],{"class":210},[200,41630,550],{"class":214},[200,41632,5041],{"class":386},[200,41634,393],{"class":379},[200,41636,3165],{"class":210},[200,41638,215],{"class":214},[200,41640,293],{"class":223},[200,41642,227],{"class":235},[200,41644,230],{"class":214},[200,41646,41647,41649,41651,41653,41655,41657,41659],{"class":202,"line":300},[200,41648,11797],{"class":235},[200,41650,239],{"class":214},[200,41652,3165],{"class":210},[200,41654,215],{"class":214},[200,41656,328],{"class":223},[200,41658,1532],{"class":235},[200,41660,252],{"class":214},[200,41662,41663,41665,41667,41669,41671,41673,41675,41677,41679,41682,41684,41686,41688,41690,41692],{"class":202,"line":310},[200,41664,5080],{"class":214},[200,41666,2089],{"class":235},[200,41668,215],{"class":214},[200,41670,41541],{"class":223},[200,41672,227],{"class":235},[200,41674,12726],{"class":210},[200,41676,2089],{"class":235},[200,41678,215],{"class":214},[200,41680,41681],{"class":223},"catch",[200,41683,227],{"class":235},[200,41685,227],{"class":214},[200,41687,19260],{"class":386},[200,41689,2089],{"class":214},[200,41691,393],{"class":379},[200,41693,280],{"class":214},[200,41695,41696,41698,41700,41702,41705,41707,41709],{"class":202,"line":336},[200,41697,5252],{"class":206},[200,41699,1095],{"class":235},[200,41701,19260],{"class":210},[200,41703,41704],{"class":214}," instanceof",[200,41706,41558],{"class":1978},[200,41708,5267],{"class":235},[200,41710,230],{"class":214},[200,41712,41713,41715,41717,41719],{"class":202,"line":342},[200,41714,20227],{"class":206},[200,41716,39441],{"class":223},[200,41718,227],{"class":235},[200,41720,230],{"class":214},[200,41722,41723,41726,41728,41730],{"class":202,"line":365},[200,41724,41725],{"class":235},"        status",[200,41727,239],{"class":214},[200,41729,41427],{"class":2045},[200,41731,252],{"class":214},[200,41733,41734,41737,41739,41741,41743,41745],{"class":202,"line":371},[200,41735,41736],{"class":235},"        message",[200,41738,239],{"class":214},[200,41740,242],{"class":214},[200,41742,41440],{"class":245},[200,41744,249],{"class":214},[200,41746,252],{"class":214},[200,41748,41749,41752,41754,41756,41758,41760,41762,41764,41766,41768,41770],{"class":202,"line":398},[200,41750,41751],{"class":235},"        data",[200,41753,239],{"class":214},[200,41755,19205],{"class":210},[200,41757,215],{"class":214},[200,41759,26789],{"class":210},[200,41761,9858],{"class":235},[200,41763,3933],{"class":2045},[200,41765,360],{"class":235},[200,41767,215],{"class":214},[200,41769,29574],{"class":210},[200,41771,252],{"class":214},[200,41773,41774,41776],{"class":202,"line":405},[200,41775,2632],{"class":214},[200,41777,416],{"class":235},[200,41779,41780],{"class":202,"line":410},[200,41781,4962],{"class":214},[200,41783,41784,41786],{"class":202,"line":861},[200,41785,5080],{"class":214},[200,41787,585],{"class":235},[200,41789,41790],{"class":202,"line":890},[200,41791,529],{"emptyLinePlaceholder":20},[200,41793,41794],{"class":202,"line":900},[200,41795,41060],{"class":401},[200,41797,41798],{"class":202,"line":905},[200,41799,529],{"emptyLinePlaceholder":20},[200,41801,41802,41804,41806,41808,41810,41812,41814],{"class":202,"line":931},[200,41803,3743],{"class":206},[200,41805,41071],{"class":223},[200,41807,227],{"class":235},[200,41809,25386],{"class":210},[200,41811,550],{"class":214},[200,41813,41080],{"class":2045},[200,41815,416],{"class":235},[200,41817,41818,41820],{"class":202,"line":3640},[200,41819,413],{"class":214},[200,41821,416],{"class":210},[35,41823,41824],{},[1217,41825,41826,41827,41829],{},"You could wrap the validator with a ",[156,41828,26244],{},", but I found it less elegant and readable.",[1913,41831,41833],{"id":41832},"validating-the-frontend","Validating the frontend",[35,41835,41836],{},"On the frontend, I can also use Zod to validate the form before sending it to the server.",[192,41838,41840],{"className":21417,"code":41839,"language":12060,"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: 'Required' }), \u002F\u002F I can customize the error message shown to the user\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: 'Invalid data',\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=\"Name\" required>\n    \u003Cbutton type=\"submit\">\n      Create Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[156,41841,41842,41862,41882,41900,41904,41923,41959,41965,41969,41984,41994,42000,42004,42017,42024,42045,42063,42078,42089,42095,42099,42112,42128,42138,42148,42162,42176,42182,42186,42190,42194,42202,42206,42214,42234,42276,42294,42298,42306,42314],{"__ignoreMap":13},[200,41843,41844,41846,41848,41850,41852,41854,41856,41858,41860],{"class":202,"line":203},[200,41845,600],{"class":214},[200,41847,14892],{"class":235},[200,41849,14590],{"class":379},[200,41851,449],{"class":214},[200,41853,611],{"class":214},[200,41855,1373],{"class":245},[200,41857,611],{"class":214},[200,41859,21781],{"class":379},[200,41861,648],{"class":214},[200,41863,41864,41866,41868,41870,41872,41874,41876,41878,41880],{"class":202,"line":14},[200,41865,973],{"class":206},[200,41867,321],{"class":206},[200,41869,318],{"class":214},[200,41871,41558],{"class":210},[200,41873,981],{"class":214},[200,41875,984],{"class":206},[200,41877,242],{"class":214},[200,41879,19790],{"class":245},[200,41881,992],{"class":214},[200,41883,41884,41886,41888,41890,41892,41894,41896,41898],{"class":202,"line":255},[200,41885,973],{"class":206},[200,41887,318],{"class":214},[200,41889,3165],{"class":210},[200,41891,981],{"class":214},[200,41893,984],{"class":206},[200,41895,242],{"class":214},[200,41897,19790],{"class":245},[200,41899,992],{"class":214},[200,41901,41902],{"class":202,"line":272},[200,41903,529],{"emptyLinePlaceholder":20},[200,41905,41906,41908,41911,41913,41915,41917,41919,41921],{"class":202,"line":283},[200,41907,443],{"class":379},[200,41909,41910],{"class":210}," schema ",[200,41912,449],{"class":214},[200,41914,3165],{"class":210},[200,41916,215],{"class":214},[200,41918,293],{"class":223},[200,41920,227],{"class":210},[200,41922,230],{"class":214},[200,41924,41925,41927,41929,41931,41933,41935,41937,41939,41941,41943,41945,41948,41950,41952,41954,41956],{"class":202,"line":300},[200,41926,236],{"class":235},[200,41928,239],{"class":214},[200,41930,3165],{"class":210},[200,41932,215],{"class":214},[200,41934,328],{"class":223},[200,41936,227],{"class":210},[200,41938,2752],{"class":214},[200,41940,39347],{"class":235},[200,41942,239],{"class":214},[200,41944,242],{"class":214},[200,41946,41947],{"class":245},"Required",[200,41949,249],{"class":214},[200,41951,981],{"class":214},[200,41953,2089],{"class":210},[200,41955,550],{"class":214},[200,41957,41958],{"class":401}," \u002F\u002F I can customize the error message shown to the user\n",[200,41960,41961,41963],{"class":202,"line":310},[200,41962,413],{"class":214},[200,41964,416],{"class":210},[200,41966,41967],{"class":202,"line":336},[200,41968,529],{"emptyLinePlaceholder":20},[200,41970,41971,41973,41975,41977,41980,41982],{"class":202,"line":342},[200,41972,443],{"class":379},[200,41974,18946],{"class":210},[200,41976,449],{"class":214},[200,41978,41979],{"class":223}," reactive",[200,41981,227],{"class":210},[200,41983,230],{"class":214},[200,41985,41986,41988,41990,41992],{"class":202,"line":365},[200,41987,236],{"class":235},[200,41989,239],{"class":214},[200,41991,3894],{"class":214},[200,41993,252],{"class":214},[200,41995,41996,41998],{"class":202,"line":371},[200,41997,413],{"class":214},[200,41999,416],{"class":210},[200,42001,42002],{"class":202,"line":398},[200,42003,529],{"emptyLinePlaceholder":20},[200,42005,42006,42008,42010,42013,42015],{"class":202,"line":405},[200,42007,3711],{"class":379},[200,42009,4987],{"class":379},[200,42011,42012],{"class":223}," onSubmit",[200,42014,1532],{"class":214},[200,42016,280],{"class":214},[200,42018,42019,42022],{"class":202,"line":410},[200,42020,42021],{"class":206},"  try",[200,42023,280],{"class":214},[200,42025,42026,42028,42030,42032,42035,42037,42039,42041,42043],{"class":202,"line":861},[200,42027,1999],{"class":379},[200,42029,5041],{"class":210},[200,42031,510],{"class":214},[200,42033,42034],{"class":210}," schema",[200,42036,215],{"class":214},[200,42038,41223],{"class":223},[200,42040,227],{"class":235},[200,42042,26836],{"class":210},[200,42044,416],{"class":235},[200,42046,42047,42049,42051,42053,42055,42057,42059,42061],{"class":202,"line":890},[200,42048,19216],{"class":206},[200,42050,18741],{"class":223},[200,42052,227],{"class":235},[200,42054,249],{"class":214},[200,42056,40917],{"class":245},[200,42058,249],{"class":214},[200,42060,550],{"class":214},[200,42062,280],{"class":214},[200,42064,42065,42068,42070,42072,42074,42076],{"class":202,"line":900},[200,42066,42067],{"class":235},"      method",[200,42069,239],{"class":214},[200,42071,242],{"class":214},[200,42073,3778],{"class":245},[200,42075,249],{"class":214},[200,42077,252],{"class":214},[200,42079,42080,42083,42085,42087],{"class":202,"line":905},[200,42081,42082],{"class":235},"      body",[200,42084,239],{"class":214},[200,42086,5041],{"class":210},[200,42088,252],{"class":214},[200,42090,42091,42093],{"class":202,"line":931},[200,42092,2096],{"class":214},[200,42094,416],{"class":235},[200,42096,42097],{"class":202,"line":3640},[200,42098,4490],{"class":214},[200,42100,42101,42104,42106,42108,42110],{"class":202,"line":3645},[200,42102,42103],{"class":206},"  catch",[200,42105,1095],{"class":235},[200,42107,19260],{"class":210},[200,42109,5267],{"class":235},[200,42111,230],{"class":214},[200,42113,42114,42116,42118,42120,42122,42124,42126],{"class":202,"line":4020},[200,42115,5252],{"class":206},[200,42117,1095],{"class":235},[200,42119,19260],{"class":210},[200,42121,41704],{"class":214},[200,42123,41558],{"class":1978},[200,42125,5267],{"class":235},[200,42127,230],{"class":214},[200,42129,42130,42132,42134,42136],{"class":202,"line":4475},[200,42131,20227],{"class":206},[200,42133,39441],{"class":223},[200,42135,227],{"class":235},[200,42137,230],{"class":214},[200,42139,42140,42142,42144,42146],{"class":202,"line":4481},[200,42141,41725],{"class":235},[200,42143,239],{"class":214},[200,42145,41427],{"class":2045},[200,42147,252],{"class":214},[200,42149,42150,42152,42154,42156,42158,42160],{"class":202,"line":4487},[200,42151,41736],{"class":235},[200,42153,239],{"class":214},[200,42155,242],{"class":214},[200,42157,41440],{"class":245},[200,42159,249],{"class":214},[200,42161,252],{"class":214},[200,42163,42164,42166,42168,42170,42172,42174],{"class":202,"line":4493},[200,42165,41751],{"class":235},[200,42167,239],{"class":214},[200,42169,19205],{"class":210},[200,42171,215],{"class":214},[200,42173,26789],{"class":210},[200,42175,252],{"class":214},[200,42177,42178,42180],{"class":202,"line":5454},[200,42179,2632],{"class":214},[200,42181,416],{"class":235},[200,42183,42184],{"class":202,"line":5459},[200,42185,4962],{"class":214},[200,42187,42188],{"class":202,"line":13227},[200,42189,4490],{"class":214},[200,42191,42192],{"class":202,"line":13253},[200,42193,1130],{"class":214},[200,42195,42196,42198,42200],{"class":202,"line":13269},[200,42197,681],{"class":214},[200,42199,14892],{"class":235},[200,42201,648],{"class":214},[200,42203,42204],{"class":202,"line":13313},[200,42205,529],{"emptyLinePlaceholder":20},[200,42207,42208,42210,42212],{"class":202,"line":13319},[200,42209,600],{"class":214},[200,42211,21428],{"class":235},[200,42213,648],{"class":214},[200,42215,42216,42218,42220,42223,42225,42227,42230,42232],{"class":202,"line":13324},[200,42217,657],{"class":214},[200,42219,603],{"class":235},[200,42221,42222],{"class":379}," @submit.prevent",[200,42224,449],{"class":214},[200,42226,611],{"class":214},[200,42228,42229],{"class":245},"onSubmit()",[200,42231,611],{"class":214},[200,42233,648],{"class":214},[200,42235,42236,42238,42240,42243,42245,42247,42250,42252,42254,42256,42258,42260,42262,42264,42266,42268,42270,42272,42274],{"class":202,"line":13350},[200,42237,804],{"class":214},[200,42239,355],{"class":235},[200,42241,42242],{"class":379}," v-model",[200,42244,449],{"class":214},[200,42246,611],{"class":214},[200,42248,42249],{"class":245},"state.name",[200,42251,611],{"class":214},[200,42253,321],{"class":379},[200,42255,449],{"class":214},[200,42257,611],{"class":214},[200,42259,698],{"class":245},[200,42261,611],{"class":214},[200,42263,40940],{"class":379},[200,42265,449],{"class":214},[200,42267,611],{"class":214},[200,42269,40947],{"class":245},[200,42271,611],{"class":214},[200,42273,40952],{"class":379},[200,42275,648],{"class":214},[200,42277,42278,42280,42282,42284,42286,42288,42290,42292],{"class":202,"line":13355},[200,42279,804],{"class":214},[200,42281,910],{"class":235},[200,42283,321],{"class":379},[200,42285,449],{"class":214},[200,42287,611],{"class":214},[200,42289,917],{"class":245},[200,42291,611],{"class":214},[200,42293,648],{"class":214},[200,42295,42296],{"class":202,"line":13375},[200,42297,40977],{"class":210},[200,42299,42300,42302,42304],{"class":202,"line":13395},[200,42301,15074],{"class":214},[200,42303,910],{"class":235},[200,42305,648],{"class":214},[200,42307,42308,42310,42312],{"class":202,"line":13400},[200,42309,893],{"class":214},[200,42311,603],{"class":235},[200,42313,648],{"class":214},[200,42315,42316,42318,42320],{"class":202,"line":13408},[200,42317,681],{"class":214},[200,42319,21428],{"class":235},[200,42321,648],{"class":214},[35,42323,42324,42325,42328,42329,42332,42333,13731,42338,215],{},"The implementation is very simple and straightforward. I could save the errors in a variable to display them to the user. If you want to go further, I recommend you try the ",[156,42326,42327],{},"Form"," component from ",[39,42330,9190],{"href":23049,"rel":42331},[77]," or a validation library like ",[39,42334,42337],{"href":42335,"rel":42336},"https:\u002F\u002Fvee-validate.logaretm.com\u002Fv4\u002F",[77],"VeeValidate",[39,42339,42342],{"href":42340,"rel":42341},"https:\u002F\u002Fformkit.com\u002F",[77],"FormKit",[51,42344,42346],{"id":42345},"lets-refactor","Let's refactor",[35,42348,42349],{},"Now that I have the validation logic on both the client and server sides, let's see if there is some interesting code to refactor.",[35,42351,42352],{},"The first thing I notice is that the validation logic, on the client and server sides, is the same, and that's totally normal. But what happens if I decide to change the validation logic on the server side because my API is used by another application? My frontend could be broken because the validation logic will be different. To avoid this, it could be better to only write the validator once and use it on both sides. What do you think?",[35,42354,42355,42356,2857,42359,42362],{},"I can create a file ",[156,42357,42358],{},"validators.ts",[156,42360,42361],{},"utils"," folder of the client part (at the root) of a Nuxt application. This file will contain all the validators for my application and shared between the client and server.",[192,42364,42366],{"className":1370,"code":42365,"language":1373,"meta":13,"style":13},"import { z } from 'zod'\n\nexport const createTagValidator = z.object({\n  name: z.string({ message: 'Required' }),\n})\n",[156,42367,42368,42386,42390,42411,42443],{"__ignoreMap":13},[200,42369,42370,42372,42374,42376,42378,42380,42382,42384],{"class":202,"line":203},[200,42371,973],{"class":206},[200,42373,318],{"class":214},[200,42375,3165],{"class":210},[200,42377,981],{"class":214},[200,42379,984],{"class":206},[200,42381,242],{"class":214},[200,42383,19790],{"class":245},[200,42385,992],{"class":214},[200,42387,42388],{"class":202,"line":14},[200,42389,529],{"emptyLinePlaceholder":20},[200,42391,42392,42394,42396,42399,42401,42403,42405,42407,42409],{"class":202,"line":255},[200,42393,1380],{"class":206},[200,42395,14301],{"class":379},[200,42397,42398],{"class":210}," createTagValidator ",[200,42400,449],{"class":214},[200,42402,3165],{"class":210},[200,42404,215],{"class":214},[200,42406,293],{"class":223},[200,42408,227],{"class":210},[200,42410,230],{"class":214},[200,42412,42413,42415,42417,42419,42421,42423,42425,42427,42429,42431,42433,42435,42437,42439,42441],{"class":202,"line":272},[200,42414,236],{"class":235},[200,42416,239],{"class":214},[200,42418,3165],{"class":210},[200,42420,215],{"class":214},[200,42422,328],{"class":223},[200,42424,227],{"class":210},[200,42426,2752],{"class":214},[200,42428,39347],{"class":235},[200,42430,239],{"class":214},[200,42432,242],{"class":214},[200,42434,41947],{"class":245},[200,42436,249],{"class":214},[200,42438,981],{"class":214},[200,42440,2089],{"class":210},[200,42442,252],{"class":214},[200,42444,42445,42447],{"class":202,"line":283},[200,42446,413],{"class":214},[200,42448,416],{"class":210},[939,42450,42451],{},[35,42452,42453,42455],{},[200,42454,31267],{},"\nThis file can be used on the server but has been created on the client part so you need to explicitly import everything you need to avoid renderer issues.",[35,42457,42458],{},"This validator contains the message for the error to help the user understand what's wrong.",[35,42460,42461],{},"Thanks to the auto-import feature of Nuxt, I can easily use it on the client:",[192,42463,42465],{"className":21417,"code":42464,"language":12060,"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,42466,42467,42487,42498,42502,42506,42514,42518,42526,42534,42539,42547],{"__ignoreMap":13},[200,42468,42469,42471,42473,42475,42477,42479,42481,42483,42485],{"class":202,"line":203},[200,42470,600],{"class":214},[200,42472,14892],{"class":235},[200,42474,14590],{"class":379},[200,42476,449],{"class":214},[200,42478,611],{"class":214},[200,42480,1373],{"class":245},[200,42482,611],{"class":214},[200,42484,21781],{"class":379},[200,42486,648],{"class":214},[200,42488,42489,42491,42493,42495],{"class":202,"line":14},[200,42490,443],{"class":379},[200,42492,41910],{"class":210},[200,42494,449],{"class":214},[200,42496,42497],{"class":210}," createTagValidator\n",[200,42499,42500],{"class":202,"line":255},[200,42501,529],{"emptyLinePlaceholder":20},[200,42503,42504],{"class":202,"line":272},[200,42505,33144],{"class":401},[200,42507,42508,42510,42512],{"class":202,"line":283},[200,42509,681],{"class":214},[200,42511,14892],{"class":235},[200,42513,648],{"class":214},[200,42515,42516],{"class":202,"line":300},[200,42517,529],{"emptyLinePlaceholder":20},[200,42519,42520,42522,42524],{"class":202,"line":310},[200,42521,600],{"class":214},[200,42523,21428],{"class":235},[200,42525,648],{"class":214},[200,42527,42528,42530,42532],{"class":202,"line":336},[200,42529,657],{"class":214},[200,42531,603],{"class":235},[200,42533,648],{"class":214},[200,42535,42536],{"class":202,"line":342},[200,42537,42538],{"class":401},"  \u003C!-- ... -->\n",[200,42540,42541,42543,42545],{"class":202,"line":365},[200,42542,893],{"class":214},[200,42544,603],{"class":235},[200,42546,648],{"class":214},[200,42548,42549,42551,42553],{"class":202,"line":371},[200,42550,681],{"class":214},[200,42552,21428],{"class":235},[200,42554,648],{"class":214},[35,42556,42557],{},"Then, you can also use it on the server side by importing it:",[192,42559,42561],{"className":1370,"code":42560,"language":1373,"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 Save in the database...\n\n  return sendNoContent(event, 201)\n})\n",[156,42562,42563,42583,42587,42609,42635,42639,42643,42647,42663],{"__ignoreMap":13},[200,42564,42565,42567,42569,42572,42574,42576,42578,42581],{"class":202,"line":203},[200,42566,973],{"class":206},[200,42568,318],{"class":214},[200,42570,42571],{"class":210}," createTagValidator",[200,42573,981],{"class":214},[200,42575,984],{"class":206},[200,42577,242],{"class":214},[200,42579,42580],{"class":245},"~\u002Futils\u002Fvalidators",[200,42582,992],{"class":214},[200,42584,42585],{"class":202,"line":14},[200,42586,529],{"emptyLinePlaceholder":20},[200,42588,42589,42591,42593,42595,42597,42599,42601,42603,42605,42607],{"class":202,"line":255},[200,42590,1380],{"class":206},[200,42592,1383],{"class":206},[200,42594,38960],{"class":223},[200,42596,227],{"class":210},[200,42598,3711],{"class":379},[200,42600,1095],{"class":214},[200,42602,25386],{"class":386},[200,42604,2089],{"class":214},[200,42606,393],{"class":379},[200,42608,280],{"class":214},[200,42610,42611,42613,42615,42617,42619,42621,42623,42625,42627,42629,42631,42633],{"class":202,"line":272},[200,42612,3723],{"class":379},[200,42614,41038],{"class":210},[200,42616,510],{"class":214},[200,42618,452],{"class":206},[200,42620,41180],{"class":223},[200,42622,227],{"class":235},[200,42624,25386],{"class":210},[200,42626,550],{"class":214},[200,42628,42571],{"class":210},[200,42630,215],{"class":214},[200,42632,41223],{"class":210},[200,42634,416],{"class":235},[200,42636,42637],{"class":202,"line":283},[200,42638,529],{"emptyLinePlaceholder":20},[200,42640,42641],{"class":202,"line":300},[200,42642,41060],{"class":401},[200,42644,42645],{"class":202,"line":310},[200,42646,529],{"emptyLinePlaceholder":20},[200,42648,42649,42651,42653,42655,42657,42659,42661],{"class":202,"line":336},[200,42650,3743],{"class":206},[200,42652,41071],{"class":223},[200,42654,227],{"class":235},[200,42656,25386],{"class":210},[200,42658,550],{"class":214},[200,42660,41080],{"class":2045},[200,42662,416],{"class":235},[200,42664,42665,42667],{"class":202,"line":342},[200,42666,413],{"class":214},[200,42668,416],{"class":210},[35,42670,42671],{},"Now, I can safely change the validation logic without breaking the frontend since they follow the same rules, and I don't have to duplicate the code.",[51,42673,42675],{"id":42674},"handling-files","Handling files",[35,42677,42678,42679,42682],{},"For this part, I'll show you two different ways to validate files on the server. The first one will use Zod and some refinements, and the second one will use the ",[156,42680,42681],{},"ensureBlob"," utility from NuxtHub.",[35,42684,42685],{},[1217,42686,42687],{},"This validation is not feature complete and should be improved to handle more cases.",[1913,42689,42691],{"id":42690},"with-zod","With Zod",[35,42693,42694],{},"For this second part, I'll show you how to upload a file to the server. First, let's add our input to the form:",[192,42696,42698],{"className":21417,"code":42697,"language":12060,"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      Create Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[156,42699,42700,42708,42749,42782,42800,42804,42812,42820],{"__ignoreMap":13},[200,42701,42702,42704,42706],{"class":202,"line":203},[200,42703,600],{"class":214},[200,42705,21428],{"class":235},[200,42707,648],{"class":214},[200,42709,42710,42712,42714,42716,42718,42720,42723,42725,42728,42730,42732,42735,42737,42739,42741,42743,42745,42747],{"class":202,"line":14},[200,42711,657],{"class":214},[200,42713,603],{"class":235},[200,42715,40910],{"class":379},[200,42717,449],{"class":214},[200,42719,611],{"class":214},[200,42721,42722],{"class":245},"\u002Fapi\u002Fimage",[200,42724,611],{"class":214},[200,42726,42727],{"class":379}," enctype",[200,42729,449],{"class":214},[200,42731,611],{"class":214},[200,42733,42734],{"class":245},"multipart\u002Fform-data",[200,42736,611],{"class":214},[200,42738,6577],{"class":379},[200,42740,449],{"class":214},[200,42742,611],{"class":214},[200,42744,3778],{"class":245},[200,42746,611],{"class":214},[200,42748,648],{"class":214},[200,42750,42751,42753,42755,42757,42759,42761,42764,42766,42769,42771,42773,42776,42778,42780],{"class":202,"line":255},[200,42752,804],{"class":214},[200,42754,355],{"class":235},[200,42756,321],{"class":379},[200,42758,449],{"class":214},[200,42760,611],{"class":214},[200,42762,42763],{"class":245},"file",[200,42765,611],{"class":214},[200,42767,42768],{"class":379}," accept",[200,42770,449],{"class":214},[200,42772,611],{"class":214},[200,42774,42775],{"class":245},"image\u002Fpng, image\u002Fjpeg, image\u002Fjpg",[200,42777,611],{"class":214},[200,42779,40952],{"class":379},[200,42781,648],{"class":214},[200,42783,42784,42786,42788,42790,42792,42794,42796,42798],{"class":202,"line":272},[200,42785,804],{"class":214},[200,42787,910],{"class":235},[200,42789,321],{"class":379},[200,42791,449],{"class":214},[200,42793,611],{"class":214},[200,42795,917],{"class":245},[200,42797,611],{"class":214},[200,42799,648],{"class":214},[200,42801,42802],{"class":202,"line":283},[200,42803,40977],{"class":210},[200,42805,42806,42808,42810],{"class":202,"line":300},[200,42807,15074],{"class":214},[200,42809,910],{"class":235},[200,42811,648],{"class":214},[200,42813,42814,42816,42818],{"class":202,"line":310},[200,42815,893],{"class":214},[200,42817,603],{"class":235},[200,42819,648],{"class":214},[200,42821,42822,42824,42826],{"class":202,"line":336},[200,42823,681],{"class":214},[200,42825,21428],{"class":235},[200,42827,648],{"class":214},[35,42829,42830,42831,42834,42835,42838],{},"Then, I can handle the file on the server side, but it's a bit more complex because I send a ",[156,42832,42833],{},"formData"," and h3 does not provide a utility like ",[156,42836,42837],{},"readValidatedFormData",". So, I need to handle it manually.",[35,42840,42841,42842,42844,42845,42848,42849,35149,42851,42853],{},"First, I will read the ",[156,42843,42833],{}," using the ",[156,42846,42847],{},"readFormData"," utility, then I will extract the ",[156,42850,9737],{},[156,42852,42833],{}," and validate it using Zod.",[192,42855,42857],{"className":1370,"code":42856,"language":1373,"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: 'Image must be a file' })\n      .refine(image => image.size \u003C 1024 * 1024, { message: 'Image must be less than 1MB' })\n      .refine(image => image.type.startsWith('image\u002F'), { message: 'Image must be an image' })\n      .refine(image => ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg'].includes(image.type), { message: 'Image must be a JPEG or PNG' })\n  }).parseAsync({ image }).catch((error) => {\n    throw createError({\n      status: 400,\n      message: 'Invalid image',\n      data: error.errors[0].message\n    })\n  })\n\n  \u002F\u002F Save the image to a database or a file system\n\n  return sendNoContent(event, 201)\n})\n",[156,42858,42859,42883,42887,42909,42929,42933,42959,42969,42980,43019,43065,43117,43191,43227,43237,43247,43262,43285,43291,43297,43301,43306,43310,43326],{"__ignoreMap":13},[200,42860,42861,42863,42865,42868,42870,42873,42875,42877,42879,42881],{"class":202,"line":203},[200,42862,973],{"class":206},[200,42864,318],{"class":214},[200,42866,42867],{"class":210}," any",[200,42869,550],{"class":214},[200,42871,42872],{"class":210}," object",[200,42874,981],{"class":214},[200,42876,984],{"class":206},[200,42878,242],{"class":214},[200,42880,19790],{"class":245},[200,42882,992],{"class":214},[200,42884,42885],{"class":202,"line":14},[200,42886,529],{"emptyLinePlaceholder":20},[200,42888,42889,42891,42893,42895,42897,42899,42901,42903,42905,42907],{"class":202,"line":255},[200,42890,1380],{"class":206},[200,42892,1383],{"class":206},[200,42894,38960],{"class":223},[200,42896,227],{"class":210},[200,42898,3711],{"class":379},[200,42900,1095],{"class":214},[200,42902,25386],{"class":386},[200,42904,2089],{"class":214},[200,42906,393],{"class":379},[200,42908,280],{"class":214},[200,42910,42911,42913,42916,42918,42920,42923,42925,42927],{"class":202,"line":272},[200,42912,3723],{"class":379},[200,42914,42915],{"class":210}," formData",[200,42917,510],{"class":214},[200,42919,452],{"class":206},[200,42921,42922],{"class":223}," readFormData",[200,42924,227],{"class":235},[200,42926,25386],{"class":210},[200,42928,416],{"class":235},[200,42930,42931],{"class":202,"line":283},[200,42932,529],{"emptyLinePlaceholder":20},[200,42934,42935,42937,42940,42942,42944,42946,42949,42951,42953,42955,42957],{"class":202,"line":300},[200,42936,3723],{"class":379},[200,42938,42939],{"class":210}," image",[200,42941,510],{"class":214},[200,42943,42915],{"class":210},[200,42945,215],{"class":214},[200,42947,42948],{"class":223},"get",[200,42950,227],{"class":235},[200,42952,249],{"class":214},[200,42954,9737],{"class":245},[200,42956,249],{"class":214},[200,42958,416],{"class":235},[200,42960,42961,42963,42965,42967],{"class":202,"line":310},[200,42962,6165],{"class":206},[200,42964,42872],{"class":223},[200,42966,227],{"class":235},[200,42968,230],{"class":214},[200,42970,42971,42974,42976,42978],{"class":202,"line":336},[200,42972,42973],{"class":235},"    image",[200,42975,239],{"class":214},[200,42977,42867],{"class":223},[200,42979,466],{"class":235},[200,42981,42982,42984,42987,42989,42991,42993,42995,42997,43000,43002,43004,43006,43008,43010,43013,43015,43017],{"class":202,"line":342},[200,42983,5051],{"class":214},[200,42985,42986],{"class":223},"refine",[200,42988,227],{"class":235},[200,42990,9737],{"class":386},[200,42992,393],{"class":379},[200,42994,42939],{"class":210},[200,42996,41704],{"class":214},[200,42998,42999],{"class":1978}," File",[200,43001,550],{"class":214},[200,43003,318],{"class":214},[200,43005,39347],{"class":235},[200,43007,239],{"class":214},[200,43009,242],{"class":214},[200,43011,43012],{"class":245},"Image must be a file",[200,43014,249],{"class":214},[200,43016,981],{"class":214},[200,43018,416],{"class":235},[200,43020,43021,43023,43025,43027,43029,43031,43033,43035,43037,43039,43042,43044,43046,43048,43050,43052,43054,43056,43059,43061,43063],{"class":202,"line":365},[200,43022,5051],{"class":214},[200,43024,42986],{"class":223},[200,43026,227],{"class":235},[200,43028,9737],{"class":386},[200,43030,393],{"class":379},[200,43032,42939],{"class":210},[200,43034,215],{"class":214},[200,43036,35364],{"class":210},[200,43038,31253],{"class":214},[200,43040,43041],{"class":2045}," 1024",[200,43043,29919],{"class":214},[200,43045,43041],{"class":2045},[200,43047,550],{"class":214},[200,43049,318],{"class":214},[200,43051,39347],{"class":235},[200,43053,239],{"class":214},[200,43055,242],{"class":214},[200,43057,43058],{"class":245},"Image must be less than 1MB",[200,43060,249],{"class":214},[200,43062,981],{"class":214},[200,43064,416],{"class":235},[200,43066,43067,43069,43071,43073,43075,43077,43079,43081,43083,43085,43087,43089,43091,43094,43096,43098,43100,43102,43104,43106,43108,43111,43113,43115],{"class":202,"line":371},[200,43068,5051],{"class":214},[200,43070,42986],{"class":223},[200,43072,227],{"class":235},[200,43074,9737],{"class":386},[200,43076,393],{"class":379},[200,43078,42939],{"class":210},[200,43080,215],{"class":214},[200,43082,3771],{"class":210},[200,43084,215],{"class":214},[200,43086,6108],{"class":223},[200,43088,227],{"class":235},[200,43090,249],{"class":214},[200,43092,43093],{"class":245},"image\u002F",[200,43095,249],{"class":214},[200,43097,2089],{"class":235},[200,43099,550],{"class":214},[200,43101,318],{"class":214},[200,43103,39347],{"class":235},[200,43105,239],{"class":214},[200,43107,242],{"class":214},[200,43109,43110],{"class":245},"Image must be an image",[200,43112,249],{"class":214},[200,43114,981],{"class":214},[200,43116,416],{"class":235},[200,43118,43119,43121,43123,43125,43127,43129,43131,43133,43136,43138,43140,43142,43145,43147,43149,43151,43154,43156,43158,43160,43162,43164,43166,43168,43170,43172,43174,43176,43178,43180,43182,43185,43187,43189],{"class":202,"line":398},[200,43120,5051],{"class":214},[200,43122,42986],{"class":223},[200,43124,227],{"class":235},[200,43126,9737],{"class":386},[200,43128,393],{"class":379},[200,43130,350],{"class":235},[200,43132,249],{"class":214},[200,43134,43135],{"class":245},"image\u002Fpng",[200,43137,249],{"class":214},[200,43139,550],{"class":214},[200,43141,242],{"class":214},[200,43143,43144],{"class":245},"image\u002Fjpeg",[200,43146,249],{"class":214},[200,43148,550],{"class":214},[200,43150,242],{"class":214},[200,43152,43153],{"class":245},"image\u002Fjpg",[200,43155,249],{"class":214},[200,43157,360],{"class":235},[200,43159,215],{"class":214},[200,43161,3841],{"class":223},[200,43163,227],{"class":235},[200,43165,9737],{"class":210},[200,43167,215],{"class":214},[200,43169,3771],{"class":210},[200,43171,2089],{"class":235},[200,43173,550],{"class":214},[200,43175,318],{"class":214},[200,43177,39347],{"class":235},[200,43179,239],{"class":214},[200,43181,242],{"class":214},[200,43183,43184],{"class":245},"Image must be a JPEG or PNG",[200,43186,249],{"class":214},[200,43188,981],{"class":214},[200,43190,416],{"class":235},[200,43192,43193,43195,43197,43199,43201,43203,43205,43207,43209,43211,43213,43215,43217,43219,43221,43223,43225],{"class":202,"line":405},[200,43194,5080],{"class":214},[200,43196,2089],{"class":235},[200,43198,215],{"class":214},[200,43200,41541],{"class":223},[200,43202,227],{"class":235},[200,43204,2752],{"class":214},[200,43206,42939],{"class":210},[200,43208,981],{"class":214},[200,43210,2089],{"class":235},[200,43212,215],{"class":214},[200,43214,41681],{"class":223},[200,43216,227],{"class":235},[200,43218,227],{"class":214},[200,43220,19260],{"class":386},[200,43222,2089],{"class":214},[200,43224,393],{"class":379},[200,43226,280],{"class":214},[200,43228,43229,43231,43233,43235],{"class":202,"line":410},[200,43230,39438],{"class":206},[200,43232,39441],{"class":223},[200,43234,227],{"class":235},[200,43236,230],{"class":214},[200,43238,43239,43241,43243,43245],{"class":202,"line":861},[200,43240,39082],{"class":235},[200,43242,239],{"class":214},[200,43244,41427],{"class":2045},[200,43246,252],{"class":214},[200,43248,43249,43251,43253,43255,43258,43260],{"class":202,"line":890},[200,43250,39471],{"class":235},[200,43252,239],{"class":214},[200,43254,242],{"class":214},[200,43256,43257],{"class":245},"Invalid image",[200,43259,249],{"class":214},[200,43261,252],{"class":214},[200,43263,43264,43266,43268,43270,43272,43274,43276,43278,43280,43282],{"class":202,"line":900},[200,43265,41449],{"class":235},[200,43267,239],{"class":214},[200,43269,19205],{"class":210},[200,43271,215],{"class":214},[200,43273,26789],{"class":210},[200,43275,9858],{"class":235},[200,43277,3933],{"class":2045},[200,43279,360],{"class":235},[200,43281,215],{"class":214},[200,43283,43284],{"class":210},"message\n",[200,43286,43287,43289],{"class":202,"line":905},[200,43288,2096],{"class":214},[200,43290,416],{"class":235},[200,43292,43293,43295],{"class":202,"line":931},[200,43294,5080],{"class":214},[200,43296,416],{"class":235},[200,43298,43299],{"class":202,"line":3640},[200,43300,529],{"emptyLinePlaceholder":20},[200,43302,43303],{"class":202,"line":3645},[200,43304,43305],{"class":401},"  \u002F\u002F Save the image to a database or a file system\n",[200,43307,43308],{"class":202,"line":4020},[200,43309,529],{"emptyLinePlaceholder":20},[200,43311,43312,43314,43316,43318,43320,43322,43324],{"class":202,"line":4475},[200,43313,3743],{"class":206},[200,43315,41071],{"class":223},[200,43317,227],{"class":235},[200,43319,25386],{"class":210},[200,43321,550],{"class":214},[200,43323,41080],{"class":2045},[200,43325,416],{"class":235},[200,43327,43328,43330],{"class":202,"line":4481},[200,43329,413],{"class":214},[200,43331,416],{"class":210},[35,43333,43334],{},"On the client side, I can also validate the file using the same technique as before and by sharing the validator between the client and server sides.",[35,43336,43337,43338,13591],{},"First, I need to move the validator to the ",[156,43339,43340],{},"utils\u002Fvalidators.ts",[192,43342,43344],{"className":1370,"code":43343,"language":1373,"meta":13,"style":13},"import { any, object } from 'zod'\n\nexport const createImageValidator = object({\n  image: any().refine(image => image instanceof File, { message: 'Image must be a file' }).refine(image => image.size \u003C 1024 * 1024, { message: 'Image must be less than 1MB' }).refine(image => image.type.startsWith('image\u002F'), { message: 'Image must be an image' }).refine(image => ['image\u002Fjpeg', 'image\u002Fpng'].includes(image.type), { message: 'Image must be a JPEG or PNG' })\n})\n",[156,43345,43346,43368,43372,43389,43585],{"__ignoreMap":13},[200,43347,43348,43350,43352,43354,43356,43358,43360,43362,43364,43366],{"class":202,"line":203},[200,43349,973],{"class":206},[200,43351,318],{"class":214},[200,43353,42867],{"class":210},[200,43355,550],{"class":214},[200,43357,42872],{"class":210},[200,43359,981],{"class":214},[200,43361,984],{"class":206},[200,43363,242],{"class":214},[200,43365,19790],{"class":245},[200,43367,992],{"class":214},[200,43369,43370],{"class":202,"line":14},[200,43371,529],{"emptyLinePlaceholder":20},[200,43373,43374,43376,43378,43381,43383,43385,43387],{"class":202,"line":255},[200,43375,1380],{"class":206},[200,43377,14301],{"class":379},[200,43379,43380],{"class":210}," createImageValidator ",[200,43382,449],{"class":214},[200,43384,42872],{"class":223},[200,43386,227],{"class":210},[200,43388,230],{"class":214},[200,43390,43391,43394,43396,43398,43400,43402,43404,43406,43408,43410,43413,43416,43418,43420,43422,43424,43426,43428,43430,43432,43434,43436,43438,43440,43442,43444,43446,43448,43450,43453,43455,43457,43459,43461,43463,43465,43467,43469,43471,43473,43475,43477,43479,43481,43483,43485,43487,43489,43491,43493,43495,43497,43499,43501,43503,43505,43507,43509,43511,43513,43515,43517,43519,43521,43523,43525,43527,43529,43531,43533,43535,43537,43539,43541,43543,43545,43547,43549,43551,43553,43555,43557,43559,43562,43564,43567,43569,43571,43573,43575,43577,43579,43581,43583],{"class":202,"line":272},[200,43392,43393],{"class":235},"  image",[200,43395,239],{"class":214},[200,43397,42867],{"class":223},[200,43399,1532],{"class":210},[200,43401,215],{"class":214},[200,43403,42986],{"class":223},[200,43405,227],{"class":210},[200,43407,9737],{"class":386},[200,43409,393],{"class":379},[200,43411,43412],{"class":210}," image ",[200,43414,43415],{"class":214},"instanceof",[200,43417,42999],{"class":1978},[200,43419,550],{"class":214},[200,43421,318],{"class":214},[200,43423,39347],{"class":235},[200,43425,239],{"class":214},[200,43427,242],{"class":214},[200,43429,43012],{"class":245},[200,43431,249],{"class":214},[200,43433,981],{"class":214},[200,43435,2089],{"class":210},[200,43437,215],{"class":214},[200,43439,42986],{"class":223},[200,43441,227],{"class":210},[200,43443,9737],{"class":386},[200,43445,393],{"class":379},[200,43447,42939],{"class":210},[200,43449,215],{"class":214},[200,43451,43452],{"class":210},"size ",[200,43454,600],{"class":214},[200,43456,43041],{"class":2045},[200,43458,29919],{"class":214},[200,43460,43041],{"class":2045},[200,43462,550],{"class":214},[200,43464,318],{"class":214},[200,43466,39347],{"class":235},[200,43468,239],{"class":214},[200,43470,242],{"class":214},[200,43472,43058],{"class":245},[200,43474,249],{"class":214},[200,43476,981],{"class":214},[200,43478,2089],{"class":210},[200,43480,215],{"class":214},[200,43482,42986],{"class":223},[200,43484,227],{"class":210},[200,43486,9737],{"class":386},[200,43488,393],{"class":379},[200,43490,42939],{"class":210},[200,43492,215],{"class":214},[200,43494,3771],{"class":210},[200,43496,215],{"class":214},[200,43498,6108],{"class":223},[200,43500,227],{"class":210},[200,43502,249],{"class":214},[200,43504,43093],{"class":245},[200,43506,249],{"class":214},[200,43508,2089],{"class":210},[200,43510,550],{"class":214},[200,43512,318],{"class":214},[200,43514,39347],{"class":235},[200,43516,239],{"class":214},[200,43518,242],{"class":214},[200,43520,43110],{"class":245},[200,43522,249],{"class":214},[200,43524,981],{"class":214},[200,43526,2089],{"class":210},[200,43528,215],{"class":214},[200,43530,42986],{"class":223},[200,43532,227],{"class":210},[200,43534,9737],{"class":386},[200,43536,393],{"class":379},[200,43538,350],{"class":210},[200,43540,249],{"class":214},[200,43542,43144],{"class":245},[200,43544,249],{"class":214},[200,43546,550],{"class":214},[200,43548,242],{"class":214},[200,43550,43135],{"class":245},[200,43552,249],{"class":214},[200,43554,360],{"class":210},[200,43556,215],{"class":214},[200,43558,3841],{"class":223},[200,43560,43561],{"class":210},"(image",[200,43563,215],{"class":214},[200,43565,43566],{"class":210},"type)",[200,43568,550],{"class":214},[200,43570,318],{"class":214},[200,43572,39347],{"class":235},[200,43574,239],{"class":214},[200,43576,242],{"class":214},[200,43578,43184],{"class":245},[200,43580,249],{"class":214},[200,43582,981],{"class":214},[200,43584,416],{"class":210},[200,43586,43587,43589],{"class":202,"line":283},[200,43588,413],{"class":214},[200,43590,416],{"class":210},[35,43592,43593],{},"Then, I can use it when the form is submitted to avoid sending invalid data to the server:",[192,43595,43597],{"className":21417,"code":43596,"language":12060,"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>Validate Forms with 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        Submit\n      \u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[156,43598,43599,43619,43641,43663,43682,43686,43710,43716,43737,43741,43759,43773,43783,43789,43793,43805,43820,43824,43828,43836,43840,43848,43856,43873,43877,43885,43890,43898,43927,43965,43983,43988,43997,44005,44013],{"__ignoreMap":13},[200,43600,43601,43603,43605,43607,43609,43611,43613,43615,43617],{"class":202,"line":203},[200,43602,600],{"class":214},[200,43604,14892],{"class":235},[200,43606,14590],{"class":379},[200,43608,449],{"class":214},[200,43610,611],{"class":214},[200,43612,1373],{"class":245},[200,43614,611],{"class":214},[200,43616,21781],{"class":379},[200,43618,648],{"class":214},[200,43620,43621,43623,43625,43628,43630,43632,43634,43637,43639],{"class":202,"line":14},[200,43622,3711],{"class":379},[200,43624,4987],{"class":379},[200,43626,43627],{"class":223}," onImageSubmit",[200,43629,227],{"class":214},[200,43631,25386],{"class":386},[200,43633,239],{"class":214},[200,43635,43636],{"class":1978}," Event",[200,43638,2089],{"class":214},[200,43640,280],{"class":214},[200,43642,43643,43645,43648,43650,43653,43655,43658,43660],{"class":202,"line":255},[200,43644,3723],{"class":379},[200,43646,43647],{"class":210}," target",[200,43649,510],{"class":214},[200,43651,43652],{"class":210}," event",[200,43654,215],{"class":214},[200,43656,43657],{"class":210},"target",[200,43659,14674],{"class":206},[200,43661,43662],{"class":1978}," HTMLFormElement\n",[200,43664,43665,43667,43669,43671,43673,43676,43678,43680],{"class":202,"line":272},[200,43666,3723],{"class":379},[200,43668,42915],{"class":210},[200,43670,510],{"class":214},[200,43672,12997],{"class":214},[200,43674,43675],{"class":223}," FormData",[200,43677,227],{"class":235},[200,43679,43657],{"class":210},[200,43681,416],{"class":235},[200,43683,43684],{"class":202,"line":283},[200,43685,529],{"emptyLinePlaceholder":20},[200,43687,43688,43690,43692,43694,43696,43698,43700,43702,43704,43706,43708],{"class":202,"line":300},[200,43689,3723],{"class":379},[200,43691,42939],{"class":210},[200,43693,510],{"class":214},[200,43695,42915],{"class":210},[200,43697,215],{"class":214},[200,43699,42948],{"class":223},[200,43701,227],{"class":235},[200,43703,249],{"class":214},[200,43705,9737],{"class":245},[200,43707,249],{"class":214},[200,43709,416],{"class":235},[200,43711,43712,43714],{"class":202,"line":310},[200,43713,42021],{"class":206},[200,43715,280],{"class":214},[200,43717,43718,43720,43723,43725,43727,43729,43731,43733,43735],{"class":202,"line":336},[200,43719,19216],{"class":206},[200,43721,43722],{"class":210}," createImageValidator",[200,43724,215],{"class":214},[200,43726,41541],{"class":223},[200,43728,227],{"class":235},[200,43730,2752],{"class":214},[200,43732,42939],{"class":210},[200,43734,981],{"class":214},[200,43736,416],{"class":235},[200,43738,43739],{"class":202,"line":342},[200,43740,529],{"emptyLinePlaceholder":20},[200,43742,43743,43745,43747,43749,43751,43753,43755,43757],{"class":202,"line":365},[200,43744,19216],{"class":206},[200,43746,18741],{"class":223},[200,43748,227],{"class":235},[200,43750,249],{"class":214},[200,43752,42722],{"class":245},[200,43754,249],{"class":214},[200,43756,550],{"class":214},[200,43758,280],{"class":214},[200,43760,43761,43763,43765,43767,43769,43771],{"class":202,"line":371},[200,43762,42067],{"class":235},[200,43764,239],{"class":214},[200,43766,242],{"class":214},[200,43768,18766],{"class":245},[200,43770,249],{"class":214},[200,43772,252],{"class":214},[200,43774,43775,43777,43779,43781],{"class":202,"line":398},[200,43776,42082],{"class":235},[200,43778,239],{"class":214},[200,43780,42915],{"class":210},[200,43782,252],{"class":214},[200,43784,43785,43787],{"class":202,"line":405},[200,43786,2096],{"class":214},[200,43788,416],{"class":235},[200,43790,43791],{"class":202,"line":410},[200,43792,4490],{"class":214},[200,43794,43795,43797,43799,43801,43803],{"class":202,"line":861},[200,43796,42103],{"class":206},[200,43798,1095],{"class":235},[200,43800,19260],{"class":210},[200,43802,5267],{"class":235},[200,43804,230],{"class":214},[200,43806,43807,43810,43812,43814,43816,43818],{"class":202,"line":890},[200,43808,43809],{"class":210},"    console",[200,43811,215],{"class":214},[200,43813,19260],{"class":223},[200,43815,227],{"class":235},[200,43817,19260],{"class":210},[200,43819,416],{"class":235},[200,43821,43822],{"class":202,"line":900},[200,43823,4490],{"class":214},[200,43825,43826],{"class":202,"line":905},[200,43827,1130],{"class":214},[200,43829,43830,43832,43834],{"class":202,"line":931},[200,43831,681],{"class":214},[200,43833,14892],{"class":235},[200,43835,648],{"class":214},[200,43837,43838],{"class":202,"line":3640},[200,43839,529],{"emptyLinePlaceholder":20},[200,43841,43842,43844,43846],{"class":202,"line":3645},[200,43843,600],{"class":214},[200,43845,21428],{"class":235},[200,43847,648],{"class":214},[200,43849,43850,43852,43854],{"class":202,"line":4020},[200,43851,657],{"class":214},[200,43853,11386],{"class":235},[200,43855,648],{"class":214},[200,43857,43858,43860,43862,43864,43867,43869,43871],{"class":202,"line":4475},[200,43859,804],{"class":214},[200,43861,10081],{"class":235},[200,43863,675],{"class":214},[200,43865,43866],{"class":210},"Validate Forms with Nuxt",[200,43868,681],{"class":214},[200,43870,10081],{"class":235},[200,43872,648],{"class":214},[200,43874,43875],{"class":202,"line":4481},[200,43876,529],{"emptyLinePlaceholder":20},[200,43878,43879,43881,43883],{"class":202,"line":4487},[200,43880,804],{"class":214},[200,43882,51],{"class":235},[200,43884,648],{"class":214},[200,43886,43887],{"class":202,"line":4493},[200,43888,43889],{"class":210},"      Image\n",[200,43891,43892,43894,43896],{"class":202,"line":5454},[200,43893,15074],{"class":214},[200,43895,51],{"class":235},[200,43897,648],{"class":214},[200,43899,43900,43902,43904,43906,43908,43910,43912,43914,43916,43918,43920,43923,43925],{"class":202,"line":5459},[200,43901,804],{"class":214},[200,43903,603],{"class":235},[200,43905,42727],{"class":379},[200,43907,449],{"class":214},[200,43909,611],{"class":214},[200,43911,42734],{"class":245},[200,43913,611],{"class":214},[200,43915,42222],{"class":379},[200,43917,449],{"class":214},[200,43919,611],{"class":214},[200,43921,43922],{"class":245},"onImageSubmit($event)",[200,43924,611],{"class":214},[200,43926,648],{"class":214},[200,43928,43929,43931,43933,43935,43937,43939,43941,43943,43945,43947,43949,43951,43953,43955,43957,43959,43961,43963],{"class":202,"line":13227},[200,43930,22027],{"class":214},[200,43932,355],{"class":235},[200,43934,321],{"class":379},[200,43936,449],{"class":214},[200,43938,611],{"class":214},[200,43940,42763],{"class":245},[200,43942,611],{"class":214},[200,43944,701],{"class":379},[200,43946,449],{"class":214},[200,43948,611],{"class":214},[200,43950,9737],{"class":245},[200,43952,611],{"class":214},[200,43954,42768],{"class":379},[200,43956,449],{"class":214},[200,43958,611],{"class":214},[200,43960,42775],{"class":245},[200,43962,611],{"class":214},[200,43964,648],{"class":214},[200,43966,43967,43969,43971,43973,43975,43977,43979,43981],{"class":202,"line":13253},[200,43968,22027],{"class":214},[200,43970,910],{"class":235},[200,43972,321],{"class":379},[200,43974,449],{"class":214},[200,43976,611],{"class":214},[200,43978,917],{"class":245},[200,43980,611],{"class":214},[200,43982,648],{"class":214},[200,43984,43985],{"class":202,"line":13269},[200,43986,43987],{"class":210},"        Submit\n",[200,43989,43990,43993,43995],{"class":202,"line":13313},[200,43991,43992],{"class":214},"      \u003C\u002F",[200,43994,910],{"class":235},[200,43996,648],{"class":214},[200,43998,43999,44001,44003],{"class":202,"line":13319},[200,44000,15074],{"class":214},[200,44002,603],{"class":235},[200,44004,648],{"class":214},[200,44006,44007,44009,44011],{"class":202,"line":13324},[200,44008,893],{"class":214},[200,44010,11386],{"class":235},[200,44012,648],{"class":214},[200,44014,44015,44017,44019],{"class":202,"line":13350},[200,44016,681],{"class":214},[200,44018,21428],{"class":235},[200,44020,648],{"class":214},[35,44022,44023,44024,44027],{},"If the validation fails, the request will not be sent to the server, and the error will be displayed in the console. Of course, you can handle the error as you want, like using a ",[156,44025,44026],{},"toast"," or showing an error message under the related input.",[35,44029,44030],{},"On the server, I can replace the validation logic with the validator:",[192,44032,44034],{"className":1370,"code":44033,"language":1373,"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,44035,44036,44055,44059,44081,44085,44089,44109,44113,44117],{"__ignoreMap":13},[200,44037,44038,44040,44042,44045,44047,44049,44051,44053],{"class":202,"line":203},[200,44039,973],{"class":206},[200,44041,318],{"class":214},[200,44043,44044],{"class":210}," saveImageValidator",[200,44046,981],{"class":214},[200,44048,984],{"class":206},[200,44050,242],{"class":214},[200,44052,42580],{"class":245},[200,44054,992],{"class":214},[200,44056,44057],{"class":202,"line":14},[200,44058,529],{"emptyLinePlaceholder":20},[200,44060,44061,44063,44065,44067,44069,44071,44073,44075,44077,44079],{"class":202,"line":255},[200,44062,1380],{"class":206},[200,44064,1383],{"class":206},[200,44066,38960],{"class":223},[200,44068,227],{"class":210},[200,44070,3711],{"class":379},[200,44072,1095],{"class":214},[200,44074,25386],{"class":386},[200,44076,2089],{"class":214},[200,44078,393],{"class":379},[200,44080,280],{"class":214},[200,44082,44083],{"class":202,"line":272},[200,44084,10634],{"class":401},[200,44086,44087],{"class":202,"line":283},[200,44088,529],{"emptyLinePlaceholder":20},[200,44090,44091,44093,44095,44097,44099,44101,44103,44105,44107],{"class":202,"line":300},[200,44092,6165],{"class":206},[200,44094,44044],{"class":210},[200,44096,215],{"class":214},[200,44098,41541],{"class":223},[200,44100,227],{"class":235},[200,44102,2752],{"class":214},[200,44104,42939],{"class":210},[200,44106,981],{"class":214},[200,44108,416],{"class":235},[200,44110,44111],{"class":202,"line":310},[200,44112,529],{"emptyLinePlaceholder":20},[200,44114,44115],{"class":202,"line":336},[200,44116,10634],{"class":401},[200,44118,44119,44121],{"class":202,"line":342},[200,44120,413],{"class":214},[200,44122,416],{"class":210},[1913,44124,44126],{"id":44125},"with-nuxthub","With NuxtHub",[35,44128,44129,44130,44132,44133],{},"NuxtHub comes with a utility named ",[156,44131,42681],{}," that can be used to check the file size and type. This utility is very useful because it will automatically throw an error if the file is not valid, but can only be used on the server. This means that I will have a Zod schema on the client and a NuxtHub utility on the server. ",[1217,44134,44135],{},"Use constants to share validation limits between the Zod Schema and the NuxtHub utility.",[192,44137,44139],{"className":1370,"code":44138,"language":1373,"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: '1MB',\n    types: ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg']\n  })\n\n  \u002F\u002F Save the image to a database or a file system\n\n  return sendNoContent(event, 201)\n})\n",[156,44140,44141,44163,44181,44185,44215,44228,44244,44277,44283,44287,44291,44295,44311],{"__ignoreMap":13},[200,44142,44143,44145,44147,44149,44151,44153,44155,44157,44159,44161],{"class":202,"line":203},[200,44144,1380],{"class":206},[200,44146,1383],{"class":206},[200,44148,38960],{"class":223},[200,44150,227],{"class":210},[200,44152,3711],{"class":379},[200,44154,1095],{"class":214},[200,44156,25386],{"class":386},[200,44158,2089],{"class":214},[200,44160,393],{"class":379},[200,44162,280],{"class":214},[200,44164,44165,44167,44169,44171,44173,44175,44177,44179],{"class":202,"line":14},[200,44166,3723],{"class":379},[200,44168,42915],{"class":210},[200,44170,510],{"class":214},[200,44172,452],{"class":206},[200,44174,42922],{"class":223},[200,44176,227],{"class":235},[200,44178,25386],{"class":210},[200,44180,416],{"class":235},[200,44182,44183],{"class":202,"line":255},[200,44184,529],{"emptyLinePlaceholder":20},[200,44186,44187,44189,44191,44193,44195,44197,44199,44201,44203,44205,44207,44209,44212],{"class":202,"line":272},[200,44188,3723],{"class":379},[200,44190,42939],{"class":210},[200,44192,510],{"class":214},[200,44194,42915],{"class":210},[200,44196,215],{"class":214},[200,44198,42948],{"class":223},[200,44200,227],{"class":235},[200,44202,249],{"class":214},[200,44204,9737],{"class":245},[200,44206,249],{"class":214},[200,44208,5267],{"class":235},[200,44210,44211],{"class":206},"as",[200,44213,44214],{"class":1978}," Blob\n",[200,44216,44217,44220,44222,44224,44226],{"class":202,"line":283},[200,44218,44219],{"class":223},"  ensureBlob",[200,44221,227],{"class":235},[200,44223,9737],{"class":210},[200,44225,550],{"class":214},[200,44227,280],{"class":214},[200,44229,44230,44233,44235,44237,44240,44242],{"class":202,"line":300},[200,44231,44232],{"class":235},"    maxSize",[200,44234,239],{"class":214},[200,44236,242],{"class":214},[200,44238,44239],{"class":245},"1MB",[200,44241,249],{"class":214},[200,44243,252],{"class":214},[200,44245,44246,44249,44251,44253,44255,44257,44259,44261,44263,44265,44267,44269,44271,44273,44275],{"class":202,"line":310},[200,44247,44248],{"class":235},"    types",[200,44250,239],{"class":214},[200,44252,350],{"class":235},[200,44254,249],{"class":214},[200,44256,43135],{"class":245},[200,44258,249],{"class":214},[200,44260,550],{"class":214},[200,44262,242],{"class":214},[200,44264,43144],{"class":245},[200,44266,249],{"class":214},[200,44268,550],{"class":214},[200,44270,242],{"class":214},[200,44272,43153],{"class":245},[200,44274,249],{"class":214},[200,44276,4496],{"class":235},[200,44278,44279,44281],{"class":202,"line":336},[200,44280,5080],{"class":214},[200,44282,416],{"class":235},[200,44284,44285],{"class":202,"line":342},[200,44286,529],{"emptyLinePlaceholder":20},[200,44288,44289],{"class":202,"line":365},[200,44290,43305],{"class":401},[200,44292,44293],{"class":202,"line":371},[200,44294,529],{"emptyLinePlaceholder":20},[200,44296,44297,44299,44301,44303,44305,44307,44309],{"class":202,"line":398},[200,44298,3743],{"class":206},[200,44300,41071],{"class":223},[200,44302,227],{"class":235},[200,44304,25386],{"class":210},[200,44306,550],{"class":214},[200,44308,41080],{"class":2045},[200,44310,416],{"class":235},[200,44312,44313,44315],{"class":202,"line":405},[200,44314,413],{"class":214},[200,44316,416],{"class":210},[51,44318,44320],{"id":44319},"text-and-blob-within-the-same-form","Text and blob within the same form",[35,44322,44323],{},"Until now, I've only one type of input in my form, text or file. But what if I want to have both in the same form?",[35,44325,44326,44327,44329],{},"You need to know that you absolutely need to use a ",[156,44328,42833],{}," to send the form to the server due to the file input.",[35,44331,44332],{},"The form is still very simple:",[192,44334,44336],{"className":21417,"code":44335,"language":12060,"meta":13,"style":13},"\u003Ctemplate>\n  \u003Cform action=\"\u002Fapi\u002Ftext-image\" enctype=\"multipart\u002Fform-data\" method=\"post\">\n    \u003Cinput type=\"text\" name=\"name\" placeholder=\"Name\" required>\n    \u003Cinput type=\"file\" name=\"image\" accept=\"image\u002Fpng, image\u002Fjpeg, image\u002Fjpg\" required>\n    \u003Cbutton type=\"submit\">\n      Create Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[156,44337,44338,44346,44385,44425,44465,44483,44487,44495,44503],{"__ignoreMap":13},[200,44339,44340,44342,44344],{"class":202,"line":203},[200,44341,600],{"class":214},[200,44343,21428],{"class":235},[200,44345,648],{"class":214},[200,44347,44348,44350,44352,44354,44356,44358,44361,44363,44365,44367,44369,44371,44373,44375,44377,44379,44381,44383],{"class":202,"line":14},[200,44349,657],{"class":214},[200,44351,603],{"class":235},[200,44353,40910],{"class":379},[200,44355,449],{"class":214},[200,44357,611],{"class":214},[200,44359,44360],{"class":245},"\u002Fapi\u002Ftext-image",[200,44362,611],{"class":214},[200,44364,42727],{"class":379},[200,44366,449],{"class":214},[200,44368,611],{"class":214},[200,44370,42734],{"class":245},[200,44372,611],{"class":214},[200,44374,6577],{"class":379},[200,44376,449],{"class":214},[200,44378,611],{"class":214},[200,44380,3778],{"class":245},[200,44382,611],{"class":214},[200,44384,648],{"class":214},[200,44386,44387,44389,44391,44393,44395,44397,44399,44401,44403,44405,44407,44409,44411,44413,44415,44417,44419,44421,44423],{"class":202,"line":255},[200,44388,804],{"class":214},[200,44390,355],{"class":235},[200,44392,321],{"class":379},[200,44394,449],{"class":214},[200,44396,611],{"class":214},[200,44398,698],{"class":245},[200,44400,611],{"class":214},[200,44402,701],{"class":379},[200,44404,449],{"class":214},[200,44406,611],{"class":214},[200,44408,20089],{"class":245},[200,44410,611],{"class":214},[200,44412,40940],{"class":379},[200,44414,449],{"class":214},[200,44416,611],{"class":214},[200,44418,40947],{"class":245},[200,44420,611],{"class":214},[200,44422,40952],{"class":379},[200,44424,648],{"class":214},[200,44426,44427,44429,44431,44433,44435,44437,44439,44441,44443,44445,44447,44449,44451,44453,44455,44457,44459,44461,44463],{"class":202,"line":272},[200,44428,804],{"class":214},[200,44430,355],{"class":235},[200,44432,321],{"class":379},[200,44434,449],{"class":214},[200,44436,611],{"class":214},[200,44438,42763],{"class":245},[200,44440,611],{"class":214},[200,44442,701],{"class":379},[200,44444,449],{"class":214},[200,44446,611],{"class":214},[200,44448,9737],{"class":245},[200,44450,611],{"class":214},[200,44452,42768],{"class":379},[200,44454,449],{"class":214},[200,44456,611],{"class":214},[200,44458,42775],{"class":245},[200,44460,611],{"class":214},[200,44462,40952],{"class":379},[200,44464,648],{"class":214},[200,44466,44467,44469,44471,44473,44475,44477,44479,44481],{"class":202,"line":283},[200,44468,804],{"class":214},[200,44470,910],{"class":235},[200,44472,321],{"class":379},[200,44474,449],{"class":214},[200,44476,611],{"class":214},[200,44478,917],{"class":245},[200,44480,611],{"class":214},[200,44482,648],{"class":214},[200,44484,44485],{"class":202,"line":300},[200,44486,40977],{"class":210},[200,44488,44489,44491,44493],{"class":202,"line":310},[200,44490,15074],{"class":214},[200,44492,910],{"class":235},[200,44494,648],{"class":214},[200,44496,44497,44499,44501],{"class":202,"line":336},[200,44498,893],{"class":214},[200,44500,603],{"class":235},[200,44502,648],{"class":214},[200,44504,44505,44507,44509],{"class":202,"line":342},[200,44506,681],{"class":214},[200,44508,21428],{"class":235},[200,44510,648],{"class":214},[35,44512,44513],{},"Now, I can create a validator using Zod:",[192,44515,44517],{"className":1370,"code":44516,"language":1373,"meta":13,"style":13},"import { any, object, string } from 'zod'\n\nexport const createValidator = object({\n  name: string({ message: 'Required' }),\n  image: any()\n    .refine(image => image instanceof File, { message: 'Image must be a file' })\n    .refine(image => image.size \u003C 1024 * 1024, { message: 'Image must be less than 1MB' })\n    .refine(image => image.type.startsWith('image\u002F'), { message: 'Image must be an image' })\n    .refine(image => ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg'].includes(image.type), { message: 'Image must be a JPEG or PNG' })\n})\n",[156,44518,44519,44545,44549,44566,44594,44604,44640,44684,44734,44800],{"__ignoreMap":13},[200,44520,44521,44523,44525,44527,44529,44531,44533,44535,44537,44539,44541,44543],{"class":202,"line":203},[200,44522,973],{"class":206},[200,44524,318],{"class":214},[200,44526,42867],{"class":210},[200,44528,550],{"class":214},[200,44530,42872],{"class":210},[200,44532,550],{"class":214},[200,44534,22708],{"class":210},[200,44536,981],{"class":214},[200,44538,984],{"class":206},[200,44540,242],{"class":214},[200,44542,19790],{"class":245},[200,44544,992],{"class":214},[200,44546,44547],{"class":202,"line":14},[200,44548,529],{"emptyLinePlaceholder":20},[200,44550,44551,44553,44555,44558,44560,44562,44564],{"class":202,"line":255},[200,44552,1380],{"class":206},[200,44554,14301],{"class":379},[200,44556,44557],{"class":210}," createValidator ",[200,44559,449],{"class":214},[200,44561,42872],{"class":223},[200,44563,227],{"class":210},[200,44565,230],{"class":214},[200,44567,44568,44570,44572,44574,44576,44578,44580,44582,44584,44586,44588,44590,44592],{"class":202,"line":272},[200,44569,236],{"class":235},[200,44571,239],{"class":214},[200,44573,22708],{"class":223},[200,44575,227],{"class":210},[200,44577,2752],{"class":214},[200,44579,39347],{"class":235},[200,44581,239],{"class":214},[200,44583,242],{"class":214},[200,44585,41947],{"class":245},[200,44587,249],{"class":214},[200,44589,981],{"class":214},[200,44591,2089],{"class":210},[200,44593,252],{"class":214},[200,44595,44596,44598,44600,44602],{"class":202,"line":283},[200,44597,43393],{"class":235},[200,44599,239],{"class":214},[200,44601,42867],{"class":223},[200,44603,466],{"class":210},[200,44605,44606,44608,44610,44612,44614,44616,44618,44620,44622,44624,44626,44628,44630,44632,44634,44636,44638],{"class":202,"line":300},[200,44607,3755],{"class":214},[200,44609,42986],{"class":223},[200,44611,227],{"class":210},[200,44613,9737],{"class":386},[200,44615,393],{"class":379},[200,44617,43412],{"class":210},[200,44619,43415],{"class":214},[200,44621,42999],{"class":1978},[200,44623,550],{"class":214},[200,44625,318],{"class":214},[200,44627,39347],{"class":235},[200,44629,239],{"class":214},[200,44631,242],{"class":214},[200,44633,43012],{"class":245},[200,44635,249],{"class":214},[200,44637,981],{"class":214},[200,44639,416],{"class":210},[200,44641,44642,44644,44646,44648,44650,44652,44654,44656,44658,44660,44662,44664,44666,44668,44670,44672,44674,44676,44678,44680,44682],{"class":202,"line":310},[200,44643,3755],{"class":214},[200,44645,42986],{"class":223},[200,44647,227],{"class":210},[200,44649,9737],{"class":386},[200,44651,393],{"class":379},[200,44653,42939],{"class":210},[200,44655,215],{"class":214},[200,44657,43452],{"class":210},[200,44659,600],{"class":214},[200,44661,43041],{"class":2045},[200,44663,29919],{"class":214},[200,44665,43041],{"class":2045},[200,44667,550],{"class":214},[200,44669,318],{"class":214},[200,44671,39347],{"class":235},[200,44673,239],{"class":214},[200,44675,242],{"class":214},[200,44677,43058],{"class":245},[200,44679,249],{"class":214},[200,44681,981],{"class":214},[200,44683,416],{"class":210},[200,44685,44686,44688,44690,44692,44694,44696,44698,44700,44702,44704,44706,44708,44710,44712,44714,44716,44718,44720,44722,44724,44726,44728,44730,44732],{"class":202,"line":336},[200,44687,3755],{"class":214},[200,44689,42986],{"class":223},[200,44691,227],{"class":210},[200,44693,9737],{"class":386},[200,44695,393],{"class":379},[200,44697,42939],{"class":210},[200,44699,215],{"class":214},[200,44701,3771],{"class":210},[200,44703,215],{"class":214},[200,44705,6108],{"class":223},[200,44707,227],{"class":210},[200,44709,249],{"class":214},[200,44711,43093],{"class":245},[200,44713,249],{"class":214},[200,44715,2089],{"class":210},[200,44717,550],{"class":214},[200,44719,318],{"class":214},[200,44721,39347],{"class":235},[200,44723,239],{"class":214},[200,44725,242],{"class":214},[200,44727,43110],{"class":245},[200,44729,249],{"class":214},[200,44731,981],{"class":214},[200,44733,416],{"class":210},[200,44735,44736,44738,44740,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],{"class":202,"line":342},[200,44737,3755],{"class":214},[200,44739,42986],{"class":223},[200,44741,227],{"class":210},[200,44743,9737],{"class":386},[200,44745,393],{"class":379},[200,44747,350],{"class":210},[200,44749,249],{"class":214},[200,44751,43135],{"class":245},[200,44753,249],{"class":214},[200,44755,550],{"class":214},[200,44757,242],{"class":214},[200,44759,43144],{"class":245},[200,44761,249],{"class":214},[200,44763,550],{"class":214},[200,44765,242],{"class":214},[200,44767,43153],{"class":245},[200,44769,249],{"class":214},[200,44771,360],{"class":210},[200,44773,215],{"class":214},[200,44775,3841],{"class":223},[200,44777,43561],{"class":210},[200,44779,215],{"class":214},[200,44781,43566],{"class":210},[200,44783,550],{"class":214},[200,44785,318],{"class":214},[200,44787,39347],{"class":235},[200,44789,239],{"class":214},[200,44791,242],{"class":214},[200,44793,43184],{"class":245},[200,44795,249],{"class":214},[200,44797,981],{"class":214},[200,44799,416],{"class":210},[200,44801,44802,44804],{"class":202,"line":365},[200,44803,413],{"class":214},[200,44805,416],{"class":210},[35,44807,44808],{},"This validator can be used as before, on both the server and client.",[35,44810,44811,44812,44814],{},"If you prefer to use the ",[156,44813,42681],{}," utility from NuxtHub, I recommend you split the validator into two parts: one for the text and one for the image. Both the text and the image will be used on the client by merging them into a single object, and only the text will be used on the server.",[192,44816,44818],{"className":1370,"code":44817,"language":1373,"meta":13,"style":13},"import { any, object, string } from 'zod'\n\nexport const createTextValidator = object({\n  name: string({ message: 'Required' }),\n})\n\nexport const createImageValidator = object({\n  image: any()\n    .refine(image => image instanceof File, { message: 'Image must be a file' })\n    .refine(image => image.size \u003C 1024 * 1024, { message: 'Image must be less than 1MB' })\n    .refine(image => image.type.startsWith('image\u002F'), { message: 'Image must be an image' })\n    .refine(image => ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg'].includes(image.type), { message: 'Image must be a JPEG or PNG' })\n})\n\nexport const createValidator = createTextValidator.merge(createImageValidator)\n",[156,44819,44820,44846,44850,44867,44895,44901,44905,44921,44931,44967,45011,45061,45127,45133,45137],{"__ignoreMap":13},[200,44821,44822,44824,44826,44828,44830,44832,44834,44836,44838,44840,44842,44844],{"class":202,"line":203},[200,44823,973],{"class":206},[200,44825,318],{"class":214},[200,44827,42867],{"class":210},[200,44829,550],{"class":214},[200,44831,42872],{"class":210},[200,44833,550],{"class":214},[200,44835,22708],{"class":210},[200,44837,981],{"class":214},[200,44839,984],{"class":206},[200,44841,242],{"class":214},[200,44843,19790],{"class":245},[200,44845,992],{"class":214},[200,44847,44848],{"class":202,"line":14},[200,44849,529],{"emptyLinePlaceholder":20},[200,44851,44852,44854,44856,44859,44861,44863,44865],{"class":202,"line":255},[200,44853,1380],{"class":206},[200,44855,14301],{"class":379},[200,44857,44858],{"class":210}," createTextValidator ",[200,44860,449],{"class":214},[200,44862,42872],{"class":223},[200,44864,227],{"class":210},[200,44866,230],{"class":214},[200,44868,44869,44871,44873,44875,44877,44879,44881,44883,44885,44887,44889,44891,44893],{"class":202,"line":272},[200,44870,236],{"class":235},[200,44872,239],{"class":214},[200,44874,22708],{"class":223},[200,44876,227],{"class":210},[200,44878,2752],{"class":214},[200,44880,39347],{"class":235},[200,44882,239],{"class":214},[200,44884,242],{"class":214},[200,44886,41947],{"class":245},[200,44888,249],{"class":214},[200,44890,981],{"class":214},[200,44892,2089],{"class":210},[200,44894,252],{"class":214},[200,44896,44897,44899],{"class":202,"line":283},[200,44898,413],{"class":214},[200,44900,416],{"class":210},[200,44902,44903],{"class":202,"line":300},[200,44904,529],{"emptyLinePlaceholder":20},[200,44906,44907,44909,44911,44913,44915,44917,44919],{"class":202,"line":310},[200,44908,1380],{"class":206},[200,44910,14301],{"class":379},[200,44912,43380],{"class":210},[200,44914,449],{"class":214},[200,44916,42872],{"class":223},[200,44918,227],{"class":210},[200,44920,230],{"class":214},[200,44922,44923,44925,44927,44929],{"class":202,"line":336},[200,44924,43393],{"class":235},[200,44926,239],{"class":214},[200,44928,42867],{"class":223},[200,44930,466],{"class":210},[200,44932,44933,44935,44937,44939,44941,44943,44945,44947,44949,44951,44953,44955,44957,44959,44961,44963,44965],{"class":202,"line":342},[200,44934,3755],{"class":214},[200,44936,42986],{"class":223},[200,44938,227],{"class":210},[200,44940,9737],{"class":386},[200,44942,393],{"class":379},[200,44944,43412],{"class":210},[200,44946,43415],{"class":214},[200,44948,42999],{"class":1978},[200,44950,550],{"class":214},[200,44952,318],{"class":214},[200,44954,39347],{"class":235},[200,44956,239],{"class":214},[200,44958,242],{"class":214},[200,44960,43012],{"class":245},[200,44962,249],{"class":214},[200,44964,981],{"class":214},[200,44966,416],{"class":210},[200,44968,44969,44971,44973,44975,44977,44979,44981,44983,44985,44987,44989,44991,44993,44995,44997,44999,45001,45003,45005,45007,45009],{"class":202,"line":365},[200,44970,3755],{"class":214},[200,44972,42986],{"class":223},[200,44974,227],{"class":210},[200,44976,9737],{"class":386},[200,44978,393],{"class":379},[200,44980,42939],{"class":210},[200,44982,215],{"class":214},[200,44984,43452],{"class":210},[200,44986,600],{"class":214},[200,44988,43041],{"class":2045},[200,44990,29919],{"class":214},[200,44992,43041],{"class":2045},[200,44994,550],{"class":214},[200,44996,318],{"class":214},[200,44998,39347],{"class":235},[200,45000,239],{"class":214},[200,45002,242],{"class":214},[200,45004,43058],{"class":245},[200,45006,249],{"class":214},[200,45008,981],{"class":214},[200,45010,416],{"class":210},[200,45012,45013,45015,45017,45019,45021,45023,45025,45027,45029,45031,45033,45035,45037,45039,45041,45043,45045,45047,45049,45051,45053,45055,45057,45059],{"class":202,"line":371},[200,45014,3755],{"class":214},[200,45016,42986],{"class":223},[200,45018,227],{"class":210},[200,45020,9737],{"class":386},[200,45022,393],{"class":379},[200,45024,42939],{"class":210},[200,45026,215],{"class":214},[200,45028,3771],{"class":210},[200,45030,215],{"class":214},[200,45032,6108],{"class":223},[200,45034,227],{"class":210},[200,45036,249],{"class":214},[200,45038,43093],{"class":245},[200,45040,249],{"class":214},[200,45042,2089],{"class":210},[200,45044,550],{"class":214},[200,45046,318],{"class":214},[200,45048,39347],{"class":235},[200,45050,239],{"class":214},[200,45052,242],{"class":214},[200,45054,43110],{"class":245},[200,45056,249],{"class":214},[200,45058,981],{"class":214},[200,45060,416],{"class":210},[200,45062,45063,45065,45067,45069,45071,45073,45075,45077,45079,45081,45083,45085,45087,45089,45091,45093,45095,45097,45099,45101,45103,45105,45107,45109,45111,45113,45115,45117,45119,45121,45123,45125],{"class":202,"line":398},[200,45064,3755],{"class":214},[200,45066,42986],{"class":223},[200,45068,227],{"class":210},[200,45070,9737],{"class":386},[200,45072,393],{"class":379},[200,45074,350],{"class":210},[200,45076,249],{"class":214},[200,45078,43135],{"class":245},[200,45080,249],{"class":214},[200,45082,550],{"class":214},[200,45084,242],{"class":214},[200,45086,43144],{"class":245},[200,45088,249],{"class":214},[200,45090,550],{"class":214},[200,45092,242],{"class":214},[200,45094,43153],{"class":245},[200,45096,249],{"class":214},[200,45098,360],{"class":210},[200,45100,215],{"class":214},[200,45102,3841],{"class":223},[200,45104,43561],{"class":210},[200,45106,215],{"class":214},[200,45108,43566],{"class":210},[200,45110,550],{"class":214},[200,45112,318],{"class":214},[200,45114,39347],{"class":235},[200,45116,239],{"class":214},[200,45118,242],{"class":214},[200,45120,43184],{"class":245},[200,45122,249],{"class":214},[200,45124,981],{"class":214},[200,45126,416],{"class":210},[200,45128,45129,45131],{"class":202,"line":405},[200,45130,413],{"class":214},[200,45132,416],{"class":210},[200,45134,45135],{"class":202,"line":410},[200,45136,529],{"emptyLinePlaceholder":20},[200,45138,45139,45141,45143,45145,45147,45150,45152,45155],{"class":202,"line":861},[200,45140,1380],{"class":206},[200,45142,14301],{"class":379},[200,45144,44557],{"class":210},[200,45146,449],{"class":214},[200,45148,45149],{"class":210}," createTextValidator",[200,45151,215],{"class":214},[200,45153,45154],{"class":223},"merge",[200,45156,45157],{"class":210},"(createImageValidator)\n",[35,45159,45160,45161,45164],{},"On the client, I will use the ",[156,45162,45163],{},"createValidator"," to validate the form before sending it to the server:",[192,45166,45168],{"className":21417,"code":45167,"language":12060,"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,45169,45170,45190,45210,45228,45246,45250,45274,45298,45304,45329,45333,45351,45365,45375,45381,45385,45397,45411,45415,45419],{"__ignoreMap":13},[200,45171,45172,45174,45176,45178,45180,45182,45184,45186,45188],{"class":202,"line":203},[200,45173,600],{"class":214},[200,45175,14892],{"class":235},[200,45177,14590],{"class":379},[200,45179,449],{"class":214},[200,45181,611],{"class":214},[200,45183,1373],{"class":245},[200,45185,611],{"class":214},[200,45187,21781],{"class":379},[200,45189,648],{"class":214},[200,45191,45192,45194,45196,45198,45200,45202,45204,45206,45208],{"class":202,"line":14},[200,45193,3711],{"class":379},[200,45195,4987],{"class":379},[200,45197,42012],{"class":223},[200,45199,227],{"class":214},[200,45201,25386],{"class":386},[200,45203,239],{"class":214},[200,45205,43636],{"class":1978},[200,45207,2089],{"class":214},[200,45209,280],{"class":214},[200,45211,45212,45214,45216,45218,45220,45222,45224,45226],{"class":202,"line":255},[200,45213,3723],{"class":379},[200,45215,43647],{"class":210},[200,45217,510],{"class":214},[200,45219,43652],{"class":210},[200,45221,215],{"class":214},[200,45223,43657],{"class":210},[200,45225,14674],{"class":206},[200,45227,43662],{"class":1978},[200,45229,45230,45232,45234,45236,45238,45240,45242,45244],{"class":202,"line":272},[200,45231,3723],{"class":379},[200,45233,42915],{"class":210},[200,45235,510],{"class":214},[200,45237,12997],{"class":214},[200,45239,43675],{"class":223},[200,45241,227],{"class":235},[200,45243,43657],{"class":210},[200,45245,416],{"class":235},[200,45247,45248],{"class":202,"line":283},[200,45249,529],{"emptyLinePlaceholder":20},[200,45251,45252,45254,45256,45258,45260,45262,45264,45266,45268,45270,45272],{"class":202,"line":300},[200,45253,3723],{"class":379},[200,45255,701],{"class":210},[200,45257,510],{"class":214},[200,45259,42915],{"class":210},[200,45261,215],{"class":214},[200,45263,42948],{"class":223},[200,45265,227],{"class":235},[200,45267,249],{"class":214},[200,45269,20089],{"class":245},[200,45271,249],{"class":214},[200,45273,416],{"class":235},[200,45275,45276,45278,45280,45282,45284,45286,45288,45290,45292,45294,45296],{"class":202,"line":310},[200,45277,3723],{"class":379},[200,45279,42939],{"class":210},[200,45281,510],{"class":214},[200,45283,42915],{"class":210},[200,45285,215],{"class":214},[200,45287,42948],{"class":223},[200,45289,227],{"class":235},[200,45291,249],{"class":214},[200,45293,9737],{"class":245},[200,45295,249],{"class":214},[200,45297,416],{"class":235},[200,45299,45300,45302],{"class":202,"line":336},[200,45301,42021],{"class":206},[200,45303,280],{"class":214},[200,45305,45306,45308,45311,45313,45315,45317,45319,45321,45323,45325,45327],{"class":202,"line":342},[200,45307,19216],{"class":206},[200,45309,45310],{"class":210}," createValidator",[200,45312,215],{"class":214},[200,45314,41541],{"class":223},[200,45316,227],{"class":235},[200,45318,2752],{"class":214},[200,45320,701],{"class":210},[200,45322,550],{"class":214},[200,45324,42939],{"class":210},[200,45326,981],{"class":214},[200,45328,416],{"class":235},[200,45330,45331],{"class":202,"line":365},[200,45332,529],{"emptyLinePlaceholder":20},[200,45334,45335,45337,45339,45341,45343,45345,45347,45349],{"class":202,"line":371},[200,45336,19216],{"class":206},[200,45338,18741],{"class":223},[200,45340,227],{"class":235},[200,45342,249],{"class":214},[200,45344,44360],{"class":245},[200,45346,249],{"class":214},[200,45348,550],{"class":214},[200,45350,280],{"class":214},[200,45352,45353,45355,45357,45359,45361,45363],{"class":202,"line":398},[200,45354,42067],{"class":235},[200,45356,239],{"class":214},[200,45358,242],{"class":214},[200,45360,18766],{"class":245},[200,45362,249],{"class":214},[200,45364,252],{"class":214},[200,45366,45367,45369,45371,45373],{"class":202,"line":405},[200,45368,42082],{"class":235},[200,45370,239],{"class":214},[200,45372,42915],{"class":210},[200,45374,252],{"class":214},[200,45376,45377,45379],{"class":202,"line":410},[200,45378,2096],{"class":214},[200,45380,416],{"class":235},[200,45382,45383],{"class":202,"line":861},[200,45384,4490],{"class":214},[200,45386,45387,45389,45391,45393,45395],{"class":202,"line":890},[200,45388,42103],{"class":206},[200,45390,1095],{"class":235},[200,45392,19260],{"class":210},[200,45394,5267],{"class":235},[200,45396,230],{"class":214},[200,45398,45399,45401,45403,45405,45407,45409],{"class":202,"line":900},[200,45400,43809],{"class":210},[200,45402,215],{"class":214},[200,45404,19260],{"class":223},[200,45406,227],{"class":235},[200,45408,19260],{"class":210},[200,45410,416],{"class":235},[200,45412,45413],{"class":202,"line":905},[200,45414,4490],{"class":214},[200,45416,45417],{"class":202,"line":931},[200,45418,1130],{"class":214},[200,45420,45421,45423,45425],{"class":202,"line":3640},[200,45422,681],{"class":214},[200,45424,14892],{"class":235},[200,45426,648],{"class":214},[35,45428,45429,45430,45433,45434,45436],{},"On the server, I will use the ",[156,45431,45432],{},"createTextValidator"," to validate the text and the ",[156,45435,42681],{}," utility to validate the image:",[192,45438,45440],{"className":1370,"code":45439,"language":1373,"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: 'Invalid data',\n      data: error.errors[0].message\n    })\n  })\n\n  \u002F\u002F Save the text in the database...\n\n  const image = formData.get('image') as Blob\n  ensureBlob(image, {\n    maxSize: '1MB',\n    types: ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg']\n  })\n\n  \u002F\u002F Save the image to a database or a file system\n\n  return sendNoContent(event, 201)\n})\n",[156,45441,45442,45460,45464,45486,45504,45508,45532,45574,45584,45594,45608,45630,45636,45642,45646,45651,45655,45683,45695,45709,45741,45747,45751,45755,45759,45775],{"__ignoreMap":13},[200,45443,45444,45446,45448,45450,45452,45454,45456,45458],{"class":202,"line":203},[200,45445,973],{"class":206},[200,45447,318],{"class":214},[200,45449,45149],{"class":210},[200,45451,981],{"class":214},[200,45453,984],{"class":206},[200,45455,242],{"class":214},[200,45457,42580],{"class":245},[200,45459,992],{"class":214},[200,45461,45462],{"class":202,"line":14},[200,45463,529],{"emptyLinePlaceholder":20},[200,45465,45466,45468,45470,45472,45474,45476,45478,45480,45482,45484],{"class":202,"line":255},[200,45467,1380],{"class":206},[200,45469,1383],{"class":206},[200,45471,38960],{"class":223},[200,45473,227],{"class":210},[200,45475,3711],{"class":379},[200,45477,1095],{"class":214},[200,45479,25386],{"class":386},[200,45481,2089],{"class":214},[200,45483,393],{"class":379},[200,45485,280],{"class":214},[200,45487,45488,45490,45492,45494,45496,45498,45500,45502],{"class":202,"line":272},[200,45489,3723],{"class":379},[200,45491,42915],{"class":210},[200,45493,510],{"class":214},[200,45495,452],{"class":206},[200,45497,42922],{"class":223},[200,45499,227],{"class":235},[200,45501,25386],{"class":210},[200,45503,416],{"class":235},[200,45505,45506],{"class":202,"line":283},[200,45507,529],{"emptyLinePlaceholder":20},[200,45509,45510,45512,45514,45516,45518,45520,45522,45524,45526,45528,45530],{"class":202,"line":300},[200,45511,3723],{"class":379},[200,45513,701],{"class":210},[200,45515,510],{"class":214},[200,45517,42915],{"class":210},[200,45519,215],{"class":214},[200,45521,42948],{"class":223},[200,45523,227],{"class":235},[200,45525,249],{"class":214},[200,45527,20089],{"class":245},[200,45529,249],{"class":214},[200,45531,416],{"class":235},[200,45533,45534,45536,45538,45540,45542,45544,45546,45548,45550,45552,45554,45556,45558,45560,45562,45564,45566,45568,45570,45572],{"class":202,"line":310},[200,45535,3723],{"class":379},[200,45537,5041],{"class":210},[200,45539,510],{"class":214},[200,45541,452],{"class":206},[200,45543,45149],{"class":210},[200,45545,215],{"class":214},[200,45547,41541],{"class":223},[200,45549,227],{"class":235},[200,45551,2752],{"class":214},[200,45553,701],{"class":210},[200,45555,981],{"class":214},[200,45557,2089],{"class":235},[200,45559,215],{"class":214},[200,45561,41681],{"class":223},[200,45563,227],{"class":235},[200,45565,227],{"class":214},[200,45567,19260],{"class":386},[200,45569,2089],{"class":214},[200,45571,393],{"class":379},[200,45573,280],{"class":214},[200,45575,45576,45578,45580,45582],{"class":202,"line":336},[200,45577,39438],{"class":206},[200,45579,39441],{"class":223},[200,45581,227],{"class":235},[200,45583,230],{"class":214},[200,45585,45586,45588,45590,45592],{"class":202,"line":342},[200,45587,39082],{"class":235},[200,45589,239],{"class":214},[200,45591,41427],{"class":2045},[200,45593,252],{"class":214},[200,45595,45596,45598,45600,45602,45604,45606],{"class":202,"line":365},[200,45597,39471],{"class":235},[200,45599,239],{"class":214},[200,45601,242],{"class":214},[200,45603,41440],{"class":245},[200,45605,249],{"class":214},[200,45607,252],{"class":214},[200,45609,45610,45612,45614,45616,45618,45620,45622,45624,45626,45628],{"class":202,"line":371},[200,45611,41449],{"class":235},[200,45613,239],{"class":214},[200,45615,19205],{"class":210},[200,45617,215],{"class":214},[200,45619,26789],{"class":210},[200,45621,9858],{"class":235},[200,45623,3933],{"class":2045},[200,45625,360],{"class":235},[200,45627,215],{"class":214},[200,45629,43284],{"class":210},[200,45631,45632,45634],{"class":202,"line":398},[200,45633,2096],{"class":214},[200,45635,416],{"class":235},[200,45637,45638,45640],{"class":202,"line":405},[200,45639,5080],{"class":214},[200,45641,416],{"class":235},[200,45643,45644],{"class":202,"line":410},[200,45645,529],{"emptyLinePlaceholder":20},[200,45647,45648],{"class":202,"line":861},[200,45649,45650],{"class":401},"  \u002F\u002F Save the text in the database...\n",[200,45652,45653],{"class":202,"line":890},[200,45654,529],{"emptyLinePlaceholder":20},[200,45656,45657,45659,45661,45663,45665,45667,45669,45671,45673,45675,45677,45679,45681],{"class":202,"line":900},[200,45658,3723],{"class":379},[200,45660,42939],{"class":210},[200,45662,510],{"class":214},[200,45664,42915],{"class":210},[200,45666,215],{"class":214},[200,45668,42948],{"class":223},[200,45670,227],{"class":235},[200,45672,249],{"class":214},[200,45674,9737],{"class":245},[200,45676,249],{"class":214},[200,45678,5267],{"class":235},[200,45680,44211],{"class":206},[200,45682,44214],{"class":1978},[200,45684,45685,45687,45689,45691,45693],{"class":202,"line":905},[200,45686,44219],{"class":223},[200,45688,227],{"class":235},[200,45690,9737],{"class":210},[200,45692,550],{"class":214},[200,45694,280],{"class":214},[200,45696,45697,45699,45701,45703,45705,45707],{"class":202,"line":931},[200,45698,44232],{"class":235},[200,45700,239],{"class":214},[200,45702,242],{"class":214},[200,45704,44239],{"class":245},[200,45706,249],{"class":214},[200,45708,252],{"class":214},[200,45710,45711,45713,45715,45717,45719,45721,45723,45725,45727,45729,45731,45733,45735,45737,45739],{"class":202,"line":3640},[200,45712,44248],{"class":235},[200,45714,239],{"class":214},[200,45716,350],{"class":235},[200,45718,249],{"class":214},[200,45720,43135],{"class":245},[200,45722,249],{"class":214},[200,45724,550],{"class":214},[200,45726,242],{"class":214},[200,45728,43144],{"class":245},[200,45730,249],{"class":214},[200,45732,550],{"class":214},[200,45734,242],{"class":214},[200,45736,43153],{"class":245},[200,45738,249],{"class":214},[200,45740,4496],{"class":235},[200,45742,45743,45745],{"class":202,"line":3645},[200,45744,5080],{"class":214},[200,45746,416],{"class":235},[200,45748,45749],{"class":202,"line":4020},[200,45750,529],{"emptyLinePlaceholder":20},[200,45752,45753],{"class":202,"line":4475},[200,45754,43305],{"class":401},[200,45756,45757],{"class":202,"line":4481},[200,45758,529],{"emptyLinePlaceholder":20},[200,45760,45761,45763,45765,45767,45769,45771,45773],{"class":202,"line":4487},[200,45762,3743],{"class":206},[200,45764,41071],{"class":223},[200,45766,227],{"class":235},[200,45768,25386],{"class":210},[200,45770,550],{"class":214},[200,45772,41080],{"class":2045},[200,45774,416],{"class":235},[200,45776,45777,45779],{"class":202,"line":4493},[200,45778,413],{"class":214},[200,45780,416],{"class":210},[35,45782,45783,45784,1552],{},"For longer forms, I could use a loop to extract every field from the form and validate them. In this loop, I can handle specific cases like arrays of multi-select fields or files or skipping some fields (that could be validated using ",[156,45785,42681],{},[192,45787,45789],{"className":1370,"code":45788,"language":1373,"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,45790,45791,45801,45832,45853,45879,45884,45888,45912,45916,45920,45926,45941,45945],{"__ignoreMap":13},[200,45792,45793,45795,45797,45799],{"class":202,"line":203},[200,45794,443],{"class":379},[200,45796,10954],{"class":210},[200,45798,449],{"class":214},[200,45800,19891],{"class":214},[200,45802,45803,45805,45807,45809,45811,45813,45815,45817,45819,45821,45823,45825,45827,45830],{"class":202,"line":14},[200,45804,1090],{"class":206},[200,45806,1095],{"class":210},[200,45808,443],{"class":379},[200,45810,350],{"class":214},[200,45812,24584],{"class":210},[200,45814,550],{"class":214},[200,45816,810],{"class":210},[200,45818,360],{"class":214},[200,45820,6445],{"class":214},[200,45822,42915],{"class":210},[200,45824,215],{"class":214},[200,45826,6453],{"class":223},[200,45828,45829],{"class":210},"()) ",[200,45831,230],{"class":214},[200,45833,45834,45836,45838,45840,45842,45844,45847,45849,45851],{"class":202,"line":255},[200,45835,20075],{"class":206},[200,45837,1095],{"class":235},[200,45839,24584],{"class":210},[200,45841,3283],{"class":214},[200,45843,242],{"class":214},[200,45845,45846],{"class":245},"moduleId[]",[200,45848,249],{"class":214},[200,45850,5267],{"class":235},[200,45852,230],{"class":214},[200,45854,45855,45858,45860,45862,45864,45866,45868,45870,45873,45875,45877],{"class":202,"line":272},[200,45856,45857],{"class":210},"    data",[200,45859,9858],{"class":235},[200,45861,24584],{"class":210},[200,45863,18836],{"class":235},[200,45865,449],{"class":214},[200,45867,42915],{"class":210},[200,45869,215],{"class":214},[200,45871,45872],{"class":223},"getAll",[200,45874,227],{"class":235},[200,45876,24584],{"class":210},[200,45878,416],{"class":235},[200,45880,45881],{"class":202,"line":283},[200,45882,45883],{"class":206},"    continue\n",[200,45885,45886],{"class":202,"line":300},[200,45887,4490],{"class":214},[200,45889,45890,45893,45896,45898,45900,45902,45904,45906,45908,45910],{"class":202,"line":310},[200,45891,45892],{"class":206},"  else",[200,45894,45895],{"class":206}," if",[200,45897,1095],{"class":235},[200,45899,24584],{"class":210},[200,45901,3283],{"class":214},[200,45903,242],{"class":214},[200,45905,9737],{"class":245},[200,45907,249],{"class":214},[200,45909,5267],{"class":235},[200,45911,230],{"class":214},[200,45913,45914],{"class":202,"line":336},[200,45915,45883],{"class":206},[200,45917,45918],{"class":202,"line":342},[200,45919,4490],{"class":214},[200,45921,45922,45924],{"class":202,"line":365},[200,45923,45892],{"class":206},[200,45925,280],{"class":214},[200,45927,45928,45930,45932,45934,45936,45938],{"class":202,"line":371},[200,45929,45857],{"class":210},[200,45931,9858],{"class":235},[200,45933,24584],{"class":210},[200,45935,18836],{"class":235},[200,45937,449],{"class":214},[200,45939,45940],{"class":210}," value\n",[200,45942,45943],{"class":202,"line":398},[200,45944,4490],{"class":214},[200,45946,45947],{"class":202,"line":405},[200,45948,1130],{"class":214},[51,45950,30681],{"id":30680},[35,45952,29256,45953,45956,45957],{},[1217,45954,45955],{},"voilà","! You are now able to validate forms on both the client and the server without duplicating the validation logic. This is a huge step forward in building a full-stack application with Nuxt. I hope this article will help you to build your next application with confidence. ",[1217,45958,45959,45960,45965],{},"You should definitely check the ",[39,45961,45964],{"href":45962,"rel":45963},"https:\u002F\u002Fhub.nuxt.com\u002Fdocs",[77],"NuxtHub documentation"," to learn more about the platform and the power they provide to build full-stack applications with Nuxt.",[35,45967,45968],{},"Please, let me know if you have any questions or feedback. I could make some mistakes, so don't hesitate to correct me. I'm always looking for ways to improve my code and my articles.",[35,45970,45971],{},"Thanks for reading!",[1171,45973,45974],{},"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":45976},[45977,45978],{"id":18634,"depth":14,"text":18635},{"id":40874,"depth":14,"text":40875,"children":45979},[45980],{"id":41102,"depth":255,"text":41103},"2024-06-07","Discover how to validate forms on both the client and server sides without duplicating the validation logic, from simple text fields to complex file uploads.",{},"\u002Fposts\u002Fnuxt-going-full-stack-how-to-streamline-form-validation","---\ncontentId: 6df6b96b-58fc-4294-8a38-d209f2b36d19\ntitle: \"Nuxt Going Full-Stack: How to Streamline Form Validation?\"\ndescription: Discover how to validate forms on both the client and server sides without duplicating the validation logic, from simple text fields to complex file uploads.\ndate: 2024-06-07\n---\n\nSince weeks, I'm building a full-stack application called Orion with Nuxt and [NuxtHub](https:\u002F\u002Fhub.nuxt.com). Throughout this journey, I've been facing a lot of challenges, and one of them is forms validation. In this article, I'll show you how I've managed to **validate forms on both the client and server sides without duplicating** the validation logic, from simple text fields to complex file uploads with NuxtHub.\n\n_Orion is a community-driven collection of templates for your next project,\nfrom landing pages to complete web applications. NuxtHub is deployment and administration platform for Nuxt, powered by Cloudflare._\n\n\u003CCalloutReadMore id=\"c21a371e-abdd-4325-b296-dadcb8deec25\" \u002F>\n\n_During this article, I'll talk about [NuxtHub](https:\u002F\u002Fhub.nuxt.com) because it provides the primitives to build full-stack application, but even if you don't use it, you can easily follow this article. Forms will be handled without any libraries._\n\nYou can browse the example code in \u003CGitHubLink repo=\"barbapapazes\u002Fvalidate-forms-in-nuxt\" \u002F>.\n\n## The problem\n\nOn the web, forms are everywhere. They are the primary way to interact and ask for user input. But on the web, there is an essential rule: **never trust the client** (absolutely never and under no circumstances). This means that **every data** that comes from the client must be **verified** and **sanitized** on the server before being processed or stored in a database. A client can be a browser, a mobile app, an HTTP request, or anything else that can send data to your server, built by you or not.\n\nWhen you build a traditional MPA (Multi-Page Application), only the server is responsible for the validation. For a SPA (Single-Page Application), both the client and the server are responsible for the validation to provide a better user experience. _This is simplified because there is some way to provide a feeling of client-side validation with a traditional MPA, but it's not the subject of this article._ BUT, even if the data is validated on the client side, it is **mandatory** to validate them on the server side.\n\n## The simplest form\n\nLet's start our journey with the simplest form: a single text input.\n\n```vue\n\u003Ctemplate>\n  \u003Cform method=\"post\" action=\"\u002Ftag\">\n    \u003Cinput type=\"text\" placeholder=\"Name\" required>\n    \u003Cbutton type=\"submit\">\n      Create Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n```\n\nThen, on the server side, I can create a route to handle the form submission:\n\n```ts\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event)\n\n  \u002F\u002F Save in the database...\n\n  return sendNoContent(event, 201)\n})\n```\n\nUntil now, I don't have any validation. I can send anything to the server, and it will accept it. _This is a huge security issue._\n\n_Remember that the way I'm handling the validation is specific to a SPA. A traditional MPA would have added errors to flash messages and redirect back the request._\n\n### Validating the body\n\nIn order to validate the body on the server side, I'm using [Zod](https:\u002F\u002Fzod.dev). _Zod is a TypeScript-first schema validation with static type inference._ The good part is that h3, the router and web framework that power Nitro, the server layer of Nuxt, provides a utility to validate the body: `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 Save in the database...\n\n  return sendNoContent(event, 201)\n})\n```\n\nThe utility `readValidatedBody` will automatically throw an error if the body doesn't match the schema. For example, if the name is missing, the request will fail.\n\n\u003CCallout icon=\"i-unjs-h3\" text=\"Validate Data using H3\" to=\"https:\u002F\u002Fh3.unjs.io\u002Fexamples\u002Fvalidate-data\" \u002F>\n\nBy default, the error will be handled by the server and throw a 500 error, but I can catch the error to return a more user-friendly error message.\n\nThe first solution is by using the method `safeParse` that will not throw an error but return a result with the error if any.\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: 'Invalid data',\n      data: result.error.errors[0].message,\n    })\n  }\n\n  const body = result.data\n\n  \u002F\u002F Save in the database...\n\n  return sendNoContent(event, 201)\n})\n```\n\nThe second solution is by using the `parseAsync` method to attach a catch to the promise and handle the error manually.\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: 'Invalid data',\n        data: error.errors[0].message,\n      })\n    }\n  }))\n\n  \u002F\u002F Save in the database...\n\n  return sendNoContent(event, 201)\n})\n```\n\n_You could wrap the validator with a `try\u002Fcatch`, but I found it less elegant and readable._\n\n### Validating the frontend\n\nOn the frontend, I can also use Zod to validate the form before sending it to the server.\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: 'Required' }), \u002F\u002F I can customize the error message shown to the user\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: 'Invalid data',\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=\"Name\" required>\n    \u003Cbutton type=\"submit\">\n      Create Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n```\n\nThe implementation is very simple and straightforward. I could save the errors in a variable to display them to the user. If you want to go further, I recommend you try the `Form` component from [Nuxt UI](https:\u002F\u002Fui.nuxt.com\u002F) or a validation library like [VeeValidate](https:\u002F\u002Fvee-validate.logaretm.com\u002Fv4\u002F) or [FormKit](https:\u002F\u002Fformkit.com\u002F).\n\n## Let's refactor\n\nNow that I have the validation logic on both the client and server sides, let's see if there is some interesting code to refactor.\n\nThe first thing I notice is that the validation logic, on the client and server sides, is the same, and that's totally normal. But what happens if I decide to change the validation logic on the server side because my API is used by another application? My frontend could be broken because the validation logic will be different. To avoid this, it could be better to only write the validator once and use it on both sides. What do you think?\n\nI can create a file `validators.ts` in the `utils` folder of the client part (at the root) of a Nuxt application. This file will contain all the validators for my application and shared between the client and server.\n\n```ts\nimport { z } from 'zod'\n\nexport const createTagValidator = z.object({\n  name: z.string({ message: 'Required' }),\n})\n```\n\n> [!TIP]\n> This file can be used on the server but has been created on the client part so you need to explicitly import everything you need to avoid renderer issues.\n\nThis validator contains the message for the error to help the user understand what's wrong.\n\nThanks to the auto-import feature of Nuxt, I can easily use it on the 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\nThen, you can also use it on the server side by importing it:\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 Save in the database...\n\n  return sendNoContent(event, 201)\n})\n```\n\nNow, I can safely change the validation logic without breaking the frontend since they follow the same rules, and I don't have to duplicate the code.\n\n## Handling files\n\nFor this part, I'll show you two different ways to validate files on the server. The first one will use Zod and some refinements, and the second one will use the `ensureBlob` utility from NuxtHub.\n\n_This validation is not feature complete and should be improved to handle more cases._\n\n### With Zod\n\nFor this second part, I'll show you how to upload a file to the server. First, let's add our input to the form:\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      Create Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n```\n\nThen, I can handle the file on the server side, but it's a bit more complex because I send a `formData` and h3 does not provide a utility like `readValidatedFormData`. So, I need to handle it manually.\n\nFirst, I will read the `formData` using the `readFormData` utility, then I will extract the `image` from the `formData` and validate it using 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: 'Image must be a file' })\n      .refine(image => image.size \u003C 1024 * 1024, { message: 'Image must be less than 1MB' })\n      .refine(image => image.type.startsWith('image\u002F'), { message: 'Image must be an image' })\n      .refine(image => ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg'].includes(image.type), { message: 'Image must be a JPEG or PNG' })\n  }).parseAsync({ image }).catch((error) => {\n    throw createError({\n      status: 400,\n      message: 'Invalid image',\n      data: error.errors[0].message\n    })\n  })\n\n  \u002F\u002F Save the image to a database or a file system\n\n  return sendNoContent(event, 201)\n})\n```\n\nOn the client side, I can also validate the file using the same technique as before and by sharing the validator between the client and server sides.\n\nFirst, I need to move the validator to the `utils\u002Fvalidators.ts` file:\n\n```ts\nimport { any, object } from 'zod'\n\nexport const createImageValidator = object({\n  image: any().refine(image => image instanceof File, { message: 'Image must be a file' }).refine(image => image.size \u003C 1024 * 1024, { message: 'Image must be less than 1MB' }).refine(image => image.type.startsWith('image\u002F'), { message: 'Image must be an image' }).refine(image => ['image\u002Fjpeg', 'image\u002Fpng'].includes(image.type), { message: 'Image must be a JPEG or PNG' })\n})\n```\n\nThen, I can use it when the form is submitted to avoid sending invalid data to the server:\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>Validate Forms with 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        Submit\n      \u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\nIf the validation fails, the request will not be sent to the server, and the error will be displayed in the console. Of course, you can handle the error as you want, like using a `toast` or showing an error message under the related input.\n\nOn the server, I can replace the validation logic with the validator:\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### With NuxtHub\n\nNuxtHub comes with a utility named `ensureBlob` that can be used to check the file size and type. This utility is very useful because it will automatically throw an error if the file is not valid, but can only be used on the server. This means that I will have a Zod schema on the client and a NuxtHub utility on the server. _Use constants to share validation limits between the Zod Schema and the NuxtHub utility._\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: '1MB',\n    types: ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg']\n  })\n\n  \u002F\u002F Save the image to a database or a file system\n\n  return sendNoContent(event, 201)\n})\n```\n\n## Text and blob within the same form\n\nUntil now, I've only one type of input in my form, text or file. But what if I want to have both in the same form?\n\nYou need to know that you absolutely need to use a `formData` to send the form to the server due to the file input.\n\nThe form is still very 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=\"Name\" required>\n    \u003Cinput type=\"file\" name=\"image\" accept=\"image\u002Fpng, image\u002Fjpeg, image\u002Fjpg\" required>\n    \u003Cbutton type=\"submit\">\n      Create Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n```\n\nNow, I can create a validator using Zod:\n\n```ts\nimport { any, object, string } from 'zod'\n\nexport const createValidator = object({\n  name: string({ message: 'Required' }),\n  image: any()\n    .refine(image => image instanceof File, { message: 'Image must be a file' })\n    .refine(image => image.size \u003C 1024 * 1024, { message: 'Image must be less than 1MB' })\n    .refine(image => image.type.startsWith('image\u002F'), { message: 'Image must be an image' })\n    .refine(image => ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg'].includes(image.type), { message: 'Image must be a JPEG or PNG' })\n})\n```\n\nThis validator can be used as before, on both the server and client.\n\nIf you prefer to use the `ensureBlob` utility from NuxtHub, I recommend you split the validator into two parts: one for the text and one for the image. Both the text and the image will be used on the client by merging them into a single object, and only the text will be used on the server.\n\n```ts\nimport { any, object, string } from 'zod'\n\nexport const createTextValidator = object({\n  name: string({ message: 'Required' }),\n})\n\nexport const createImageValidator = object({\n  image: any()\n    .refine(image => image instanceof File, { message: 'Image must be a file' })\n    .refine(image => image.size \u003C 1024 * 1024, { message: 'Image must be less than 1MB' })\n    .refine(image => image.type.startsWith('image\u002F'), { message: 'Image must be an image' })\n    .refine(image => ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg'].includes(image.type), { message: 'Image must be a JPEG or PNG' })\n})\n\nexport const createValidator = createTextValidator.merge(createImageValidator)\n```\n\nOn the client, I will use the `createValidator` to validate the form before sending it to the server:\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\nOn the server, I will use the `createTextValidator` to validate the text and the `ensureBlob` utility to validate the 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: 'Invalid data',\n      data: error.errors[0].message\n    })\n  })\n\n  \u002F\u002F Save the text in the database...\n\n  const image = formData.get('image') as Blob\n  ensureBlob(image, {\n    maxSize: '1MB',\n    types: ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg']\n  })\n\n  \u002F\u002F Save the image to a database or a file system\n\n  return sendNoContent(event, 201)\n})\n```\n\nFor longer forms, I could use a loop to extract every field from the form and validate them. In this loop, I can handle specific cases like arrays of multi-select fields or files or skipping some fields (that could be validated using `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\nAnd _voilà_! You are now able to validate forms on both the client and the server without duplicating the validation logic. This is a huge step forward in building a full-stack application with Nuxt. I hope this article will help you to build your next application with confidence. _You should definitely check the [NuxtHub documentation](https:\u002F\u002Fhub.nuxt.com\u002Fdocs) to learn more about the platform and the power they provide to build full-stack applications with Nuxt._\n\nPlease, let me know if you have any questions or feedback. I could make some mistakes, so don't hesitate to correct me. I'm always looking for ways to improve my code and my articles.\n\nThanks for reading!\n",{"title":40805,"description":45982},{"loc":45984},"posts\u002Fnuxt-going-full-stack-how-to-streamline-form-validation","sZN7dZMbuNKRMM9lErEft0QE-mw2moxSubiD2pPyYb8",{"id":45991,"title":45992,"body":45993,"contentId":30840,"date":60380,"description":60381,"extension":18,"meta":60382,"navigation":20,"path":60383,"rawbody":60384,"robots":23,"schemaOrg":23,"seo":60385,"sitemap":60386,"stem":60412,"__hash__":60413},"posts\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey.md","Create a Full-Stack Nuxt Application: A Twitch Journey",{"type":7,"value":45994,"toc":60376},[45995,46000,46003,46026,46029,46034,46037,46040,46044,46050,46070,46076,46082,46089,46102,46115,46272,46275,46285,46308,46311,46316,46322,46326,46332,46348,46351,46354,60373],[35,45996,45997],{},[6850,45998,45999],{},"I finally did it!",[35,46001,46002],{},"After months of thinking about it, I finally started my first stream on Twitch, the 28th of April 2024. It's a very important step for me because I love sharing my knowledge and talking about what I'm doing.",[11386,46004,132,46010],{"className":46005},[46006,46007,46008,46009],"grid","grid-cols-1","md:grid-cols-2","gap-4",[46011,46012,132,46013],"calloutfollowmeontwitch",{},[7139,46014,132,46017],{"icon":7141,"text":46015,"href":46016},"View Replays","https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes",[7139,46018,132,46021],{"icon":46019,"text":46020,"href":36517},"i-simple-icons-x","Don't Miss a Thing",[7139,46022],{"icon":46023,"text":46024,"href":46025},"i-simple-icons-github","The Full-Stack Application","https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion",[35,46027,46028],{},"When I was at school, I organized some lessons for my classmates to explain programming. I also joined a community to explain things about computers, I gave some talks, and recently, I started to write articles on my blog.",[35,46030,46031],{},[6850,46032,46033],{},"Twitch is just a new way to continue doing what I love!",[35,46035,46036],{},"For the coming weeks, I'll share with you my journey to build a full-stack Nuxt application, deployed on Cloudflare, called Orion. I'll explain how I built it, the choices I made, and the problems I encountered. My goal through this article is to give you an honest and realistic view of the pros and cons of building a full-stack application on the edge with Nuxt in May 2024 through the process of building a real-world application.",[35,46038,46039],{},"In this article, I'll share with you every step of this first series of streams. If you don't speak French (because streams are in French), if you missed a stream, or if you simply prefer to read, this article is for you!",[51,46041,46043],{"id":46042},"discovering-nuxthub-and-starting-the-full-stack-nuxt-application","Discovering NuxtHub and starting the full-stack Nuxt application",[35,46045,46046],{},[1690,46047],{"alt":46048,"src":46049},"Thumbnail of my first live stream.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-04-28.png",[35,46051,46052],{},[1217,46053,46054,46057,46058,46062,46063,46066,46067,215],{},[39,46055,38639],{"href":46025,"rel":46056},[77]," is a full-stack application built with Nuxt and developed live ",[39,46059,46061],{"href":18567,"rel":46060},[77],"on Twitch",". You can see the replay on ",[39,46064,19412],{"href":46016,"rel":46065},[77]," and the source code is open-source on ",[39,46068,11622],{"href":46025,"rel":46069},[77],[35,46071,46072,46073],{},"This was my first stream, and to be honest, I was a bit stressed. The moment when you have to change the scene from the \"Starting Soon\" to the facecam is hard. But once I started to talk, everything went well. ",[1217,46074,46075],{},"However, that doesn't mean it's the best stream you'll ever see. We're going to improve!",[35,46077,46078],{},[1690,46079],{"alt":46080,"src":46081},"Myself, not very comfortable in front of the camera.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-04-28-me.png",[35,46083,46084,46085,46088],{},"First, I introduce ",[39,46086,36822],{"href":38142,"rel":46087},[77],". It is a platform built by the Nuxt team to facilitate the deployment and management of Nuxt applications deployed on Cloudflare. In a few clicks, you can deploy your Nuxt application on Cloudflare. That's really magical! Thanks to a Nuxt module, you will be able to develop your application locally and then deploy it without tweaking your code. Everything, from the database to the blog storage, works seamlessly in both environments.",[35,46090,46091,46092,46101],{},"I tried to deploy a template from NuxtHub to show how easy it is to use the platform. I chose the Atinotes template. Unfortunately, this application produces a bundle bigger than 1MB, the limit for the free tier, so the deployment fails. Sad for a demonstration. ",[1217,46093,46094,46095,46100],{},"After this fail, I was thinking about a way to prevent this and to tell users whether a template requires a paid plan or not. ",[39,46096,46099],{"href":46097,"rel":46098},"https:\u002F\u002Fgithub.com\u002Fnuxt-hub\u002Ffeedback\u002Fissues\u002F13",[77],"I raised an issue"," that was quickly fixed!",". Demo will wait for later.",[35,46103,46104,46105,17879,46110,46114],{},"After this beautiful demo effect, we start coding the full-stack Nuxt application by adding authentication. To do so, we use the module ",[39,46106,46108],{"href":38274,"rel":46107},[77],[156,46109,38276],{},[39,46111,38138],{"href":46112,"rel":46113},"https:\u002F\u002Fgithub.com\u002FAtinux",[77],". With a few lines of code, we add authentication. Here is the code we used:",[192,46116,46118],{"className":1370,"code":46117,"language":1373,"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,46119,46120,46138,46147,46158,46162,46187,46202,46211,46227,46231,46237,46241,46262,46266],{"__ignoreMap":13},[200,46121,46122,46124,46126,46129,46131,46134,46136],{"class":202,"line":203},[200,46123,1380],{"class":206},[200,46125,1383],{"class":206},[200,46127,46128],{"class":210}," oauth",[200,46130,215],{"class":214},[200,46132,46133],{"class":223},"githubEventHandler",[200,46135,227],{"class":210},[200,46137,230],{"class":214},[200,46139,46140,46143,46145],{"class":202,"line":14},[200,46141,46142],{"class":235},"  config",[200,46144,239],{"class":214},[200,46146,280],{"class":214},[200,46148,46149,46152,46154,46156],{"class":202,"line":255},[200,46150,46151],{"class":235},"    emailRequired",[200,46153,239],{"class":214},[200,46155,2424],{"class":2423},[200,46157,252],{"class":214},[200,46159,46160],{"class":202,"line":272},[200,46161,368],{"class":214},[200,46163,46164,46166,46169,46171,46173,46175,46177,46179,46181,46183,46185],{"class":202,"line":283},[200,46165,3219],{"class":379},[200,46167,46168],{"class":235}," onSuccess",[200,46170,227],{"class":214},[200,46172,25386],{"class":386},[200,46174,550],{"class":214},[200,46176,318],{"class":214},[200,46178,34776],{"class":386},[200,46180,550],{"class":214},[200,46182,9855],{"class":386},[200,46184,390],{"class":214},[200,46186,280],{"class":214},[200,46188,46189,46191,46194,46196,46198,46200],{"class":202,"line":300},[200,46190,19216],{"class":206},[200,46192,46193],{"class":223}," setUserSession",[200,46195,227],{"class":235},[200,46197,25386],{"class":210},[200,46199,550],{"class":214},[200,46201,280],{"class":214},[200,46203,46204,46207,46209],{"class":202,"line":310},[200,46205,46206],{"class":235},"      user",[200,46208,239],{"class":214},[200,46210,280],{"class":214},[200,46212,46213,46216,46218,46220,46222,46225],{"class":202,"line":336},[200,46214,46215],{"class":235},"        githubId",[200,46217,239],{"class":214},[200,46219,34776],{"class":210},[200,46221,215],{"class":214},[200,46223,46224],{"class":210},"githubId",[200,46226,252],{"class":214},[200,46228,46229],{"class":202,"line":342},[200,46230,12083],{"class":214},[200,46232,46233,46235],{"class":202,"line":365},[200,46234,2096],{"class":214},[200,46236,416],{"class":235},[200,46238,46239],{"class":202,"line":371},[200,46240,529],{"emptyLinePlaceholder":20},[200,46242,46243,46245,46248,46250,46252,46254,46256,46258,46260],{"class":202,"line":398},[200,46244,3296],{"class":206},[200,46246,46247],{"class":223}," sendRedirect",[200,46249,227],{"class":235},[200,46251,25386],{"class":210},[200,46253,550],{"class":214},[200,46255,242],{"class":214},[200,46257,2882],{"class":245},[200,46259,249],{"class":214},[200,46261,416],{"class":235},[200,46263,46264],{"class":202,"line":405},[200,46265,368],{"class":214},[200,46267,46268,46270],{"class":202,"line":410},[200,46269,413],{"class":214},[200,46271,416],{"class":210},[35,46273,46274],{},"That's really simple and it works like a charm, nicely integrated into a Nuxt application.",[35,46276,46277,46278,46284],{},"To finish the stream, I quickly set up NuxtHub into my application to show how easy it is to play with a database. I tried to use raw SQL, but the recipe to migrate the database was not working. ",[1217,46279,46280],{},[39,46281,46099],{"href":46282,"rel":46283},"https:\u002F\u002Fgithub.com\u002Fnuxt-hub\u002Ffeedback\u002Fissues\u002F15",[77]," about it and it's now fixed! After this second fail, I decided to start using Drizzle ORM, a simple but powerful ORM compatible with the workers of Cloudflare. In all cases, I should have to use Drizzle because managing everything with raw SQL is too much work.",[35,46286,46287,46288,46294,46295,46298,46299,46304,46305],{},"During the live, someone asked how to apply migration to the production database. I have to admit that I didn't know the answer and nothing in the documentation was clear about it. ",[1217,46289,46290],{},[39,46291,46099],{"href":46292,"rel":46293},"https:\u002F\u002Fgithub.com\u002Fnuxt-hub\u002Ffeedback\u002Fissues\u002F14",[77]," about it that received an answer. ",[1217,46296,46297],{},"You have to deploy your app and then start the local dev with a remote connection to run migrations"," Clearly, it's far from being good for the moment, but with the upcoming ",[39,46300,46303],{"href":46301,"rel":46302},"https:\u002F\u002Fnitro.unjs.io\u002Fguide\u002Ftasks",[77],"Nitro tasks",", it should be easier in the near future. ",[1217,46306,46307],{},"I really hope.",[35,46309,46310],{},"Globally, I'm so happy to have started this stream. Even if it's far from being good, but getting started is already a big step forward, and I know that future streams will only get easier to start.",[35,46312,46313],{},[1217,46314,46315],{},"I was so stressed that I went very fast in my explanations, so I decided that the next stream would be more talkative to re-explain different concepts.",[35,46317,46318,46319],{},"The project is open-source and available on GitHub at ",[1582,46320,46321],{"repo":38655},"! Give it a star if you like it! ⭐",[51,46323,46325],{"id":46324},"discussion-on-cloudflare-nuxthub-and-github-oauth","Discussion on Cloudflare, NuxtHub, and GitHub OAuth",[35,46327,46328],{},[1690,46329],{"alt":46330,"src":46331},"Thumbnail of my second live stream.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-02.png",[35,46333,46334],{},[1217,46335,46336,46057,46339,46062,46342,46066,46345,215],{},[39,46337,38639],{"href":46025,"rel":46338},[77],[39,46340,46061],{"href":18567,"rel":46341},[77],[39,46343,19412],{"href":46016,"rel":46344},[77],[39,46346,11622],{"href":46025,"rel":46347},[77],[35,46349,46350],{},"I was frustrated by my first live stream. With the stress, I didn't explain things like what is Cloudflare, what is the edge and how NuxtHub is related to Cloudflare. But I know it's important to explain these concepts since it's new technologies and not everyone knows them.",[35,46352,46353],{},"At StrasbourgJS or Devoxx, I talked about UnJS and problems solved by the ecosystem. Many people, during or after, ask me about the edge. What is it? Why do we need it? Where does it come from?",[7905,46355,46356],{"id":30890},[7905,46357,46358,46361,46365,46368,46371,46377,46380,46389,46399,46402,46405,46442,46445,46448,46454,46457,46464,46470,46480,46486,46489,46492,46514,46518,46524,46527,46533,46562,46565,46569,46575,46581,47243,47246,47248,47251,47255,47259,47265,47281,47284,47288,47291,47297,47300,47306,47315,47318,47350,47353,47355,47373,47380,47383,47387,47397,47444,47448,47451,47457,47461,47472,47612,47628,47635,47781,47787,47791,47794,47917,47922,47927,48212,48218,48221,48348,48351,48812,48822,48829,48834,48837],{"id":20826},[35,46359,46360],{},"The goal of my streams is to try this new technology, and with these feedbacks, it was a necessity to explain it.",[1913,46362,46364],{"id":46363},"cloudflare-at-a-glance","Cloudflare at a glance",[35,46366,46367],{},"Cloudflare is a company that provides a lot of services to make the web faster and more secure like a zero-trust solution, a WAF, a CDN, a DNS, a DDoS protection, and more for both companies and individuals.",[35,46369,46370],{},"Their most known product is the reverse proxy. Cloudflare will act as the main and single entry point for your traffic. Then, it will route, internally, the traffic to the right server using the DNS. This allows you to hide your IP server, to cache static assets, and to protect your server from DDoS attacks.",[35,46372,46373],{},[1690,46374],{"alt":46375,"src":46376},"Reverse proxy schema.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Freverse-proxy.png",[35,46378,46379],{},"In order to provide these services, they built a network of data centers around the world to be as close as possible to the users to serve cached content faster and to be able to mitigate attacks. It's called the edge network. It is at the frontier between users and the web, for which it is the gateway.",[35,46381,46382,46383,46388],{},"In September 2017, ",[39,46384,46387],{"href":46385,"rel":46386},"https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-cloudflare-workers",[77],"they announced Cloudflare Workers",". To understand the objective at the time, let's quote the article:",[939,46390,46391,46394],{},[35,46392,46393],{},"What if you want to load balance with a custom affinity algorithm? What if standard HTTP caching rules aren't quite right, and you need some custom logic to boost your cache hit rate? What if you want to write custom WAF rules tailored for your application?",[35,46395,46396],{},[6850,46397,46398],{},"You want to write code",[35,46400,46401],{},"Workers have been released to allow developers to customize the infrastructure to their needs. The Cloudflare Workers solution is built on top of the Service Worker API, a standard in the browser to intercept and modify requests and responses.",[35,46403,46404],{},"Since then, they have built a full capabilities platform for developers around the workers:",[1576,46406,46407,46414,46421,46428,46435],{},[71,46408,46409],{},[39,46410,46413],{"href":46411,"rel":46412},"https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-workers-kv",[77],"Workers KV, serverless key-value store",[71,46415,46416],{},[39,46417,46420],{"href":46418,"rel":46419},"https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-d1",[77],"D1, serverless SQL database",[71,46422,46423],{},[39,46424,46427],{"href":46425,"rel":46426},"https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-r2-object-storage",[77],"R2, serverless blob storage",[71,46429,46430],{},[39,46431,46434],{"href":46432,"rel":46433},"https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-cloudflare-queues",[77],"Queues, serverless task queues",[71,46436,46437],{},[39,46438,46441],{"href":46439,"rel":46440},"https:\u002F\u002Fblog.cloudflare.com\u002Fworkers-ai",[77],"Workers AI, serverless GPU-powered inference",[35,46443,46444],{},"With all these services, it's now (theoretically and that's the purpose of this series to answer the question) possible to build an application entirely on the Cloudflare's global network.",[1913,46446,36822],{"id":46447},"nuxthub",[35,46449,46450],{},[1690,46451],{"alt":46452,"src":46453},"NuxtHub home page.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fnuxt-hub-home-page.png",[35,46455,46456],{},"Since the version 3 of Nuxt, the team is pushing the edge. Nitro, the server part of Nuxt, can be deployed everywhere, current and future platform, and is optimized for the edge. H3, the underlying HTTP server wakes up in less than 2ms, and the build output of Nitro is less than 1 MB, node modules included.",[35,46458,46459,46460,46463],{},"NuxtHub is a continuation of this effort. First, it's a platform to deploy Nuxt applications on Cloudflare. It's a two-click deployment. You connect your Cloudflare account because it's built on top of Cloudflare, you choose a template or a repo, you click on deploy, and that's it. ",[1217,46461,46462],{},"Your application is live on the edge."," It's feeling like magic.",[35,46465,46466],{},[1690,46467],{"alt":46468,"src":46469},"NuxtHub templates that can be deployed with two clicks.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fnuxt-hub-deploy.png",[35,46471,46472,46473,215],{},"NuxtHub is also a module for your Nuxt app to add data access capabilities. Locally, you will be able to play instantly with a database, a KV store, and even a blob storage. In production, you do not need to change your code, it will work the same way. This is clearly the true magic of NuxtHub, powered with ",[39,46474,46477],{"href":46475,"rel":46476},"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fworker",[77],[156,46478,46479],{},"workerd",[35,46481,46482],{},[1690,46483],{"alt":46484,"src":46485},"NuxtHub schema (from hub.nuxt.com).","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fnuxt-hub-schema.png",[35,46487,46488],{},"There is also a feature to allow a local dev environment to interact with the remote data. Not sure about the use case, but it's definitely interesting.",[35,46490,46491],{},"Even if the platform is still new, announced during Vue Amsterdam 2024, it's already very promising. The team is very reactive, and the platform is evolving quickly. I'm very excited to see what the future holds for NuxtHub but have some hints in documentation:",[939,46493,46494,46497],{},[35,46495,46496],{},"We plan to provide a complete backend experience for Nuxt apps through various @nuxthub packages.",[1576,46498,46499,46502,46505,46508,46511],{},[71,46500,46501],{},"@nuxthub\u002Fcore: Main package to provide storage features",[71,46503,46504],{},"@nuxthub\u002Fauth: Add authentication for user management (soon)",[71,46506,46507],{},"@nuxthub\u002Femail: Send transactional emails to your users (soon)",[71,46509,46510],{},"@nuxthub\u002Fanalytics: Understand your traffic and track events within your application and API (soon)",[71,46512,46513],{},"@nuxthub\u002F...: You name it!",[1913,46515,46517],{"id":46516},"github-oauth","GitHub OAuth",[35,46519,46520,46521,46523],{},"During the previous live stream, I added authentication, using ",[156,46522,38276],{}," to Orion, the full-stack Nuxt application. I used GitHub OAuth, and I think it's important to see how it works under the hood.",[35,46525,46526],{},"During the live, we jumped into the code, and I explained how it works. Here is a schema to help you understand the process:",[35,46528,46529],{},[1690,46530],{"alt":46531,"src":46532},"GitHub OAuth schema.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Foauth-github.png",[68,46534,46535,46541,46544,46547,46550,46553,46556,46559],{},[71,46536,46537,46538,215],{},"The user clicks on the login button, which points to ",[156,46539,46540],{},"\u002Fauth\u002Fgithub",[71,46542,46543],{},"The server sends a redirect to the GitHub OAuth page.",[71,46545,46546],{},"The user accepts the authorization from GitHub.",[71,46548,46549],{},"GitHub redirects the user back to the application callback URL with a code.",[71,46551,46552],{},"The server exchanges the code for the tokens.",[71,46554,46555],{},"The server uses the tokens to get the user.",[71,46557,46558],{},"The server stores the user in a session.",[71,46560,46561],{},"The server redirects the user to the home page.",[35,46563,46564],{},"In these steps, the server is Nitro, and we do not talk about the possible errors. There are many possible errors like the user refuses the authorization, the code is invalid, the tokens are invalid, the user is not found, etc. Each error must be handled to provide a good user experience.",[38677,46566,46568],{"id":46567},"code-of-the-github-oauth-handler","Code of the GitHub OAuth handler",[35,46570,46571,46572,46574],{},"Now, let's dive into the code of ",[156,46573,38276],{}," to see how it works.",[35,46576,46577,46578,215],{},"To simplify, I removed some parts of the code that are not necessary to understand the process, and I focused on the main steps. You can find the full code in the ",[39,46579,2145],{"href":38759,"rel":46580},[77],[192,46582,46584],{"className":1370,"code":46583,"language":1373,"meta":13,"style":13},"\u002F**\n * GitHub OAuth event handler.\n *\n * _The code is simplified for the article and doesn't work as is._\n *\u002F\nexport function githubEventHandler({ config, onSuccess }: OAuthConfig\u003COAuthGitHubConfig>) {\n  return eventHandler(async (event: H3Event) => {\n    config = { \u002F** ... *\u002F}\n\n    \u002F**\n     * Get the query from the event.\n     *\u002F\n    const query = getQuery(event)\n\n    if (query.error) {\n      \u002F\u002F Handle error\n      return\n    }\n\n    if (!query.code) {\n      \u002F**\n       * Redirect to GitHub OAuth page if there is no code in the query.\n       * If the user accepts the authorization, GitHub will redirect the user back to the application with the code.\n       * The code is the one-time code to exchange for the 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     * Use the code to get the 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     * Use the access token to get the user.\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     * Call the onSuccess callback with the user and the tokens.\n     *\n     * The OAuth is done, and the user can now be authenticated using our system.\n     * With Nitro, we store the user in a session within a cookie.\n     *\u002F\n    return onSuccess(event, {\n      user,\n      tokens,\n    })\n  })\n}\n",[156,46585,46586,46590,46595,46600,46605,46609,46643,46668,46681,46685,46689,46694,46698,46715,46719,46735,46740,46745,46749,46753,46771,46776,46781,46786,46791,46796,46819,46827,46834,46856,46872,46883,46911,46925,46933,46938,46942,46946,46950,46955,46959,46977,46993,46997,47012,47021,47035,47039,47043,47047,47051,47067,47071,47075,47080,47084,47113,47122,47143,47148,47155,47160,47165,47171,47177,47183,47189,47194,47209,47216,47224,47231,47238],{"__ignoreMap":13},[200,46587,46588],{"class":202,"line":203},[200,46589,26078],{"class":401},[200,46591,46592],{"class":202,"line":14},[200,46593,46594],{"class":401}," * GitHub OAuth event handler.\n",[200,46596,46597],{"class":202,"line":255},[200,46598,46599],{"class":401}," *\n",[200,46601,46602],{"class":202,"line":272},[200,46603,46604],{"class":401}," * _The code is simplified for the article and doesn't work as is._\n",[200,46606,46607],{"class":202,"line":283},[200,46608,26093],{"class":401},[200,46610,46611,46613,46615,46618,46620,46623,46625,46627,46630,46633,46635,46638,46641],{"class":202,"line":300},[200,46612,1380],{"class":206},[200,46614,4987],{"class":379},[200,46616,46617],{"class":223}," githubEventHandler",[200,46619,3951],{"class":214},[200,46621,46622],{"class":386}," config",[200,46624,550],{"class":214},[200,46626,46168],{"class":386},[200,46628,46629],{"class":214}," }:",[200,46631,46632],{"class":1978}," OAuthConfig",[200,46634,600],{"class":214},[200,46636,46637],{"class":1978},"OAuthGitHubConfig",[200,46639,46640],{"class":214},">)",[200,46642,280],{"class":214},[200,46644,46645,46647,46650,46652,46654,46656,46658,46660,46662,46664,46666],{"class":202,"line":310},[200,46646,3743],{"class":206},[200,46648,46649],{"class":223}," eventHandler",[200,46651,227],{"class":235},[200,46653,3711],{"class":379},[200,46655,1095],{"class":214},[200,46657,25386],{"class":386},[200,46659,239],{"class":214},[200,46661,39325],{"class":1978},[200,46663,2089],{"class":214},[200,46665,393],{"class":379},[200,46667,280],{"class":214},[200,46669,46670,46672,46674,46676,46679],{"class":202,"line":336},[200,46671,24931],{"class":210},[200,46673,510],{"class":214},[200,46675,318],{"class":214},[200,46677,46678],{"class":401}," \u002F** ... *\u002F",[200,46680,1130],{"class":214},[200,46682,46683],{"class":202,"line":342},[200,46684,529],{"emptyLinePlaceholder":20},[200,46686,46687],{"class":202,"line":365},[200,46688,19618],{"class":401},[200,46690,46691],{"class":202,"line":371},[200,46692,46693],{"class":401},"     * Get the query from the event.\n",[200,46695,46696],{"class":202,"line":398},[200,46697,19628],{"class":401},[200,46699,46700,46702,46704,46706,46709,46711,46713],{"class":202,"line":405},[200,46701,1999],{"class":379},[200,46703,3484],{"class":210},[200,46705,510],{"class":214},[200,46707,46708],{"class":223}," getQuery",[200,46710,227],{"class":235},[200,46712,25386],{"class":210},[200,46714,416],{"class":235},[200,46716,46717],{"class":202,"line":410},[200,46718,529],{"emptyLinePlaceholder":20},[200,46720,46721,46723,46725,46727,46729,46731,46733],{"class":202,"line":861},[200,46722,5252],{"class":206},[200,46724,1095],{"class":235},[200,46726,3509],{"class":210},[200,46728,215],{"class":214},[200,46730,19260],{"class":210},[200,46732,5267],{"class":235},[200,46734,230],{"class":214},[200,46736,46737],{"class":202,"line":890},[200,46738,46739],{"class":401},"      \u002F\u002F Handle error\n",[200,46741,46742],{"class":202,"line":900},[200,46743,46744],{"class":206},"      return\n",[200,46746,46747],{"class":202,"line":905},[200,46748,4962],{"class":214},[200,46750,46751],{"class":202,"line":931},[200,46752,529],{"emptyLinePlaceholder":20},[200,46754,46755,46757,46759,46761,46763,46765,46767,46769],{"class":202,"line":3640},[200,46756,5252],{"class":206},[200,46758,1095],{"class":235},[200,46760,11837],{"class":214},[200,46762,3509],{"class":210},[200,46764,215],{"class":214},[200,46766,156],{"class":210},[200,46768,5267],{"class":235},[200,46770,230],{"class":214},[200,46772,46773],{"class":202,"line":3645},[200,46774,46775],{"class":401},"      \u002F**\n",[200,46777,46778],{"class":202,"line":4020},[200,46779,46780],{"class":401},"       * Redirect to GitHub OAuth page if there is no code in the query.\n",[200,46782,46783],{"class":202,"line":4475},[200,46784,46785],{"class":401},"       * If the user accepts the authorization, GitHub will redirect the user back to the application with the code.\n",[200,46787,46788],{"class":202,"line":4481},[200,46789,46790],{"class":401},"       * The code is the one-time code to exchange for the tokens.\n",[200,46792,46793],{"class":202,"line":4487},[200,46794,46795],{"class":401},"       *\u002F\n",[200,46797,46798,46800,46803,46805,46808,46810,46812,46814,46816],{"class":202,"line":4493},[200,46799,20113],{"class":379},[200,46801,46802],{"class":210}," redirectUrl",[200,46804,510],{"class":214},[200,46806,46807],{"class":223}," getRequestURL",[200,46809,227],{"class":235},[200,46811,25386],{"class":210},[200,46813,2089],{"class":235},[200,46815,215],{"class":214},[200,46817,46818],{"class":210},"href\n",[200,46820,46821,46823,46825],{"class":202,"line":5454},[200,46822,11715],{"class":206},[200,46824,46247],{"class":223},[200,46826,3115],{"class":235},[200,46828,46829,46832],{"class":202,"line":5459},[200,46830,46831],{"class":210},"        event",[200,46833,252],{"class":214},[200,46835,46836,46839,46841,46843,46845,46848,46850,46852,46854],{"class":202,"line":13227},[200,46837,46838],{"class":223},"        withQuery",[200,46840,227],{"class":235},[200,46842,13480],{"class":210},[200,46844,215],{"class":214},[200,46846,46847],{"class":210},"authorizationURL",[200,46849,14674],{"class":206},[200,46851,22708],{"class":1978},[200,46853,550],{"class":214},[200,46855,280],{"class":214},[200,46857,46858,46861,46863,46865,46867,46870],{"class":202,"line":13253},[200,46859,46860],{"class":235},"          client_id",[200,46862,239],{"class":214},[200,46864,46622],{"class":210},[200,46866,215],{"class":214},[200,46868,46869],{"class":210},"clientId",[200,46871,252],{"class":214},[200,46873,46874,46877,46879,46881],{"class":202,"line":13269},[200,46875,46876],{"class":235},"          redirect_uri",[200,46878,239],{"class":214},[200,46880,46802],{"class":210},[200,46882,252],{"class":214},[200,46884,46885,46888,46890,46892,46894,46897,46899,46901,46903,46905,46907,46909],{"class":202,"line":13313},[200,46886,46887],{"class":235},"          scope",[200,46889,239],{"class":214},[200,46891,46622],{"class":210},[200,46893,215],{"class":214},[200,46895,46896],{"class":210},"scope",[200,46898,215],{"class":214},[200,46900,6190],{"class":223},[200,46902,227],{"class":235},[200,46904,249],{"class":214},[200,46906,242],{"class":214},[200,46908,2089],{"class":235},[200,46910,252],{"class":214},[200,46912,46913,46916,46918,46920,46923],{"class":202,"line":13319},[200,46914,46915],{"class":214},"          ...",[200,46917,13480],{"class":210},[200,46919,215],{"class":214},[200,46921,46922],{"class":210},"authorizationParams",[200,46924,252],{"class":214},[200,46926,46927,46929,46931],{"class":202,"line":13324},[200,46928,13378],{"class":214},[200,46930,2089],{"class":235},[200,46932,252],{"class":214},[200,46934,46935],{"class":202,"line":13350},[200,46936,46937],{"class":235},"      )\n",[200,46939,46940],{"class":202,"line":13355},[200,46941,4962],{"class":214},[200,46943,46944],{"class":202,"line":13375},[200,46945,529],{"emptyLinePlaceholder":20},[200,46947,46948],{"class":202,"line":13395},[200,46949,19618],{"class":401},[200,46951,46952],{"class":202,"line":13400},[200,46953,46954],{"class":401},"     * Use the code to get the tokens.\n",[200,46956,46957],{"class":202,"line":13408},[200,46958,19628],{"class":401},[200,46960,46961,46963,46965,46967,46969,46971,46973,46975],{"class":202,"line":13426},[200,46962,1999],{"class":379},[200,46964,9855],{"class":210},[200,46966,239],{"class":214},[200,46968,42867],{"class":1978},[200,46970,510],{"class":214},[200,46972,452],{"class":206},[200,46974,18741],{"class":223},[200,46976,3115],{"class":235},[200,46978,46979,46982,46984,46987,46989,46991],{"class":202,"line":13456},[200,46980,46981],{"class":210},"      config",[200,46983,215],{"class":214},[200,46985,46986],{"class":210},"tokenURL",[200,46988,14674],{"class":206},[200,46990,22708],{"class":1978},[200,46992,252],{"class":214},[200,46994,46995],{"class":202,"line":13462},[200,46996,4442],{"class":214},[200,46998,46999,47002,47004,47006,47008,47010],{"class":202,"line":13467},[200,47000,47001],{"class":235},"        method",[200,47003,239],{"class":214},[200,47005,242],{"class":214},[200,47007,18766],{"class":245},[200,47009,249],{"class":214},[200,47011,252],{"class":214},[200,47013,47014,47017,47019],{"class":202,"line":13472},[200,47015,47016],{"class":235},"        body",[200,47018,239],{"class":214},[200,47020,280],{"class":214},[200,47022,47023,47025,47027,47029,47031,47033],{"class":202,"line":13487},[200,47024,13411],{"class":235},[200,47026,239],{"class":214},[200,47028,3484],{"class":210},[200,47030,215],{"class":214},[200,47032,156],{"class":210},[200,47034,252],{"class":214},[200,47036,47037],{"class":202,"line":13498},[200,47038,4950],{"class":214},[200,47040,47041],{"class":202,"line":13503},[200,47042,12083],{"class":214},[200,47044,47045],{"class":202,"line":13508},[200,47046,3854],{"class":235},[200,47048,47049],{"class":202,"line":20224},[200,47050,529],{"emptyLinePlaceholder":20},[200,47052,47053,47055,47058,47060,47062,47064],{"class":202,"line":20246},[200,47054,1999],{"class":379},[200,47056,47057],{"class":210}," accessToken",[200,47059,510],{"class":214},[200,47061,9855],{"class":210},[200,47063,215],{"class":214},[200,47065,47066],{"class":210},"access_token\n",[200,47068,47069],{"class":202,"line":20251},[200,47070,529],{"emptyLinePlaceholder":20},[200,47072,47073],{"class":202,"line":20256},[200,47074,19618],{"class":401},[200,47076,47077],{"class":202,"line":20261},[200,47078,47079],{"class":401},"     * Use the access token to get the user.\n",[200,47081,47082],{"class":202,"line":20282},[200,47083,19628],{"class":401},[200,47085,47086,47088,47090,47092,47094,47096,47098,47100,47102,47104,47107,47109,47111],{"class":202,"line":20289},[200,47087,1999],{"class":379},[200,47089,34776],{"class":210},[200,47091,239],{"class":214},[200,47093,42867],{"class":1978},[200,47095,510],{"class":214},[200,47097,452],{"class":206},[200,47099,4843],{"class":223},[200,47101,227],{"class":235},[200,47103,249],{"class":214},[200,47105,47106],{"class":245},"https:\u002F\u002Fapi.github.com\u002Fuser",[200,47108,249],{"class":214},[200,47110,550],{"class":214},[200,47112,280],{"class":214},[200,47114,47115,47118,47120],{"class":202,"line":20294},[200,47116,47117],{"class":235},"      headers",[200,47119,239],{"class":214},[200,47121,280],{"class":214},[200,47123,47124,47127,47129,47131,47134,47136,47139,47141],{"class":202,"line":20310},[200,47125,47126],{"class":235},"        Authorization",[200,47128,239],{"class":214},[200,47130,11139],{"class":214},[200,47132,47133],{"class":245},"token ",[200,47135,4853],{"class":214},[200,47137,47138],{"class":210},"accessToken",[200,47140,23001],{"class":214},[200,47142,252],{"class":214},[200,47144,47146],{"class":202,"line":47145},59,[200,47147,12083],{"class":214},[200,47149,47151,47153],{"class":202,"line":47150},60,[200,47152,2096],{"class":214},[200,47154,416],{"class":235},[200,47156,47158],{"class":202,"line":47157},61,[200,47159,529],{"emptyLinePlaceholder":20},[200,47161,47163],{"class":202,"line":47162},62,[200,47164,19618],{"class":401},[200,47166,47168],{"class":202,"line":47167},63,[200,47169,47170],{"class":401},"     * Call the onSuccess callback with the user and the tokens.\n",[200,47172,47174],{"class":202,"line":47173},64,[200,47175,47176],{"class":401},"     *\n",[200,47178,47180],{"class":202,"line":47179},65,[200,47181,47182],{"class":401},"     * The OAuth is done, and the user can now be authenticated using our system.\n",[200,47184,47186],{"class":202,"line":47185},66,[200,47187,47188],{"class":401},"     * With Nitro, we store the user in a session within a cookie.\n",[200,47190,47192],{"class":202,"line":47191},67,[200,47193,19628],{"class":401},[200,47195,47197,47199,47201,47203,47205,47207],{"class":202,"line":47196},68,[200,47198,3296],{"class":206},[200,47200,46168],{"class":223},[200,47202,227],{"class":235},[200,47204,25386],{"class":210},[200,47206,550],{"class":214},[200,47208,280],{"class":214},[200,47210,47212,47214],{"class":202,"line":47211},69,[200,47213,46206],{"class":210},[200,47215,252],{"class":214},[200,47217,47219,47222],{"class":202,"line":47218},70,[200,47220,47221],{"class":210},"      tokens",[200,47223,252],{"class":214},[200,47225,47227,47229],{"class":202,"line":47226},71,[200,47228,2096],{"class":214},[200,47230,416],{"class":235},[200,47232,47234,47236],{"class":202,"line":47233},72,[200,47235,5080],{"class":214},[200,47237,416],{"class":235},[200,47239,47241],{"class":202,"line":47240},73,[200,47242,1130],{"class":214},[35,47244,47245],{},"As we can expect, the code is quite simple, and we easily recognize the steps we saw in the schema.",[1913,47247,30681],{"id":30680},[35,47249,47250],{},"I didn't code during this live. I wanted to explain many important concepts to help everyone understand the context of the project Orion. I hope it was clear, and that you learned something new.",[35,47252,46318,47253],{},[1582,47254,46321],{"repo":38655},[51,47256,47258],{"id":47257},"sql-schema-and-first-blocks-of-orions-admin-interface","SQL schema and first blocks of Orion's admin interface",[35,47260,47261],{},[1690,47262],{"alt":47263,"src":47264},"Thumbnail of my third live stream.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-03.png",[35,47266,47267],{},[1217,47268,47269,46057,47272,46062,47275,46066,47278,215],{},[39,47270,38639],{"href":46025,"rel":47271},[77],[39,47273,46061],{"href":18567,"rel":47274},[77],[39,47276,19412],{"href":46016,"rel":47277},[77],[39,47279,11622],{"href":46025,"rel":47280},[77],[35,47282,47283],{},"In this third stream, I created the SQL schema for the database. I then set up Nuxt UI and Nuxt UI Pro to create the admin layout and the first form for categories. This article is a summary of the stream and the work done.",[1913,47285,47287],{"id":47286},"sql-schema","SQL schema",[35,47289,47290],{},"What is a SQL schema and why did I create the SQL schema? These are two good questions.",[35,47292,47293,47294],{},"A SQL schema is a way to organize the database. It is a blueprint of how the database will be structured. So it's very useful to think about it before starting to code. It does not need to be perfect, but helps to iterate on the database structure, avoiding errors, and to have a clear vision of the project. ",[1217,47295,47296],{},"I personally think it's a must-have when starting a new project.",[35,47298,47299],{},"So, before writing any code for the project Orion, I created the SQL schema.",[35,47301,47302],{},[1690,47303],{"alt":47304,"src":47305},"SQL Schema","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fsql-schema.png",[35,47307,47308,47309,47314],{},"To build this schema, I used ",[39,47310,47313],{"href":47311,"rel":47312},"https:\u002F\u002Fdrawsql.app\u002F",[77],"drawSQL",". It's a web app to create SQL schema with a nice UI. It's very easy to use and free for small projects. I recommend it.",[35,47316,47317],{},"The database for the project Orion, the full-stack Nuxt application, is simple and common for this type of project.",[1576,47319,47320,47327,47333,47338,47344],{},[71,47321,47322,47323,47326],{},"A ",[156,47324,47325],{},"users"," table to store the users of the application. Storing users is useful for the admin interface, to ban a user, or to give admin rights.",[71,47328,47322,47329,47332],{},[156,47330,47331],{},"categories"," table to store the categories of the theme like blog, e-commerce, documentation, etc. It's a simple table with a name and a slug. The slug is used for the URL of the category.",[71,47334,47322,47335,47337],{},[156,47336,2461],{}," table to store the modules of the theme. The table will have a name, a slug, and a type (official, community). This will be useful to filter the themes by modules.",[71,47339,47322,47340,47343],{},[156,47341,47342],{},"themes"," table to store the main content of the application. It will have a lot of columns but we will see that in another stream.",[71,47345,47322,47346,47349],{},[156,47347,47348],{},"feedbacks"," table to store the feedbacks from admins about a theme. Indeed, a theme must be reviewed before being published and the feedback will be used to tell the creator what to improve.",[35,47351,47352],{},"This schema is a good start to clarify things and to know where to get started but I already know that it will evolve.",[1913,47354,31704],{"id":31703},[35,47356,47357,47358,47363,47364,47369,47370],{},"Nuxt UI is a component library made by the Nuxt team. Initially, it was an internal tool for their own projects like ",[39,47359,47362],{"href":47360,"rel":47361},"https:\u002F\u002Fvolta.net",[77],"Volta",". In May 2023, they decided to ",[39,47365,47368],{"href":47366,"rel":47367},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Freleases\u002Ftag\u002Fv2.0.0",[77],"open-source it"," and to make it available for everyone for free. ",[1217,47371,47372],{},"Today, Nuxt UI is my go-to for building user interfaces with Nuxt because it's simple, beautiful but yet highly customizable.",[35,47374,47375,47376,47379],{},"Nuxt UI Pro is built on top of Nuxt UI and provides layouts related components like a page, a hero, a header and complete layouts like a dashboard, a blog, a documentation, etc. It's a paid product, but it's worth it. ",[1217,47377,47378],{},"I'm using it for Orion, the full-stack application, because it's a time saver and it's beautiful."," The better part is that it's free during development (the build process will require a key), so you can try it before buying it.",[35,47381,47382],{},"During the stream, I start by installing Nuxt UI and Nuxt UI Pro. I then create the layout for the admin interface and the page for the categories. Let's see the code.",[1913,47384,47386],{"id":47385},"admin-layout","Admin layout",[35,47388,47389,47390,47393,47394,215],{},"With Nuxt UI Pro, this part was very easy. There is no navbar, nor sidebar for the moment. ",[1217,47391,47392],{},"The goal is to add features and components step by step, when needed, and to avoid to anticipate too much."," Here is the code for the layout ",[156,47395,47396],{},"layouts\u002Fadmin.vue",[192,47398,47400],{"className":21417,"code":47399,"language":12060,"meta":13,"style":13},"\u003Ctemplate>\n  \u003CUDashboardLayout>\n    \u003Cslot \u002F>\n  \u003C\u002FUDashboardLayout>\n\u003C\u002Ftemplate>\n",[156,47401,47402,47410,47419,47428,47436],{"__ignoreMap":13},[200,47403,47404,47406,47408],{"class":202,"line":203},[200,47405,600],{"class":214},[200,47407,21428],{"class":235},[200,47409,648],{"class":214},[200,47411,47412,47414,47417],{"class":202,"line":14},[200,47413,657],{"class":214},[200,47415,47416],{"class":235},"UDashboardLayout",[200,47418,648],{"class":214},[200,47420,47421,47423,47426],{"class":202,"line":255},[200,47422,804],{"class":214},[200,47424,47425],{"class":235},"slot",[200,47427,21487],{"class":214},[200,47429,47430,47432,47434],{"class":202,"line":272},[200,47431,893],{"class":214},[200,47433,47416],{"class":235},[200,47435,648],{"class":214},[200,47437,47438,47440,47442],{"class":202,"line":283},[200,47439,681],{"class":214},[200,47441,21428],{"class":235},[200,47443,648],{"class":214},[1913,47445,47447],{"id":47446},"categories-page","Categories page",[35,47449,47450],{},"Before developing the view, I create the API endpoint to get and update the categories.",[35,47452,47453],{},[1690,47454],{"alt":47455,"src":47456},"Result of the Layout and Page for Categories.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Flayout-and-page-for-categories.png",[38677,47458,47460],{"id":47459},"categories-api","Categories API",[35,47462,47463,47464,47467,47468,47471],{},"Thanks to Nitro and Drizzle (setup in the first stream), it is very easy. In the file ",[156,47465,47466],{},"server\u002Fapi\u002Fcategories\u002Findex.get.ts"," (the ",[156,47469,47470],{},".get."," is used to specify the HTTP verb), I wrote the following code:",[192,47473,47475],{"className":1370,"code":47474,"language":1373,"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,47476,47477,47495,47519,47538,47558,47574,47595,47599,47606],{"__ignoreMap":13},[200,47478,47479,47481,47483,47485,47487,47489,47491,47493],{"class":202,"line":203},[200,47480,1380],{"class":206},[200,47482,1383],{"class":206},[200,47484,38960],{"class":223},[200,47486,227],{"class":210},[200,47488,3711],{"class":379},[200,47490,3714],{"class":214},[200,47492,393],{"class":379},[200,47494,280],{"class":214},[200,47496,47497,47499,47502,47504,47506,47509,47511,47513,47515,47517],{"class":202,"line":14},[200,47498,3723],{"class":379},[200,47500,47501],{"class":210}," categories",[200,47503,510],{"class":214},[200,47505,452],{"class":206},[200,47507,47508],{"class":223}," useDrizzle",[200,47510,1532],{"class":235},[200,47512,215],{"class":214},[200,47514,770],{"class":223},[200,47516,227],{"class":235},[200,47518,230],{"class":214},[200,47520,47521,47523,47525,47528,47530,47532,47534,47536],{"class":202,"line":255},[200,47522,15858],{"class":235},[200,47524,239],{"class":214},[200,47526,47527],{"class":210}," tables",[200,47529,215],{"class":214},[200,47531,47331],{"class":210},[200,47533,215],{"class":214},[200,47535,3280],{"class":210},[200,47537,252],{"class":214},[200,47539,47540,47543,47545,47547,47549,47551,47553,47556],{"class":202,"line":272},[200,47541,47542],{"class":235},"    slug",[200,47544,239],{"class":214},[200,47546,47527],{"class":210},[200,47548,215],{"class":214},[200,47550,47331],{"class":210},[200,47552,215],{"class":214},[200,47554,47555],{"class":210},"slug",[200,47557,252],{"class":214},[200,47559,47560,47562,47564,47566,47568,47570,47572],{"class":202,"line":283},[200,47561,11797],{"class":235},[200,47563,239],{"class":214},[200,47565,47527],{"class":210},[200,47567,215],{"class":214},[200,47569,47331],{"class":210},[200,47571,215],{"class":214},[200,47573,30209],{"class":210},[200,47575,47576,47578,47580,47582,47584,47586,47589,47591,47593],{"class":202,"line":300},[200,47577,5080],{"class":214},[200,47579,2089],{"class":235},[200,47581,215],{"class":214},[200,47583,1472],{"class":223},[200,47585,227],{"class":235},[200,47587,47588],{"class":210},"tables",[200,47590,215],{"class":214},[200,47592,47331],{"class":210},[200,47594,416],{"class":235},[200,47596,47597],{"class":202,"line":310},[200,47598,529],{"emptyLinePlaceholder":20},[200,47600,47601,47603],{"class":202,"line":336},[200,47602,3743],{"class":206},[200,47604,47605],{"class":210}," categories\n",[200,47607,47608,47610],{"class":202,"line":342},[200,47609,413],{"class":214},[200,47611,416],{"class":210},[35,47613,419,47614,47617,47618,3076,47620,3080,47622,47624,47625],{},[156,47615,47616],{},"useDrizzle"," utils is a wrapper around the Drizzle ORM. This code will return all the categories with only the ",[156,47619,3280],{},[156,47621,47555],{},[156,47623,20089],{}," fields. ",[1217,47626,47627],{},"It's a good practice to only return the fields you need to avoid sending too much data.",[35,47629,47630,47631,47634],{},"Then, I created the endpoint to create a new category, and it's the same process. I created the file ",[156,47632,47633],{},"server\u002Fapi\u002Fcategories\u002Findex.post.ts"," with the following code:",[192,47636,47638],{"className":1370,"code":47637,"language":1373,"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,47639,47640,47662,47680,47684,47716,47737,47751,47764,47768,47775],{"__ignoreMap":13},[200,47641,47642,47644,47646,47648,47650,47652,47654,47656,47658,47660],{"class":202,"line":203},[200,47643,1380],{"class":206},[200,47645,1383],{"class":206},[200,47647,38960],{"class":223},[200,47649,227],{"class":210},[200,47651,3711],{"class":379},[200,47653,1095],{"class":214},[200,47655,25386],{"class":386},[200,47657,2089],{"class":214},[200,47659,393],{"class":379},[200,47661,280],{"class":214},[200,47663,47664,47666,47668,47670,47672,47674,47676,47678],{"class":202,"line":14},[200,47665,3723],{"class":379},[200,47667,41038],{"class":210},[200,47669,510],{"class":214},[200,47671,452],{"class":206},[200,47673,41045],{"class":223},[200,47675,227],{"class":235},[200,47677,25386],{"class":210},[200,47679,416],{"class":235},[200,47681,47682],{"class":202,"line":255},[200,47683,529],{"emptyLinePlaceholder":20},[200,47685,47686,47688,47690,47692,47694,47697,47699,47701,47703,47705,47707,47709,47712,47714],{"class":202,"line":272},[200,47687,6165],{"class":206},[200,47689,47508],{"class":223},[200,47691,1532],{"class":235},[200,47693,215],{"class":214},[200,47695,47696],{"class":223},"insert",[200,47698,227],{"class":235},[200,47700,47588],{"class":210},[200,47702,215],{"class":214},[200,47704,47331],{"class":210},[200,47706,2089],{"class":235},[200,47708,215],{"class":214},[200,47710,47711],{"class":223},"values",[200,47713,227],{"class":235},[200,47715,230],{"class":214},[200,47717,47718,47720,47722,47725,47727,47729,47731,47733,47735],{"class":202,"line":283},[200,47719,47542],{"class":235},[200,47721,239],{"class":214},[200,47723,47724],{"class":223}," useSlugify",[200,47726,227],{"class":235},[200,47728,23870],{"class":210},[200,47730,215],{"class":214},[200,47732,20089],{"class":210},[200,47734,2089],{"class":235},[200,47736,252],{"class":214},[200,47738,47739,47741,47743,47745,47747,47749],{"class":202,"line":300},[200,47740,11797],{"class":235},[200,47742,239],{"class":214},[200,47744,41038],{"class":210},[200,47746,215],{"class":214},[200,47748,20089],{"class":210},[200,47750,252],{"class":214},[200,47752,47753,47755,47757,47759,47762],{"class":202,"line":310},[200,47754,5080],{"class":214},[200,47756,2089],{"class":235},[200,47758,215],{"class":214},[200,47760,47761],{"class":223},"execute",[200,47763,466],{"class":235},[200,47765,47766],{"class":202,"line":336},[200,47767,529],{"emptyLinePlaceholder":20},[200,47769,47770,47772],{"class":202,"line":342},[200,47771,3743],{"class":206},[200,47773,47774],{"class":210}," body\n",[200,47776,47777,47779],{"class":202,"line":365},[200,47778,413],{"class":214},[200,47780,416],{"class":210},[35,47782,47783,47784,215],{},"In this code, there is no security check, and every user can create a category. It's clearly not a use case for our application, but the bouncer will be added later. In the same time, I did not verify the body. It's important to validate data received from a client to avoid security issues. ",[6850,47785,47786],{},"It will be the subject for the next stream",[38677,47788,47790],{"id":47789},"categories-content","Categories content",[35,47792,47793],{},"The page for the categories is built with Nuxt UI Pro, which makes the code very simple.",[192,47795,47797],{"className":21417,"code":47796,"language":12060,"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!-- Content -->\n    \u003C\u002FUDashboardPanel>\n  \u003C\u002FUDashboardPage>\n\u003C\u002Ftemplate>\n",[156,47798,47799,47819,47828,47841,47847,47855,47859,47867,47876,47888,47893,47901,47909],{"__ignoreMap":13},[200,47800,47801,47803,47805,47807,47809,47811,47813,47815,47817],{"class":202,"line":203},[200,47802,600],{"class":214},[200,47804,14892],{"class":235},[200,47806,21781],{"class":379},[200,47808,14590],{"class":379},[200,47810,449],{"class":214},[200,47812,611],{"class":214},[200,47814,1373],{"class":245},[200,47816,611],{"class":214},[200,47818,648],{"class":214},[200,47820,47821,47824,47826],{"class":202,"line":14},[200,47822,47823],{"class":223},"definePageMeta",[200,47825,227],{"class":210},[200,47827,230],{"class":214},[200,47829,47830,47833,47835,47837,47839],{"class":202,"line":255},[200,47831,47832],{"class":235},"  layout",[200,47834,239],{"class":214},[200,47836,242],{"class":214},[200,47838,39427],{"class":245},[200,47840,992],{"class":214},[200,47842,47843,47845],{"class":202,"line":272},[200,47844,413],{"class":214},[200,47846,416],{"class":210},[200,47848,47849,47851,47853],{"class":202,"line":283},[200,47850,681],{"class":214},[200,47852,14892],{"class":235},[200,47854,648],{"class":214},[200,47856,47857],{"class":202,"line":300},[200,47858,529],{"emptyLinePlaceholder":20},[200,47860,47861,47863,47865],{"class":202,"line":310},[200,47862,600],{"class":214},[200,47864,21428],{"class":235},[200,47866,648],{"class":214},[200,47868,47869,47871,47874],{"class":202,"line":336},[200,47870,657],{"class":214},[200,47872,47873],{"class":235},"UDashboardPage",[200,47875,648],{"class":214},[200,47877,47878,47880,47883,47886],{"class":202,"line":342},[200,47879,804],{"class":214},[200,47881,47882],{"class":235},"UDashboardPanel",[200,47884,47885],{"class":379}," grow",[200,47887,648],{"class":214},[200,47889,47890],{"class":202,"line":365},[200,47891,47892],{"class":401},"      \u003C!-- Content -->\n",[200,47894,47895,47897,47899],{"class":202,"line":371},[200,47896,15074],{"class":214},[200,47898,47882],{"class":235},[200,47900,648],{"class":214},[200,47902,47903,47905,47907],{"class":202,"line":398},[200,47904,893],{"class":214},[200,47906,47873],{"class":235},[200,47908,648],{"class":214},[200,47910,47911,47913,47915],{"class":202,"line":405},[200,47912,681],{"class":214},[200,47914,21428],{"class":235},[200,47916,648],{"class":214},[35,47918,12236,47919,47921],{},[156,47920,47882],{},", we can add a header with the page title and a button to create a new category and a table to display the categories.",[35,47923,47924,47925,239],{},"First, let's add the table in the ",[156,47926,47882],{},[192,47928,47930],{"className":21417,"code":47929,"language":12060,"meta":13,"style":13},"\u003Cscript setup lang=\"ts\">\nconst columns = [{\n  key: 'id',\n  label: '#'\n}, {\n  key: 'name',\n  label: 'Name',\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,47931,47932,47952,47965,47979,47992,47998,48012,48026,48035,48041,48055,48070,48076,48080,48119,48130,48136,48144,48148,48156,48204],{"__ignoreMap":13},[200,47933,47934,47936,47938,47940,47942,47944,47946,47948,47950],{"class":202,"line":203},[200,47935,600],{"class":214},[200,47937,14892],{"class":235},[200,47939,21781],{"class":379},[200,47941,14590],{"class":379},[200,47943,449],{"class":214},[200,47945,611],{"class":214},[200,47947,1373],{"class":245},[200,47949,611],{"class":214},[200,47951,648],{"class":214},[200,47953,47954,47956,47959,47961,47963],{"class":202,"line":14},[200,47955,443],{"class":379},[200,47957,47958],{"class":210}," columns ",[200,47960,449],{"class":214},[200,47962,350],{"class":210},[200,47964,230],{"class":214},[200,47966,47967,47969,47971,47973,47975,47977],{"class":202,"line":255},[200,47968,18962],{"class":235},[200,47970,239],{"class":214},[200,47972,242],{"class":214},[200,47974,3280],{"class":245},[200,47976,249],{"class":214},[200,47978,252],{"class":214},[200,47980,47981,47984,47986,47988,47990],{"class":202,"line":272},[200,47982,47983],{"class":235},"  label",[200,47985,239],{"class":214},[200,47987,242],{"class":214},[200,47989,2886],{"class":245},[200,47991,992],{"class":214},[200,47993,47994,47996],{"class":202,"line":283},[200,47995,26858],{"class":214},[200,47997,280],{"class":214},[200,47999,48000,48002,48004,48006,48008,48010],{"class":202,"line":300},[200,48001,18962],{"class":235},[200,48003,239],{"class":214},[200,48005,242],{"class":214},[200,48007,20089],{"class":245},[200,48009,249],{"class":214},[200,48011,252],{"class":214},[200,48013,48014,48016,48018,48020,48022,48024],{"class":202,"line":310},[200,48015,47983],{"class":235},[200,48017,239],{"class":214},[200,48019,242],{"class":214},[200,48021,40947],{"class":245},[200,48023,249],{"class":214},[200,48025,252],{"class":214},[200,48027,48028,48031,48033],{"class":202,"line":336},[200,48029,48030],{"class":235},"  sortable",[200,48032,239],{"class":214},[200,48034,22192],{"class":2423},[200,48036,48037,48039],{"class":202,"line":342},[200,48038,26858],{"class":214},[200,48040,280],{"class":214},[200,48042,48043,48045,48047,48049,48051,48053],{"class":202,"line":365},[200,48044,18962],{"class":235},[200,48046,239],{"class":214},[200,48048,242],{"class":214},[200,48050,47555],{"class":245},[200,48052,249],{"class":214},[200,48054,252],{"class":214},[200,48056,48057,48059,48061,48063,48066,48068],{"class":202,"line":371},[200,48058,47983],{"class":235},[200,48060,239],{"class":214},[200,48062,242],{"class":214},[200,48064,48065],{"class":245},"Slug",[200,48067,249],{"class":214},[200,48069,252],{"class":214},[200,48071,48072,48074],{"class":202,"line":398},[200,48073,413],{"class":214},[200,48075,4496],{"class":210},[200,48077,48078],{"class":202,"line":405},[200,48079,529],{"emptyLinePlaceholder":20},[200,48081,48082,48084,48086,48088,48090,48092,48094,48097,48099,48101,48103,48106,48108,48110,48113,48115,48117],{"class":202,"line":410},[200,48083,443],{"class":379},[200,48085,318],{"class":214},[200,48087,5041],{"class":235},[200,48089,239],{"class":214},[200,48091,47501],{"class":210},[200,48093,550],{"class":214},[200,48095,48096],{"class":210}," pending ",[200,48098,413],{"class":214},[200,48100,510],{"class":214},[200,48102,452],{"class":206},[200,48104,48105],{"class":223}," useFetch",[200,48107,227],{"class":210},[200,48109,249],{"class":214},[200,48111,48112],{"class":245},"\u002Fapi\u002Fcategories",[200,48114,249],{"class":214},[200,48116,550],{"class":214},[200,48118,280],{"class":214},[200,48120,48121,48124,48126,48128],{"class":202,"line":861},[200,48122,48123],{"class":235},"  deep",[200,48125,239],{"class":214},[200,48127,16166],{"class":2423},[200,48129,252],{"class":214},[200,48131,48132,48134],{"class":202,"line":890},[200,48133,413],{"class":214},[200,48135,416],{"class":210},[200,48137,48138,48140,48142],{"class":202,"line":900},[200,48139,681],{"class":214},[200,48141,14892],{"class":235},[200,48143,648],{"class":214},[200,48145,48146],{"class":202,"line":905},[200,48147,529],{"emptyLinePlaceholder":20},[200,48149,48150,48152,48154],{"class":202,"line":931},[200,48151,600],{"class":214},[200,48153,21428],{"class":235},[200,48155,648],{"class":214},[200,48157,48158,48160,48163,48165,48168,48170,48172,48174,48176,48178,48181,48183,48185,48187,48189,48191,48193,48195,48197,48200,48202],{"class":202,"line":3640},[200,48159,657],{"class":214},[200,48161,48162],{"class":235},"UTable",[200,48164,28009],{"class":214},[200,48166,48167],{"class":379},"columns",[200,48169,449],{"class":214},[200,48171,611],{"class":214},[200,48173,48167],{"class":210},[200,48175,611],{"class":214},[200,48177,28009],{"class":214},[200,48179,48180],{"class":379},"rows",[200,48182,449],{"class":214},[200,48184,611],{"class":214},[200,48186,47331],{"class":210},[200,48188,611],{"class":214},[200,48190,28009],{"class":214},[200,48192,9886],{"class":379},[200,48194,449],{"class":214},[200,48196,611],{"class":214},[200,48198,48199],{"class":210},"pending",[200,48201,611],{"class":214},[200,48203,21487],{"class":214},[200,48205,48206,48208,48210],{"class":202,"line":3645},[200,48207,681],{"class":214},[200,48209,21428],{"class":235},[200,48211,648],{"class":214},[35,48213,48214,48215],{},"It's hard to be simpler! ",[1217,48216,48217],{},"Thanks Nuxt UI for the work!",[35,48219,48220],{},"Then, I add the header with the title and the button to create a new category:",[192,48222,48224],{"className":21417,"code":48223,"language":12060,"meta":13,"style":13},"\u003Ctemplate>\n  \u003CUDashboardNavbar\n    title=\"Categories\"\n  >\n    \u003Ctemplate #right>\n      \u003CUButton\n        label=\"New category\"\n        trailing-icon=\"i-heroicons-plus\"\n        color=\"gray\"\n      \u002F>\n    \u003C\u002Ftemplate>\n  \u003C\u002FUDashboardNavbar>\n\u003C\u002Ftemplate>\n",[156,48225,48226,48234,48241,48254,48258,48271,48278,48291,48305,48319,48323,48331,48340],{"__ignoreMap":13},[200,48227,48228,48230,48232],{"class":202,"line":203},[200,48229,600],{"class":214},[200,48231,21428],{"class":235},[200,48233,648],{"class":214},[200,48235,48236,48238],{"class":202,"line":14},[200,48237,657],{"class":214},[200,48239,48240],{"class":235},"UDashboardNavbar\n",[200,48242,48243,48245,48247,48249,48252],{"class":202,"line":255},[200,48244,21442],{"class":379},[200,48246,449],{"class":214},[200,48248,611],{"class":214},[200,48250,48251],{"class":245},"Categories",[200,48253,617],{"class":214},[200,48255,48256],{"class":202,"line":272},[200,48257,11536],{"class":214},[200,48259,48260,48262,48264,48266,48269],{"class":202,"line":283},[200,48261,804],{"class":214},[200,48263,21428],{"class":235},[200,48265,23867],{"class":214},[200,48267,48268],{"class":379},"right",[200,48270,648],{"class":214},[200,48272,48273,48275],{"class":202,"line":300},[200,48274,22027],{"class":214},[200,48276,48277],{"class":235},"UButton\n",[200,48279,48280,48282,48284,48286,48289],{"class":202,"line":310},[200,48281,23884],{"class":379},[200,48283,449],{"class":214},[200,48285,611],{"class":214},[200,48287,48288],{"class":245},"New category",[200,48290,617],{"class":214},[200,48292,48293,48296,48298,48300,48303],{"class":202,"line":336},[200,48294,48295],{"class":379},"        trailing-icon",[200,48297,449],{"class":214},[200,48299,611],{"class":214},[200,48301,48302],{"class":245},"i-heroicons-plus",[200,48304,617],{"class":214},[200,48306,48307,48310,48312,48314,48317],{"class":202,"line":342},[200,48308,48309],{"class":379},"        color",[200,48311,449],{"class":214},[200,48313,611],{"class":214},[200,48315,48316],{"class":245},"gray",[200,48318,617],{"class":214},[200,48320,48321],{"class":202,"line":365},[200,48322,23913],{"class":214},[200,48324,48325,48327,48329],{"class":202,"line":371},[200,48326,15074],{"class":214},[200,48328,21428],{"class":235},[200,48330,648],{"class":214},[200,48332,48333,48335,48338],{"class":202,"line":398},[200,48334,893],{"class":214},[200,48336,48337],{"class":235},"UDashboardNavbar",[200,48339,648],{"class":214},[200,48341,48342,48344,48346],{"class":202,"line":405},[200,48343,681],{"class":214},[200,48345,21428],{"class":235},[200,48347,648],{"class":214},[35,48349,48350],{},"Still very easy. The button will be used to open a modal with a form to create a new category. I started to create the form but in a very simple way:",[192,48352,48354],{"className":21417,"code":48353,"language":12060,"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: 'Required' }),\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=\"Name\" name=\"name\">\n      \u003CUInput v-model=\"state.name\" \u002F>\n    \u003C\u002FUFormGroup>\n\n    \u003CUButton type=\"submit\">\n      Submit\n    \u003C\u002FUButton>\n  \u003C\u002FUForm>\n\u003C\u002Ftemplate>\n",[156,48355,48356,48376,48398,48419,48441,48445,48459,48487,48493,48497,48515,48519,48533,48542,48548,48552,48577,48595,48609,48623,48629,48633,48641,48645,48653,48700,48729,48752,48760,48764,48783,48788,48796,48804],{"__ignoreMap":13},[200,48357,48358,48360,48362,48364,48366,48368,48370,48372,48374],{"class":202,"line":203},[200,48359,600],{"class":214},[200,48361,14892],{"class":235},[200,48363,14590],{"class":379},[200,48365,449],{"class":214},[200,48367,611],{"class":214},[200,48369,1373],{"class":245},[200,48371,611],{"class":214},[200,48373,21781],{"class":379},[200,48375,648],{"class":214},[200,48377,48378,48380,48382,48384,48387,48389,48391,48393,48396],{"class":202,"line":14},[200,48379,973],{"class":206},[200,48381,321],{"class":206},[200,48383,318],{"class":214},[200,48385,48386],{"class":210}," FormSubmitEvent",[200,48388,981],{"class":214},[200,48390,984],{"class":206},[200,48392,242],{"class":214},[200,48394,48395],{"class":245},"#ui\u002Ftypes",[200,48397,992],{"class":214},[200,48399,48400,48402,48404,48406,48409,48411,48413,48415,48417],{"class":202,"line":255},[200,48401,973],{"class":206},[200,48403,321],{"class":206},[200,48405,318],{"class":214},[200,48407,48408],{"class":210}," output",[200,48410,981],{"class":214},[200,48412,984],{"class":206},[200,48414,242],{"class":214},[200,48416,19790],{"class":245},[200,48418,992],{"class":214},[200,48420,48421,48423,48425,48427,48429,48431,48433,48435,48437,48439],{"class":202,"line":272},[200,48422,973],{"class":206},[200,48424,318],{"class":214},[200,48426,42872],{"class":210},[200,48428,550],{"class":214},[200,48430,22708],{"class":210},[200,48432,981],{"class":214},[200,48434,984],{"class":206},[200,48436,242],{"class":214},[200,48438,19790],{"class":245},[200,48440,992],{"class":214},[200,48442,48443],{"class":202,"line":283},[200,48444,529],{"emptyLinePlaceholder":20},[200,48446,48447,48449,48451,48453,48455,48457],{"class":202,"line":300},[200,48448,443],{"class":379},[200,48450,41910],{"class":210},[200,48452,449],{"class":214},[200,48454,42872],{"class":223},[200,48456,227],{"class":210},[200,48458,230],{"class":214},[200,48460,48461,48463,48465,48467,48469,48471,48473,48475,48477,48479,48481,48483,48485],{"class":202,"line":310},[200,48462,236],{"class":235},[200,48464,239],{"class":214},[200,48466,22708],{"class":223},[200,48468,227],{"class":210},[200,48470,2752],{"class":214},[200,48472,39347],{"class":235},[200,48474,239],{"class":214},[200,48476,242],{"class":214},[200,48478,41947],{"class":245},[200,48480,249],{"class":214},[200,48482,981],{"class":214},[200,48484,2089],{"class":210},[200,48486,252],{"class":214},[200,48488,48489,48491],{"class":202,"line":336},[200,48490,413],{"class":214},[200,48492,416],{"class":210},[200,48494,48495],{"class":202,"line":342},[200,48496,529],{"emptyLinePlaceholder":20},[200,48498,48499,48501,48504,48506,48508,48511,48513],{"class":202,"line":365},[200,48500,3771],{"class":379},[200,48502,48503],{"class":1978}," Schema",[200,48505,510],{"class":214},[200,48507,48408],{"class":1978},[200,48509,48510],{"class":214},"\u003Ctypeof",[200,48512,42034],{"class":210},[200,48514,648],{"class":214},[200,48516,48517],{"class":202,"line":371},[200,48518,529],{"emptyLinePlaceholder":20},[200,48520,48521,48523,48525,48527,48529,48531],{"class":202,"line":398},[200,48522,443],{"class":379},[200,48524,18946],{"class":210},[200,48526,449],{"class":214},[200,48528,41979],{"class":223},[200,48530,227],{"class":210},[200,48532,230],{"class":214},[200,48534,48535,48537,48539],{"class":202,"line":405},[200,48536,236],{"class":235},[200,48538,239],{"class":214},[200,48540,48541],{"class":214}," undefined,\n",[200,48543,48544,48546],{"class":202,"line":410},[200,48545,413],{"class":214},[200,48547,416],{"class":210},[200,48549,48550],{"class":202,"line":861},[200,48551,529],{"emptyLinePlaceholder":20},[200,48553,48554,48556,48558,48560,48562,48564,48566,48568,48570,48573,48575],{"class":202,"line":890},[200,48555,3711],{"class":379},[200,48557,4987],{"class":379},[200,48559,42012],{"class":223},[200,48561,227],{"class":214},[200,48563,25386],{"class":386},[200,48565,239],{"class":214},[200,48567,48386],{"class":1978},[200,48569,600],{"class":214},[200,48571,48572],{"class":1978},"Schema",[200,48574,46640],{"class":214},[200,48576,280],{"class":214},[200,48578,48579,48581,48583,48585,48587,48589,48591,48593],{"class":202,"line":900},[200,48580,6165],{"class":206},[200,48582,18741],{"class":223},[200,48584,227],{"class":235},[200,48586,249],{"class":214},[200,48588,48112],{"class":245},[200,48590,249],{"class":214},[200,48592,550],{"class":214},[200,48594,280],{"class":214},[200,48596,48597,48599,48601,48603,48605,48607],{"class":202,"line":905},[200,48598,18759],{"class":235},[200,48600,239],{"class":214},[200,48602,242],{"class":214},[200,48604,18766],{"class":245},[200,48606,249],{"class":214},[200,48608,252],{"class":214},[200,48610,48611,48613,48615,48617,48619,48621],{"class":202,"line":931},[200,48612,18775],{"class":235},[200,48614,239],{"class":214},[200,48616,43652],{"class":210},[200,48618,215],{"class":214},[200,48620,12726],{"class":210},[200,48622,252],{"class":214},[200,48624,48625,48627],{"class":202,"line":3640},[200,48626,5080],{"class":214},[200,48628,416],{"class":235},[200,48630,48631],{"class":202,"line":3645},[200,48632,1130],{"class":214},[200,48634,48635,48637,48639],{"class":202,"line":4020},[200,48636,681],{"class":214},[200,48638,14892],{"class":235},[200,48640,648],{"class":214},[200,48642,48643],{"class":202,"line":4475},[200,48644,529],{"emptyLinePlaceholder":20},[200,48646,48647,48649,48651],{"class":202,"line":4481},[200,48648,600],{"class":214},[200,48650,21428],{"class":235},[200,48652,648],{"class":214},[200,48654,48655,48657,48660,48662,48665,48667,48669,48671,48673,48675,48677,48679,48681,48683,48685,48687,48689,48691,48693,48696,48698],{"class":202,"line":4487},[200,48656,657],{"class":214},[200,48658,48659],{"class":235},"UForm",[200,48661,28009],{"class":214},[200,48663,48664],{"class":379},"schema",[200,48666,449],{"class":214},[200,48668,611],{"class":214},[200,48670,48664],{"class":210},[200,48672,611],{"class":214},[200,48674,28009],{"class":214},[200,48676,26836],{"class":379},[200,48678,449],{"class":214},[200,48680,611],{"class":214},[200,48682,26836],{"class":210},[200,48684,611],{"class":214},[200,48686,22373],{"class":214},[200,48688,917],{"class":379},[200,48690,449],{"class":214},[200,48692,611],{"class":214},[200,48694,48695],{"class":210},"onSubmit",[200,48697,611],{"class":214},[200,48699,648],{"class":214},[200,48701,48702,48704,48707,48709,48711,48713,48715,48717,48719,48721,48723,48725,48727],{"class":202,"line":4493},[200,48703,804],{"class":214},[200,48705,48706],{"class":235},"UFormGroup",[200,48708,21476],{"class":379},[200,48710,449],{"class":214},[200,48712,611],{"class":214},[200,48714,40947],{"class":245},[200,48716,611],{"class":214},[200,48718,701],{"class":379},[200,48720,449],{"class":214},[200,48722,611],{"class":214},[200,48724,20089],{"class":245},[200,48726,611],{"class":214},[200,48728,648],{"class":214},[200,48730,48731,48733,48736,48738,48740,48742,48744,48746,48748,48750],{"class":202,"line":5454},[200,48732,22027],{"class":214},[200,48734,48735],{"class":235},"UInput",[200,48737,42242],{"class":379},[200,48739,449],{"class":214},[200,48741,611],{"class":214},[200,48743,26836],{"class":210},[200,48745,215],{"class":214},[200,48747,20089],{"class":210},[200,48749,611],{"class":214},[200,48751,21487],{"class":214},[200,48753,48754,48756,48758],{"class":202,"line":5459},[200,48755,15074],{"class":214},[200,48757,48706],{"class":235},[200,48759,648],{"class":214},[200,48761,48762],{"class":202,"line":13227},[200,48763,529],{"emptyLinePlaceholder":20},[200,48765,48766,48768,48771,48773,48775,48777,48779,48781],{"class":202,"line":13253},[200,48767,804],{"class":214},[200,48769,48770],{"class":235},"UButton",[200,48772,321],{"class":379},[200,48774,449],{"class":214},[200,48776,611],{"class":214},[200,48778,917],{"class":245},[200,48780,611],{"class":214},[200,48782,648],{"class":214},[200,48784,48785],{"class":202,"line":13269},[200,48786,48787],{"class":210},"      Submit\n",[200,48789,48790,48792,48794],{"class":202,"line":13313},[200,48791,15074],{"class":214},[200,48793,48770],{"class":235},[200,48795,648],{"class":214},[200,48797,48798,48800,48802],{"class":202,"line":13319},[200,48799,893],{"class":214},[200,48801,48659],{"class":235},[200,48803,648],{"class":214},[200,48805,48806,48808,48810],{"class":202,"line":13324},[200,48807,681],{"class":214},[200,48809,21428],{"class":235},[200,48811,648],{"class":214},[35,48813,48814,48815,48818,48819,48821],{},"I use the form as explained in the Nuxt UI documentation. I do not really choose Yup for the validation but it was the first proposed. ",[1217,48816,48817],{},"We will see if it's a good choice in the future."," In the ",[156,48820,48695],{}," method, I just do a fetch to the API to create a new category. In the next stream, we will improve it by handling the success and the error state.",[35,48823,48824,48825,48828],{},"This component is put into a modal using the component ",[156,48826,48827],{},"UModal"," and listening to the click event on the button to open a modal.",[35,48830,48831],{},[1217,48832,48833],{},"A point I'm not sure about is how to handle the response of the API. Should I return the new category and put it in the table, or should I refresh the table? For the moment, I opted for the second one.",[35,48835,48836],{},"Using Nuxt UI helps me to build the admin interface quickly, without any custom components.",[7139,48838,48840,48844,48847,48851,48855,48861,48877,48880,48884,48887,48905,49092,49101,49113,49117,49124,49130,49292,49298,49747,49750,49757,50091,50095,50098,50105,50215,50222,50229,50239,50444,50447,50501,50504,50508,50511,50515,50519,50525,50541,50556,50636,50650,50672],{"icon":46023,"text":48839,"to":46025,"target":7144},"View Source Code",[1913,48841,48843],{"id":48842},"end-of-the-stream","End of the stream",[35,48845,48846],{},"It was definitely a good stream. The project is moving forward without a lot of difficulties and it is a pleasure to work on it. I am looking forward to the next stream to continue working on the admin interface and the forms. Nuxt UI and Nuxt UI Pro are game changers, simple code, easy to use, and I can focus on the features of the application.",[35,48848,46318,48849],{},[1582,48850,46321],{"repo":38655},[51,48852,48854],{"id":48853},"lets-continue-the-admin-interface","Let's continue the admin interface",[35,48856,48857],{},[1690,48858],{"alt":48859,"src":48860},"Thumbnail of my fourth live stream.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-04.png",[35,48862,48863],{},[1217,48864,48865,46057,48868,46062,48871,46066,48874,215],{},[39,48866,38639],{"href":46025,"rel":48867},[77],[39,48869,46061],{"href":18567,"rel":48870},[77],[39,48872,19412],{"href":46016,"rel":48873},[77],[39,48875,11622],{"href":46025,"rel":48876},[77],[35,48878,48879],{},"During the previous stream, I started creating the categories page. I implemented the API endpoints to get and create categories, and I built the UI to display the categories and the form to create a new category. In this stream, I've continued working on the admin interface by adding the edit category form and creating a new page to manage users. I've also added the bouncer to secure the access to the admin part of the application, both for the frontend and the backend. Let's see the work done in more detail.",[1913,48881,48883],{"id":48882},"edit-category-form","Edit category form",[35,48885,48886],{},"This part was very similar to the create category form. Instead of submitting the form data to the API to create a new category, I changed the endpoint to update an existing category.",[35,48888,48889,48890,48893,48894,48897,48898,48901,48902,48904],{},"I created the form in a new component ",[156,48891,48892],{},"components\u002Fcategories\u002FCategoryEditForm.vue"," (and renamed the previous form to ",[156,48895,48896],{},"CategoryCreateForm.vue","). I also created a new endpoint ",[156,48899,48900],{},"server\u002Fapi\u002Fcategories\u002F[id].put.ts"," to update a category. This endpoint updates a category using the ",[156,48903,3280],{}," in the URL and the body of the request:",[192,48906,48908],{"className":1370,"code":48907,"language":1373,"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,48909,48910,48932,48950,48954,48986,49006,49020,49065,49069,49086],{"__ignoreMap":13},[200,48911,48912,48914,48916,48918,48920,48922,48924,48926,48928,48930],{"class":202,"line":203},[200,48913,1380],{"class":206},[200,48915,1383],{"class":206},[200,48917,38960],{"class":223},[200,48919,227],{"class":210},[200,48921,3711],{"class":379},[200,48923,1095],{"class":214},[200,48925,25386],{"class":386},[200,48927,2089],{"class":214},[200,48929,393],{"class":379},[200,48931,280],{"class":214},[200,48933,48934,48936,48938,48940,48942,48944,48946,48948],{"class":202,"line":14},[200,48935,3723],{"class":379},[200,48937,41038],{"class":210},[200,48939,510],{"class":214},[200,48941,452],{"class":206},[200,48943,41045],{"class":223},[200,48945,227],{"class":235},[200,48947,25386],{"class":210},[200,48949,416],{"class":235},[200,48951,48952],{"class":202,"line":255},[200,48953,529],{"emptyLinePlaceholder":20},[200,48955,48956,48958,48960,48962,48964,48967,48969,48971,48973,48975,48977,48979,48982,48984],{"class":202,"line":272},[200,48957,6165],{"class":206},[200,48959,47508],{"class":223},[200,48961,1532],{"class":235},[200,48963,215],{"class":214},[200,48965,48966],{"class":223},"update",[200,48968,227],{"class":235},[200,48970,47588],{"class":210},[200,48972,215],{"class":214},[200,48974,47331],{"class":210},[200,48976,2089],{"class":235},[200,48978,215],{"class":214},[200,48980,48981],{"class":223},"set",[200,48983,227],{"class":235},[200,48985,230],{"class":214},[200,48987,48988,48990,48992,48994,48996,48998,49000,49002,49004],{"class":202,"line":283},[200,48989,47542],{"class":235},[200,48991,239],{"class":214},[200,48993,47724],{"class":223},[200,48995,227],{"class":235},[200,48997,23870],{"class":210},[200,48999,215],{"class":214},[200,49001,20089],{"class":210},[200,49003,2089],{"class":235},[200,49005,252],{"class":214},[200,49007,49008,49010,49012,49014,49016,49018],{"class":202,"line":300},[200,49009,11797],{"class":235},[200,49011,239],{"class":214},[200,49013,41038],{"class":210},[200,49015,215],{"class":214},[200,49017,20089],{"class":210},[200,49019,252],{"class":214},[200,49021,49022,49024,49026,49028,49031,49033,49036,49038,49040,49042,49044,49046,49048,49050,49053,49055,49057,49059,49061,49063],{"class":202,"line":310},[200,49023,5080],{"class":214},[200,49025,2089],{"class":235},[200,49027,215],{"class":214},[200,49029,49030],{"class":223},"where",[200,49032,227],{"class":235},[200,49034,49035],{"class":223},"eq",[200,49037,227],{"class":235},[200,49039,47588],{"class":210},[200,49041,215],{"class":214},[200,49043,47331],{"class":210},[200,49045,215],{"class":214},[200,49047,3280],{"class":210},[200,49049,550],{"class":214},[200,49051,49052],{"class":210}," params",[200,49054,215],{"class":214},[200,49056,3280],{"class":210},[200,49058,6119],{"class":235},[200,49060,215],{"class":214},[200,49062,47761],{"class":223},[200,49064,466],{"class":235},[200,49066,49067],{"class":202,"line":336},[200,49068,529],{"emptyLinePlaceholder":20},[200,49070,49071,49073,49075,49077,49079,49081,49084],{"class":202,"line":342},[200,49072,3743],{"class":206},[200,49074,41071],{"class":223},[200,49076,227],{"class":235},[200,49078,25386],{"class":210},[200,49080,550],{"class":214},[200,49082,49083],{"class":2045}," 200",[200,49085,416],{"class":235},[200,49087,49088,49090],{"class":202,"line":365},[200,49089,413],{"class":214},[200,49091,416],{"class":210},[35,49093,49094],{},[1217,49095,49096,49097,49100],{},"The 200 status code could not be the best choice (204 is better with a ",[156,49098,49099],{},"PUT","), but I'm still thinking if I should return the updated category or not.",[35,49102,49103,49104,49109,49110],{},"I also tried to validate the body of the request using Yup because it was the tool used in the frontend. However, I quickly realized that it was not possible to cast a string to a number. I know myzod can do it so I tried it. I encountered ",[39,49105,49108],{"href":49106,"rel":49107},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fh3\u002Fissues\u002F745",[77],"an issue"," and instead used Zod. ",[1217,49111,49112],{},"I moved the frontend to Zod off-stream.",[1913,49114,49116],{"id":49115},"users-page","Users page",[35,49118,49119,49120,49123],{},"Actually, when a user logs in, related data from GitHub are stored in the database (",[1217,49121,49122],{},"while writing this part, I'm wondering if this is necessary because it means that a log in is a write which is more expensive than a read",") and in the session. This behavior is useful to promote the user to an admin or to ban a user. But for the moment, there is no way to manage users. So, I created a new page to manage users. Like the categories page, the page will display the users in a table. However, it will not be possible to create or update a user for the moment.",[35,49125,49126,49127,239],{},"In order to retrieve the users from the database, I created the API endpoint ",[156,49128,49129],{},"server\u002Fapi\u002Fusers\u002Findex.get.ts",[192,49131,49133],{"className":1370,"code":49132,"language":1373,"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,49134,49135,49157,49180,49198,49217,49237,49255,49275,49279,49286],{"__ignoreMap":13},[200,49136,49137,49139,49141,49143,49145,49147,49149,49151,49153,49155],{"class":202,"line":203},[200,49138,1380],{"class":206},[200,49140,1383],{"class":206},[200,49142,38960],{"class":223},[200,49144,227],{"class":210},[200,49146,3711],{"class":379},[200,49148,1095],{"class":214},[200,49150,25386],{"class":386},[200,49152,2089],{"class":214},[200,49154,393],{"class":379},[200,49156,280],{"class":214},[200,49158,49159,49161,49164,49166,49168,49170,49172,49174,49176,49178],{"class":202,"line":14},[200,49160,3723],{"class":379},[200,49162,49163],{"class":210}," users",[200,49165,510],{"class":214},[200,49167,452],{"class":206},[200,49169,47508],{"class":223},[200,49171,1532],{"class":235},[200,49173,215],{"class":214},[200,49175,770],{"class":223},[200,49177,227],{"class":235},[200,49179,230],{"class":214},[200,49181,49182,49184,49186,49188,49190,49192,49194,49196],{"class":202,"line":255},[200,49183,15858],{"class":235},[200,49185,239],{"class":214},[200,49187,47527],{"class":210},[200,49189,215],{"class":214},[200,49191,47325],{"class":210},[200,49193,215],{"class":214},[200,49195,3280],{"class":210},[200,49197,252],{"class":214},[200,49199,49200,49203,49205,49207,49209,49211,49213,49215],{"class":202,"line":272},[200,49201,49202],{"class":235},"    githubId",[200,49204,239],{"class":214},[200,49206,47527],{"class":210},[200,49208,215],{"class":214},[200,49210,47325],{"class":210},[200,49212,215],{"class":214},[200,49214,46224],{"class":210},[200,49216,252],{"class":214},[200,49218,49219,49222,49224,49226,49228,49230,49232,49235],{"class":202,"line":283},[200,49220,49221],{"class":235},"    username",[200,49223,239],{"class":214},[200,49225,47527],{"class":210},[200,49227,215],{"class":214},[200,49229,47325],{"class":210},[200,49231,215],{"class":214},[200,49233,49234],{"class":210},"username",[200,49236,252],{"class":214},[200,49238,49239,49242,49244,49246,49248,49250,49252],{"class":202,"line":300},[200,49240,49241],{"class":235},"    roleType",[200,49243,239],{"class":214},[200,49245,47527],{"class":210},[200,49247,215],{"class":214},[200,49249,47325],{"class":210},[200,49251,215],{"class":214},[200,49253,49254],{"class":210},"roleType\n",[200,49256,49257,49259,49261,49263,49265,49267,49269,49271,49273],{"class":202,"line":310},[200,49258,5080],{"class":214},[200,49260,2089],{"class":235},[200,49262,215],{"class":214},[200,49264,1472],{"class":223},[200,49266,227],{"class":235},[200,49268,47588],{"class":210},[200,49270,215],{"class":214},[200,49272,47325],{"class":210},[200,49274,416],{"class":235},[200,49276,49277],{"class":202,"line":336},[200,49278,529],{"emptyLinePlaceholder":20},[200,49280,49281,49283],{"class":202,"line":342},[200,49282,3743],{"class":206},[200,49284,49285],{"class":210}," users\n",[200,49287,49288,49290],{"class":202,"line":365},[200,49289,413],{"class":214},[200,49291,416],{"class":210},[35,49293,49294,49295,49297],{},"To show them, I use the ",[156,49296,48162],{}," component from Nuxt UI Pro. The code is very similar to the categories page:",[192,49299,49301],{"className":21417,"code":49300,"language":12060,"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: 'Username',\n  sortable: true\n}, {\n  key: 'roleType',\n  label: 'Role Type'\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,49302,49303,49323,49335,49349,49361,49367,49381,49396,49402,49416,49431,49439,49445,49459,49472,49478,49512,49522,49528,49536,49540,49548,49556,49566,49598,49625,49692,49697,49706,49715,49723,49731,49739],{"__ignoreMap":13},[200,49304,49305,49307,49309,49311,49313,49315,49317,49319,49321],{"class":202,"line":203},[200,49306,600],{"class":214},[200,49308,14892],{"class":235},[200,49310,14590],{"class":379},[200,49312,449],{"class":214},[200,49314,611],{"class":214},[200,49316,1373],{"class":245},[200,49318,611],{"class":214},[200,49320,21781],{"class":379},[200,49322,648],{"class":214},[200,49324,49325,49327,49329,49331,49333],{"class":202,"line":14},[200,49326,443],{"class":379},[200,49328,47958],{"class":210},[200,49330,449],{"class":214},[200,49332,350],{"class":210},[200,49334,230],{"class":214},[200,49336,49337,49339,49341,49343,49345,49347],{"class":202,"line":255},[200,49338,18962],{"class":235},[200,49340,239],{"class":214},[200,49342,242],{"class":214},[200,49344,3280],{"class":245},[200,49346,249],{"class":214},[200,49348,252],{"class":214},[200,49350,49351,49353,49355,49357,49359],{"class":202,"line":272},[200,49352,47983],{"class":235},[200,49354,239],{"class":214},[200,49356,242],{"class":214},[200,49358,2886],{"class":245},[200,49360,992],{"class":214},[200,49362,49363,49365],{"class":202,"line":283},[200,49364,26858],{"class":214},[200,49366,280],{"class":214},[200,49368,49369,49371,49373,49375,49377,49379],{"class":202,"line":300},[200,49370,18962],{"class":235},[200,49372,239],{"class":214},[200,49374,242],{"class":214},[200,49376,46224],{"class":245},[200,49378,249],{"class":214},[200,49380,252],{"class":214},[200,49382,49383,49385,49387,49389,49392,49394],{"class":202,"line":310},[200,49384,47983],{"class":235},[200,49386,239],{"class":214},[200,49388,242],{"class":214},[200,49390,49391],{"class":245},"GitHub ID",[200,49393,249],{"class":214},[200,49395,252],{"class":214},[200,49397,49398,49400],{"class":202,"line":336},[200,49399,26858],{"class":214},[200,49401,280],{"class":214},[200,49403,49404,49406,49408,49410,49412,49414],{"class":202,"line":342},[200,49405,18962],{"class":235},[200,49407,239],{"class":214},[200,49409,242],{"class":214},[200,49411,49234],{"class":245},[200,49413,249],{"class":214},[200,49415,252],{"class":214},[200,49417,49418,49420,49422,49424,49427,49429],{"class":202,"line":365},[200,49419,47983],{"class":235},[200,49421,239],{"class":214},[200,49423,242],{"class":214},[200,49425,49426],{"class":245},"Username",[200,49428,249],{"class":214},[200,49430,252],{"class":214},[200,49432,49433,49435,49437],{"class":202,"line":371},[200,49434,48030],{"class":235},[200,49436,239],{"class":214},[200,49438,22192],{"class":2423},[200,49440,49441,49443],{"class":202,"line":398},[200,49442,26858],{"class":214},[200,49444,280],{"class":214},[200,49446,49447,49449,49451,49453,49455,49457],{"class":202,"line":405},[200,49448,18962],{"class":235},[200,49450,239],{"class":214},[200,49452,242],{"class":214},[200,49454,39420],{"class":245},[200,49456,249],{"class":214},[200,49458,252],{"class":214},[200,49460,49461,49463,49465,49467,49470],{"class":202,"line":410},[200,49462,47983],{"class":235},[200,49464,239],{"class":214},[200,49466,242],{"class":214},[200,49468,49469],{"class":245},"Role Type",[200,49471,992],{"class":214},[200,49473,49474,49476],{"class":202,"line":861},[200,49475,413],{"class":214},[200,49477,4496],{"class":210},[200,49479,49480,49482,49484,49486,49488,49491,49493,49495,49497,49499,49501,49503,49506,49508,49510],{"class":202,"line":890},[200,49481,443],{"class":379},[200,49483,318],{"class":214},[200,49485,5041],{"class":235},[200,49487,239],{"class":214},[200,49489,49490],{"class":210}," users ",[200,49492,413],{"class":214},[200,49494,510],{"class":214},[200,49496,452],{"class":206},[200,49498,48105],{"class":223},[200,49500,227],{"class":210},[200,49502,249],{"class":214},[200,49504,49505],{"class":245},"\u002Fapi\u002Fusers",[200,49507,249],{"class":214},[200,49509,550],{"class":214},[200,49511,280],{"class":214},[200,49513,49514,49516,49518,49520],{"class":202,"line":900},[200,49515,48123],{"class":235},[200,49517,239],{"class":214},[200,49519,16166],{"class":2423},[200,49521,252],{"class":214},[200,49523,49524,49526],{"class":202,"line":905},[200,49525,413],{"class":214},[200,49527,416],{"class":210},[200,49529,49530,49532,49534],{"class":202,"line":931},[200,49531,681],{"class":214},[200,49533,14892],{"class":235},[200,49535,648],{"class":214},[200,49537,49538],{"class":202,"line":3640},[200,49539,529],{"emptyLinePlaceholder":20},[200,49541,49542,49544,49546],{"class":202,"line":3645},[200,49543,600],{"class":214},[200,49545,21428],{"class":235},[200,49547,648],{"class":214},[200,49549,49550,49552,49554],{"class":202,"line":4020},[200,49551,657],{"class":214},[200,49553,47873],{"class":235},[200,49555,648],{"class":214},[200,49557,49558,49560,49562,49564],{"class":202,"line":4475},[200,49559,804],{"class":214},[200,49561,47882],{"class":235},[200,49563,47885],{"class":379},[200,49565,648],{"class":214},[200,49567,49568,49570,49572,49574,49576,49578,49580,49582,49584,49586,49588,49590,49592,49594,49596],{"class":202,"line":4481},[200,49569,22027],{"class":214},[200,49571,48162],{"class":235},[200,49573,28009],{"class":214},[200,49575,48167],{"class":379},[200,49577,449],{"class":214},[200,49579,611],{"class":214},[200,49581,48167],{"class":210},[200,49583,611],{"class":214},[200,49585,28009],{"class":214},[200,49587,48180],{"class":379},[200,49589,449],{"class":214},[200,49591,611],{"class":214},[200,49593,47325],{"class":210},[200,49595,611],{"class":214},[200,49597,648],{"class":214},[200,49599,49600,49603,49605,49607,49610,49612,49614,49616,49619,49621,49623],{"class":202,"line":4487},[200,49601,49602],{"class":214},"        \u003C",[200,49604,21428],{"class":235},[200,49606,23867],{"class":214},[200,49608,49609],{"class":379},"roleType-data",[200,49611,449],{"class":214},[200,49613,611],{"class":214},[200,49615,2752],{"class":214},[200,49617,49618],{"class":210}," row ",[200,49620,413],{"class":214},[200,49622,611],{"class":214},[200,49624,648],{"class":214},[200,49626,49627,49630,49633,49636,49638,49640,49643,49645,49647,49650,49652,49654,49657,49659,49661,49664,49666,49668,49670,49673,49675,49678,49680,49683,49685,49688,49690],{"class":202,"line":4493},[200,49628,49629],{"class":214},"          \u003C",[200,49631,49632],{"class":235},"UBadge",[200,49634,49635],{"class":379}," variant",[200,49637,449],{"class":214},[200,49639,611],{"class":214},[200,49641,49642],{"class":245},"subtle",[200,49644,611],{"class":214},[200,49646,28009],{"class":214},[200,49648,49649],{"class":379},"color",[200,49651,449],{"class":214},[200,49653,611],{"class":214},[200,49655,49656],{"class":210},"row",[200,49658,215],{"class":214},[200,49660,39420],{"class":210},[200,49662,49663],{"class":214}," === ",[200,49665,249],{"class":214},[200,49667,39427],{"class":245},[200,49669,249],{"class":214},[200,49671,49672],{"class":214}," ? ",[200,49674,249],{"class":214},[200,49676,49677],{"class":245},"amber",[200,49679,249],{"class":214},[200,49681,49682],{"class":214}," : ",[200,49684,249],{"class":214},[200,49686,49687],{"class":245},"primary",[200,49689,14702],{"class":214},[200,49691,648],{"class":214},[200,49693,49694],{"class":202,"line":5454},[200,49695,49696],{"class":210},"            {{ row.roleType }}\n",[200,49698,49699,49702,49704],{"class":202,"line":5459},[200,49700,49701],{"class":214},"          \u003C\u002F",[200,49703,49632],{"class":235},[200,49705,648],{"class":214},[200,49707,49708,49711,49713],{"class":202,"line":13227},[200,49709,49710],{"class":214},"        \u003C\u002F",[200,49712,21428],{"class":235},[200,49714,648],{"class":214},[200,49716,49717,49719,49721],{"class":202,"line":13253},[200,49718,43992],{"class":214},[200,49720,48162],{"class":235},[200,49722,648],{"class":214},[200,49724,49725,49727,49729],{"class":202,"line":13269},[200,49726,15074],{"class":214},[200,49728,47882],{"class":235},[200,49730,648],{"class":214},[200,49732,49733,49735,49737],{"class":202,"line":13313},[200,49734,893],{"class":214},[200,49736,47873],{"class":235},[200,49738,648],{"class":214},[200,49740,49741,49743,49745],{"class":202,"line":13319},[200,49742,681],{"class":214},[200,49744,21428],{"class":235},[200,49746,648],{"class":214},[35,49748,49749],{},"As expected, the code is very similar to the categories page.",[35,49751,49752,49753,49756],{},"Now we have two pages, one for categories and one for users, so I added a sidebar to navigate between them. Nothing complicated here, I just used the ",[156,49754,49755],{},"UDashboardSidebar"," component from Nuxt UI Pro and added the links to the categories and users pages:",[192,49758,49760],{"className":21417,"code":49759,"language":12060,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nconst links = [{\n  id: 'categories',\n  label: 'Categories',\n  icon: 'i-heroicons-tag',\n  to: '\u002Fadmin\u002Fcategories',\n  tooltip: {\n    text: 'Categories',\n  }\n}, {\n  id: 'users',\n  label: 'Users',\n  icon: 'i-heroicons-user-group',\n  to: '\u002Fadmin\u002Fusers',\n  tooltip: {\n    text: 'Users',\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,49761,49762,49782,49795,49810,49824,49840,49856,49865,49880,49884,49890,49904,49919,49934,49949,49957,49971,49975,49981,49989,49993,50001,50009,50017,50025,50047,50055,50063,50067,50075,50083],{"__ignoreMap":13},[200,49763,49764,49766,49768,49770,49772,49774,49776,49778,49780],{"class":202,"line":203},[200,49765,600],{"class":214},[200,49767,14892],{"class":235},[200,49769,14590],{"class":379},[200,49771,449],{"class":214},[200,49773,611],{"class":214},[200,49775,1373],{"class":245},[200,49777,611],{"class":214},[200,49779,21781],{"class":379},[200,49781,648],{"class":214},[200,49783,49784,49786,49789,49791,49793],{"class":202,"line":14},[200,49785,443],{"class":379},[200,49787,49788],{"class":210}," links ",[200,49790,449],{"class":214},[200,49792,350],{"class":210},[200,49794,230],{"class":214},[200,49796,49797,49800,49802,49804,49806,49808],{"class":202,"line":255},[200,49798,49799],{"class":235},"  id",[200,49801,239],{"class":214},[200,49803,242],{"class":214},[200,49805,47331],{"class":245},[200,49807,249],{"class":214},[200,49809,252],{"class":214},[200,49811,49812,49814,49816,49818,49820,49822],{"class":202,"line":272},[200,49813,47983],{"class":235},[200,49815,239],{"class":214},[200,49817,242],{"class":214},[200,49819,48251],{"class":245},[200,49821,249],{"class":214},[200,49823,252],{"class":214},[200,49825,49826,49829,49831,49833,49836,49838],{"class":202,"line":283},[200,49827,49828],{"class":235},"  icon",[200,49830,239],{"class":214},[200,49832,242],{"class":214},[200,49834,49835],{"class":245},"i-heroicons-tag",[200,49837,249],{"class":214},[200,49839,252],{"class":214},[200,49841,49842,49845,49847,49849,49852,49854],{"class":202,"line":300},[200,49843,49844],{"class":235},"  to",[200,49846,239],{"class":214},[200,49848,242],{"class":214},[200,49850,49851],{"class":245},"\u002Fadmin\u002Fcategories",[200,49853,249],{"class":214},[200,49855,252],{"class":214},[200,49857,49858,49861,49863],{"class":202,"line":310},[200,49859,49860],{"class":235},"  tooltip",[200,49862,239],{"class":214},[200,49864,280],{"class":214},[200,49866,49867,49870,49872,49874,49876,49878],{"class":202,"line":336},[200,49868,49869],{"class":235},"    text",[200,49871,239],{"class":214},[200,49873,242],{"class":214},[200,49875,48251],{"class":245},[200,49877,249],{"class":214},[200,49879,252],{"class":214},[200,49881,49882],{"class":202,"line":342},[200,49883,4490],{"class":214},[200,49885,49886,49888],{"class":202,"line":365},[200,49887,26858],{"class":214},[200,49889,280],{"class":214},[200,49891,49892,49894,49896,49898,49900,49902],{"class":202,"line":371},[200,49893,49799],{"class":235},[200,49895,239],{"class":214},[200,49897,242],{"class":214},[200,49899,47325],{"class":245},[200,49901,249],{"class":214},[200,49903,252],{"class":214},[200,49905,49906,49908,49910,49912,49915,49917],{"class":202,"line":398},[200,49907,47983],{"class":235},[200,49909,239],{"class":214},[200,49911,242],{"class":214},[200,49913,49914],{"class":245},"Users",[200,49916,249],{"class":214},[200,49918,252],{"class":214},[200,49920,49921,49923,49925,49927,49930,49932],{"class":202,"line":405},[200,49922,49828],{"class":235},[200,49924,239],{"class":214},[200,49926,242],{"class":214},[200,49928,49929],{"class":245},"i-heroicons-user-group",[200,49931,249],{"class":214},[200,49933,252],{"class":214},[200,49935,49936,49938,49940,49942,49945,49947],{"class":202,"line":410},[200,49937,49844],{"class":235},[200,49939,239],{"class":214},[200,49941,242],{"class":214},[200,49943,49944],{"class":245},"\u002Fadmin\u002Fusers",[200,49946,249],{"class":214},[200,49948,252],{"class":214},[200,49950,49951,49953,49955],{"class":202,"line":861},[200,49952,49860],{"class":235},[200,49954,239],{"class":214},[200,49956,280],{"class":214},[200,49958,49959,49961,49963,49965,49967,49969],{"class":202,"line":890},[200,49960,49869],{"class":235},[200,49962,239],{"class":214},[200,49964,242],{"class":214},[200,49966,49914],{"class":245},[200,49968,249],{"class":214},[200,49970,252],{"class":214},[200,49972,49973],{"class":202,"line":900},[200,49974,4490],{"class":214},[200,49976,49977,49979],{"class":202,"line":905},[200,49978,413],{"class":214},[200,49980,4496],{"class":210},[200,49982,49983,49985,49987],{"class":202,"line":931},[200,49984,681],{"class":214},[200,49986,14892],{"class":235},[200,49988,648],{"class":214},[200,49990,49991],{"class":202,"line":3640},[200,49992,529],{"emptyLinePlaceholder":20},[200,49994,49995,49997,49999],{"class":202,"line":3645},[200,49996,600],{"class":214},[200,49998,21428],{"class":235},[200,50000,648],{"class":214},[200,50002,50003,50005,50007],{"class":202,"line":4020},[200,50004,657],{"class":214},[200,50006,47416],{"class":235},[200,50008,648],{"class":214},[200,50010,50011,50013,50015],{"class":202,"line":4475},[200,50012,804],{"class":214},[200,50014,47882],{"class":235},[200,50016,648],{"class":214},[200,50018,50019,50021,50023],{"class":202,"line":4481},[200,50020,22027],{"class":214},[200,50022,49755],{"class":235},[200,50024,648],{"class":214},[200,50026,50027,50029,50032,50034,50037,50039,50041,50043,50045],{"class":202,"line":4487},[200,50028,49602],{"class":214},[200,50030,50031],{"class":235},"UDashboardSidebarLinks",[200,50033,28009],{"class":214},[200,50035,50036],{"class":379},"links",[200,50038,449],{"class":214},[200,50040,611],{"class":214},[200,50042,50036],{"class":210},[200,50044,611],{"class":214},[200,50046,21487],{"class":214},[200,50048,50049,50051,50053],{"class":202,"line":4493},[200,50050,43992],{"class":214},[200,50052,49755],{"class":235},[200,50054,648],{"class":214},[200,50056,50057,50059,50061],{"class":202,"line":5454},[200,50058,15074],{"class":214},[200,50060,47882],{"class":235},[200,50062,648],{"class":214},[200,50064,50065],{"class":202,"line":5459},[200,50066,529],{"emptyLinePlaceholder":20},[200,50068,50069,50071,50073],{"class":202,"line":13227},[200,50070,804],{"class":214},[200,50072,47425],{"class":235},[200,50074,21487],{"class":214},[200,50076,50077,50079,50081],{"class":202,"line":13253},[200,50078,893],{"class":214},[200,50080,47416],{"class":235},[200,50082,648],{"class":214},[200,50084,50085,50087,50089],{"class":202,"line":13269},[200,50086,681],{"class":214},[200,50088,21428],{"class":235},[200,50090,648],{"class":214},[1913,50092,50094],{"id":50093},"protecting-the-admin-part","Protecting the admin part",[35,50096,50097],{},"When building the administration part of an application, it's important to secure and restrict access to it, for both the frontend and the backend. This means that only authorized users can view and manipulate the data.",[35,50099,50100,50101,50104],{},"To restrict access to the UI part of the admin, i.e., the frontend, I used a middleware. It's a function that runs before navigating to the page and can be used to check if the user is authenticated and has the right permissions. In this case, I created a middleware ",[156,50102,50103],{},"middleware\u002Fadmin.ts"," to check if the user is authenticated and has the role of an admin:",[192,50106,50108],{"className":1370,"code":50107,"language":1373,"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,50109,50110,50127,50144,50148,50175,50179,50192,50209],{"__ignoreMap":13},[200,50111,50112,50114,50116,50119,50121,50123,50125],{"class":202,"line":203},[200,50113,1380],{"class":206},[200,50115,1383],{"class":206},[200,50117,50118],{"class":223}," defineNuxtRouteMiddleware",[200,50120,227],{"class":210},[200,50122,1532],{"class":214},[200,50124,393],{"class":379},[200,50126,280],{"class":214},[200,50128,50129,50131,50133,50135,50137,50139,50142],{"class":202,"line":14},[200,50130,3723],{"class":379},[200,50132,318],{"class":214},[200,50134,34776],{"class":210},[200,50136,981],{"class":214},[200,50138,510],{"class":214},[200,50140,50141],{"class":223}," useUserSession",[200,50143,466],{"class":235},[200,50145,50146],{"class":202,"line":255},[200,50147,529],{"emptyLinePlaceholder":20},[200,50149,50150,50152,50155,50157,50159,50161,50163,50165,50167,50169,50171,50173],{"class":202,"line":272},[200,50151,3723],{"class":379},[200,50153,50154],{"class":210}," isAdmin",[200,50156,510],{"class":214},[200,50158,34776],{"class":210},[200,50160,215],{"class":214},[200,50162,15837],{"class":210},[200,50164,6524],{"class":214},[200,50166,39420],{"class":210},[200,50168,3283],{"class":214},[200,50170,242],{"class":214},[200,50172,39427],{"class":245},[200,50174,992],{"class":214},[200,50176,50177],{"class":202,"line":283},[200,50178,529],{"emptyLinePlaceholder":20},[200,50180,50181,50183,50185,50187,50190],{"class":202,"line":300},[200,50182,20075],{"class":206},[200,50184,1095],{"class":235},[200,50186,11837],{"class":214},[200,50188,50189],{"class":210},"isAdmin",[200,50191,416],{"class":235},[200,50193,50194,50196,50199,50201,50203,50205,50207],{"class":202,"line":310},[200,50195,3296],{"class":206},[200,50197,50198],{"class":223}," redirectTo",[200,50200,227],{"class":235},[200,50202,249],{"class":214},[200,50204,2882],{"class":245},[200,50206,249],{"class":214},[200,50208,416],{"class":235},[200,50210,50211,50213],{"class":202,"line":336},[200,50212,413],{"class":214},[200,50214,416],{"class":210},[35,50216,50217],{},[1217,50218,50219,50220,215],{},"The middleware must be used on pages that need to be protected using ",[156,50221,47823],{},[35,50223,50224,50225,50228],{},"I get the user from the session using ",[156,50226,50227],{},"useUserSession"," and check if the user has the role of an admin, if not, I redirect the user to the home page.",[35,50230,50231,50232,50235,50236,239],{},"The backend is way more critical than the frontend but it's not more difficult than the frontend to secure. In order to do so, I create a util named ",[156,50233,50234],{},"requireAdminUser"," in the file ",[156,50237,50238],{},"server\u002Futils\u002Fsession.ts",[192,50240,50242],{"className":1370,"code":50241,"language":1373,"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,50243,50244,50266,50286,50290,50322,50340,50344,50382,50392,50402,50416,50422,50426,50430,50440],{"__ignoreMap":13},[200,50245,50246,50248,50250,50252,50255,50257,50259,50261,50264],{"class":202,"line":203},[200,50247,973],{"class":206},[200,50249,321],{"class":206},[200,50251,318],{"class":214},[200,50253,50254],{"class":210}," UserSessionRequired",[200,50256,981],{"class":214},[200,50258,984],{"class":206},[200,50260,242],{"class":214},[200,50262,50263],{"class":245},"#auth-utils",[200,50265,992],{"class":214},[200,50267,50268,50270,50272,50274,50276,50278,50280,50282,50284],{"class":202,"line":14},[200,50269,973],{"class":206},[200,50271,321],{"class":206},[200,50273,318],{"class":214},[200,50275,39325],{"class":210},[200,50277,981],{"class":214},[200,50279,984],{"class":206},[200,50281,242],{"class":214},[200,50283,1913],{"class":245},[200,50285,992],{"class":214},[200,50287,50288],{"class":202,"line":255},[200,50289,529],{"emptyLinePlaceholder":20},[200,50291,50292,50294,50296,50298,50301,50303,50305,50307,50309,50312,50314,50316,50318,50320],{"class":202,"line":272},[200,50293,1380],{"class":206},[200,50295,380],{"class":379},[200,50297,4987],{"class":379},[200,50299,50300],{"class":223}," requireAdminUser",[200,50302,227],{"class":214},[200,50304,25386],{"class":386},[200,50306,239],{"class":214},[200,50308,39325],{"class":1978},[200,50310,50311],{"class":214},"):",[200,50313,4996],{"class":1978},[200,50315,600],{"class":214},[200,50317,39365],{"class":1978},[200,50319,675],{"class":214},[200,50321,280],{"class":214},[200,50323,50324,50326,50328,50330,50332,50334,50336,50338],{"class":202,"line":283},[200,50325,3723],{"class":379},[200,50327,39376],{"class":210},[200,50329,510],{"class":214},[200,50331,452],{"class":206},[200,50333,39713],{"class":223},[200,50335,227],{"class":235},[200,50337,25386],{"class":210},[200,50339,416],{"class":235},[200,50341,50342],{"class":202,"line":300},[200,50343,529],{"emptyLinePlaceholder":20},[200,50345,50346,50348,50350,50352,50354,50356,50358,50360,50362,50364,50366,50368,50370,50372,50374,50376,50378,50380],{"class":202,"line":310},[200,50347,20075],{"class":206},[200,50349,1095],{"class":235},[200,50351,11837],{"class":214},[200,50353,39782],{"class":210},[200,50355,215],{"class":214},[200,50357,26808],{"class":210},[200,50359,15009],{"class":214},[200,50361,39376],{"class":210},[200,50363,215],{"class":214},[200,50365,26808],{"class":210},[200,50367,215],{"class":214},[200,50369,39420],{"class":210},[200,50371,13388],{"class":214},[200,50373,242],{"class":214},[200,50375,39427],{"class":245},[200,50377,249],{"class":214},[200,50379,5267],{"class":235},[200,50381,230],{"class":214},[200,50383,50384,50386,50388,50390],{"class":202,"line":336},[200,50385,39438],{"class":206},[200,50387,39441],{"class":223},[200,50389,227],{"class":235},[200,50391,230],{"class":214},[200,50393,50394,50396,50398,50400],{"class":202,"line":342},[200,50395,39450],{"class":235},[200,50397,239],{"class":214},[200,50399,39813],{"class":2045},[200,50401,252],{"class":214},[200,50403,50404,50406,50408,50410,50412,50414],{"class":202,"line":365},[200,50405,39471],{"class":235},[200,50407,239],{"class":214},[200,50409,242],{"class":214},[200,50411,39486],{"class":245},[200,50413,249],{"class":214},[200,50415,252],{"class":214},[200,50417,50418,50420],{"class":202,"line":371},[200,50419,2096],{"class":214},[200,50421,416],{"class":235},[200,50423,50424],{"class":202,"line":398},[200,50425,4490],{"class":214},[200,50427,50428],{"class":202,"line":405},[200,50429,529],{"emptyLinePlaceholder":20},[200,50431,50432,50434,50436,50438],{"class":202,"line":410},[200,50433,3743],{"class":206},[200,50435,39376],{"class":210},[200,50437,14674],{"class":206},[200,50439,39515],{"class":1978},[200,50441,50442],{"class":202,"line":861},[200,50443,1130],{"class":214},[35,50445,50446],{},"This function checks if the user is authenticated and has the role of an admin. If not, it throws an error with a 401 status code. This function is used in the API endpoints to secure them:",[192,50448,50450],{"className":1370,"code":50449,"language":1373,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  await requireAdminUser(event)\n\n  \u002F\u002F Code to handle the request\n})\n",[156,50451,50452,50474,50486,50490,50495],{"__ignoreMap":13},[200,50453,50454,50456,50458,50460,50462,50464,50466,50468,50470,50472],{"class":202,"line":203},[200,50455,1380],{"class":206},[200,50457,1383],{"class":206},[200,50459,38960],{"class":223},[200,50461,227],{"class":210},[200,50463,3711],{"class":379},[200,50465,1095],{"class":214},[200,50467,25386],{"class":386},[200,50469,2089],{"class":214},[200,50471,393],{"class":379},[200,50473,280],{"class":214},[200,50475,50476,50478,50480,50482,50484],{"class":202,"line":14},[200,50477,6165],{"class":206},[200,50479,50300],{"class":223},[200,50481,227],{"class":235},[200,50483,25386],{"class":210},[200,50485,416],{"class":235},[200,50487,50488],{"class":202,"line":255},[200,50489,529],{"emptyLinePlaceholder":20},[200,50491,50492],{"class":202,"line":272},[200,50493,50494],{"class":401},"  \u002F\u002F Code to handle the request\n",[200,50496,50497,50499],{"class":202,"line":283},[200,50498,413],{"class":214},[200,50500,416],{"class":210},[35,50502,50503],{},"With these two functions, the frontend and the backend of the admin part are secured. Only authenticated users with the role of admin can access the admin part of the application.",[1913,50505,50507],{"id":50506},"coming","Coming",[35,50509,50510],{},"This stream was really good and set important things for the future. The next stream will be about the modules and the theme creator. I really hope to be able to push in production soon. I'm really excited about it and to see the feedback from the community.",[35,50512,46318,50513],{},[1582,50514,46321],{"repo":38655},[51,50516,50518],{"id":50517},"fetch-an-external-api-for-the-modules","Fetch an external API for the modules",[35,50520,50521],{},[1690,50522],{"alt":50523,"src":50524},"Thumbnail of my fifth live stream.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-18.png",[35,50526,50527],{},[1217,50528,50529,46057,50532,46062,50535,46066,50538,215],{},[39,50530,38639],{"href":46025,"rel":50531},[77],[39,50533,46061],{"href":18567,"rel":50534},[77],[39,50536,19412],{"href":46016,"rel":50537},[77],[39,50539,11622],{"href":46025,"rel":50540},[77],[35,50542,50543,50544,50546,50547,50549,50550,50552,50553,50555],{},"Before going further, and implementing the modules page, I decided to change the ",[156,50545,33403],{}," behavior from current Categories and Users page. Actually, the ",[156,50548,33403],{}," composable block the client-side navigation until the data is fetched. This gives the sensation of a slow application. To avoid this, I can use the flag ",[156,50551,9895],{},". On server-side, this does not change anything and the page will still be served with data. On the client-side, the navigation will be instant and I will show a skeleton while the data is fetching using the ",[156,50554,48199],{}," ref.",[192,50557,50559],{"className":1370,"code":50558,"language":1373,"meta":13,"style":13},"const { data, pending } = await useFetch('\u002Fapi\u002Fcategories', {\n  deep: false,\n  lazy: true, \u002F\u002F \u003C= Do not block the navigation\n  default: () => []\n})\n",[156,50560,50561,50593,50603,50617,50630],{"__ignoreMap":13},[200,50562,50563,50565,50567,50569,50571,50573,50575,50577,50579,50581,50583,50585,50587,50589,50591],{"class":202,"line":203},[200,50564,443],{"class":379},[200,50566,318],{"class":214},[200,50568,5041],{"class":210},[200,50570,550],{"class":214},[200,50572,48096],{"class":210},[200,50574,413],{"class":214},[200,50576,510],{"class":214},[200,50578,452],{"class":206},[200,50580,48105],{"class":223},[200,50582,227],{"class":210},[200,50584,249],{"class":214},[200,50586,48112],{"class":245},[200,50588,249],{"class":214},[200,50590,550],{"class":214},[200,50592,280],{"class":214},[200,50594,50595,50597,50599,50601],{"class":202,"line":14},[200,50596,48123],{"class":235},[200,50598,239],{"class":214},[200,50600,16166],{"class":2423},[200,50602,252],{"class":214},[200,50604,50605,50608,50610,50612,50614],{"class":202,"line":255},[200,50606,50607],{"class":235},"  lazy",[200,50609,239],{"class":214},[200,50611,2424],{"class":2423},[200,50613,550],{"class":214},[200,50615,50616],{"class":401}," \u002F\u002F \u003C= Do not block the navigation\n",[200,50618,50619,50622,50624,50626,50628],{"class":202,"line":272},[200,50620,50621],{"class":223},"  default",[200,50623,239],{"class":214},[200,50625,3714],{"class":214},[200,50627,393],{"class":379},[200,50629,6421],{"class":210},[200,50631,50632,50634],{"class":202,"line":283},[200,50633,413],{"class":214},[200,50635,416],{"class":210},[35,50637,419,50638,50640,50641,50643,50644,50646,50647,50649],{},[156,50639,48199],{}," ref is a boolean that tells if the data is fetching or not. I simply pass it to the ",[156,50642,9886],{}," props of the ",[156,50645,48162],{}," component from Nuxt UI Pro and ",[1217,50648,45955],{},"! The user will see a loader while the data is fetching and the navigation will be instant.",[11386,50651,132,50654,132,50663],{"flex":50652,"md":50653,"gap-x-2":13},"~ col","flex-row",[130,50655,136,50656,136,50660,132],{},[1690,50657],{"src":50658,"alt":50659,"loading":9895},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fblocking-fetch.gif","Without flag",[143,50661,50662],{},"Without lazy flag (blocking navigation)",[130,50664,136,50665,136,50669,132],{},[1690,50666],{"src":50667,"alt":50668,"loading":9895},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Flazy-fetch.gif","With lazy flag",[143,50670,50671],{},"With lazy flag (instant navigation)",[7139,50673,50676,50680,50687,50694,50698,50709,50712,50716,50729,50738,50809,50823,50941,50951,50960,51181,51188,51191,51229,51242,51259,51262,51503,51509,51514,51517,51521,51528,51683,51686,51689,51692,51698,51701,51704,51708,51713,51716,51728,51731,51742,51745,51751,51767,51770,51801,51804,52210,52224,52231,52876,52879,52883,52889,52905,52908,52912,52917,52920,53397,53406,53424,53435,53438,53547,53551,53554,53561,54007,54023,54032,54036,54041,54051,54057,54226,54229,54252,54258,54264,55560,55566,55761,55764,55891,55897,56011,56017,56034,56405,56421,56424,56427,56430,56434,56440,56456,56459,56470,56476,56480,56505,56508,56551,56569,56592,56595,56817,56823,57038,57041,57045,57048,57051,57105,57112,57122,57135,57325,57334,57338,57343,57348,57355,57379,57382,57388,57401,57485,57488,57494,57497,57541,57547,57550,57553,57738,57741,57745,57748,57751,57755,57760,57763,57769,57777,57781,57784,57787,57839,57849,57856,57877,57880,57945,57954,57958,57967,58046,58050,58053,58331,58334,58337,58634,58637,58643,58646,58649,58653,58659,58675,58678,58681,58684,58686,58692,58698,58707,59034,59038,59041,59045,59052,59224,59228,59235,59379,59382,59386,59389,59710,59715,59719,59724,59740,59752,59755,59884,59891,59905,59914,60202,60211,60214,60358,60364,60367,60370],{"icon":7141,"text":50674,"to":50675,"target":7144},"Learn More about useFetch","https:\u002F\u002Fyoutu.be\u002Fb1S5os65Urs",[1913,50677,50679],{"id":50678},"modules-page","Modules page",[35,50681,50682,50683,50686],{},"Modules are ",[39,50684,2454],{"href":2452,"rel":50685},[77],". They will be linked to the themes and will be used to filter the themes. Imagine you're looking for a theme with good SEO and precisely a sitemap, you'll be able to filter the themes by the modules they use. This is the goal of the modules feature. It will also help the user to understand how the theme is built and what are the features.",[35,50688,50689,50690,50693],{},"The first thing I do is adding a new page ",[156,50691,50692],{},"pages\u002Fadmin\u002Fmodules.vue",". Then, I added the table to display the modules and page meta to use the admin layout and admin middleware. It's similar to the categories and users pages so I will not detail it here.",[38677,50695,50697],{"id":50696},"filling-the-database","Filling the database",[35,50699,50700,50701,50708],{},"The main question is: how to fill the database with modules? For the categories, I create them manually. This is okay because there are not a lot of categories and no way to get a predefined list, but for the modules, it's different. Modules are made by the core team or the community and there are a lot of them. Also, the list is not static and can change over time. So I need to find an API to fetch modules from. Fortunately, Nuxt has an API to get the modules list: ",[39,50702,50705],{"href":50703,"rel":50704},"https:\u002F\u002Fapi.nuxt.com\u002Fmodules",[77],[156,50706,50707],{},"api.nuxt.com\u002Fmodules",". This API is not documented but it's public and I can use it.",[35,50710,50711],{},"Thanks to this API, I will be able to fetch data, sanitize it to keep only the useful information, and store it in the database. I will create an endpoint, accessible only for the admin, to fetch the modules and store them in the database. A second endpoint will be available to retrieve the modules from the database. This will be useful for the frontend to display the modules.",[38677,50713,50715],{"id":50714},"fetching-the-external-api","Fetching the external API",[35,50717,50718,50719,1095,50722,50728],{},"I create an endpoint ",[156,50720,50721],{},"server\u002Fapi\u002Fmodules\u002Ffetch.post.ts",[1217,50723,50724,50725,50727],{},"off stream, I found that calling this endpoint ",[156,50726,24434],{}," was a better idea",") to fetch the modules from the external API and store them in the database.",[35,50730,50731,50732,50734,50735,50737],{},"First, I add the utility ",[156,50733,50234],{}," to secure the endpoint. Then, I fetch the modules from the external API using the global ",[156,50736,33406],{}," function:",[192,50739,50741],{"className":1370,"code":50740,"language":1373,"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,50742,50743,50765,50777,50781,50803],{"__ignoreMap":13},[200,50744,50745,50747,50749,50751,50753,50755,50757,50759,50761,50763],{"class":202,"line":203},[200,50746,1380],{"class":206},[200,50748,1383],{"class":206},[200,50750,38960],{"class":223},[200,50752,227],{"class":210},[200,50754,3711],{"class":379},[200,50756,1095],{"class":214},[200,50758,25386],{"class":386},[200,50760,2089],{"class":214},[200,50762,393],{"class":379},[200,50764,280],{"class":214},[200,50766,50767,50769,50771,50773,50775],{"class":202,"line":14},[200,50768,6165],{"class":206},[200,50770,50300],{"class":223},[200,50772,227],{"class":235},[200,50774,25386],{"class":210},[200,50776,416],{"class":235},[200,50778,50779],{"class":202,"line":255},[200,50780,529],{"emptyLinePlaceholder":20},[200,50782,50783,50785,50787,50789,50791,50793,50795,50797,50799,50801],{"class":202,"line":272},[200,50784,3723],{"class":379},[200,50786,5041],{"class":210},[200,50788,510],{"class":214},[200,50790,452],{"class":206},[200,50792,18741],{"class":223},[200,50794,227],{"class":235},[200,50796,249],{"class":214},[200,50798,50703],{"class":245},[200,50800,249],{"class":214},[200,50802,416],{"class":235},[200,50804,50805,50807],{"class":202,"line":283},[200,50806,413],{"class":214},[200,50808,416],{"class":210},[35,50810,50811,50812,3076,50814,3076,50817,3080,50819,50822],{},"The data returned by the API contains a lot of data that I don't need. I only need the ",[156,50813,20089],{},[156,50815,50816],{},"repo",[156,50818,3771],{},[156,50820,50821],{},"icon"," of the modules. So I sanitize the data to keep only the useful information:",[192,50824,50826],{"className":1370,"code":50825,"language":1373,"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,50827,50828,50844,50898],{"__ignoreMap":13},[200,50829,50830,50832,50835,50837,50839,50841],{"class":202,"line":203},[200,50831,443],{"class":379},[200,50833,50834],{"class":210}," modules ",[200,50836,449],{"class":214},[200,50838,5041],{"class":210},[200,50840,215],{"class":214},[200,50842,50843],{"class":210},"modules\n",[200,50845,50846,50849,50851,50853,50855,50857,50859,50861,50863,50866,50868,50871,50873,50875,50877,50879,50881,50883,50885,50887,50889,50891,50894,50896],{"class":202,"line":14},[200,50847,50848],{"class":214},"  .",[200,50850,3526],{"class":223},[200,50852,227],{"class":210},[200,50854,3951],{"class":214},[200,50856,701],{"class":386},[200,50858,550],{"class":214},[200,50860,321],{"class":386},[200,50862,550],{"class":214},[200,50864,50865],{"class":386}," icon",[200,50867,550],{"class":214},[200,50869,50870],{"class":386}," repo",[200,50872,390],{"class":214},[200,50874,393],{"class":379},[200,50876,1095],{"class":210},[200,50878,2752],{"class":214},[200,50880,701],{"class":210},[200,50882,550],{"class":214},[200,50884,321],{"class":210},[200,50886,550],{"class":214},[200,50888,50865],{"class":210},[200,50890,550],{"class":214},[200,50892,50893],{"class":210}," repo ",[200,50895,413],{"class":214},[200,50897,585],{"class":210},[200,50899,50900,50902,50904,50906,50908,50910,50912,50914,50917,50919,50921,50924,50926,50928,50930,50932,50934,50937,50939],{"class":202,"line":255},[200,50901,50848],{"class":214},[200,50903,3758],{"class":223},[200,50905,227],{"class":210},[200,50907,3951],{"class":214},[200,50909,321],{"class":386},[200,50911,390],{"class":214},[200,50913,393],{"class":379},[200,50915,50916],{"class":210}," type ",[200,50918,6546],{"class":214},[200,50920,242],{"class":214},[200,50922,50923],{"class":245},"official",[200,50925,249],{"class":214},[200,50927,15009],{"class":214},[200,50929,50916],{"class":210},[200,50931,6546],{"class":214},[200,50933,242],{"class":214},[200,50935,50936],{"class":245},"community",[200,50938,249],{"class":214},[200,50940,416],{"class":210},[35,50942,50943,50944,50947,50948,50950],{},"I choose to not keep the type ",[156,50945,50946],{},"third party"," because it can be a module from anybody and I don't know the quality of them. The ",[156,50949,50816],{}," key was not planned to have it but I think it's a good idea to have it. I could use it to add a link to the GitHub repository of the module on a theme page.",[35,50952,50953,50954,50957,50958,239],{},"Now I have my modules, I need to store them in the database, and I also need to create the table to store them. In the file ",[156,50955,50956],{},"server\u002Fdatabase\u002Fschema.ts",", I create the table ",[156,50959,2461],{},[192,50961,50963],{"className":1370,"code":50962,"language":1373,"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,50964,50965,50990,51031,51066,51099,51155,51175],{"__ignoreMap":13},[200,50966,50967,50969,50971,50973,50975,50978,50980,50982,50984,50986,50988],{"class":202,"line":203},[200,50968,1380],{"class":206},[200,50970,14301],{"class":379},[200,50972,50834],{"class":210},[200,50974,449],{"class":214},[200,50976,50977],{"class":223}," sqliteTable",[200,50979,227],{"class":210},[200,50981,249],{"class":214},[200,50983,2461],{"class":245},[200,50985,249],{"class":214},[200,50987,550],{"class":214},[200,50989,280],{"class":214},[200,50991,50992,50994,50996,50999,51001,51003,51005,51007,51009,51011,51014,51016,51018,51021,51023,51025,51027,51029],{"class":202,"line":14},[200,50993,49799],{"class":235},[200,50995,239],{"class":214},[200,50997,50998],{"class":223}," integer",[200,51000,227],{"class":210},[200,51002,249],{"class":214},[200,51004,3280],{"class":245},[200,51006,249],{"class":214},[200,51008,2089],{"class":210},[200,51010,215],{"class":214},[200,51012,51013],{"class":223},"primaryKey",[200,51015,227],{"class":210},[200,51017,2752],{"class":214},[200,51019,51020],{"class":235}," autoIncrement",[200,51022,239],{"class":214},[200,51024,2424],{"class":2423},[200,51026,981],{"class":214},[200,51028,2089],{"class":210},[200,51030,252],{"class":214},[200,51032,51033,51035,51037,51040,51042,51044,51046,51048,51050,51052,51055,51057,51059,51062,51064],{"class":202,"line":255},[200,51034,236],{"class":235},[200,51036,239],{"class":214},[200,51038,51039],{"class":223}," text",[200,51041,227],{"class":210},[200,51043,249],{"class":214},[200,51045,20089],{"class":245},[200,51047,249],{"class":214},[200,51049,2089],{"class":210},[200,51051,215],{"class":214},[200,51053,51054],{"class":223},"notNull",[200,51056,1532],{"class":210},[200,51058,215],{"class":214},[200,51060,51061],{"class":223},"unique",[200,51063,1532],{"class":210},[200,51065,252],{"class":214},[200,51067,51068,51071,51073,51075,51077,51079,51081,51083,51085,51087,51089,51091,51093,51095,51097],{"class":202,"line":272},[200,51069,51070],{"class":235},"  repo",[200,51072,239],{"class":214},[200,51074,51039],{"class":223},[200,51076,227],{"class":210},[200,51078,249],{"class":214},[200,51080,50816],{"class":245},[200,51082,249],{"class":214},[200,51084,2089],{"class":210},[200,51086,215],{"class":214},[200,51088,51054],{"class":223},[200,51090,1532],{"class":210},[200,51092,215],{"class":214},[200,51094,51061],{"class":223},[200,51096,1532],{"class":210},[200,51098,252],{"class":214},[200,51100,51101,51104,51106,51108,51110,51112,51114,51116,51118,51120,51123,51125,51127,51129,51131,51133,51135,51137,51139,51141,51143,51145,51147,51149,51151,51153],{"class":202,"line":283},[200,51102,51103],{"class":235},"  type",[200,51105,239],{"class":214},[200,51107,51039],{"class":223},[200,51109,227],{"class":210},[200,51111,249],{"class":214},[200,51113,3771],{"class":245},[200,51115,249],{"class":214},[200,51117,550],{"class":214},[200,51119,318],{"class":214},[200,51121,51122],{"class":235}," enum",[200,51124,239],{"class":214},[200,51126,350],{"class":210},[200,51128,249],{"class":214},[200,51130,50923],{"class":245},[200,51132,249],{"class":214},[200,51134,550],{"class":214},[200,51136,242],{"class":214},[200,51138,50936],{"class":245},[200,51140,249],{"class":214},[200,51142,18836],{"class":210},[200,51144,413],{"class":214},[200,51146,2089],{"class":210},[200,51148,215],{"class":214},[200,51150,51054],{"class":223},[200,51152,1532],{"class":210},[200,51154,252],{"class":214},[200,51156,51157,51159,51161,51163,51165,51167,51169,51171,51173],{"class":202,"line":300},[200,51158,49828],{"class":235},[200,51160,239],{"class":214},[200,51162,51039],{"class":223},[200,51164,227],{"class":210},[200,51166,249],{"class":214},[200,51168,50821],{"class":245},[200,51170,249],{"class":214},[200,51172,2089],{"class":210},[200,51174,252],{"class":214},[200,51176,51177,51179],{"class":202,"line":310},[200,51178,413],{"class":214},[200,51180,416],{"class":210},[939,51182,51183],{},[35,51184,51185,51187],{},[200,51186,945],{},"\nDo not forget to run the drizzle command to generate the SQL migration.",[35,51189,51190],{},"In the endpoint, it's now possible to insert the modules into the database:",[192,51192,51194],{"className":1370,"code":51193,"language":1373,"meta":13,"style":13},"await useDrizzle().insert(tables.modules).values(modules).execute()\n",[156,51195,51196],{"__ignoreMap":13},[200,51197,51198,51200,51202,51204,51206,51208,51211,51213,51216,51218,51220,51223,51225,51227],{"class":202,"line":203},[200,51199,207],{"class":206},[200,51201,47508],{"class":223},[200,51203,1532],{"class":210},[200,51205,215],{"class":214},[200,51207,47696],{"class":223},[200,51209,51210],{"class":210},"(tables",[200,51212,215],{"class":214},[200,51214,51215],{"class":210},"modules)",[200,51217,215],{"class":214},[200,51219,47711],{"class":223},[200,51221,51222],{"class":210},"(modules)",[200,51224,215],{"class":214},[200,51226,47761],{"class":223},[200,51228,466],{"class":210},[35,51230,51231,51232,51235,51236,51241],{},"BUT, surprise, it ",[6850,51233,51234],{},"does not work",". I did not know, but the ",[39,51237,51240],{"href":51238,"rel":51239},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fd1\u002Fplatform\u002Flimits\u002F",[77],"Cloudflare D1 has a (several) limit"," on the bound parameters at 100. In reality, this limit exists because they use a SQLite under the hood which has this limit for security reasons.",[35,51243,51244,51245,17885,51248,51250,51251,51254,51255,51258],{},"What is a bound parameter? It's a parameter in a SQL query that is replaced by a value when the query is executed. For example, in the query ",[156,51246,51247],{},"SELECT * FROM users WHERE id = ?",[156,51249,28004],{}," is a bound parameter. In the case of the insert query ",[156,51252,51253],{},"INSERT INTO users (name, email) VALUES (?, ?)",", the bound parameters are the values to insert in the table. The limit is 100 bound parameters. Warning, ",[6850,51256,51257],{},"a row is not a bound parameter",", a bound parameter is a single value, meaning that if you insert a row with 10 columns, it will count as 10 bound parameters. There are 42 modules with 4 columns, so 168 bound parameters.",[35,51260,51261],{},"This is a real problem because I'll have to split the insert into multiple queries and a worker is time limited, so if it runs too long, the worker could fail.",[192,51263,51265],{"className":1370,"code":51264,"language":1373,"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,51266,51267,51293,51320,51349,51353,51390,51434,51499],{"__ignoreMap":13},[200,51268,51269,51271,51274,51276,51278,51280,51282,51285,51287,51289,51291],{"class":202,"line":203},[200,51270,443],{"class":379},[200,51272,51273],{"class":210}," columnsPerModule ",[200,51275,449],{"class":214},[200,51277,6448],{"class":210},[200,51279,215],{"class":214},[200,51281,27398],{"class":223},[200,51283,51284],{"class":210},"(modules[",[200,51286,3933],{"class":2045},[200,51288,32457],{"class":210},[200,51290,215],{"class":214},[200,51292,13220],{"class":210},[200,51294,51295,51297,51300,51302,51305,51307,51310,51312,51315,51317],{"class":202,"line":14},[200,51296,443],{"class":379},[200,51298,51299],{"class":210}," insertPerLoop ",[200,51301,449],{"class":214},[200,51303,51304],{"class":210}," Math",[200,51306,215],{"class":214},[200,51308,51309],{"class":223},"floor",[200,51311,227],{"class":210},[200,51313,51314],{"class":2045},"100",[200,51316,11945],{"class":214},[200,51318,51319],{"class":210}," columnsPerModule)\n",[200,51321,51322,51324,51327,51329,51331,51333,51336,51339,51341,51344,51346],{"class":202,"line":255},[200,51323,443],{"class":379},[200,51325,51326],{"class":210}," loops ",[200,51328,449],{"class":214},[200,51330,51304],{"class":210},[200,51332,215],{"class":214},[200,51334,51335],{"class":223},"ceil",[200,51337,51338],{"class":210},"(modules",[200,51340,215],{"class":214},[200,51342,51343],{"class":210},"length ",[200,51345,2882],{"class":214},[200,51347,51348],{"class":210}," insertPerLoop)\n",[200,51350,51351],{"class":202,"line":272},[200,51352,529],{"emptyLinePlaceholder":20},[200,51354,51355,51357,51359,51361,51364,51366,51369,51371,51373,51375,51378,51380,51383,51386,51388],{"class":202,"line":283},[200,51356,1090],{"class":206},[200,51358,1095],{"class":210},[200,51360,12327],{"class":379},[200,51362,51363],{"class":210}," loop ",[200,51365,449],{"class":214},[200,51367,51368],{"class":2045}," 0",[200,51370,5496],{"class":214},[200,51372,51363],{"class":210},[200,51374,600],{"class":214},[200,51376,51377],{"class":210}," loops",[200,51379,5496],{"class":214},[200,51381,51382],{"class":210}," loop",[200,51384,51385],{"class":214},"++",[200,51387,5267],{"class":210},[200,51389,230],{"class":214},[200,51391,51392,51394,51397,51399,51402,51404,51406,51408,51411,51413,51416,51418,51420,51422,51424,51426,51428,51430,51432],{"class":202,"line":300},[200,51393,3723],{"class":379},[200,51395,51396],{"class":210}," values",[200,51398,510],{"class":214},[200,51400,51401],{"class":210}," modules",[200,51403,215],{"class":214},[200,51405,3928],{"class":223},[200,51407,227],{"class":235},[200,51409,51410],{"class":210},"loop",[200,51412,29919],{"class":214},[200,51414,51415],{"class":210}," insertPerLoop",[200,51417,550],{"class":214},[200,51419,1095],{"class":235},[200,51421,51410],{"class":210},[200,51423,13205],{"class":214},[200,51425,3938],{"class":2045},[200,51427,5267],{"class":235},[200,51429,29471],{"class":214},[200,51431,51415],{"class":210},[200,51433,416],{"class":235},[200,51435,51436,51438,51440,51442,51444,51446,51448,51450,51452,51454,51456,51458,51460,51462,51464,51466,51468,51471,51473,51475,51477,51479,51481,51483,51485,51487,51489,51491,51493,51495,51497],{"class":202,"line":310},[200,51437,6165],{"class":206},[200,51439,47508],{"class":223},[200,51441,1532],{"class":235},[200,51443,215],{"class":214},[200,51445,47696],{"class":223},[200,51447,227],{"class":235},[200,51449,47588],{"class":210},[200,51451,215],{"class":214},[200,51453,2461],{"class":210},[200,51455,2089],{"class":235},[200,51457,215],{"class":214},[200,51459,47711],{"class":223},[200,51461,227],{"class":235},[200,51463,47711],{"class":210},[200,51465,2089],{"class":235},[200,51467,215],{"class":214},[200,51469,51470],{"class":223},"onConflictDoNothing",[200,51472,227],{"class":235},[200,51474,2752],{"class":214},[200,51476,43647],{"class":235},[200,51478,239],{"class":214},[200,51480,47527],{"class":210},[200,51482,215],{"class":214},[200,51484,2461],{"class":210},[200,51486,215],{"class":214},[200,51488,50816],{"class":210},[200,51490,981],{"class":214},[200,51492,2089],{"class":235},[200,51494,215],{"class":214},[200,51496,47761],{"class":223},[200,51498,466],{"class":235},[200,51500,51501],{"class":202,"line":336},[200,51502,1130],{"class":214},[35,51504,51505,51506,51508],{},"In order to have a dynamic insert in case I change the number of columns, I calculate the maximum number of rows I can insert in a single query. Then, I loop over the modules and insert the maximum number of rows in each query. I also use the ",[156,51507,51470],{}," method to avoid inserting the same module twice if I run the endpoint multiple times.",[35,51510,51511],{},[1217,51512,51513],{},"I tried this in a production environment, and everything is working fine.",[35,51515,51516],{},"I choose to return a 204 status code when the modules are fetched. If everything is ok, I can refresh the modules list in the UI to view the new modules.",[38677,51518,51520],{"id":51519},"serving-the-modules","Serving the modules",[35,51522,51523,51524,51527],{},"This part is exactly the same as the categories and users index endpoint. I create an endpoint ",[156,51525,51526],{},"server\u002Fapi\u002Fmodules\u002Findex.get.ts"," to get the modules from the database:",[192,51529,51531],{"className":1370,"code":51530,"language":1373,"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,51532,51533,51551,51573,51591,51609,51627,51646,51666,51670,51677],{"__ignoreMap":13},[200,51534,51535,51537,51539,51541,51543,51545,51547,51549],{"class":202,"line":203},[200,51536,1380],{"class":206},[200,51538,1383],{"class":206},[200,51540,38960],{"class":223},[200,51542,227],{"class":210},[200,51544,3711],{"class":379},[200,51546,3714],{"class":214},[200,51548,393],{"class":379},[200,51550,280],{"class":214},[200,51552,51553,51555,51557,51559,51561,51563,51565,51567,51569,51571],{"class":202,"line":14},[200,51554,3723],{"class":379},[200,51556,51401],{"class":210},[200,51558,510],{"class":214},[200,51560,452],{"class":206},[200,51562,47508],{"class":223},[200,51564,1532],{"class":235},[200,51566,215],{"class":214},[200,51568,770],{"class":223},[200,51570,227],{"class":235},[200,51572,230],{"class":214},[200,51574,51575,51577,51579,51581,51583,51585,51587,51589],{"class":202,"line":255},[200,51576,15858],{"class":235},[200,51578,239],{"class":214},[200,51580,47527],{"class":210},[200,51582,215],{"class":214},[200,51584,2461],{"class":210},[200,51586,215],{"class":214},[200,51588,3280],{"class":210},[200,51590,252],{"class":214},[200,51592,51593,51595,51597,51599,51601,51603,51605,51607],{"class":202,"line":272},[200,51594,11797],{"class":235},[200,51596,239],{"class":214},[200,51598,47527],{"class":210},[200,51600,215],{"class":214},[200,51602,2461],{"class":210},[200,51604,215],{"class":214},[200,51606,20089],{"class":210},[200,51608,252],{"class":214},[200,51610,51611,51613,51615,51617,51619,51621,51623,51625],{"class":202,"line":283},[200,51612,286],{"class":235},[200,51614,239],{"class":214},[200,51616,47527],{"class":210},[200,51618,215],{"class":214},[200,51620,2461],{"class":210},[200,51622,215],{"class":214},[200,51624,3771],{"class":210},[200,51626,252],{"class":214},[200,51628,51629,51632,51634,51636,51638,51640,51642,51644],{"class":202,"line":300},[200,51630,51631],{"class":235},"    icon",[200,51633,239],{"class":214},[200,51635,47527],{"class":210},[200,51637,215],{"class":214},[200,51639,2461],{"class":210},[200,51641,215],{"class":214},[200,51643,50821],{"class":210},[200,51645,252],{"class":214},[200,51647,51648,51650,51652,51654,51656,51658,51660,51662,51664],{"class":202,"line":310},[200,51649,5080],{"class":214},[200,51651,2089],{"class":235},[200,51653,215],{"class":214},[200,51655,1472],{"class":223},[200,51657,227],{"class":235},[200,51659,47588],{"class":210},[200,51661,215],{"class":214},[200,51663,2461],{"class":210},[200,51665,416],{"class":235},[200,51667,51668],{"class":202,"line":336},[200,51669,529],{"emptyLinePlaceholder":20},[200,51671,51672,51674],{"class":202,"line":342},[200,51673,3743],{"class":206},[200,51675,51676],{"class":210}," modules\n",[200,51678,51679,51681],{"class":202,"line":365},[200,51680,413],{"class":214},[200,51682,416],{"class":210},[35,51684,51685],{},"Nothing new here, I just select the modules from the database and return them.",[1913,51687,30681],{"id":51688},"conclusion-1",[35,51690,51691],{},"All of this stuff gives us this result:",[35,51693,51694],{},[1690,51695],{"alt":51696,"src":51697},"Modules Page","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fmodules-page.png",[35,51699,51700],{},"This stream was different from the others. First, I now have a new mic and the sound is way better than before!",[35,51702,51703],{},"Second, I encountered a problem with the D1 limit (that could result in a need to rethink the project, hopefully not) on the bound parameters. This is a good lesson for me, and I hope it will be useful for you.",[51,51705,51707],{"id":51706},"refactoring-the-user-management","Refactoring the user management",[35,51709,51710],{},[1217,51711,51712],{},"This part was done off-stream because it was not interesting to see and I was not sure of the direction I wanted to take.",[35,51714,51715],{},"While writing a part of this article, I ask myself if storing the user's data was a good idea. Couldn't it be better? This question comes from the way I handle the authentication endpoint.",[35,51717,51718,51719,51724,51725],{},"Actually, when a user logged in, I always write to the database the user's data to be sure to be up-to-date. The main concern is that a write is way more expensive than a read. To find ideas, I read the ",[39,51720,51723],{"href":51721,"rel":51722},"https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-todos-edge",[77],"Atinux's Nuxt todo list project",". In this project, it does not store the user's data. When the user logs in, it only stores the user's ID in the cookie. When the user creates a todo, it simply has to read the user's ID from the cookie to know who is the user. It's very smart and simple. I will do the same. ",[1217,51726,51727],{},"First through.",[35,51729,51730],{},"Let's analyze if this way is possible for the Orion project. To know if it's possible, I need to answer some questions:",[68,51732,51733,51736,51739],{},[71,51734,51735],{},"Can I ban or promote a user without storing the user's data?",[71,51737,51738],{},"Can a user create data without storing the user's data?",[71,51740,51741],{},"Can I show user's data without storing the user's data?",[35,51743,51744],{},"For the first question, it's yes. I can imagine a system where you enter the login of a user, and when the user logs in, I will search in the database if the entry exists and get relevant data. It's not the best way but it's possible.",[35,51746,51747,51748],{},"For the second question, it's yes. Since data are in the cookie, I can easily know who is the user and create data using this information. ",[1217,51749,51750],{},"And that's how it works actually. I do not fetch the user for every request.",[35,51752,51753,51754,2857,51757,51760,51761,51763,51764],{},"For the third question, it's no. Imagine, I want to show the template created by the user 'X'. In our SQL schema, we have a column ",[156,51755,51756],{},"user_id",[156,51758,51759],{},"templates"," table. If I do not store the user's data, I will not be able to know who is the user 'X', associated with the ",[156,51762,51756],{},". I will have to store the user's data in the database. ",[1217,51765,51766],{},"It could be possible with a non-relational database but it's not the case here. We could also imagine a system where the user is saved when he creates a template. It's possible but it will add a lot of complexity. At the end, this answer and the one from the question one just over complicate the system for no real gain.",[35,51768,51769],{},"Now we know that we have to store the users in the database, what can we improve?",[1576,51771,51772,51775,51778,51787,51798],{},[71,51773,51774],{},"Saving the user's data only if it's necessary. For example, I could first search for the user before saving its data when the user logs in.",[71,51776,51777],{},"Saving the user's mail. This will be useful for the feedback system.",[71,51779,51780,51781,51783,51784,215],{},"Saving the user's name and renaming the ",[156,51782,49234],{}," column to ",[156,51785,51786],{},"login",[71,51788,51789,51790,51793,51794,51797],{},"Adding a ",[156,51791,51792],{},"created_at"," and an ",[156,51795,51796],{},"updated_at"," column.",[71,51799,51800],{},"Updating data stored in the cookie.",[35,51802,51803],{},"With all of these points, I also update the UI accordingly. There is nothing difficult here; it's mainly renaming and adding fields in the schema.",[192,51805,51807],{"className":1370,"code":51806,"language":1373,"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,51808,51809,51833,51871,51905,51939,51972,51992,52020,52086,52136,52204],{"__ignoreMap":13},[200,51810,51811,51813,51815,51817,51819,51821,51823,51825,51827,51829,51831],{"class":202,"line":203},[200,51812,1380],{"class":206},[200,51814,14301],{"class":379},[200,51816,49490],{"class":210},[200,51818,449],{"class":214},[200,51820,50977],{"class":223},[200,51822,227],{"class":210},[200,51824,249],{"class":214},[200,51826,47325],{"class":245},[200,51828,249],{"class":214},[200,51830,550],{"class":214},[200,51832,280],{"class":214},[200,51834,51835,51837,51839,51841,51843,51845,51847,51849,51851,51853,51855,51857,51859,51861,51863,51865,51867,51869],{"class":202,"line":14},[200,51836,49799],{"class":235},[200,51838,239],{"class":214},[200,51840,50998],{"class":223},[200,51842,227],{"class":210},[200,51844,249],{"class":214},[200,51846,3280],{"class":245},[200,51848,249],{"class":214},[200,51850,2089],{"class":210},[200,51852,215],{"class":214},[200,51854,51013],{"class":223},[200,51856,227],{"class":210},[200,51858,2752],{"class":214},[200,51860,51020],{"class":235},[200,51862,239],{"class":214},[200,51864,2424],{"class":2423},[200,51866,981],{"class":214},[200,51868,2089],{"class":210},[200,51870,252],{"class":214},[200,51872,51873,51876,51878,51880,51882,51884,51887,51889,51891,51893,51895,51897,51899,51901,51903],{"class":202,"line":255},[200,51874,51875],{"class":235},"  githubId",[200,51877,239],{"class":214},[200,51879,50998],{"class":223},[200,51881,227],{"class":210},[200,51883,249],{"class":214},[200,51885,51886],{"class":245},"github_id",[200,51888,249],{"class":214},[200,51890,2089],{"class":210},[200,51892,215],{"class":214},[200,51894,51054],{"class":223},[200,51896,1532],{"class":210},[200,51898,215],{"class":214},[200,51900,51061],{"class":223},[200,51902,1532],{"class":210},[200,51904,252],{"class":214},[200,51906,51907,51910,51912,51914,51916,51918,51921,51923,51925,51927,51929,51931,51933,51935,51937],{"class":202,"line":272},[200,51908,51909],{"class":235},"  email",[200,51911,239],{"class":214},[200,51913,51039],{"class":223},[200,51915,227],{"class":210},[200,51917,249],{"class":214},[200,51919,51920],{"class":245},"email",[200,51922,249],{"class":214},[200,51924,2089],{"class":210},[200,51926,215],{"class":214},[200,51928,51054],{"class":223},[200,51930,1532],{"class":210},[200,51932,215],{"class":214},[200,51934,51061],{"class":223},[200,51936,1532],{"class":210},[200,51938,252],{"class":214},[200,51940,51941,51944,51946,51948,51950,51952,51954,51956,51958,51960,51962,51964,51966,51968,51970],{"class":202,"line":283},[200,51942,51943],{"class":235},"  login",[200,51945,239],{"class":214},[200,51947,51039],{"class":223},[200,51949,227],{"class":210},[200,51951,249],{"class":214},[200,51953,51786],{"class":245},[200,51955,249],{"class":214},[200,51957,2089],{"class":210},[200,51959,215],{"class":214},[200,51961,51054],{"class":223},[200,51963,1532],{"class":210},[200,51965,215],{"class":214},[200,51967,51061],{"class":223},[200,51969,1532],{"class":210},[200,51971,252],{"class":214},[200,51973,51974,51976,51978,51980,51982,51984,51986,51988,51990],{"class":202,"line":300},[200,51975,236],{"class":235},[200,51977,239],{"class":214},[200,51979,51039],{"class":223},[200,51981,227],{"class":210},[200,51983,249],{"class":214},[200,51985,20089],{"class":245},[200,51987,249],{"class":214},[200,51989,2089],{"class":210},[200,51991,252],{"class":214},[200,51993,51994,51997,51999,52001,52003,52005,52008,52010,52012,52014,52016,52018],{"class":202,"line":310},[200,51995,51996],{"class":235},"  avatarUrl",[200,51998,239],{"class":214},[200,52000,51039],{"class":223},[200,52002,227],{"class":210},[200,52004,249],{"class":214},[200,52006,52007],{"class":245},"avatar_url",[200,52009,249],{"class":214},[200,52011,2089],{"class":210},[200,52013,215],{"class":214},[200,52015,51054],{"class":223},[200,52017,1532],{"class":210},[200,52019,252],{"class":214},[200,52021,52022,52025,52027,52029,52031,52033,52036,52038,52040,52042,52044,52046,52048,52050,52052,52054,52056,52058,52061,52063,52065,52067,52069,52071,52074,52076,52078,52080,52082,52084],{"class":202,"line":336},[200,52023,52024],{"class":235},"  roleType",[200,52026,239],{"class":214},[200,52028,51039],{"class":223},[200,52030,227],{"class":210},[200,52032,249],{"class":214},[200,52034,52035],{"class":245},"role_type",[200,52037,249],{"class":214},[200,52039,550],{"class":214},[200,52041,318],{"class":214},[200,52043,51122],{"class":235},[200,52045,239],{"class":214},[200,52047,350],{"class":210},[200,52049,249],{"class":214},[200,52051,39427],{"class":245},[200,52053,249],{"class":214},[200,52055,550],{"class":214},[200,52057,242],{"class":214},[200,52059,52060],{"class":245},"creator",[200,52062,249],{"class":214},[200,52064,18836],{"class":210},[200,52066,413],{"class":214},[200,52068,2089],{"class":210},[200,52070,215],{"class":214},[200,52072,52073],{"class":223},"default",[200,52075,227],{"class":210},[200,52077,249],{"class":214},[200,52079,52060],{"class":245},[200,52081,249],{"class":214},[200,52083,2089],{"class":210},[200,52085,252],{"class":214},[200,52087,52088,52091,52093,52095,52097,52099,52101,52103,52105,52107,52109,52111,52113,52116,52118,52120,52122,52125,52127,52130,52132,52134],{"class":202,"line":342},[200,52089,52090],{"class":235},"  createdAt",[200,52092,239],{"class":214},[200,52094,51039],{"class":223},[200,52096,227],{"class":210},[200,52098,249],{"class":214},[200,52100,51792],{"class":245},[200,52102,249],{"class":214},[200,52104,2089],{"class":210},[200,52106,215],{"class":214},[200,52108,51054],{"class":223},[200,52110,1532],{"class":210},[200,52112,215],{"class":214},[200,52114,52115],{"class":223},"$defaultFn",[200,52117,227],{"class":210},[200,52119,1532],{"class":214},[200,52121,393],{"class":379},[200,52123,52124],{"class":223}," sql",[200,52126,3318],{"class":214},[200,52128,52129],{"class":245},"(current_timestamp)",[200,52131,3318],{"class":214},[200,52133,2089],{"class":210},[200,52135,252],{"class":214},[200,52137,52138,52141,52143,52145,52147,52149,52151,52153,52155,52157,52159,52161,52163,52165,52167,52169,52171,52173,52175,52177,52179,52181,52183,52186,52188,52190,52192,52194,52196,52198,52200,52202],{"class":202,"line":365},[200,52139,52140],{"class":235},"  updatedAt",[200,52142,239],{"class":214},[200,52144,51039],{"class":223},[200,52146,227],{"class":210},[200,52148,249],{"class":214},[200,52150,51796],{"class":245},[200,52152,249],{"class":214},[200,52154,2089],{"class":210},[200,52156,215],{"class":214},[200,52158,51054],{"class":223},[200,52160,1532],{"class":210},[200,52162,215],{"class":214},[200,52164,52115],{"class":223},[200,52166,227],{"class":210},[200,52168,1532],{"class":214},[200,52170,393],{"class":379},[200,52172,52124],{"class":223},[200,52174,3318],{"class":214},[200,52176,52129],{"class":245},[200,52178,3318],{"class":214},[200,52180,2089],{"class":210},[200,52182,215],{"class":214},[200,52184,52185],{"class":223},"$onUpdateFn",[200,52187,227],{"class":210},[200,52189,1532],{"class":214},[200,52191,393],{"class":379},[200,52193,52124],{"class":223},[200,52195,3318],{"class":214},[200,52197,52129],{"class":245},[200,52199,3318],{"class":214},[200,52201,2089],{"class":210},[200,52203,252],{"class":214},[200,52205,52206,52208],{"class":202,"line":371},[200,52207,413],{"class":214},[200,52209,416],{"class":210},[35,52211,52212,52213,1638,52215,52217,52218,52220,52221,52223],{},"Functions ",[156,52214,52115],{},[156,52216,52185],{}," are used to set the default value of the column. In the case of ",[156,52219,37278],{},", the default value is the current timestamp. In the case of ",[156,52222,37303],{},", the default value is the current timestamp, and the value is updated when the row is updated. It's all automatic and handled by Drizzle.",[35,52225,52226,52227,52230],{},"I also update the ",[156,52228,52229],{},"server\u002Froutes\u002Fauth\u002Fgithub.ts"," to store the user's data only if it's necessary:",[192,52232,52234],{"className":1370,"code":52233,"language":1373,"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     * If the user is not in the database or their data has changed, update the user data.\n     * A write is more expensive than a read, so we only write if necessary.\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     * Set only the necessary data in the 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,52235,52236,52252,52260,52270,52274,52292,52311,52315,52331,52335,52351,52355,52420,52424,52428,52433,52438,52442,52469,52503,52509,52524,52539,52553,52568,52583,52602,52611,52626,52641,52656,52671,52675,52694,52698,52702,52706,52711,52715,52729,52737,52752,52766,52780,52794,52808,52831,52835,52841,52845,52866,52870],{"__ignoreMap":13},[200,52237,52238,52240,52242,52244,52246,52248,52250],{"class":202,"line":203},[200,52239,1380],{"class":206},[200,52241,1383],{"class":206},[200,52243,46128],{"class":210},[200,52245,215],{"class":214},[200,52247,46133],{"class":223},[200,52249,227],{"class":210},[200,52251,230],{"class":214},[200,52253,52254,52256,52258],{"class":202,"line":14},[200,52255,46142],{"class":235},[200,52257,239],{"class":214},[200,52259,280],{"class":214},[200,52261,52262,52264,52266,52268],{"class":202,"line":255},[200,52263,46151],{"class":235},[200,52265,239],{"class":214},[200,52267,2424],{"class":2423},[200,52269,252],{"class":214},[200,52271,52272],{"class":202,"line":272},[200,52273,368],{"class":214},[200,52275,52276,52278,52280,52282,52284,52286,52288,52290],{"class":202,"line":283},[200,52277,3219],{"class":379},[200,52279,46168],{"class":235},[200,52281,227],{"class":214},[200,52283,25386],{"class":386},[200,52285,550],{"class":214},[200,52287,3546],{"class":386},[200,52289,2089],{"class":214},[200,52291,280],{"class":214},[200,52293,52294,52296,52298,52300,52302,52305,52307,52309],{"class":202,"line":300},[200,52295,1999],{"class":379},[200,52297,318],{"class":214},[200,52299,34776],{"class":235},[200,52301,239],{"class":214},[200,52303,52304],{"class":210}," ghUser",[200,52306,981],{"class":214},[200,52308,510],{"class":214},[200,52310,4200],{"class":210},[200,52312,52313],{"class":202,"line":310},[200,52314,529],{"emptyLinePlaceholder":20},[200,52316,52317,52319,52322,52324,52326,52328],{"class":202,"line":336},[200,52318,1999],{"class":379},[200,52320,52321],{"class":210}," githubId",[200,52323,510],{"class":214},[200,52325,52304],{"class":210},[200,52327,215],{"class":214},[200,52329,52330],{"class":210},"id\n",[200,52332,52333],{"class":202,"line":342},[200,52334,529],{"emptyLinePlaceholder":20},[200,52336,52337,52340,52342,52344,52346,52348],{"class":202,"line":365},[200,52338,52339],{"class":379},"    let",[200,52341,34776],{"class":210},[200,52343,239],{"class":214},[200,52345,34877],{"class":1978},[200,52347,40229],{"class":214},[200,52349,52350],{"class":1978}," undefined\n",[200,52352,52353],{"class":202,"line":371},[200,52354,529],{"emptyLinePlaceholder":20},[200,52356,52357,52360,52362,52364,52366,52368,52370,52372,52374,52376,52378,52380,52382,52384,52386,52388,52390,52392,52394,52396,52398,52400,52402,52404,52406,52408,52410,52412,52414,52416,52418],{"class":202,"line":398},[200,52358,52359],{"class":210},"    user",[200,52361,510],{"class":214},[200,52363,452],{"class":206},[200,52365,47508],{"class":223},[200,52367,1532],{"class":235},[200,52369,215],{"class":214},[200,52371,770],{"class":223},[200,52373,1532],{"class":235},[200,52375,215],{"class":214},[200,52377,1472],{"class":223},[200,52379,227],{"class":235},[200,52381,47588],{"class":210},[200,52383,215],{"class":214},[200,52385,47325],{"class":210},[200,52387,2089],{"class":235},[200,52389,215],{"class":214},[200,52391,49030],{"class":223},[200,52393,227],{"class":235},[200,52395,49035],{"class":223},[200,52397,227],{"class":235},[200,52399,47588],{"class":210},[200,52401,215],{"class":214},[200,52403,47325],{"class":210},[200,52405,215],{"class":214},[200,52407,46224],{"class":210},[200,52409,550],{"class":214},[200,52411,52321],{"class":210},[200,52413,6119],{"class":235},[200,52415,215],{"class":214},[200,52417,42948],{"class":223},[200,52419,466],{"class":235},[200,52421,52422],{"class":202,"line":405},[200,52423,529],{"emptyLinePlaceholder":20},[200,52425,52426],{"class":202,"line":410},[200,52427,19618],{"class":401},[200,52429,52430],{"class":202,"line":861},[200,52431,52432],{"class":401},"     * If the user is not in the database or their data has changed, update the user data.\n",[200,52434,52435],{"class":202,"line":890},[200,52436,52437],{"class":401},"     * A write is more expensive than a read, so we only write if necessary.\n",[200,52439,52440],{"class":202,"line":900},[200,52441,19628],{"class":401},[200,52443,52444,52446,52448,52450,52452,52454,52457,52459,52461,52463,52465,52467],{"class":202,"line":905},[200,52445,5252],{"class":206},[200,52447,1095],{"class":235},[200,52449,11837],{"class":214},[200,52451,26808],{"class":210},[200,52453,15009],{"class":214},[200,52455,52456],{"class":223}," userDataChanged",[200,52458,227],{"class":235},[200,52460,26808],{"class":210},[200,52462,550],{"class":214},[200,52464,52304],{"class":210},[200,52466,6466],{"class":235},[200,52468,230],{"class":214},[200,52470,52471,52473,52475,52477,52479,52481,52483,52485,52487,52489,52491,52493,52495,52497,52499,52501],{"class":202,"line":931},[200,52472,46206],{"class":210},[200,52474,510],{"class":214},[200,52476,452],{"class":206},[200,52478,47508],{"class":223},[200,52480,1532],{"class":235},[200,52482,215],{"class":214},[200,52484,47696],{"class":223},[200,52486,227],{"class":235},[200,52488,47588],{"class":210},[200,52490,215],{"class":214},[200,52492,47325],{"class":210},[200,52494,2089],{"class":235},[200,52496,215],{"class":214},[200,52498,47711],{"class":223},[200,52500,227],{"class":235},[200,52502,230],{"class":214},[200,52504,52505,52507],{"class":202,"line":3640},[200,52506,46215],{"class":210},[200,52508,252],{"class":214},[200,52510,52511,52514,52516,52518,52520,52522],{"class":202,"line":3645},[200,52512,52513],{"class":235},"        login",[200,52515,239],{"class":214},[200,52517,52304],{"class":210},[200,52519,215],{"class":214},[200,52521,51786],{"class":210},[200,52523,252],{"class":214},[200,52525,52526,52529,52531,52533,52535,52537],{"class":202,"line":4020},[200,52527,52528],{"class":235},"        email",[200,52530,239],{"class":214},[200,52532,52304],{"class":210},[200,52534,215],{"class":214},[200,52536,51920],{"class":210},[200,52538,252],{"class":214},[200,52540,52541,52543,52545,52547,52549,52551],{"class":202,"line":4475},[200,52542,11722],{"class":235},[200,52544,239],{"class":214},[200,52546,52304],{"class":210},[200,52548,215],{"class":214},[200,52550,20089],{"class":210},[200,52552,252],{"class":214},[200,52554,52555,52558,52560,52562,52564,52566],{"class":202,"line":4481},[200,52556,52557],{"class":235},"        avatarUrl",[200,52559,239],{"class":214},[200,52561,52304],{"class":210},[200,52563,215],{"class":214},[200,52565,52007],{"class":210},[200,52567,252],{"class":214},[200,52569,52570,52572,52574,52576,52579,52581],{"class":202,"line":4487},[200,52571,2632],{"class":214},[200,52573,2089],{"class":235},[200,52575,215],{"class":214},[200,52577,52578],{"class":223},"onConflictDoUpdate",[200,52580,227],{"class":235},[200,52582,230],{"class":214},[200,52584,52585,52588,52590,52592,52594,52596,52598,52600],{"class":202,"line":4493},[200,52586,52587],{"class":235},"        target",[200,52589,239],{"class":214},[200,52591,47527],{"class":210},[200,52593,215],{"class":214},[200,52595,47325],{"class":210},[200,52597,215],{"class":214},[200,52599,46224],{"class":210},[200,52601,252],{"class":214},[200,52603,52604,52607,52609],{"class":202,"line":5454},[200,52605,52606],{"class":235},"        set",[200,52608,239],{"class":214},[200,52610,280],{"class":214},[200,52612,52613,52616,52618,52620,52622,52624],{"class":202,"line":5459},[200,52614,52615],{"class":235},"          login",[200,52617,239],{"class":214},[200,52619,52304],{"class":210},[200,52621,215],{"class":214},[200,52623,51786],{"class":210},[200,52625,252],{"class":214},[200,52627,52628,52631,52633,52635,52637,52639],{"class":202,"line":13227},[200,52629,52630],{"class":235},"          email",[200,52632,239],{"class":214},[200,52634,52304],{"class":210},[200,52636,215],{"class":214},[200,52638,51920],{"class":210},[200,52640,252],{"class":214},[200,52642,52643,52646,52648,52650,52652,52654],{"class":202,"line":13253},[200,52644,52645],{"class":235},"          name",[200,52647,239],{"class":214},[200,52649,52304],{"class":210},[200,52651,215],{"class":214},[200,52653,20089],{"class":210},[200,52655,252],{"class":214},[200,52657,52658,52661,52663,52665,52667,52669],{"class":202,"line":13269},[200,52659,52660],{"class":235},"          avatarUrl",[200,52662,239],{"class":214},[200,52664,52304],{"class":210},[200,52666,215],{"class":214},[200,52668,52007],{"class":210},[200,52670,252],{"class":214},[200,52672,52673],{"class":202,"line":13313},[200,52674,4950],{"class":214},[200,52676,52677,52679,52681,52683,52686,52688,52690,52692],{"class":202,"line":13319},[200,52678,2632],{"class":214},[200,52680,2089],{"class":235},[200,52682,215],{"class":214},[200,52684,52685],{"class":223},"returning",[200,52687,1532],{"class":235},[200,52689,215],{"class":214},[200,52691,42948],{"class":223},[200,52693,466],{"class":235},[200,52695,52696],{"class":202,"line":13324},[200,52697,4962],{"class":214},[200,52699,52700],{"class":202,"line":13350},[200,52701,529],{"emptyLinePlaceholder":20},[200,52703,52704],{"class":202,"line":13355},[200,52705,19618],{"class":401},[200,52707,52708],{"class":202,"line":13375},[200,52709,52710],{"class":401},"     * Set only the necessary data in the session.\n",[200,52712,52713],{"class":202,"line":13395},[200,52714,19628],{"class":401},[200,52716,52717,52719,52721,52723,52725,52727],{"class":202,"line":13400},[200,52718,19216],{"class":206},[200,52720,46193],{"class":223},[200,52722,227],{"class":235},[200,52724,25386],{"class":210},[200,52726,550],{"class":214},[200,52728,280],{"class":214},[200,52730,52731,52733,52735],{"class":202,"line":13408},[200,52732,46206],{"class":235},[200,52734,239],{"class":214},[200,52736,280],{"class":214},[200,52738,52739,52741,52743,52745,52747,52749],{"class":202,"line":13426},[200,52740,12050],{"class":235},[200,52742,239],{"class":214},[200,52744,34776],{"class":210},[200,52746,215],{"class":214},[200,52748,3280],{"class":210},[200,52750,52751],{"class":214},"!,\n",[200,52753,52754,52756,52758,52760,52762,52764],{"class":202,"line":13456},[200,52755,52513],{"class":235},[200,52757,239],{"class":214},[200,52759,52304],{"class":210},[200,52761,215],{"class":214},[200,52763,51786],{"class":210},[200,52765,252],{"class":214},[200,52767,52768,52770,52772,52774,52776,52778],{"class":202,"line":13462},[200,52769,52528],{"class":235},[200,52771,239],{"class":214},[200,52773,52304],{"class":210},[200,52775,215],{"class":214},[200,52777,51920],{"class":210},[200,52779,252],{"class":214},[200,52781,52782,52784,52786,52788,52790,52792],{"class":202,"line":13467},[200,52783,11722],{"class":235},[200,52785,239],{"class":214},[200,52787,52304],{"class":210},[200,52789,215],{"class":214},[200,52791,20089],{"class":210},[200,52793,252],{"class":214},[200,52795,52796,52798,52800,52802,52804,52806],{"class":202,"line":13472},[200,52797,52557],{"class":235},[200,52799,239],{"class":214},[200,52801,52304],{"class":210},[200,52803,215],{"class":214},[200,52805,52007],{"class":210},[200,52807,252],{"class":214},[200,52809,52810,52813,52815,52817,52819,52821,52823,52825,52827,52829],{"class":202,"line":13487},[200,52811,52812],{"class":235},"        roleType",[200,52814,239],{"class":214},[200,52816,34776],{"class":210},[200,52818,215],{"class":214},[200,52820,39420],{"class":210},[200,52822,15009],{"class":214},[200,52824,242],{"class":214},[200,52826,52060],{"class":245},[200,52828,249],{"class":214},[200,52830,252],{"class":214},[200,52832,52833],{"class":202,"line":13498},[200,52834,12083],{"class":214},[200,52836,52837,52839],{"class":202,"line":13503},[200,52838,2096],{"class":214},[200,52840,416],{"class":235},[200,52842,52843],{"class":202,"line":13508},[200,52844,529],{"emptyLinePlaceholder":20},[200,52846,52847,52849,52851,52853,52855,52857,52859,52862,52864],{"class":202,"line":20224},[200,52848,3296],{"class":206},[200,52850,46247],{"class":223},[200,52852,227],{"class":235},[200,52854,25386],{"class":210},[200,52856,550],{"class":214},[200,52858,242],{"class":214},[200,52860,52861],{"class":245},"\u002Fportal",[200,52863,249],{"class":214},[200,52865,416],{"class":235},[200,52867,52868],{"class":202,"line":20246},[200,52869,368],{"class":214},[200,52871,52872,52874],{"class":202,"line":20251},[200,52873,413],{"class":214},[200,52875,416],{"class":210},[35,52877,52878],{},"With this new way to handle the user's data, I reduce the number of writes to the database while still updating the user's data when necessary.",[51,52880,52882],{"id":52881},"creating-a-form-for-templates","Creating a form for templates",[35,52884,52885],{},[1690,52886],{"alt":52887,"src":52888},"Thumbnail of my sixth live stream.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-19.png",[35,52890,52891],{},[1217,52892,52893,46057,52896,46062,52899,46066,52902,215],{},[39,52894,38639],{"href":46025,"rel":52895},[77],[39,52897,46061],{"href":18567,"rel":52898},[77],[39,52900,19412],{"href":46016,"rel":52901},[77],[39,52903,11622],{"href":46025,"rel":52904},[77],[35,52906,52907],{},"Since the beginning of the series, I've been working on the admin interface of the web application. Today, it's time to create the form to add a new template. This form will be used by the creators to add their templates or themes to the platform. As a reminder, Orion is a community-driven collection of templates for your next project, from landing pages to complete web applications.",[1913,52909,52911],{"id":52910},"template-schema","Template schema",[35,52913,52914],{},[1217,52915,52916],{},"Remember that things evolve and the schema is not definitive.",[35,52918,52919],{},"Since the template feature is a big one, I split it into multiple live streams. The first step is to create the simplest but working form possible and adding features over time. This explains why the schema above is not exactly the same as the one we drew in the SQL schema (no modules, no images, and no markdown, for the moment).",[192,52921,52923],{"className":1370,"code":52922,"language":1373,"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,52924,52925,52950,52988,53022,53049,53075,53155,53226,53248,53276,53302,53347,53391],{"__ignoreMap":13},[200,52926,52927,52929,52931,52934,52936,52938,52940,52942,52944,52946,52948],{"class":202,"line":203},[200,52928,1380],{"class":206},[200,52930,14301],{"class":379},[200,52932,52933],{"class":210}," templates ",[200,52935,449],{"class":214},[200,52937,50977],{"class":223},[200,52939,227],{"class":210},[200,52941,249],{"class":214},[200,52943,51759],{"class":245},[200,52945,249],{"class":214},[200,52947,550],{"class":214},[200,52949,280],{"class":214},[200,52951,52952,52954,52956,52958,52960,52962,52964,52966,52968,52970,52972,52974,52976,52978,52980,52982,52984,52986],{"class":202,"line":14},[200,52953,49799],{"class":235},[200,52955,239],{"class":214},[200,52957,50998],{"class":223},[200,52959,227],{"class":210},[200,52961,249],{"class":214},[200,52963,3280],{"class":245},[200,52965,249],{"class":214},[200,52967,2089],{"class":210},[200,52969,215],{"class":214},[200,52971,51013],{"class":223},[200,52973,227],{"class":210},[200,52975,2752],{"class":214},[200,52977,51020],{"class":235},[200,52979,239],{"class":214},[200,52981,2424],{"class":2423},[200,52983,981],{"class":214},[200,52985,2089],{"class":210},[200,52987,252],{"class":214},[200,52989,52990,52993,52995,52997,52999,53001,53004,53006,53008,53010,53012,53014,53016,53018,53020],{"class":202,"line":255},[200,52991,52992],{"class":235},"  hash",[200,52994,239],{"class":214},[200,52996,51039],{"class":223},[200,52998,227],{"class":210},[200,53000,249],{"class":214},[200,53002,53003],{"class":245},"hash",[200,53005,249],{"class":214},[200,53007,2089],{"class":210},[200,53009,215],{"class":214},[200,53011,51054],{"class":223},[200,53013,1532],{"class":210},[200,53015,215],{"class":214},[200,53017,51061],{"class":223},[200,53019,1532],{"class":210},[200,53021,252],{"class":214},[200,53023,53024,53027,53029,53031,53033,53035,53037,53039,53041,53043,53045,53047],{"class":202,"line":272},[200,53025,53026],{"class":235},"  slug",[200,53028,239],{"class":214},[200,53030,51039],{"class":223},[200,53032,227],{"class":210},[200,53034,249],{"class":214},[200,53036,47555],{"class":245},[200,53038,249],{"class":214},[200,53040,2089],{"class":210},[200,53042,215],{"class":214},[200,53044,51054],{"class":223},[200,53046,1532],{"class":210},[200,53048,252],{"class":214},[200,53050,53051,53053,53055,53057,53059,53061,53063,53065,53067,53069,53071,53073],{"class":202,"line":283},[200,53052,23659],{"class":235},[200,53054,239],{"class":214},[200,53056,51039],{"class":223},[200,53058,227],{"class":210},[200,53060,249],{"class":214},[200,53062,3566],{"class":245},[200,53064,249],{"class":214},[200,53066,2089],{"class":210},[200,53068,215],{"class":214},[200,53070,51054],{"class":223},[200,53072,1532],{"class":210},[200,53074,252],{"class":214},[200,53076,53077,53080,53082,53084,53086,53088,53090,53092,53094,53096,53098,53100,53102,53104,53107,53109,53111,53113,53116,53118,53120,53122,53125,53127,53129,53131,53133,53135,53137,53139,53141,53143,53145,53147,53149,53151,53153],{"class":202,"line":300},[200,53078,53079],{"class":235},"  status",[200,53081,239],{"class":214},[200,53083,51039],{"class":223},[200,53085,227],{"class":210},[200,53087,249],{"class":214},[200,53089,39751],{"class":245},[200,53091,249],{"class":214},[200,53093,550],{"class":214},[200,53095,318],{"class":214},[200,53097,51122],{"class":235},[200,53099,239],{"class":214},[200,53101,350],{"class":210},[200,53103,249],{"class":214},[200,53105,53106],{"class":245},"submitted",[200,53108,249],{"class":214},[200,53110,550],{"class":214},[200,53112,242],{"class":214},[200,53114,53115],{"class":245},"refused",[200,53117,249],{"class":214},[200,53119,550],{"class":214},[200,53121,242],{"class":214},[200,53123,53124],{"class":245},"validated",[200,53126,249],{"class":214},[200,53128,18836],{"class":210},[200,53130,413],{"class":214},[200,53132,2089],{"class":210},[200,53134,215],{"class":214},[200,53136,51054],{"class":223},[200,53138,1532],{"class":210},[200,53140,215],{"class":214},[200,53142,52073],{"class":223},[200,53144,227],{"class":210},[200,53146,249],{"class":214},[200,53148,53106],{"class":245},[200,53150,249],{"class":214},[200,53152,2089],{"class":210},[200,53154,252],{"class":214},[200,53156,53157,53160,53162,53164,53166,53168,53171,53173,53175,53177,53179,53181,53183,53185,53187,53189,53191,53193,53196,53198,53200,53202,53204,53206,53208,53210,53212,53214,53216,53218,53220,53222,53224],{"class":202,"line":310},[200,53158,53159],{"class":235},"  paidStatus",[200,53161,239],{"class":214},[200,53163,51039],{"class":223},[200,53165,227],{"class":210},[200,53167,249],{"class":214},[200,53169,53170],{"class":245},"paid_status",[200,53172,249],{"class":214},[200,53174,550],{"class":214},[200,53176,318],{"class":214},[200,53178,51122],{"class":235},[200,53180,239],{"class":214},[200,53182,350],{"class":210},[200,53184,249],{"class":214},[200,53186,38206],{"class":245},[200,53188,249],{"class":214},[200,53190,550],{"class":214},[200,53192,242],{"class":214},[200,53194,53195],{"class":245},"paid",[200,53197,249],{"class":214},[200,53199,18836],{"class":210},[200,53201,413],{"class":214},[200,53203,2089],{"class":210},[200,53205,215],{"class":214},[200,53207,51054],{"class":223},[200,53209,1532],{"class":210},[200,53211,215],{"class":214},[200,53213,52073],{"class":223},[200,53215,227],{"class":210},[200,53217,249],{"class":214},[200,53219,38206],{"class":245},[200,53221,249],{"class":214},[200,53223,2089],{"class":210},[200,53225,252],{"class":214},[200,53227,53228,53231,53233,53235,53237,53239,53242,53244,53246],{"class":202,"line":336},[200,53229,53230],{"class":235},"  liveUrl",[200,53232,239],{"class":214},[200,53234,51039],{"class":223},[200,53236,227],{"class":210},[200,53238,249],{"class":214},[200,53240,53241],{"class":245},"live_url",[200,53243,249],{"class":214},[200,53245,2089],{"class":210},[200,53247,252],{"class":214},[200,53249,53250,53253,53255,53257,53259,53261,53264,53266,53268,53270,53272,53274],{"class":202,"line":342},[200,53251,53252],{"class":235},"  accessUrl",[200,53254,239],{"class":214},[200,53256,51039],{"class":223},[200,53258,227],{"class":210},[200,53260,249],{"class":214},[200,53262,53263],{"class":245},"access_url",[200,53265,249],{"class":214},[200,53267,2089],{"class":210},[200,53269,215],{"class":214},[200,53271,51054],{"class":223},[200,53273,1532],{"class":210},[200,53275,252],{"class":214},[200,53277,53278,53280,53282,53284,53286,53288,53290,53292,53294,53296,53298,53300],{"class":202,"line":365},[200,53279,258],{"class":235},[200,53281,239],{"class":214},[200,53283,51039],{"class":223},[200,53285,227],{"class":210},[200,53287,249],{"class":214},[200,53289,3582],{"class":245},[200,53291,249],{"class":214},[200,53293,2089],{"class":210},[200,53295,215],{"class":214},[200,53297,51054],{"class":223},[200,53299,1532],{"class":210},[200,53301,252],{"class":214},[200,53303,53304,53307,53309,53311,53313,53315,53317,53319,53321,53323,53325,53327,53329,53332,53334,53336,53338,53340,53342,53345],{"class":202,"line":371},[200,53305,53306],{"class":235},"  userId",[200,53308,239],{"class":214},[200,53310,50998],{"class":223},[200,53312,227],{"class":210},[200,53314,249],{"class":214},[200,53316,51756],{"class":245},[200,53318,249],{"class":214},[200,53320,2089],{"class":210},[200,53322,215],{"class":214},[200,53324,51054],{"class":223},[200,53326,1532],{"class":210},[200,53328,215],{"class":214},[200,53330,53331],{"class":223},"references",[200,53333,227],{"class":210},[200,53335,1532],{"class":214},[200,53337,393],{"class":379},[200,53339,49163],{"class":210},[200,53341,215],{"class":214},[200,53343,53344],{"class":210},"id)",[200,53346,252],{"class":214},[200,53348,53349,53352,53354,53356,53358,53360,53363,53365,53367,53369,53371,53373,53375,53377,53379,53381,53383,53385,53387,53389],{"class":202,"line":398},[200,53350,53351],{"class":235},"  categoryId",[200,53353,239],{"class":214},[200,53355,50998],{"class":223},[200,53357,227],{"class":210},[200,53359,249],{"class":214},[200,53361,53362],{"class":245},"category_id",[200,53364,249],{"class":214},[200,53366,2089],{"class":210},[200,53368,215],{"class":214},[200,53370,51054],{"class":223},[200,53372,1532],{"class":210},[200,53374,215],{"class":214},[200,53376,53331],{"class":223},[200,53378,227],{"class":210},[200,53380,1532],{"class":214},[200,53382,393],{"class":379},[200,53384,47501],{"class":210},[200,53386,215],{"class":214},[200,53388,53344],{"class":210},[200,53390,252],{"class":214},[200,53392,53393,53395],{"class":202,"line":405},[200,53394,413],{"class":214},[200,53396,416],{"class":210},[35,53398,419,53399,53401,53402,53405],{},[156,53400,53003],{}," column is a small ID I'll store in the database to create URL like Notion or Dev.to: ",[156,53403,53404],{},"\u003Cslug>-\u003Chash>",". This is helpful to have a readable URL, for both humans and SEO, but because two templates can have the same name, or the name of a template can change, I need to have a unique ID.",[35,53407,53408,53409,53411,53412,53414,53415,53418,53419,53421,53422,19487],{},"For the first time, we create a relation between two tables: a one-to-many between the ",[156,53410,47325],{}," table and the ",[156,53413,51759],{}," table. This relation is used to know who is the creator of the template. The ",[156,53416,53417],{},"userId"," column is a foreign key that references the ",[156,53420,3280],{}," column of the ",[156,53423,47325],{},[35,53425,419,53426,53429,53430,53421,53432,53434],{},[156,53427,53428],{},"categoryId"," column is also a foreign key that references the ",[156,53431,3280],{},[156,53433,47331],{}," table. This relation is used to know in which category the template is.",[35,53436,53437],{},"In order to be able to retrieve templates from a user or a category, I also need to define the relation the other way around. Thanks to Drizzle, it's pretty easy.",[192,53439,53441],{"className":1370,"code":53440,"language":1373,"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,53442,53443,53475,53489,53495,53499,53529,53541],{"__ignoreMap":13},[200,53444,53445,53447,53449,53452,53454,53457,53460,53462,53464,53467,53469,53471,53473],{"class":202,"line":203},[200,53446,1380],{"class":206},[200,53448,14301],{"class":379},[200,53450,53451],{"class":210}," usersRelations ",[200,53453,449],{"class":214},[200,53455,53456],{"class":223}," relations",[200,53458,53459],{"class":210},"(users",[200,53461,550],{"class":214},[200,53463,383],{"class":214},[200,53465,53466],{"class":386}," many",[200,53468,390],{"class":214},[200,53470,393],{"class":379},[200,53472,1095],{"class":210},[200,53474,230],{"class":214},[200,53476,53477,53480,53482,53484,53487],{"class":202,"line":14},[200,53478,53479],{"class":235},"  templates",[200,53481,239],{"class":214},[200,53483,53466],{"class":223},[200,53485,53486],{"class":210},"(templates)",[200,53488,252],{"class":214},[200,53490,53491,53493],{"class":202,"line":255},[200,53492,413],{"class":214},[200,53494,585],{"class":210},[200,53496,53497],{"class":202,"line":272},[200,53498,529],{"emptyLinePlaceholder":20},[200,53500,53501,53503,53505,53508,53510,53512,53515,53517,53519,53521,53523,53525,53527],{"class":202,"line":283},[200,53502,1380],{"class":206},[200,53504,14301],{"class":379},[200,53506,53507],{"class":210}," categoriesRelations ",[200,53509,449],{"class":214},[200,53511,53456],{"class":223},[200,53513,53514],{"class":210},"(categories",[200,53516,550],{"class":214},[200,53518,383],{"class":214},[200,53520,53466],{"class":386},[200,53522,390],{"class":214},[200,53524,393],{"class":379},[200,53526,1095],{"class":210},[200,53528,230],{"class":214},[200,53530,53531,53533,53535,53537,53539],{"class":202,"line":300},[200,53532,53479],{"class":235},[200,53534,239],{"class":214},[200,53536,53466],{"class":223},[200,53538,53486],{"class":210},[200,53540,252],{"class":214},[200,53542,53543,53545],{"class":202,"line":310},[200,53544,413],{"class":214},[200,53546,585],{"class":210},[1913,53548,53550],{"id":53549},"template-form","Template form",[35,53552,53553],{},"During this phase, I create the form on the UI, exactly like I did for the categories. I define the state, the schema, and the action on submit!",[35,53555,53556,53557,53560],{},"Now that a user is able to fill a form, I need an endpoint to save the data in the database. I create the endpoint ",[156,53558,53559],{},"server\u002Fapi\u002Ftemplates\u002Findex.post.ts"," to save the template in the database:",[192,53562,53564],{"className":1370,"code":53563,"language":1373,"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,53565,53566,53601,53605,53627,53649,53653,53677,53689,53718,53731,53751,53764,53787,53799,53803,53833,53847,53867,53881,53895,53910,53925,53940,53954,53969,53981,53985,54001],{"__ignoreMap":13},[200,53567,53568,53570,53572,53574,53576,53578,53580,53582,53584,53586,53588,53591,53593,53595,53597,53599],{"class":202,"line":203},[200,53569,973],{"class":206},[200,53571,318],{"class":214},[200,53573,20132],{"class":210},[200,53575,550],{"class":214},[200,53577,42872],{"class":210},[200,53579,550],{"class":214},[200,53581,22708],{"class":210},[200,53583,550],{"class":214},[200,53585,51122],{"class":210},[200,53587,14674],{"class":206},[200,53589,53590],{"class":210}," zEnum",[200,53592,981],{"class":214},[200,53594,984],{"class":206},[200,53596,242],{"class":214},[200,53598,19790],{"class":245},[200,53600,992],{"class":214},[200,53602,53603],{"class":202,"line":14},[200,53604,529],{"emptyLinePlaceholder":20},[200,53606,53607,53609,53611,53613,53615,53617,53619,53621,53623,53625],{"class":202,"line":255},[200,53608,1380],{"class":206},[200,53610,1383],{"class":206},[200,53612,38960],{"class":223},[200,53614,227],{"class":210},[200,53616,3711],{"class":379},[200,53618,1095],{"class":214},[200,53620,25386],{"class":386},[200,53622,2089],{"class":214},[200,53624,393],{"class":379},[200,53626,280],{"class":214},[200,53628,53629,53631,53633,53635,53637,53639,53641,53643,53645,53647],{"class":202,"line":272},[200,53630,3723],{"class":379},[200,53632,318],{"class":214},[200,53634,34776],{"class":210},[200,53636,981],{"class":214},[200,53638,510],{"class":214},[200,53640,452],{"class":206},[200,53642,39235],{"class":223},[200,53644,227],{"class":235},[200,53646,25386],{"class":210},[200,53648,416],{"class":235},[200,53650,53651],{"class":202,"line":283},[200,53652,529],{"emptyLinePlaceholder":20},[200,53654,53655,53657,53659,53661,53663,53665,53667,53669,53671,53673,53675],{"class":202,"line":300},[200,53656,3723],{"class":379},[200,53658,41038],{"class":210},[200,53660,510],{"class":214},[200,53662,452],{"class":206},[200,53664,41180],{"class":223},[200,53666,227],{"class":235},[200,53668,25386],{"class":210},[200,53670,550],{"class":214},[200,53672,42872],{"class":223},[200,53674,227],{"class":235},[200,53676,230],{"class":214},[200,53678,53679,53681,53683,53685,53687],{"class":202,"line":310},[200,53680,21442],{"class":235},[200,53682,239],{"class":214},[200,53684,22708],{"class":223},[200,53686,1532],{"class":235},[200,53688,252],{"class":214},[200,53690,53691,53694,53696,53698,53700,53702,53704,53706,53708,53710,53712,53714,53716],{"class":202,"line":336},[200,53692,53693],{"class":235},"    paidStatus",[200,53695,239],{"class":214},[200,53697,53590],{"class":223},[200,53699,25183],{"class":235},[200,53701,249],{"class":214},[200,53703,38206],{"class":245},[200,53705,249],{"class":214},[200,53707,550],{"class":214},[200,53709,242],{"class":214},[200,53711,53195],{"class":245},[200,53713,249],{"class":214},[200,53715,32457],{"class":235},[200,53717,252],{"class":214},[200,53719,53720,53723,53725,53727,53729],{"class":202,"line":342},[200,53721,53722],{"class":235},"    categoryId",[200,53724,239],{"class":214},[200,53726,20132],{"class":223},[200,53728,1532],{"class":235},[200,53730,252],{"class":214},[200,53732,53733,53736,53738,53740,53742,53744,53747,53749],{"class":202,"line":365},[200,53734,53735],{"class":235},"    liveUrl",[200,53737,239],{"class":214},[200,53739,22708],{"class":223},[200,53741,1532],{"class":235},[200,53743,215],{"class":214},[200,53745,53746],{"class":223},"optional",[200,53748,1532],{"class":235},[200,53750,252],{"class":214},[200,53752,53753,53756,53758,53760,53762],{"class":202,"line":371},[200,53754,53755],{"class":235},"    accessUrl",[200,53757,239],{"class":214},[200,53759,22708],{"class":223},[200,53761,1532],{"class":235},[200,53763,252],{"class":214},[200,53765,53766,53768,53770,53772,53774,53776,53778,53780,53783,53785],{"class":202,"line":398},[200,53767,3136],{"class":235},[200,53769,239],{"class":214},[200,53771,22708],{"class":223},[200,53773,1532],{"class":235},[200,53775,215],{"class":214},[200,53777,4123],{"class":223},[200,53779,227],{"class":235},[200,53781,53782],{"class":2045},"1000",[200,53784,2089],{"class":235},[200,53786,252],{"class":214},[200,53788,53789,53791,53793,53795,53797],{"class":202,"line":405},[200,53790,5080],{"class":214},[200,53792,2089],{"class":235},[200,53794,215],{"class":214},[200,53796,41223],{"class":210},[200,53798,416],{"class":235},[200,53800,53801],{"class":202,"line":410},[200,53802,529],{"emptyLinePlaceholder":20},[200,53804,53805,53807,53809,53811,53813,53815,53817,53819,53821,53823,53825,53827,53829,53831],{"class":202,"line":861},[200,53806,6165],{"class":206},[200,53808,47508],{"class":223},[200,53810,1532],{"class":235},[200,53812,215],{"class":214},[200,53814,47696],{"class":223},[200,53816,227],{"class":235},[200,53818,47588],{"class":210},[200,53820,215],{"class":214},[200,53822,51759],{"class":210},[200,53824,2089],{"class":235},[200,53826,215],{"class":214},[200,53828,47711],{"class":223},[200,53830,227],{"class":235},[200,53832,230],{"class":214},[200,53834,53835,53838,53840,53843,53845],{"class":202,"line":890},[200,53836,53837],{"class":235},"    hash",[200,53839,239],{"class":214},[200,53841,53842],{"class":223}," useHash",[200,53844,1532],{"class":235},[200,53846,252],{"class":214},[200,53848,53849,53851,53853,53855,53857,53859,53861,53863,53865],{"class":202,"line":900},[200,53850,47542],{"class":235},[200,53852,239],{"class":214},[200,53854,47724],{"class":223},[200,53856,227],{"class":235},[200,53858,23870],{"class":210},[200,53860,215],{"class":214},[200,53862,3566],{"class":210},[200,53864,2089],{"class":235},[200,53866,252],{"class":214},[200,53868,53869,53871,53873,53875,53877,53879],{"class":202,"line":905},[200,53870,21442],{"class":235},[200,53872,239],{"class":214},[200,53874,41038],{"class":210},[200,53876,215],{"class":214},[200,53878,3566],{"class":210},[200,53880,252],{"class":214},[200,53882,53883,53885,53887,53889,53891,53893],{"class":202,"line":931},[200,53884,53722],{"class":235},[200,53886,239],{"class":214},[200,53888,41038],{"class":210},[200,53890,215],{"class":214},[200,53892,53428],{"class":210},[200,53894,252],{"class":214},[200,53896,53897,53899,53901,53903,53905,53908],{"class":202,"line":3640},[200,53898,53693],{"class":235},[200,53900,239],{"class":214},[200,53902,41038],{"class":210},[200,53904,215],{"class":214},[200,53906,53907],{"class":210},"paidStatus",[200,53909,252],{"class":214},[200,53911,53912,53914,53916,53918,53920,53923],{"class":202,"line":3645},[200,53913,53735],{"class":235},[200,53915,239],{"class":214},[200,53917,41038],{"class":210},[200,53919,215],{"class":214},[200,53921,53922],{"class":210},"liveUrl",[200,53924,252],{"class":214},[200,53926,53927,53929,53931,53933,53935,53938],{"class":202,"line":4020},[200,53928,53755],{"class":235},[200,53930,239],{"class":214},[200,53932,41038],{"class":210},[200,53934,215],{"class":214},[200,53936,53937],{"class":210},"accessUrl",[200,53939,252],{"class":214},[200,53941,53942,53944,53946,53948,53950,53952],{"class":202,"line":4475},[200,53943,3136],{"class":235},[200,53945,239],{"class":214},[200,53947,41038],{"class":210},[200,53949,215],{"class":214},[200,53951,3582],{"class":210},[200,53953,252],{"class":214},[200,53955,53956,53959,53961,53963,53965,53967],{"class":202,"line":4481},[200,53957,53958],{"class":235},"    userId",[200,53960,239],{"class":214},[200,53962,34776],{"class":210},[200,53964,215],{"class":214},[200,53966,3280],{"class":210},[200,53968,252],{"class":214},[200,53970,53971,53973,53975,53977,53979],{"class":202,"line":4487},[200,53972,5080],{"class":214},[200,53974,2089],{"class":235},[200,53976,215],{"class":214},[200,53978,47761],{"class":223},[200,53980,466],{"class":235},[200,53982,53983],{"class":202,"line":4493},[200,53984,529],{"emptyLinePlaceholder":20},[200,53986,53987,53989,53991,53993,53995,53997,53999],{"class":202,"line":5454},[200,53988,3743],{"class":206},[200,53990,41071],{"class":223},[200,53992,227],{"class":235},[200,53994,25386],{"class":210},[200,53996,550],{"class":214},[200,53998,41080],{"class":2045},[200,54000,416],{"class":235},[200,54002,54003,54005],{"class":202,"line":5459},[200,54004,413],{"class":214},[200,54006,416],{"class":210},[35,54008,54009,54010,54013,54014,54016,54017,54019,54020,54022],{},"Pretty similar to the endpoint to create a new category. ",[6850,54011,54012],{},"One difference"," is the usage of the util ",[156,54015,39197],{}," to ensure that the request is from an authenticated user and to get the user's data. I use the data to fill the ",[156,54018,53417],{}," column in the ",[156,54021,51759],{}," table. Of course, I validate the data received from the client using Zod.",[35,54024,419,54025,54028,54029,215],{},[156,54026,54027],{},"useHash"," util is a little wrapper around the package ",[156,54030,54031],{},"nanoid",[1913,54033,54035],{"id":54034},"templates-in-admin-interface","Templates in admin interface",[35,54037,54038],{},[1217,54039,54040],{},"During this part, I encountered an important bug. Read carefully the next part, it could save you a lot of time if you also plan to use Drizzle on Cloudflare.",[35,54042,54043,54044,54047,54048],{},"Users are able to create templates, that's nice. In order to monitor and approve them, I need to create a page in the admin interface to manage the templates. This page will display the templates in a table and allow the admin to approve or refuse them. ",[1217,54045,54046],{},"I only implement the view, not the action."," The task is simple, it's like any other page in the admin interface. ",[1217,54049,54050],{},"If only I knew...",[35,54052,54053,54054,239],{},"First, let's create the endpoint to get the templates from the database. I create the endpoint ",[156,54055,54056],{},"server\u002Fapi\u002Ftemplates\u002Findex.get.ts",[192,54058,54060],{"className":1370,"code":54059,"language":1373,"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,54061,54062,54080,54095,54103,54119,54165,54209,54213,54220],{"__ignoreMap":13},[200,54063,54064,54066,54068,54070,54072,54074,54076,54078],{"class":202,"line":203},[200,54065,1380],{"class":206},[200,54067,1383],{"class":206},[200,54069,38960],{"class":223},[200,54071,227],{"class":210},[200,54073,3711],{"class":379},[200,54075,3714],{"class":214},[200,54077,393],{"class":379},[200,54079,280],{"class":214},[200,54081,54082,54084,54087,54089,54091,54093],{"class":202,"line":14},[200,54083,3723],{"class":379},[200,54085,54086],{"class":210}," templates",[200,54088,510],{"class":214},[200,54090,452],{"class":206},[200,54092,47508],{"class":223},[200,54094,466],{"class":235},[200,54096,54097,54099,54101],{"class":202,"line":255},[200,54098,3755],{"class":214},[200,54100,770],{"class":223},[200,54102,466],{"class":235},[200,54104,54105,54107,54109,54111,54113,54115,54117],{"class":202,"line":272},[200,54106,3755],{"class":214},[200,54108,1472],{"class":223},[200,54110,227],{"class":235},[200,54112,47588],{"class":210},[200,54114,215],{"class":214},[200,54116,51759],{"class":210},[200,54118,416],{"class":235},[200,54120,54121,54123,54126,54128,54130,54132,54134,54136,54139,54141,54143,54145,54147,54149,54151,54153,54155,54157,54159,54161,54163],{"class":202,"line":283},[200,54122,3755],{"class":214},[200,54124,54125],{"class":223},"leftJoin",[200,54127,227],{"class":235},[200,54129,47588],{"class":210},[200,54131,215],{"class":214},[200,54133,47325],{"class":210},[200,54135,550],{"class":214},[200,54137,54138],{"class":223}," eq",[200,54140,227],{"class":235},[200,54142,47588],{"class":210},[200,54144,215],{"class":214},[200,54146,51759],{"class":210},[200,54148,215],{"class":214},[200,54150,53417],{"class":210},[200,54152,550],{"class":214},[200,54154,47527],{"class":210},[200,54156,215],{"class":214},[200,54158,47325],{"class":210},[200,54160,215],{"class":214},[200,54162,3280],{"class":210},[200,54164,585],{"class":235},[200,54166,54167,54169,54171,54173,54175,54177,54179,54181,54183,54185,54187,54189,54191,54193,54195,54197,54199,54201,54203,54205,54207],{"class":202,"line":300},[200,54168,3755],{"class":214},[200,54170,54125],{"class":223},[200,54172,227],{"class":235},[200,54174,47588],{"class":210},[200,54176,215],{"class":214},[200,54178,47331],{"class":210},[200,54180,550],{"class":214},[200,54182,54138],{"class":223},[200,54184,227],{"class":235},[200,54186,47588],{"class":210},[200,54188,215],{"class":214},[200,54190,51759],{"class":210},[200,54192,215],{"class":214},[200,54194,53428],{"class":210},[200,54196,550],{"class":214},[200,54198,47527],{"class":210},[200,54200,215],{"class":214},[200,54202,47331],{"class":210},[200,54204,215],{"class":214},[200,54206,3280],{"class":210},[200,54208,585],{"class":235},[200,54210,54211],{"class":202,"line":310},[200,54212,529],{"emptyLinePlaceholder":20},[200,54214,54215,54217],{"class":202,"line":336},[200,54216,3743],{"class":206},[200,54218,54219],{"class":210}," templates\n",[200,54221,54222,54224],{"class":202,"line":342},[200,54223,413],{"class":214},[200,54225,416],{"class":210},[35,54227,54228],{},"This endpoint gets all the templates from the database and populates them using a join and the relation we define earlier in the schema.",[35,54230,47322,54231,54233,54234,54236,54237,53411,54239,54241,54242,54244,54245,54247,54248,13731,54250,19487],{},[156,54232,6190],{}," is a SQL operation that combines rows from two or more tables based on a related column between them. In this case, I join the ",[156,54235,51759],{}," table with the ",[156,54238,47325],{},[156,54240,47331],{}," table. The ",[156,54243,54125],{}," method is used to keep all the rows from the ",[156,54246,51759],{}," table, even if there is no match in the ",[156,54249,47325],{},[156,54251,47331],{},[35,54253,54254],{},[1690,54255],{"alt":54256,"src":54257},"A schematic explanation of the join operation.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fleft-join.png",[35,54259,54260,54261,54263],{},"Now that I have the data, I can display it on the UI. I use the ",[156,54262,48162],{}," component from Nuxt UI Pro to display the templates in a table:",[192,54265,54267],{"className":21417,"code":54266,"language":12060,"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,54268,54269,54289,54301,54315,54329,54335,54349,54364,54374,54380,54394,54409,54419,54425,54439,54454,54464,54470,54484,54499,54505,54519,54534,54540,54554,54569,54575,54590,54605,54611,54626,54641,54647,54651,54693,54703,54713,54727,54733,54741,54745,54753,54760,54774,54788,54802,54806,54831,54837,54854,54867,54879,54899,54913,54918,54935,54939,54958,54966,54986,54994,55019,55025,55037,55049,55067,55079,55083,55100,55104,55122,55130,55138,55163,55193,55201,55216,55230,55236,55242,55251,55260,55290,55297,55311,55324,55329,55335,55344,55353,55362,55388,55408,55415,55430,55445,55460,55465,55483,55492,55501,55527,55533,55542,55551],{"__ignoreMap":13},[200,54270,54271,54273,54275,54277,54279,54281,54283,54285,54287],{"class":202,"line":203},[200,54272,600],{"class":214},[200,54274,14892],{"class":235},[200,54276,14590],{"class":379},[200,54278,449],{"class":214},[200,54280,611],{"class":214},[200,54282,1373],{"class":245},[200,54284,611],{"class":214},[200,54286,21781],{"class":379},[200,54288,648],{"class":214},[200,54290,54291,54293,54295,54297,54299],{"class":202,"line":14},[200,54292,443],{"class":379},[200,54294,47958],{"class":210},[200,54296,449],{"class":214},[200,54298,350],{"class":210},[200,54300,230],{"class":214},[200,54302,54303,54305,54307,54309,54311,54313],{"class":202,"line":255},[200,54304,18962],{"class":235},[200,54306,239],{"class":214},[200,54308,242],{"class":214},[200,54310,3280],{"class":245},[200,54312,249],{"class":214},[200,54314,252],{"class":214},[200,54316,54317,54319,54321,54323,54325,54327],{"class":202,"line":272},[200,54318,47983],{"class":235},[200,54320,239],{"class":214},[200,54322,242],{"class":214},[200,54324,2886],{"class":245},[200,54326,249],{"class":214},[200,54328,252],{"class":214},[200,54330,54331,54333],{"class":202,"line":283},[200,54332,26858],{"class":214},[200,54334,280],{"class":214},[200,54336,54337,54339,54341,54343,54345,54347],{"class":202,"line":300},[200,54338,18962],{"class":235},[200,54340,239],{"class":214},[200,54342,242],{"class":214},[200,54344,3566],{"class":245},[200,54346,249],{"class":214},[200,54348,252],{"class":214},[200,54350,54351,54353,54355,54357,54360,54362],{"class":202,"line":310},[200,54352,47983],{"class":235},[200,54354,239],{"class":214},[200,54356,242],{"class":214},[200,54358,54359],{"class":245},"Title",[200,54361,249],{"class":214},[200,54363,252],{"class":214},[200,54365,54366,54368,54370,54372],{"class":202,"line":336},[200,54367,48030],{"class":235},[200,54369,239],{"class":214},[200,54371,2424],{"class":2423},[200,54373,252],{"class":214},[200,54375,54376,54378],{"class":202,"line":342},[200,54377,26858],{"class":214},[200,54379,280],{"class":214},[200,54381,54382,54384,54386,54388,54390,54392],{"class":202,"line":365},[200,54383,18962],{"class":235},[200,54385,239],{"class":214},[200,54387,242],{"class":214},[200,54389,39751],{"class":245},[200,54391,249],{"class":214},[200,54393,252],{"class":214},[200,54395,54396,54398,54400,54402,54405,54407],{"class":202,"line":371},[200,54397,47983],{"class":235},[200,54399,239],{"class":214},[200,54401,242],{"class":214},[200,54403,54404],{"class":245},"Status",[200,54406,249],{"class":214},[200,54408,252],{"class":214},[200,54410,54411,54413,54415,54417],{"class":202,"line":398},[200,54412,48030],{"class":235},[200,54414,239],{"class":214},[200,54416,2424],{"class":2423},[200,54418,252],{"class":214},[200,54420,54421,54423],{"class":202,"line":405},[200,54422,26858],{"class":214},[200,54424,280],{"class":214},[200,54426,54427,54429,54431,54433,54435,54437],{"class":202,"line":410},[200,54428,18962],{"class":235},[200,54430,239],{"class":214},[200,54432,242],{"class":214},[200,54434,53907],{"class":245},[200,54436,249],{"class":214},[200,54438,252],{"class":214},[200,54440,54441,54443,54445,54447,54450,54452],{"class":202,"line":861},[200,54442,47983],{"class":235},[200,54444,239],{"class":214},[200,54446,242],{"class":214},[200,54448,54449],{"class":245},"Paid Status",[200,54451,249],{"class":214},[200,54453,252],{"class":214},[200,54455,54456,54458,54460,54462],{"class":202,"line":890},[200,54457,48030],{"class":235},[200,54459,239],{"class":214},[200,54461,2424],{"class":2423},[200,54463,252],{"class":214},[200,54465,54466,54468],{"class":202,"line":900},[200,54467,26858],{"class":214},[200,54469,280],{"class":214},[200,54471,54472,54474,54476,54478,54480,54482],{"class":202,"line":905},[200,54473,18962],{"class":235},[200,54475,239],{"class":214},[200,54477,242],{"class":214},[200,54479,53922],{"class":245},[200,54481,249],{"class":214},[200,54483,252],{"class":214},[200,54485,54486,54488,54490,54492,54495,54497],{"class":202,"line":931},[200,54487,47983],{"class":235},[200,54489,239],{"class":214},[200,54491,242],{"class":214},[200,54493,54494],{"class":245},"Live URL",[200,54496,249],{"class":214},[200,54498,252],{"class":214},[200,54500,54501,54503],{"class":202,"line":3640},[200,54502,26858],{"class":214},[200,54504,280],{"class":214},[200,54506,54507,54509,54511,54513,54515,54517],{"class":202,"line":3645},[200,54508,18962],{"class":235},[200,54510,239],{"class":214},[200,54512,242],{"class":214},[200,54514,53937],{"class":245},[200,54516,249],{"class":214},[200,54518,252],{"class":214},[200,54520,54521,54523,54525,54527,54530,54532],{"class":202,"line":4020},[200,54522,47983],{"class":235},[200,54524,239],{"class":214},[200,54526,242],{"class":214},[200,54528,54529],{"class":245},"Access URL",[200,54531,249],{"class":214},[200,54533,252],{"class":214},[200,54535,54536,54538],{"class":202,"line":4475},[200,54537,26858],{"class":214},[200,54539,280],{"class":214},[200,54541,54542,54544,54546,54548,54550,54552],{"class":202,"line":4481},[200,54543,18962],{"class":235},[200,54545,239],{"class":214},[200,54547,242],{"class":214},[200,54549,3582],{"class":245},[200,54551,249],{"class":214},[200,54553,252],{"class":214},[200,54555,54556,54558,54560,54562,54565,54567],{"class":202,"line":4487},[200,54557,47983],{"class":235},[200,54559,239],{"class":214},[200,54561,242],{"class":214},[200,54563,54564],{"class":245},"Description",[200,54566,249],{"class":214},[200,54568,252],{"class":214},[200,54570,54571,54573],{"class":202,"line":4493},[200,54572,26858],{"class":214},[200,54574,280],{"class":214},[200,54576,54577,54579,54581,54583,54586,54588],{"class":202,"line":5454},[200,54578,18962],{"class":235},[200,54580,239],{"class":214},[200,54582,242],{"class":214},[200,54584,54585],{"class":245},"category",[200,54587,249],{"class":214},[200,54589,252],{"class":214},[200,54591,54592,54594,54596,54598,54601,54603],{"class":202,"line":5459},[200,54593,47983],{"class":235},[200,54595,239],{"class":214},[200,54597,242],{"class":214},[200,54599,54600],{"class":245},"Category",[200,54602,249],{"class":214},[200,54604,252],{"class":214},[200,54606,54607,54609],{"class":202,"line":13227},[200,54608,26858],{"class":214},[200,54610,280],{"class":214},[200,54612,54613,54615,54617,54619,54622,54624],{"class":202,"line":13253},[200,54614,18962],{"class":235},[200,54616,239],{"class":214},[200,54618,242],{"class":214},[200,54620,54621],{"class":245},"createdBy",[200,54623,249],{"class":214},[200,54625,252],{"class":214},[200,54627,54628,54630,54632,54634,54637,54639],{"class":202,"line":13269},[200,54629,47983],{"class":235},[200,54631,239],{"class":214},[200,54633,242],{"class":214},[200,54635,54636],{"class":245},"Created By",[200,54638,249],{"class":214},[200,54640,252],{"class":214},[200,54642,54643,54645],{"class":202,"line":13313},[200,54644,413],{"class":214},[200,54646,4496],{"class":210},[200,54648,54649],{"class":202,"line":13319},[200,54650,529],{"emptyLinePlaceholder":20},[200,54652,54653,54655,54657,54659,54661,54663,54665,54668,54670,54672,54674,54676,54678,54680,54682,54684,54687,54689,54691],{"class":202,"line":13324},[200,54654,443],{"class":379},[200,54656,318],{"class":214},[200,54658,5041],{"class":235},[200,54660,239],{"class":214},[200,54662,54086],{"class":210},[200,54664,550],{"class":214},[200,54666,54667],{"class":210}," refresh",[200,54669,550],{"class":214},[200,54671,48096],{"class":210},[200,54673,413],{"class":214},[200,54675,510],{"class":214},[200,54677,452],{"class":206},[200,54679,48105],{"class":223},[200,54681,227],{"class":210},[200,54683,249],{"class":214},[200,54685,54686],{"class":245},"\u002Fapi\u002Ftemplates",[200,54688,249],{"class":214},[200,54690,550],{"class":214},[200,54692,280],{"class":214},[200,54694,54695,54697,54699,54701],{"class":202,"line":13350},[200,54696,48123],{"class":235},[200,54698,239],{"class":214},[200,54700,16166],{"class":2423},[200,54702,252],{"class":214},[200,54704,54705,54707,54709,54711],{"class":202,"line":13355},[200,54706,50607],{"class":235},[200,54708,239],{"class":214},[200,54710,2424],{"class":2423},[200,54712,252],{"class":214},[200,54714,54715,54717,54719,54721,54723,54725],{"class":202,"line":13375},[200,54716,50621],{"class":223},[200,54718,239],{"class":214},[200,54720,3714],{"class":214},[200,54722,393],{"class":379},[200,54724,27960],{"class":210},[200,54726,252],{"class":214},[200,54728,54729,54731],{"class":202,"line":13395},[200,54730,413],{"class":214},[200,54732,416],{"class":210},[200,54734,54735,54737,54739],{"class":202,"line":13400},[200,54736,681],{"class":214},[200,54738,14892],{"class":235},[200,54740,648],{"class":214},[200,54742,54743],{"class":202,"line":13408},[200,54744,529],{"emptyLinePlaceholder":20},[200,54746,54747,54749,54751],{"class":202,"line":13426},[200,54748,600],{"class":214},[200,54750,21428],{"class":235},[200,54752,648],{"class":214},[200,54754,54755,54757],{"class":202,"line":13456},[200,54756,657],{"class":214},[200,54758,54759],{"class":235},"UTable\n",[200,54761,54762,54764,54766,54768,54770,54772],{"class":202,"line":13462},[200,54763,23821],{"class":214},[200,54765,48167],{"class":379},[200,54767,449],{"class":214},[200,54769,611],{"class":214},[200,54771,48167],{"class":210},[200,54773,617],{"class":214},[200,54775,54776,54778,54780,54782,54784,54786],{"class":202,"line":13467},[200,54777,23821],{"class":214},[200,54779,48180],{"class":379},[200,54781,449],{"class":214},[200,54783,611],{"class":214},[200,54785,51759],{"class":210},[200,54787,617],{"class":214},[200,54789,54790,54792,54794,54796,54798,54800],{"class":202,"line":13472},[200,54791,23821],{"class":214},[200,54793,9886],{"class":379},[200,54795,449],{"class":214},[200,54797,611],{"class":214},[200,54799,48199],{"class":210},[200,54801,617],{"class":214},[200,54803,54804],{"class":202,"line":13487},[200,54805,11536],{"class":214},[200,54807,54808,54810,54812,54814,54817,54819,54821,54823,54825,54827,54829],{"class":202,"line":13498},[200,54809,804],{"class":214},[200,54811,21428],{"class":235},[200,54813,23867],{"class":214},[200,54815,54816],{"class":379},"liveUrl-data",[200,54818,449],{"class":214},[200,54820,611],{"class":214},[200,54822,2752],{"class":214},[200,54824,49618],{"class":210},[200,54826,413],{"class":214},[200,54828,611],{"class":214},[200,54830,648],{"class":214},[200,54832,54833,54835],{"class":202,"line":13503},[200,54834,22027],{"class":214},[200,54836,48277],{"class":235},[200,54838,54839,54842,54844,54846,54848,54850,54852],{"class":202,"line":13508},[200,54840,54841],{"class":206},"        v-if",[200,54843,449],{"class":214},[200,54845,611],{"class":214},[200,54847,49656],{"class":210},[200,54849,215],{"class":214},[200,54851,53922],{"class":210},[200,54853,617],{"class":214},[200,54855,54856,54859,54861,54863,54865],{"class":202,"line":20224},[200,54857,54858],{"class":379},"        variant",[200,54860,449],{"class":214},[200,54862,611],{"class":214},[200,54864,14616],{"class":245},[200,54866,617],{"class":214},[200,54868,54869,54871,54873,54875,54877],{"class":202,"line":20246},[200,54870,52587],{"class":379},[200,54872,449],{"class":214},[200,54874,611],{"class":214},[200,54876,7144],{"class":245},[200,54878,617],{"class":214},[200,54880,54881,54884,54887,54889,54891,54893,54895,54897],{"class":202,"line":20251},[200,54882,54883],{"class":214},"        :",[200,54885,54886],{"class":379},"to",[200,54888,449],{"class":214},[200,54890,611],{"class":214},[200,54892,49656],{"class":210},[200,54894,215],{"class":214},[200,54896,53922],{"class":210},[200,54898,617],{"class":214},[200,54900,54901,54904,54906,54908,54911],{"class":202,"line":20256},[200,54902,54903],{"class":379},"        class",[200,54905,449],{"class":214},[200,54907,611],{"class":214},[200,54909,54910],{"class":245},"flex flex-row items-center gap-1",[200,54912,617],{"class":214},[200,54914,54915],{"class":202,"line":20261},[200,54916,54917],{"class":214},"      >\n",[200,54919,54920,54922,54924,54926,54929,54931,54933],{"class":202,"line":20282},[200,54921,49602],{"class":214},[200,54923,200],{"class":235},[200,54925,675],{"class":214},[200,54927,54928],{"class":210},"{{ row.liveUrl }}",[200,54930,681],{"class":214},[200,54932,200],{"class":235},[200,54934,648],{"class":214},[200,54936,54937],{"class":202,"line":20289},[200,54938,529],{"emptyLinePlaceholder":20},[200,54940,54941,54943,54945,54947,54949,54951,54954,54956],{"class":202,"line":20294},[200,54942,49602],{"class":214},[200,54944,200],{"class":235},[200,54946,26173],{"class":379},[200,54948,449],{"class":214},[200,54950,611],{"class":214},[200,54952,54953],{"class":245},"i-heroicons-arrow-top-right-on-square-16-solid inline-block h-4 w-4",[200,54955,611],{"class":214},[200,54957,21487],{"class":214},[200,54959,54960,54962,54964],{"class":202,"line":20310},[200,54961,43992],{"class":214},[200,54963,48770],{"class":235},[200,54965,648],{"class":214},[200,54967,54968,54970,54972,54975,54977,54980,54982,54984],{"class":202,"line":47145},[200,54969,22027],{"class":214},[200,54971,200],{"class":235},[200,54973,54974],{"class":379}," v-else",[200,54976,675],{"class":214},[200,54978,54979],{"class":210}," - ",[200,54981,681],{"class":214},[200,54983,200],{"class":235},[200,54985,648],{"class":214},[200,54987,54988,54990,54992],{"class":202,"line":47150},[200,54989,15074],{"class":214},[200,54991,21428],{"class":235},[200,54993,648],{"class":214},[200,54995,54996,54998,55000,55002,55005,55007,55009,55011,55013,55015,55017],{"class":202,"line":47157},[200,54997,804],{"class":214},[200,54999,21428],{"class":235},[200,55001,23867],{"class":214},[200,55003,55004],{"class":379},"accessUrl-data",[200,55006,449],{"class":214},[200,55008,611],{"class":214},[200,55010,2752],{"class":214},[200,55012,49618],{"class":210},[200,55014,413],{"class":214},[200,55016,611],{"class":214},[200,55018,648],{"class":214},[200,55020,55021,55023],{"class":202,"line":47162},[200,55022,22027],{"class":214},[200,55024,48277],{"class":235},[200,55026,55027,55029,55031,55033,55035],{"class":202,"line":47167},[200,55028,54858],{"class":379},[200,55030,449],{"class":214},[200,55032,611],{"class":214},[200,55034,14616],{"class":245},[200,55036,617],{"class":214},[200,55038,55039,55041,55043,55045,55047],{"class":202,"line":47173},[200,55040,52587],{"class":379},[200,55042,449],{"class":214},[200,55044,611],{"class":214},[200,55046,7144],{"class":245},[200,55048,617],{"class":214},[200,55050,55051,55053,55055,55057,55059,55061,55063,55065],{"class":202,"line":47179},[200,55052,54883],{"class":214},[200,55054,54886],{"class":379},[200,55056,449],{"class":214},[200,55058,611],{"class":214},[200,55060,49656],{"class":210},[200,55062,215],{"class":214},[200,55064,53937],{"class":210},[200,55066,617],{"class":214},[200,55068,55069,55071,55073,55075,55077],{"class":202,"line":47185},[200,55070,54903],{"class":379},[200,55072,449],{"class":214},[200,55074,611],{"class":214},[200,55076,54910],{"class":245},[200,55078,617],{"class":214},[200,55080,55081],{"class":202,"line":47191},[200,55082,54917],{"class":214},[200,55084,55085,55087,55089,55091,55094,55096,55098],{"class":202,"line":47196},[200,55086,49602],{"class":214},[200,55088,200],{"class":235},[200,55090,675],{"class":214},[200,55092,55093],{"class":210},"{{ row.accessUrl }}",[200,55095,681],{"class":214},[200,55097,200],{"class":235},[200,55099,648],{"class":214},[200,55101,55102],{"class":202,"line":47211},[200,55103,529],{"emptyLinePlaceholder":20},[200,55105,55106,55108,55110,55112,55114,55116,55118,55120],{"class":202,"line":47218},[200,55107,49602],{"class":214},[200,55109,200],{"class":235},[200,55111,26173],{"class":379},[200,55113,449],{"class":214},[200,55115,611],{"class":214},[200,55117,54953],{"class":245},[200,55119,611],{"class":214},[200,55121,21487],{"class":214},[200,55123,55124,55126,55128],{"class":202,"line":47226},[200,55125,43992],{"class":214},[200,55127,48770],{"class":235},[200,55129,648],{"class":214},[200,55131,55132,55134,55136],{"class":202,"line":47233},[200,55133,15074],{"class":214},[200,55135,21428],{"class":235},[200,55137,648],{"class":214},[200,55139,55140,55142,55144,55146,55149,55151,55153,55155,55157,55159,55161],{"class":202,"line":47240},[200,55141,804],{"class":214},[200,55143,21428],{"class":235},[200,55145,23867],{"class":214},[200,55147,55148],{"class":379},"paidStatus-data",[200,55150,449],{"class":214},[200,55152,611],{"class":214},[200,55154,2752],{"class":214},[200,55156,49618],{"class":210},[200,55158,413],{"class":214},[200,55160,611],{"class":214},[200,55162,648],{"class":214},[200,55164,55166,55168,55170,55172,55174,55176,55178,55180,55183,55185,55187,55189,55191],{"class":202,"line":55165},74,[200,55167,22027],{"class":214},[200,55169,21428],{"class":235},[200,55171,34696],{"class":206},[200,55173,449],{"class":214},[200,55175,611],{"class":214},[200,55177,49656],{"class":210},[200,55179,215],{"class":214},[200,55181,55182],{"class":210},"paidStatus ",[200,55184,6546],{"class":214},[200,55186,242],{"class":214},[200,55188,38206],{"class":245},[200,55190,14702],{"class":214},[200,55192,648],{"class":214},[200,55194,55196,55198],{"class":202,"line":55195},75,[200,55197,49602],{"class":214},[200,55199,55200],{"class":235},"UBadge\n",[200,55202,55204,55207,55209,55211,55214],{"class":202,"line":55203},76,[200,55205,55206],{"class":379},"          color",[200,55208,449],{"class":214},[200,55210,611],{"class":214},[200,55212,55213],{"class":245},"green",[200,55215,617],{"class":214},[200,55217,55219,55222,55224,55226,55228],{"class":202,"line":55218},77,[200,55220,55221],{"class":379},"          variant",[200,55223,449],{"class":214},[200,55225,611],{"class":214},[200,55227,49642],{"class":245},[200,55229,617],{"class":214},[200,55231,55233],{"class":202,"line":55232},78,[200,55234,55235],{"class":214},"        >\n",[200,55237,55239],{"class":202,"line":55238},79,[200,55240,55241],{"class":210},"          Free\n",[200,55243,55245,55247,55249],{"class":202,"line":55244},80,[200,55246,49710],{"class":214},[200,55248,49632],{"class":235},[200,55250,648],{"class":214},[200,55252,55254,55256,55258],{"class":202,"line":55253},81,[200,55255,43992],{"class":214},[200,55257,21428],{"class":235},[200,55259,648],{"class":214},[200,55261,55263,55265,55267,55270,55272,55274,55276,55278,55280,55282,55284,55286,55288],{"class":202,"line":55262},82,[200,55264,22027],{"class":214},[200,55266,21428],{"class":235},[200,55268,55269],{"class":206}," v-else-if",[200,55271,449],{"class":214},[200,55273,611],{"class":214},[200,55275,49656],{"class":210},[200,55277,215],{"class":214},[200,55279,55182],{"class":210},[200,55281,6546],{"class":214},[200,55283,242],{"class":214},[200,55285,53195],{"class":245},[200,55287,14702],{"class":214},[200,55289,648],{"class":214},[200,55291,55293,55295],{"class":202,"line":55292},83,[200,55294,49602],{"class":214},[200,55296,55200],{"class":235},[200,55298,55300,55302,55304,55306,55309],{"class":202,"line":55299},84,[200,55301,55206],{"class":379},[200,55303,449],{"class":214},[200,55305,611],{"class":214},[200,55307,55308],{"class":245},"yellow",[200,55310,617],{"class":214},[200,55312,55314,55316,55318,55320,55322],{"class":202,"line":55313},85,[200,55315,55221],{"class":379},[200,55317,449],{"class":214},[200,55319,611],{"class":214},[200,55321,49642],{"class":245},[200,55323,617],{"class":214},[200,55325,55327],{"class":202,"line":55326},86,[200,55328,55235],{"class":214},[200,55330,55332],{"class":202,"line":55331},87,[200,55333,55334],{"class":210},"          Paid\n",[200,55336,55338,55340,55342],{"class":202,"line":55337},88,[200,55339,49710],{"class":214},[200,55341,49632],{"class":235},[200,55343,648],{"class":214},[200,55345,55347,55349,55351],{"class":202,"line":55346},89,[200,55348,43992],{"class":214},[200,55350,21428],{"class":235},[200,55352,648],{"class":214},[200,55354,55356,55358,55360],{"class":202,"line":55355},90,[200,55357,15074],{"class":214},[200,55359,21428],{"class":235},[200,55361,648],{"class":214},[200,55363,55365,55367,55369,55371,55374,55376,55378,55380,55382,55384,55386],{"class":202,"line":55364},91,[200,55366,804],{"class":214},[200,55368,21428],{"class":235},[200,55370,23867],{"class":214},[200,55372,55373],{"class":379},"createdBy-data",[200,55375,449],{"class":214},[200,55377,611],{"class":214},[200,55379,2752],{"class":214},[200,55381,49618],{"class":210},[200,55383,413],{"class":214},[200,55385,611],{"class":214},[200,55387,648],{"class":214},[200,55389,55391,55393,55395,55397,55399,55401,55404,55406],{"class":202,"line":55390},92,[200,55392,22027],{"class":214},[200,55394,11386],{"class":235},[200,55396,26173],{"class":379},[200,55398,449],{"class":214},[200,55400,611],{"class":214},[200,55402,55403],{"class":245},"flex flex-row items-center gap-2",[200,55405,611],{"class":214},[200,55407,648],{"class":214},[200,55409,55411,55413],{"class":202,"line":55410},93,[200,55412,49602],{"class":214},[200,55414,11449],{"class":235},[200,55416,55418,55421,55423,55425,55428],{"class":202,"line":55417},94,[200,55419,55420],{"class":379},"          :src",[200,55422,449],{"class":214},[200,55424,611],{"class":214},[200,55426,55427],{"class":245},"row.user.avatarUrl",[200,55429,617],{"class":214},[200,55431,55433,55436,55438,55440,55443],{"class":202,"line":55432},95,[200,55434,55435],{"class":379},"          alt",[200,55437,449],{"class":214},[200,55439,611],{"class":214},[200,55441,55442],{"class":245},"avatar",[200,55444,617],{"class":214},[200,55446,55448,55451,55453,55455,55458],{"class":202,"line":55447},96,[200,55449,55450],{"class":379},"          class",[200,55452,449],{"class":214},[200,55454,611],{"class":214},[200,55456,55457],{"class":245},"h-6 w-6 rounded-full",[200,55459,617],{"class":214},[200,55461,55463],{"class":202,"line":55462},97,[200,55464,55235],{"class":214},[200,55466,55468,55470,55472,55474,55477,55479,55481],{"class":202,"line":55467},98,[200,55469,49602],{"class":214},[200,55471,200],{"class":235},[200,55473,675],{"class":214},[200,55475,55476],{"class":210},"{{ row.user.name ?? row.user.login }}",[200,55478,681],{"class":214},[200,55480,200],{"class":235},[200,55482,648],{"class":214},[200,55484,55486,55488,55490],{"class":202,"line":55485},99,[200,55487,43992],{"class":214},[200,55489,11386],{"class":235},[200,55491,648],{"class":214},[200,55493,55495,55497,55499],{"class":202,"line":55494},100,[200,55496,15074],{"class":214},[200,55498,21428],{"class":235},[200,55500,648],{"class":214},[200,55502,55504,55506,55508,55510,55513,55515,55517,55519,55521,55523,55525],{"class":202,"line":55503},101,[200,55505,804],{"class":214},[200,55507,21428],{"class":235},[200,55509,23867],{"class":214},[200,55511,55512],{"class":379},"category-data",[200,55514,449],{"class":214},[200,55516,611],{"class":214},[200,55518,2752],{"class":214},[200,55520,49618],{"class":210},[200,55522,413],{"class":214},[200,55524,611],{"class":214},[200,55526,648],{"class":214},[200,55528,55530],{"class":202,"line":55529},102,[200,55531,55532],{"class":210},"      {{ row.category.name }}\n",[200,55534,55536,55538,55540],{"class":202,"line":55535},103,[200,55537,15074],{"class":214},[200,55539,21428],{"class":235},[200,55541,648],{"class":214},[200,55543,55545,55547,55549],{"class":202,"line":55544},104,[200,55546,893],{"class":214},[200,55548,48162],{"class":235},[200,55550,648],{"class":214},[200,55552,55554,55556,55558],{"class":202,"line":55553},105,[200,55555,681],{"class":214},[200,55557,21428],{"class":235},[200,55559,648],{"class":214},[35,55561,55562,55565],{},[6850,55563,55564],{},"But it does not work."," Using a lot of Adonis, I was familiar to having an object like this:",[192,55567,55569],{"className":26986,"code":55568,"language":26988,"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,55570,55571,55575,55589,55608,55626,55644,55656,55670,55688,55706,55710,55722,55736,55753,55757],{"__ignoreMap":13},[200,55572,55573],{"class":202,"line":203},[200,55574,230],{"class":214},[200,55576,55577,55579,55581,55583,55585,55587],{"class":202,"line":14},[200,55578,26999],{"class":214},[200,55580,3280],{"class":379},[200,55582,611],{"class":214},[200,55584,239],{"class":214},[200,55586,3938],{"class":2045},[200,55588,252],{"class":214},[200,55590,55591,55593,55595,55597,55599,55601,55604,55606],{"class":202,"line":255},[200,55592,26999],{"class":214},[200,55594,3566],{"class":379},[200,55596,611],{"class":214},[200,55598,239],{"class":214},[200,55600,27008],{"class":214},[200,55602,55603],{"class":245},"My template",[200,55605,611],{"class":214},[200,55607,252],{"class":214},[200,55609,55610,55612,55614,55616,55618,55620,55622,55624],{"class":202,"line":272},[200,55611,26999],{"class":214},[200,55613,39751],{"class":379},[200,55615,611],{"class":214},[200,55617,239],{"class":214},[200,55619,27008],{"class":214},[200,55621,53106],{"class":245},[200,55623,611],{"class":214},[200,55625,252],{"class":214},[200,55627,55628,55630,55632,55634,55636,55638,55640,55642],{"class":202,"line":283},[200,55629,26999],{"class":214},[200,55631,53922],{"class":379},[200,55633,611],{"class":214},[200,55635,239],{"class":214},[200,55637,27008],{"class":214},[200,55639,29707],{"class":245},[200,55641,611],{"class":214},[200,55643,252],{"class":214},[200,55645,55646,55648,55650,55652,55654],{"class":202,"line":300},[200,55647,26999],{"class":214},[200,55649,26808],{"class":379},[200,55651,611],{"class":214},[200,55653,239],{"class":214},[200,55655,280],{"class":214},[200,55657,55658,55660,55662,55664,55666,55668],{"class":202,"line":310},[200,55659,27031],{"class":214},[200,55661,3280],{"class":1978},[200,55663,611],{"class":214},[200,55665,239],{"class":214},[200,55667,3938],{"class":2045},[200,55669,252],{"class":214},[200,55671,55672,55674,55676,55678,55680,55682,55684,55686],{"class":202,"line":336},[200,55673,27031],{"class":214},[200,55675,20089],{"class":1978},[200,55677,611],{"class":214},[200,55679,239],{"class":214},[200,55681,27008],{"class":214},[200,55683,34793],{"class":245},[200,55685,611],{"class":214},[200,55687,252],{"class":214},[200,55689,55690,55692,55695,55697,55699,55701,55704],{"class":202,"line":342},[200,55691,27031],{"class":214},[200,55693,55694],{"class":1978},"avatarUrl",[200,55696,611],{"class":214},[200,55698,239],{"class":214},[200,55700,27008],{"class":214},[200,55702,55703],{"class":245},"https:\u002F\u002Fexample.com\u002Favatar.jpg",[200,55705,617],{"class":214},[200,55707,55708],{"class":202,"line":365},[200,55709,368],{"class":214},[200,55711,55712,55714,55716,55718,55720],{"class":202,"line":371},[200,55713,26999],{"class":214},[200,55715,54585],{"class":379},[200,55717,611],{"class":214},[200,55719,239],{"class":214},[200,55721,280],{"class":214},[200,55723,55724,55726,55728,55730,55732,55734],{"class":202,"line":398},[200,55725,27031],{"class":214},[200,55727,3280],{"class":1978},[200,55729,611],{"class":214},[200,55731,239],{"class":214},[200,55733,3938],{"class":2045},[200,55735,252],{"class":214},[200,55737,55738,55740,55742,55744,55746,55748,55751],{"class":202,"line":405},[200,55739,27031],{"class":214},[200,55741,20089],{"class":1978},[200,55743,611],{"class":214},[200,55745,239],{"class":214},[200,55747,27008],{"class":214},[200,55749,55750],{"class":245},"Landing Page",[200,55752,617],{"class":214},[200,55754,55755],{"class":202,"line":410},[200,55756,4490],{"class":214},[200,55758,55759],{"class":202,"line":861},[200,55760,1130],{"class":214},[35,55762,55763],{},"But I received an object like this:",[192,55765,55767],{"className":26986,"code":55766,"language":26988,"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,55768,55769,55773,55785,55799,55817,55835,55851,55855,55867,55871,55883,55887],{"__ignoreMap":13},[200,55770,55771],{"class":202,"line":203},[200,55772,230],{"class":214},[200,55774,55775,55777,55779,55781,55783],{"class":202,"line":14},[200,55776,26999],{"class":214},[200,55778,21428],{"class":379},[200,55780,611],{"class":214},[200,55782,239],{"class":214},[200,55784,280],{"class":214},[200,55786,55787,55789,55791,55793,55795,55797],{"class":202,"line":255},[200,55788,27031],{"class":214},[200,55790,3280],{"class":1978},[200,55792,611],{"class":214},[200,55794,239],{"class":214},[200,55796,3938],{"class":2045},[200,55798,252],{"class":214},[200,55800,55801,55803,55805,55807,55809,55811,55813,55815],{"class":202,"line":272},[200,55802,27031],{"class":214},[200,55804,3566],{"class":1978},[200,55806,611],{"class":214},[200,55808,239],{"class":214},[200,55810,27008],{"class":214},[200,55812,55603],{"class":245},[200,55814,611],{"class":214},[200,55816,252],{"class":214},[200,55818,55819,55821,55823,55825,55827,55829,55831,55833],{"class":202,"line":283},[200,55820,27031],{"class":214},[200,55822,39751],{"class":1978},[200,55824,611],{"class":214},[200,55826,239],{"class":214},[200,55828,27008],{"class":214},[200,55830,53106],{"class":245},[200,55832,611],{"class":214},[200,55834,252],{"class":214},[200,55836,55837,55839,55841,55843,55845,55847,55849],{"class":202,"line":300},[200,55838,27031],{"class":214},[200,55840,53922],{"class":1978},[200,55842,611],{"class":214},[200,55844,239],{"class":214},[200,55846,27008],{"class":214},[200,55848,29707],{"class":245},[200,55850,617],{"class":214},[200,55852,55853],{"class":202,"line":310},[200,55854,368],{"class":214},[200,55856,55857,55859,55861,55863,55865],{"class":202,"line":336},[200,55858,26999],{"class":214},[200,55860,26808],{"class":379},[200,55862,611],{"class":214},[200,55864,239],{"class":214},[200,55866,280],{"class":214},[200,55868,55869],{"class":202,"line":342},[200,55870,368],{"class":214},[200,55872,55873,55875,55877,55879,55881],{"class":202,"line":365},[200,55874,26999],{"class":214},[200,55876,54585],{"class":379},[200,55878,611],{"class":214},[200,55880,239],{"class":214},[200,55882,280],{"class":214},[200,55884,55885],{"class":202,"line":371},[200,55886,4490],{"class":214},[200,55888,55889],{"class":202,"line":398},[200,55890,1130],{"class":214},[35,55892,55893,55894],{},"and what a surprise it was to see that the keys and the values were exchanged between them. ",[1217,55895,55896],{},"Hard to explain but see the example.",[192,55898,55900],{"className":26986,"code":55899,"language":26988,"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,55901,55902,55906,55918,55936,55952,55956,55968,55986,56003,56007],{"__ignoreMap":13},[200,55903,55904],{"class":202,"line":203},[200,55905,230],{"class":214},[200,55907,55908,55910,55912,55914,55916],{"class":202,"line":14},[200,55909,26999],{"class":214},[200,55911,21428],{"class":379},[200,55913,611],{"class":214},[200,55915,239],{"class":214},[200,55917,280],{"class":214},[200,55919,55920,55922,55924,55926,55928,55930,55932,55934],{"class":202,"line":255},[200,55921,27031],{"class":214},[200,55923,3280],{"class":1978},[200,55925,611],{"class":214},[200,55927,239],{"class":214},[200,55929,27008],{"class":214},[200,55931,55603],{"class":245},[200,55933,611],{"class":214},[200,55935,252],{"class":214},[200,55937,55938,55940,55942,55944,55946,55948,55950],{"class":202,"line":272},[200,55939,27031],{"class":214},[200,55941,39751],{"class":1978},[200,55943,611],{"class":214},[200,55945,239],{"class":214},[200,55947,27008],{"class":214},[200,55949,29707],{"class":245},[200,55951,617],{"class":214},[200,55953,55954],{"class":202,"line":283},[200,55955,368],{"class":214},[200,55957,55958,55960,55962,55964,55966],{"class":202,"line":300},[200,55959,26999],{"class":214},[200,55961,26808],{"class":379},[200,55963,611],{"class":214},[200,55965,239],{"class":214},[200,55967,280],{"class":214},[200,55969,55970,55972,55974,55976,55978,55980,55982,55984],{"class":202,"line":310},[200,55971,27031],{"class":214},[200,55973,3280],{"class":1978},[200,55975,611],{"class":214},[200,55977,239],{"class":214},[200,55979,27008],{"class":214},[200,55981,34793],{"class":245},[200,55983,611],{"class":214},[200,55985,252],{"class":214},[200,55987,55988,55990,55992,55994,55996,55998,56001],{"class":202,"line":336},[200,55989,27031],{"class":214},[200,55991,51786],{"class":1978},[200,55993,611],{"class":214},[200,55995,239],{"class":214},[200,55997,27008],{"class":214},[200,55999,56000],{"class":245},"https:\u002F\u002Favatars.github.com\u002Fu\u002F1",[200,56002,617],{"class":214},[200,56004,56005],{"class":202,"line":342},[200,56006,4490],{"class":214},[200,56008,56009],{"class":202,"line":365},[200,56010,1130],{"class":214},[35,56012,56013,56014],{},"Very hard to play with data now! ",[1217,56015,56016],{},"The ID should be a number and the login should definitely not be a URL.",[35,56018,56019,56020,1638,56024,56028,56029,56033],{},"After a long research, I found two issues: ",[39,56021,56022],{"href":56022,"rel":56023},"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fworkers-sdk\u002Fissues\u002F3160",[77],[39,56025,56026],{"href":56026,"rel":56027},"https:\u002F\u002Fgithub.com\u002Fdrizzle-team\u002Fdrizzle-orm\u002Fissues\u002F555",[77]," explaining why it does not work. When two tables have the same column name, ",[56030,56031,56032],"del",{},"Drizzle and"," Cloudflare are unable to resolve them correctly and mix the data. The solution is to explicitly select the columns and rename similar columns.",[192,56035,56037],{"className":1370,"code":56036,"language":1373,"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,56038,56039,56061,56079,56097,56115,56133,56151,56169,56187,56196,56214,56233,56252,56256,56265,56310,56314],{"__ignoreMap":13},[200,56040,56041,56043,56045,56047,56049,56051,56053,56055,56057,56059],{"class":202,"line":203},[200,56042,443],{"class":379},[200,56044,52933],{"class":210},[200,56046,449],{"class":214},[200,56048,452],{"class":206},[200,56050,47508],{"class":223},[200,56052,1532],{"class":210},[200,56054,215],{"class":214},[200,56056,770],{"class":223},[200,56058,227],{"class":210},[200,56060,230],{"class":214},[200,56062,56063,56065,56067,56069,56071,56073,56075,56077],{"class":202,"line":14},[200,56064,49799],{"class":235},[200,56066,239],{"class":214},[200,56068,47527],{"class":210},[200,56070,215],{"class":214},[200,56072,51759],{"class":210},[200,56074,215],{"class":214},[200,56076,3280],{"class":210},[200,56078,252],{"class":214},[200,56080,56081,56083,56085,56087,56089,56091,56093,56095],{"class":202,"line":255},[200,56082,23659],{"class":235},[200,56084,239],{"class":214},[200,56086,47527],{"class":210},[200,56088,215],{"class":214},[200,56090,51759],{"class":210},[200,56092,215],{"class":214},[200,56094,3566],{"class":210},[200,56096,252],{"class":214},[200,56098,56099,56101,56103,56105,56107,56109,56111,56113],{"class":202,"line":272},[200,56100,258],{"class":235},[200,56102,239],{"class":214},[200,56104,47527],{"class":210},[200,56106,215],{"class":214},[200,56108,51759],{"class":210},[200,56110,215],{"class":214},[200,56112,3582],{"class":210},[200,56114,252],{"class":214},[200,56116,56117,56119,56121,56123,56125,56127,56129,56131],{"class":202,"line":283},[200,56118,53079],{"class":235},[200,56120,239],{"class":214},[200,56122,47527],{"class":210},[200,56124,215],{"class":214},[200,56126,51759],{"class":210},[200,56128,215],{"class":214},[200,56130,39751],{"class":210},[200,56132,252],{"class":214},[200,56134,56135,56137,56139,56141,56143,56145,56147,56149],{"class":202,"line":300},[200,56136,53159],{"class":235},[200,56138,239],{"class":214},[200,56140,47527],{"class":210},[200,56142,215],{"class":214},[200,56144,51759],{"class":210},[200,56146,215],{"class":214},[200,56148,53907],{"class":210},[200,56150,252],{"class":214},[200,56152,56153,56155,56157,56159,56161,56163,56165,56167],{"class":202,"line":310},[200,56154,53230],{"class":235},[200,56156,239],{"class":214},[200,56158,47527],{"class":210},[200,56160,215],{"class":214},[200,56162,51759],{"class":210},[200,56164,215],{"class":214},[200,56166,53922],{"class":210},[200,56168,252],{"class":214},[200,56170,56171,56173,56175,56177,56179,56181,56183,56185],{"class":202,"line":336},[200,56172,53252],{"class":235},[200,56174,239],{"class":214},[200,56176,47527],{"class":210},[200,56178,215],{"class":214},[200,56180,51759],{"class":210},[200,56182,215],{"class":214},[200,56184,53937],{"class":210},[200,56186,252],{"class":214},[200,56188,56189,56192,56194],{"class":202,"line":342},[200,56190,56191],{"class":235},"  user",[200,56193,239],{"class":214},[200,56195,280],{"class":214},[200,56197,56198,56200,56202,56204,56206,56208,56210,56212],{"class":202,"line":365},[200,56199,11797],{"class":235},[200,56201,239],{"class":214},[200,56203,47527],{"class":210},[200,56205,215],{"class":214},[200,56207,47325],{"class":210},[200,56209,215],{"class":214},[200,56211,20089],{"class":210},[200,56213,252],{"class":214},[200,56215,56216,56219,56221,56223,56225,56227,56229,56231],{"class":202,"line":371},[200,56217,56218],{"class":235},"    login",[200,56220,239],{"class":214},[200,56222,47527],{"class":210},[200,56224,215],{"class":214},[200,56226,47325],{"class":210},[200,56228,215],{"class":214},[200,56230,51786],{"class":210},[200,56232,252],{"class":214},[200,56234,56235,56238,56240,56242,56244,56246,56248,56250],{"class":202,"line":398},[200,56236,56237],{"class":235},"    avatarUrl",[200,56239,239],{"class":214},[200,56241,47527],{"class":210},[200,56243,215],{"class":214},[200,56245,47325],{"class":210},[200,56247,215],{"class":214},[200,56249,55694],{"class":210},[200,56251,252],{"class":214},[200,56253,56254],{"class":202,"line":405},[200,56255,368],{"class":214},[200,56257,56258,56261,56263],{"class":202,"line":410},[200,56259,56260],{"class":235},"  category",[200,56262,239],{"class":214},[200,56264,280],{"class":214},[200,56266,56267,56269,56271,56273,56275,56277,56279,56281,56283,56285,56287,56289,56291,56293,56295,56297,56299,56301,56304,56306,56308],{"class":202,"line":861},[200,56268,11797],{"class":235},[200,56270,239],{"class":214},[200,56272,52124],{"class":223},[200,56274,600],{"class":214},[200,56276,328],{"class":1978},[200,56278,675],{"class":214},[200,56280,3304],{"class":214},[200,56282,47588],{"class":210},[200,56284,215],{"class":214},[200,56286,47331],{"class":210},[200,56288,215],{"class":214},[200,56290,20089],{"class":210},[200,56292,23001],{"class":214},[200,56294,215],{"class":214},[200,56296,44211],{"class":223},[200,56298,227],{"class":210},[200,56300,249],{"class":214},[200,56302,56303],{"class":245},"c_name",[200,56305,249],{"class":214},[200,56307,2089],{"class":210},[200,56309,252],{"class":214},[200,56311,56312],{"class":202,"line":890},[200,56313,368],{"class":214},[200,56315,56316,56318,56320,56322,56324,56326,56328,56331,56333,56335,56337,56339,56341,56343,56345,56347,56349,56351,56353,56355,56357,56359,56361,56363,56365,56368,56370,56372,56374,56376,56378,56380,56382,56384,56386,56388,56390,56392,56394,56396,56398,56400,56402],{"class":202,"line":900},[200,56317,413],{"class":214},[200,56319,2089],{"class":210},[200,56321,215],{"class":214},[200,56323,1472],{"class":223},[200,56325,51210],{"class":210},[200,56327,215],{"class":214},[200,56329,56330],{"class":210},"templates)",[200,56332,215],{"class":214},[200,56334,54125],{"class":223},[200,56336,51210],{"class":210},[200,56338,215],{"class":214},[200,56340,47325],{"class":210},[200,56342,550],{"class":214},[200,56344,54138],{"class":223},[200,56346,51210],{"class":210},[200,56348,215],{"class":214},[200,56350,51759],{"class":210},[200,56352,215],{"class":214},[200,56354,53417],{"class":210},[200,56356,550],{"class":214},[200,56358,47527],{"class":210},[200,56360,215],{"class":214},[200,56362,47325],{"class":210},[200,56364,215],{"class":214},[200,56366,56367],{"class":210},"id))",[200,56369,215],{"class":214},[200,56371,54125],{"class":223},[200,56373,51210],{"class":210},[200,56375,215],{"class":214},[200,56377,47331],{"class":210},[200,56379,550],{"class":214},[200,56381,54138],{"class":223},[200,56383,51210],{"class":210},[200,56385,215],{"class":214},[200,56387,51759],{"class":210},[200,56389,215],{"class":214},[200,56391,53428],{"class":210},[200,56393,550],{"class":214},[200,56395,47527],{"class":210},[200,56397,215],{"class":214},[200,56399,47331],{"class":210},[200,56401,215],{"class":214},[200,56403,56404],{"class":210},"id))\n",[35,56406,56407,56410,56411,5557,56413,56415,56416,1638,56418,56420],{},[156,56408,56409],{},"sql\u003Cstring>'${tables.categories.name}'.as('c_name')"," is the important code. I rename the column ",[156,56412,20089],{},[156,56414,56303],{}," to avoid the conflict. I also use this syntax to format the object with the key ",[156,56417,26808],{},[156,56419,54585],{},". Now, everything works as expected.",[1913,56422,30681],{"id":56423},"conclusion-2",[35,56425,56426],{},"Working on the client side, and not only on the admin part, makes the project even more concrete. There is still a lot of work on the template form like the markdown or the image, but it's a pretty good start.",[35,56428,56429],{},"Really enjoy this project and I hope you too.",[51,56431,56433],{"id":56432},"continuing-the-template-form","Continuing the template form",[35,56435,56436],{},[1690,56437],{"alt":56438,"src":56439},"Thumbnail of my eighth live stream.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-20.png",[35,56441,56442],{},[1217,56443,56444,46057,56447,46062,56450,46066,56453,215],{},[39,56445,38639],{"href":46025,"rel":56446},[77],[39,56448,46061],{"href":18567,"rel":56449},[77],[39,56451,19412],{"href":46016,"rel":56452},[77],[39,56454,11622],{"href":46025,"rel":56455},[77],[35,56457,56458],{},"This stream was short so I try to make interesting stuff. I mainly do three things:",[1576,56460,56461,56464,56467],{},[71,56462,56463],{},"Create the show template page",[71,56465,56466],{},"Handle markdown on the server-side for the template description",[71,56468,56469],{},"Add a markdown editor for the template description",[35,56471,56472,56473],{},"Let's see how it goes. ",[1217,56474,56475],{},"During the stream, it goes well but since, I completely rewrite the markdown editor part.",[1913,56477,56479],{"id":56478},"show-template-page","Show template page",[35,56481,56482,56483,56486,56487,56489,56490,56492,56493,56495,56496,56498,56499,56501,56502,56504],{},"This item was more about the usage of the URL that the design of the page. Indeed, the URL will work like Notion or Dev.to: ",[156,56484,56485],{},"\u002F\u003Cslug>-\u003Chash>"," where the ",[156,56488,47555],{}," is the name of the template and the ",[156,56491,53003],{}," is a unique identifier. The ",[156,56494,47555],{}," is used for SEO and the ",[156,56497,53003],{}," is used to get the template. The ",[156,56500,47555],{}," is not unique and can change over time. The ",[156,56503,53003],{}," is unique and serves to get the template.",[35,56506,56507],{},"In the page, we first start by getting the params from the route:",[192,56509,56511],{"className":1370,"code":56510,"language":1373,"meta":13,"style":13},"const route = useRoute()\nconst slug = route.params.slug \u002F\u002F \u003Cslug>-\u003Chash>\n",[156,56512,56513,56527],{"__ignoreMap":13},[200,56514,56515,56517,56520,56522,56525],{"class":202,"line":203},[200,56516,443],{"class":379},[200,56518,56519],{"class":210}," route ",[200,56521,449],{"class":214},[200,56523,56524],{"class":223}," useRoute",[200,56526,466],{"class":210},[200,56528,56529,56531,56534,56536,56539,56541,56543,56545,56548],{"class":202,"line":14},[200,56530,443],{"class":379},[200,56532,56533],{"class":210}," slug ",[200,56535,449],{"class":214},[200,56537,56538],{"class":210}," route",[200,56540,215],{"class":214},[200,56542,20084],{"class":210},[200,56544,215],{"class":214},[200,56546,56547],{"class":210},"slug ",[200,56549,56550],{"class":401},"\u002F\u002F \u003Cslug>-\u003Chash>\n",[35,56552,56553,56554,56556,56557,1638,56559,56561,56562,56564,56565,36817,56567,239],{},"The variable ",[156,56555,47555],{}," contains both the template ",[156,56558,47555],{},[156,56560,53003],{},". We know the hash is exactly 12 characters long so we can split the ",[156,56563,47555],{}," to get the ",[156,56566,47555],{},[156,56568,53003],{},[192,56570,56572],{"className":1370,"code":56571,"language":1373,"meta":13,"style":13},"slug.slice(-12)\n",[156,56573,56574],{"__ignoreMap":13},[200,56575,56576,56578,56580,56582,56584,56587,56590],{"class":202,"line":203},[200,56577,47555],{"class":210},[200,56579,215],{"class":214},[200,56581,3928],{"class":223},[200,56583,227],{"class":210},[200,56585,56586],{"class":214},"-",[200,56588,56589],{"class":2045},"12",[200,56591,416],{"class":210},[35,56593,56594],{},"Finally, we can make a request to the backend to retrieve the template. All of this gives us this code:",[192,56596,56598],{"className":21417,"code":56597,"language":12060,"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,56599,56600,56620,56632,56652,56687,56693,56735,56745,56751,56759,56763,56771,56778,56791,56795,56800,56809],{"__ignoreMap":13},[200,56601,56602,56604,56606,56608,56610,56612,56614,56616,56618],{"class":202,"line":203},[200,56603,600],{"class":214},[200,56605,14892],{"class":235},[200,56607,14590],{"class":379},[200,56609,449],{"class":214},[200,56611,611],{"class":214},[200,56613,1373],{"class":245},[200,56615,611],{"class":214},[200,56617,21781],{"class":379},[200,56619,648],{"class":214},[200,56621,56622,56624,56626,56628,56630],{"class":202,"line":14},[200,56623,443],{"class":379},[200,56625,56519],{"class":210},[200,56627,449],{"class":214},[200,56629,56524],{"class":223},[200,56631,466],{"class":210},[200,56633,56634,56636,56639,56641,56644,56646,56648,56650],{"class":202,"line":255},[200,56635,443],{"class":379},[200,56637,56638],{"class":210}," hash ",[200,56640,449],{"class":214},[200,56642,56643],{"class":223}," computed",[200,56645,227],{"class":210},[200,56647,1532],{"class":214},[200,56649,393],{"class":379},[200,56651,280],{"class":214},[200,56653,56654,56656,56658,56661,56663,56665,56667,56669,56671,56673,56675,56677,56679,56681,56683,56685],{"class":202,"line":272},[200,56655,3743],{"class":206},[200,56657,1095],{"class":235},[200,56659,56660],{"class":210},"route",[200,56662,215],{"class":214},[200,56664,20084],{"class":210},[200,56666,215],{"class":214},[200,56668,47555],{"class":210},[200,56670,14674],{"class":206},[200,56672,22708],{"class":1978},[200,56674,2089],{"class":235},[200,56676,215],{"class":214},[200,56678,3928],{"class":223},[200,56680,227],{"class":235},[200,56682,56586],{"class":214},[200,56684,56589],{"class":2045},[200,56686,416],{"class":235},[200,56688,56689,56691],{"class":202,"line":283},[200,56690,413],{"class":214},[200,56692,416],{"class":210},[200,56694,56695,56697,56699,56701,56703,56706,56708,56710,56712,56714,56716,56718,56721,56723,56725,56727,56729,56731,56733],{"class":202,"line":300},[200,56696,443],{"class":379},[200,56698,318],{"class":214},[200,56700,5041],{"class":235},[200,56702,239],{"class":214},[200,56704,56705],{"class":210}," template ",[200,56707,413],{"class":214},[200,56709,510],{"class":214},[200,56711,452],{"class":206},[200,56713,48105],{"class":223},[200,56715,227],{"class":210},[200,56717,3318],{"class":214},[200,56719,56720],{"class":245},"\u002Fapi\u002Ftemplates\u002F",[200,56722,4853],{"class":214},[200,56724,53003],{"class":210},[200,56726,215],{"class":214},[200,56728,15837],{"class":210},[200,56730,23001],{"class":214},[200,56732,550],{"class":214},[200,56734,280],{"class":214},[200,56736,56737,56739,56741,56743],{"class":202,"line":310},[200,56738,48123],{"class":235},[200,56740,239],{"class":214},[200,56742,16166],{"class":2423},[200,56744,252],{"class":214},[200,56746,56747,56749],{"class":202,"line":336},[200,56748,413],{"class":214},[200,56750,416],{"class":210},[200,56752,56753,56755,56757],{"class":202,"line":342},[200,56754,681],{"class":214},[200,56756,14892],{"class":235},[200,56758,648],{"class":214},[200,56760,56761],{"class":202,"line":365},[200,56762,529],{"emptyLinePlaceholder":20},[200,56764,56765,56767,56769],{"class":202,"line":371},[200,56766,600],{"class":214},[200,56768,21428],{"class":235},[200,56770,648],{"class":214},[200,56772,56773,56775],{"class":202,"line":398},[200,56774,657],{"class":214},[200,56776,56777],{"class":235},"article\n",[200,56779,56780,56783,56785,56787,56789],{"class":202,"line":405},[200,56781,56782],{"class":379},"    v-if",[200,56784,449],{"class":214},[200,56786,611],{"class":214},[200,56788,21428],{"class":245},[200,56790,617],{"class":214},[200,56792,56793],{"class":202,"line":410},[200,56794,11536],{"class":214},[200,56796,56797],{"class":202,"line":861},[200,56798,56799],{"class":210},"    {{ template }}\n",[200,56801,56802,56804,56807],{"class":202,"line":890},[200,56803,893],{"class":214},[200,56805,56806],{"class":235},"article",[200,56808,648],{"class":214},[200,56810,56811,56813,56815],{"class":202,"line":900},[200,56812,681],{"class":214},[200,56814,21428],{"class":235},[200,56816,648],{"class":214},[35,56818,56819,56820,56822],{},"The server part is not that complicated since we only get the hash from the router param and search for the template using a ",[156,56821,49030],{}," clause:",[192,56824,56826],{"className":1370,"code":56825,"language":1373,"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,56827,56828,56850,56854,56876,56901,56924,56936,56940,56956,56960,57021,57025,57032],{"__ignoreMap":13},[200,56829,56830,56832,56834,56836,56838,56840,56842,56844,56846,56848],{"class":202,"line":203},[200,56831,973],{"class":206},[200,56833,318],{"class":214},[200,56835,42872],{"class":210},[200,56837,550],{"class":214},[200,56839,22708],{"class":210},[200,56841,981],{"class":214},[200,56843,984],{"class":206},[200,56845,242],{"class":214},[200,56847,19790],{"class":245},[200,56849,992],{"class":214},[200,56851,56852],{"class":202,"line":14},[200,56853,529],{"emptyLinePlaceholder":20},[200,56855,56856,56858,56860,56862,56864,56866,56868,56870,56872,56874],{"class":202,"line":255},[200,56857,1380],{"class":206},[200,56859,1383],{"class":206},[200,56861,38960],{"class":223},[200,56863,227],{"class":210},[200,56865,3711],{"class":379},[200,56867,1095],{"class":214},[200,56869,25386],{"class":386},[200,56871,2089],{"class":214},[200,56873,393],{"class":379},[200,56875,280],{"class":214},[200,56877,56878,56880,56882,56884,56886,56889,56891,56893,56895,56897,56899],{"class":202,"line":272},[200,56879,3723],{"class":379},[200,56881,49052],{"class":210},[200,56883,510],{"class":214},[200,56885,452],{"class":206},[200,56887,56888],{"class":223}," getValidatedRouterParams",[200,56890,227],{"class":235},[200,56892,25386],{"class":210},[200,56894,550],{"class":214},[200,56896,42872],{"class":223},[200,56898,227],{"class":235},[200,56900,230],{"class":214},[200,56902,56903,56905,56907,56909,56911,56913,56916,56918,56920,56922],{"class":202,"line":283},[200,56904,53837],{"class":235},[200,56906,239],{"class":214},[200,56908,22708],{"class":223},[200,56910,1532],{"class":235},[200,56912,215],{"class":214},[200,56914,56915],{"class":223},"length",[200,56917,227],{"class":235},[200,56919,56589],{"class":2045},[200,56921,2089],{"class":235},[200,56923,252],{"class":214},[200,56925,56926,56928,56930,56932,56934],{"class":202,"line":300},[200,56927,5080],{"class":214},[200,56929,2089],{"class":235},[200,56931,215],{"class":214},[200,56933,41223],{"class":210},[200,56935,416],{"class":235},[200,56937,56938],{"class":202,"line":310},[200,56939,529],{"emptyLinePlaceholder":20},[200,56941,56942,56944,56946,56949,56951,56953],{"class":202,"line":336},[200,56943,3723],{"class":379},[200,56945,318],{"class":214},[200,56947,56948],{"class":210}," hash",[200,56950,981],{"class":214},[200,56952,510],{"class":214},[200,56954,56955],{"class":210}," params\n",[200,56957,56958],{"class":202,"line":342},[200,56959,529],{"emptyLinePlaceholder":20},[200,56961,56962,56964,56967,56969,56971,56973,56975,56977,56979,56981,56983,56985,56987,56989,56991,56993,56995,56997,56999,57001,57003,57005,57007,57009,57011,57013,57015,57017,57019],{"class":202,"line":365},[200,56963,3723],{"class":379},[200,56965,56966],{"class":210}," template",[200,56968,510],{"class":214},[200,56970,452],{"class":206},[200,56972,47508],{"class":223},[200,56974,1532],{"class":235},[200,56976,215],{"class":214},[200,56978,770],{"class":223},[200,56980,1532],{"class":235},[200,56982,215],{"class":214},[200,56984,1472],{"class":223},[200,56986,227],{"class":235},[200,56988,47588],{"class":210},[200,56990,215],{"class":214},[200,56992,51759],{"class":210},[200,56994,2089],{"class":235},[200,56996,215],{"class":214},[200,56998,49030],{"class":223},[200,57000,227],{"class":235},[200,57002,49035],{"class":223},[200,57004,227],{"class":235},[200,57006,47588],{"class":210},[200,57008,215],{"class":214},[200,57010,51759],{"class":210},[200,57012,215],{"class":214},[200,57014,53003],{"class":210},[200,57016,550],{"class":214},[200,57018,56948],{"class":210},[200,57020,585],{"class":235},[200,57022,57023],{"class":202,"line":371},[200,57024,529],{"emptyLinePlaceholder":20},[200,57026,57027,57029],{"class":202,"line":398},[200,57028,3743],{"class":206},[200,57030,57031],{"class":210}," template\n",[200,57033,57034,57036],{"class":202,"line":405},[200,57035,413],{"class":214},[200,57037,416],{"class":210},[35,57039,57040],{},"Later we will need to join the user's data and the category's data to have a complete template.",[1913,57042,57044],{"id":57043},"server-side-markdown","Server-side Markdown",[35,57046,57047],{},"The idea of receiving markdown from the client is to easily sanitize it to avoid HTML injection.",[35,57049,57050],{},"Imagine if the client send this HTML code:",[192,57052,57054],{"className":591,"code":57053,"language":593,"meta":13,"style":13},"\u003Cp> Hello World!\u003C\u002Fp>\n\u003Cscript> console.log('hacked') \u003C\u002Fscript>\n",[156,57055,57056,57073],{"__ignoreMap":13},[200,57057,57058,57060,57062,57064,57067,57069,57071],{"class":202,"line":203},[200,57059,600],{"class":214},[200,57061,35],{"class":235},[200,57063,675],{"class":214},[200,57065,57066],{"class":210}," Hello World!",[200,57068,681],{"class":214},[200,57070,35],{"class":235},[200,57072,648],{"class":214},[200,57074,57075,57077,57079,57081,57084,57086,57088,57090,57092,57095,57097,57099,57101,57103],{"class":202,"line":14},[200,57076,600],{"class":214},[200,57078,14892],{"class":235},[200,57080,675],{"class":214},[200,57082,57083],{"class":210}," console",[200,57085,215],{"class":214},[200,57087,1118],{"class":223},[200,57089,227],{"class":210},[200,57091,249],{"class":214},[200,57093,57094],{"class":245},"hacked",[200,57096,249],{"class":214},[200,57098,5267],{"class":210},[200,57100,681],{"class":214},[200,57102,14892],{"class":235},[200,57104,648],{"class":214},[35,57106,57107,57108,57111],{},"If the server stores this content in the database and then display it using the directive ",[156,57109,57110],{},"v-html",", every user that will see the page will execute the page. That's a real security issue.",[35,57113,57114,57115,57117,57118,57121],{},"By receiving markdown from the client, I can easily transform it using ",[156,57116,9674],{}," and store the ",[6850,57119,57120],{},"sanitized HTML"," in the database. The sanitized HTML is HTML that does not contain any script or dangerous tag.",[35,57123,57124,57125,57127,57128,57130,57131,57134],{},"So to do this, I need to install ",[156,57126,9674],{}," and update the endpoint to create a template ",[156,57129,53559],{},". I also add a new column ",[156,57132,57133],{},"descriptionHTML"," to the schema to store the sanitized HTML. Storing the HTML avoids to do it every time we render the page.",[192,57136,57138],{"className":1370,"code":57137,"language":1373,"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,57139,57140,57166,57178,57196,57210,57224,57238,57252,57266,57280,57299,57313],{"__ignoreMap":13},[200,57141,57142,57144,57146,57148,57150,57152,57154,57156,57158,57160,57162,57164],{"class":202,"line":203},[200,57143,207],{"class":206},[200,57145,47508],{"class":223},[200,57147,1532],{"class":210},[200,57149,215],{"class":214},[200,57151,47696],{"class":223},[200,57153,51210],{"class":210},[200,57155,215],{"class":214},[200,57157,56330],{"class":210},[200,57159,215],{"class":214},[200,57161,47711],{"class":223},[200,57163,227],{"class":210},[200,57165,230],{"class":214},[200,57167,57168,57170,57172,57174,57176],{"class":202,"line":14},[200,57169,52992],{"class":235},[200,57171,239],{"class":214},[200,57173,53842],{"class":223},[200,57175,1532],{"class":210},[200,57177,252],{"class":214},[200,57179,57180,57182,57184,57186,57189,57191,57194],{"class":202,"line":255},[200,57181,53026],{"class":235},[200,57183,239],{"class":214},[200,57185,47724],{"class":223},[200,57187,57188],{"class":210},"(body",[200,57190,215],{"class":214},[200,57192,57193],{"class":210},"title)",[200,57195,252],{"class":214},[200,57197,57198,57200,57202,57204,57206,57208],{"class":202,"line":272},[200,57199,23659],{"class":235},[200,57201,239],{"class":214},[200,57203,41038],{"class":210},[200,57205,215],{"class":214},[200,57207,3566],{"class":210},[200,57209,252],{"class":214},[200,57211,57212,57214,57216,57218,57220,57222],{"class":202,"line":283},[200,57213,53351],{"class":235},[200,57215,239],{"class":214},[200,57217,41038],{"class":210},[200,57219,215],{"class":214},[200,57221,53428],{"class":210},[200,57223,252],{"class":214},[200,57225,57226,57228,57230,57232,57234,57236],{"class":202,"line":300},[200,57227,53159],{"class":235},[200,57229,239],{"class":214},[200,57231,41038],{"class":210},[200,57233,215],{"class":214},[200,57235,53907],{"class":210},[200,57237,252],{"class":214},[200,57239,57240,57242,57244,57246,57248,57250],{"class":202,"line":310},[200,57241,53230],{"class":235},[200,57243,239],{"class":214},[200,57245,41038],{"class":210},[200,57247,215],{"class":214},[200,57249,53922],{"class":210},[200,57251,252],{"class":214},[200,57253,57254,57256,57258,57260,57262,57264],{"class":202,"line":336},[200,57255,53252],{"class":235},[200,57257,239],{"class":214},[200,57259,41038],{"class":210},[200,57261,215],{"class":214},[200,57263,53937],{"class":210},[200,57265,252],{"class":214},[200,57267,57268,57270,57272,57274,57276,57278],{"class":202,"line":342},[200,57269,258],{"class":235},[200,57271,239],{"class":214},[200,57273,41038],{"class":210},[200,57275,215],{"class":214},[200,57277,3582],{"class":210},[200,57279,252],{"class":214},[200,57281,57282,57285,57287,57290,57292,57294,57297],{"class":202,"line":365},[200,57283,57284],{"class":235},"  descriptionHTML",[200,57286,239],{"class":214},[200,57288,57289],{"class":223}," useMarkdown",[200,57291,57188],{"class":210},[200,57293,215],{"class":214},[200,57295,57296],{"class":210},"description)",[200,57298,252],{"class":214},[200,57300,57301,57303,57305,57307,57309,57311],{"class":202,"line":371},[200,57302,53306],{"class":235},[200,57304,239],{"class":214},[200,57306,34776],{"class":210},[200,57308,215],{"class":214},[200,57310,3280],{"class":210},[200,57312,252],{"class":214},[200,57314,57315,57317,57319,57321,57323],{"class":202,"line":398},[200,57316,413],{"class":214},[200,57318,2089],{"class":210},[200,57320,215],{"class":214},[200,57322,47761],{"class":223},[200,57324,466],{"class":210},[35,57326,419,57327,57330,57331,57333],{},[156,57328,57329],{},"useMarkdown"," function is a simple function that uses ",[156,57332,9674],{}," to transform the markdown to HTML.",[1913,57335,57337],{"id":57336},"markdown-editor","Markdown editor",[35,57339,57340],{},[1217,57341,57342],{},"This part is highly experimental and could change in the future. This is a first implementation.",[35,57344,57345],{},[1217,57346,57347],{},"While writing this part, I made some more research and everything will change. I think it's still interesting to see the process.",[35,57349,57350,57351,57354],{},"Before starting, I need to make a choice. Which markdown editor should I use? ",[1217,57352,57353],{},"Remember that we've just implemented a server endpoint to handle markdown."," I have many options:",[1576,57356,57357,57360,57362,57365,57368,57370,57373,57376],{},[71,57358,57359],{},"SimpleMDE",[71,57361,34247],{},[71,57363,57364],{},"TinyMDE",[71,57366,57367],{},"Editor.js",[71,57369,34252],{},[71,57371,57372],{},"Trix",[71,57374,57375],{},"CKEditor",[71,57377,57378],{},"TinyMCE",[35,57380,57381],{},"That's a lot of options. But, what are my needs? I need a dead simple editor that can return markdown. Sounds easy, right?",[35,57383,57384,57385],{},"Editor.js, CKEditor and TinyMCE are advanced editors so I will not use them. SimpleMDE is a good option but way too old. It has not received any updates since 6 years. SimpleMDE and TinyMDE are forks of SimpleMDE. EasyMDE is powerful, simple and returns markdown. It's a good option. TinyMDE does not have enough customization options. Trix and Quill are not markdown editors. They are rich text editors so they do not output markdown. I will go with EasyMDE. ",[1217,57386,57387],{},"For the moment.",[35,57389,57390,57391,57394,57395],{},"So, we can now install the package and use it. The usage is a little tricky. Since a client only package, we cannot execute, nor import, it on the server. To use it, we will dynamically import it in a ",[156,57392,57393],{},"onMounted"," hook. ",[1217,57396,57397,57400],{},[156,57398,57399],{},"onBeforeMount"," is even better!",[192,57402,57404],{"className":1370,"code":57403,"language":1373,"meta":13,"style":13},"onMounted(async () => {\n  const EasyMDE = await import('easymde').then(m => m.default)\n  const easymde = new EasyMDE()\n})\n",[156,57405,57406,57420,57464,57479],{"__ignoreMap":13},[200,57407,57408,57410,57412,57414,57416,57418],{"class":202,"line":203},[200,57409,57393],{"class":223},[200,57411,227],{"class":210},[200,57413,3711],{"class":379},[200,57415,3714],{"class":214},[200,57417,393],{"class":379},[200,57419,280],{"class":214},[200,57421,57422,57424,57427,57429,57431,57433,57435,57437,57440,57442,57444,57446,57448,57450,57453,57455,57458,57460,57462],{"class":202,"line":14},[200,57423,3723],{"class":379},[200,57425,57426],{"class":210}," EasyMDE",[200,57428,510],{"class":214},[200,57430,452],{"class":206},[200,57432,27725],{"class":214},[200,57434,227],{"class":235},[200,57436,249],{"class":214},[200,57438,57439],{"class":245},"easymde",[200,57441,249],{"class":214},[200,57443,2089],{"class":235},[200,57445,215],{"class":214},[200,57447,10929],{"class":223},[200,57449,227],{"class":235},[200,57451,57452],{"class":386},"m",[200,57454,393],{"class":379},[200,57456,57457],{"class":210}," m",[200,57459,215],{"class":214},[200,57461,52073],{"class":210},[200,57463,416],{"class":235},[200,57465,57466,57468,57471,57473,57475,57477],{"class":202,"line":255},[200,57467,3723],{"class":379},[200,57469,57470],{"class":210}," easymde",[200,57472,510],{"class":214},[200,57474,12997],{"class":214},[200,57476,57426],{"class":223},[200,57478,466],{"class":235},[200,57480,57481,57483],{"class":202,"line":272},[200,57482,413],{"class":214},[200,57484,416],{"class":210},[35,57486,57487],{},"And it works? Yes but I think we completely forgot to import the style!",[35,57489,57490],{},[1690,57491],{"alt":57492,"src":57493},"EasyMDE without style","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Feasymde-without-style.png",[35,57495,57496],{},"I think we forgot to add the style for EasyMDE. Thanks to Vite and its CSS loader, we can import the CSS file directly in our script.",[192,57498,57500],{"className":21417,"code":57499,"language":12060,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nimport 'easymde\u002Fdist\u002Feasymde.min.css'\n\u003C\u002Fscript>\n",[156,57501,57502,57522,57533],{"__ignoreMap":13},[200,57503,57504,57506,57508,57510,57512,57514,57516,57518,57520],{"class":202,"line":203},[200,57505,600],{"class":214},[200,57507,14892],{"class":235},[200,57509,14590],{"class":379},[200,57511,449],{"class":214},[200,57513,611],{"class":214},[200,57515,1373],{"class":245},[200,57517,611],{"class":214},[200,57519,21781],{"class":379},[200,57521,648],{"class":214},[200,57523,57524,57526,57528,57531],{"class":202,"line":14},[200,57525,973],{"class":206},[200,57527,242],{"class":214},[200,57529,57530],{"class":245},"easymde\u002Fdist\u002Feasymde.min.css",[200,57532,992],{"class":214},[200,57534,57535,57537,57539],{"class":202,"line":255},[200,57536,681],{"class":214},[200,57538,14892],{"class":235},[200,57540,648],{"class":214},[35,57542,57543],{},[1690,57544],{"alt":57545,"src":57546},"EasyMDE with style","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Feasymde-with-style.png",[35,57548,57549],{},"Ok, it's not perfect and I will have some work to do, on both styles and icons. But it's a good start. The only part that I do not like is the markdown symbol. I would prefer to not have them because it's not user-friendly (anyone could have to create a template).",[35,57551,57552],{},"But anyway, it's a good start and I can now easily listen to the editor changes and update the description.",[192,57554,57556],{"className":21417,"code":57555,"language":12060,"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,57557,57558,57578,57600,57614,57654,57668,57699,57718,57724,57730],{"__ignoreMap":13},[200,57559,57560,57562,57564,57566,57568,57570,57572,57574,57576],{"class":202,"line":203},[200,57561,600],{"class":214},[200,57563,14892],{"class":235},[200,57565,14590],{"class":379},[200,57567,449],{"class":214},[200,57569,611],{"class":214},[200,57571,1373],{"class":245},[200,57573,611],{"class":214},[200,57575,21781],{"class":379},[200,57577,648],{"class":214},[200,57579,57580,57582,57584,57586,57588,57590,57592,57594,57596,57598],{"class":202,"line":14},[200,57581,443],{"class":379},[200,57583,22666],{"class":210},[200,57585,449],{"class":214},[200,57587,18685],{"class":223},[200,57589,600],{"class":214},[200,57591,328],{"class":1978},[200,57593,675],{"class":214},[200,57595,227],{"class":210},[200,57597,22656],{"class":214},[200,57599,416],{"class":210},[200,57601,57602,57604,57606,57608,57610,57612],{"class":202,"line":255},[200,57603,57393],{"class":223},[200,57605,227],{"class":210},[200,57607,3711],{"class":379},[200,57609,3714],{"class":214},[200,57611,393],{"class":379},[200,57613,280],{"class":214},[200,57615,57616,57618,57620,57622,57624,57626,57628,57630,57632,57634,57636,57638,57640,57642,57644,57646,57648,57650,57652],{"class":202,"line":272},[200,57617,3723],{"class":379},[200,57619,57426],{"class":210},[200,57621,510],{"class":214},[200,57623,452],{"class":206},[200,57625,27725],{"class":214},[200,57627,227],{"class":235},[200,57629,249],{"class":214},[200,57631,57439],{"class":245},[200,57633,249],{"class":214},[200,57635,2089],{"class":235},[200,57637,215],{"class":214},[200,57639,10929],{"class":223},[200,57641,227],{"class":235},[200,57643,57452],{"class":386},[200,57645,393],{"class":379},[200,57647,57457],{"class":210},[200,57649,215],{"class":214},[200,57651,52073],{"class":210},[200,57653,416],{"class":235},[200,57655,57656,57658,57660,57662,57664,57666],{"class":202,"line":283},[200,57657,3723],{"class":379},[200,57659,57470],{"class":210},[200,57661,510],{"class":214},[200,57663,12997],{"class":214},[200,57665,57426],{"class":223},[200,57667,466],{"class":235},[200,57669,57670,57673,57675,57678,57680,57682,57684,57686,57689,57691,57693,57695,57697],{"class":202,"line":300},[200,57671,57672],{"class":210},"  easyMDE",[200,57674,215],{"class":214},[200,57676,57677],{"class":210},"codemirror",[200,57679,215],{"class":214},[200,57681,25370],{"class":223},[200,57683,227],{"class":235},[200,57685,249],{"class":214},[200,57687,57688],{"class":245},"change",[200,57690,249],{"class":214},[200,57692,550],{"class":214},[200,57694,3714],{"class":214},[200,57696,393],{"class":379},[200,57698,280],{"class":214},[200,57700,57701,57703,57705,57707,57709,57712,57714,57716],{"class":202,"line":310},[200,57702,3136],{"class":210},[200,57704,215],{"class":214},[200,57706,15837],{"class":210},[200,57708,510],{"class":214},[200,57710,57711],{"class":210}," easyMDE",[200,57713,215],{"class":214},[200,57715,15837],{"class":223},[200,57717,466],{"class":235},[200,57719,57720,57722],{"class":202,"line":336},[200,57721,5080],{"class":214},[200,57723,416],{"class":235},[200,57725,57726,57728],{"class":202,"line":342},[200,57727,413],{"class":214},[200,57729,416],{"class":210},[200,57731,57732,57734,57736],{"class":202,"line":365},[200,57733,681],{"class":214},[200,57735,14892],{"class":235},[200,57737,648],{"class":214},[35,57739,57740],{},"Now, I can send the description to the server and it will be transformed to HTML!",[1913,57742,57744],{"id":57743},"whats-next-for-the-form","What's next for the form?",[35,57746,57747],{},"The form is far from done but it's progressing well. I will add the support for the images and the modules in the next stream.",[35,57749,57750],{},"The Orion project is nearly ready for an alpha release and I'm excited to see how the community will use it and discover the content they will share.",[51,57752,57754],{"id":57753},"replacing-the-markdown-editor","Replacing the Markdown editor",[35,57756,57757],{},[1217,57758,57759],{},"This part was done off-stream because I was not satisfied with the EasyMDE editor but I did not know what to do.",[35,57761,57762],{},"After thinking about it, I realized that using Markdown, for a simple description and that can be used by non-technical users, is not the best idea. Furthermore, the EasyMDE editor uses CodeMirror which is slow and way too heavy for a simple editor.",[35,57764,57765,57766],{},"Now we know that the EasyMDE editor is not the best solution for our needs, we need to find a new one. We need a simple Rich Text Editor and there are two main options: Quill and Trix. In that case, Quill seems to be simpler so I will go with it. ",[1217,57767,57768],{},"I will not detail the installation of Quill, it's pretty simple.",[35,57770,57771,57772,57774,57775,51797],{},"Because Quill is a rich text editor, it does not output markdown but HTML. So I need to update the endpoint to store the HTML instead of the markdown. I also need to update the schema to remove the ",[156,57773,3582],{}," column and add a ",[156,57776,57133],{},[1913,57778,57780],{"id":57779},"updating-the-endpoint","Updating the endpoint",[35,57782,57783],{},"Why do I need to update the endpoint? Quill returns HTML that is already sanitized so I could directly store it in the database. Yes but no. We are building an API and it's impossible to trust the client, even our own client. Anyone can change the HTML or use an HTTP client to send a request to the server with malicious HTML. So I need to sanitize the HTML on the server.",[35,57785,57786],{},"For example, here's some malicious HTML:",[192,57788,57790],{"className":591,"code":57789,"language":593,"meta":13,"style":13},"\u003Cp> Hello World! \u003C\u002Fp>\n\u003Cscript> console.log('hacked') \u003C\u002Fscript>\n",[156,57791,57792,57809],{"__ignoreMap":13},[200,57793,57794,57796,57798,57800,57803,57805,57807],{"class":202,"line":203},[200,57795,600],{"class":214},[200,57797,35],{"class":235},[200,57799,675],{"class":214},[200,57801,57802],{"class":210}," Hello World! ",[200,57804,681],{"class":214},[200,57806,35],{"class":235},[200,57808,648],{"class":214},[200,57810,57811,57813,57815,57817,57819,57821,57823,57825,57827,57829,57831,57833,57835,57837],{"class":202,"line":14},[200,57812,600],{"class":214},[200,57814,14892],{"class":235},[200,57816,675],{"class":214},[200,57818,57083],{"class":210},[200,57820,215],{"class":214},[200,57822,1118],{"class":223},[200,57824,227],{"class":210},[200,57826,249],{"class":214},[200,57828,57094],{"class":245},[200,57830,249],{"class":214},[200,57832,5267],{"class":210},[200,57834,681],{"class":214},[200,57836,14892],{"class":235},[200,57838,648],{"class":214},[35,57840,57841,57842,57844,57845,57848],{},"If you store this HTML in the database and display it using the directive ",[156,57843,57110],{},", every user that will see the page will execute the script. That's a real security issue. We can't use ",[156,57846,57847],{},"{{ html }}"," because Vue will escape the HTML so sanitizing our content is the only solution.",[35,57850,57851,57852,57855],{},"I will use the ",[156,57853,57854],{},"sanitize-html"," package to sanitize the HTML. After sanitizing using this package, the HTML will be:",[192,57857,57859],{"className":591,"code":57858,"language":593,"meta":13,"style":13},"\u003Cp> Hello World! \u003C\u002Fp>\n",[156,57860,57861],{"__ignoreMap":13},[200,57862,57863,57865,57867,57869,57871,57873,57875],{"class":202,"line":203},[200,57864,600],{"class":214},[200,57866,35],{"class":235},[200,57868,675],{"class":214},[200,57870,57802],{"class":210},[200,57872,681],{"class":214},[200,57874,35],{"class":235},[200,57876,648],{"class":214},[35,57878,57879],{},"That's perfect!",[192,57881,57883],{"className":1370,"code":57882,"language":1373,"meta":13,"style":13},"await useDrizzle().insert(tables.templates).values({\n  description: body.description ? sanitizeHtml(body.description) : null,\n})\n",[156,57884,57885,57911,57939],{"__ignoreMap":13},[200,57886,57887,57889,57891,57893,57895,57897,57899,57901,57903,57905,57907,57909],{"class":202,"line":203},[200,57888,207],{"class":206},[200,57890,47508],{"class":223},[200,57892,1532],{"class":210},[200,57894,215],{"class":214},[200,57896,47696],{"class":223},[200,57898,51210],{"class":210},[200,57900,215],{"class":214},[200,57902,56330],{"class":210},[200,57904,215],{"class":214},[200,57906,47711],{"class":223},[200,57908,227],{"class":210},[200,57910,230],{"class":214},[200,57912,57913,57915,57917,57919,57921,57923,57925,57928,57930,57932,57935,57937],{"class":202,"line":14},[200,57914,258],{"class":235},[200,57916,239],{"class":214},[200,57918,41038],{"class":210},[200,57920,215],{"class":214},[200,57922,3818],{"class":210},[200,57924,28004],{"class":214},[200,57926,57927],{"class":223}," sanitizeHtml",[200,57929,57188],{"class":210},[200,57931,215],{"class":214},[200,57933,57934],{"class":210},"description) ",[200,57936,239],{"class":214},[200,57938,11896],{"class":214},[200,57940,57941,57943],{"class":202,"line":255},[200,57942,413],{"class":214},[200,57944,416],{"class":210},[35,57946,419,57947,57950,57951,57953],{},[156,57948,57949],{},"sanitizeHtml"," function is the default export of the ",[156,57952,57854],{}," package. Easy right?",[1913,57955,57957],{"id":57956},"updating-the-schema","Updating the schema",[35,57959,57960,57961,57963,57964,57966],{},"Now, I need to update the schema to remove the ",[156,57962,57133],{}," column since the ",[156,57965,3582],{}," is now HTML by default. It's important to know that if the user needs to edit his template, the HTML will be used by the Quill editor.",[192,57968,57971],{"className":57969,"code":57970,"language":1373,"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,57972,57973,57997,58017,58040],{"__ignoreMap":13},[200,57974,57975,57977,57979,57981,57983,57985,57987,57989,57991,57993,57995],{"class":202,"line":203},[200,57976,1380],{"class":206},[200,57978,14301],{"class":379},[200,57980,52933],{"class":210},[200,57982,449],{"class":214},[200,57984,50977],{"class":223},[200,57986,227],{"class":210},[200,57988,249],{"class":214},[200,57990,51759],{"class":245},[200,57992,249],{"class":214},[200,57994,550],{"class":214},[200,57996,280],{"class":214},[200,57998,57999,58001,58003,58005,58007,58009,58011,58013,58015],{"class":202,"line":14},[200,58000,258],{"class":235},[200,58002,239],{"class":214},[200,58004,51039],{"class":223},[200,58006,227],{"class":210},[200,58008,249],{"class":214},[200,58010,3582],{"class":245},[200,58012,249],{"class":214},[200,58014,2089],{"class":210},[200,58016,252],{"class":214},[200,58018,58021,58023,58025,58027,58029,58031,58034,58036,58038],{"class":58019,"line":255},[202,58020,37394],"diff",[200,58022,57284],{"class":235},[200,58024,239],{"class":214},[200,58026,51039],{"class":223},[200,58028,227],{"class":210},[200,58030,249],{"class":214},[200,58032,58033],{"class":245},"description_html",[200,58035,249],{"class":214},[200,58037,2089],{"class":210},[200,58039,550],{"class":214},[200,58041,58042,58044],{"class":202,"line":272},[200,58043,413],{"class":214},[200,58045,416],{"class":210},[1913,58047,58049],{"id":58048},"updating-the-ui","Updating the UI",[35,58051,58052],{},"The usage of Quill is not that different from EasyMDE. I lazy load the Quill package, create a new instance of it, and listen for the changes. I do not forget to import the CSS file.",[192,58054,58056],{"className":21417,"code":58055,"language":12060,"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,58057,58058,58078,58089,58093,58107,58149,58177,58193,58199,58242,58272,58278,58284,58292,58296,58304,58323],{"__ignoreMap":13},[200,58059,58060,58062,58064,58066,58068,58070,58072,58074,58076],{"class":202,"line":203},[200,58061,600],{"class":214},[200,58063,14892],{"class":235},[200,58065,14590],{"class":379},[200,58067,449],{"class":214},[200,58069,611],{"class":214},[200,58071,1373],{"class":245},[200,58073,611],{"class":214},[200,58075,21781],{"class":379},[200,58077,648],{"class":214},[200,58079,58080,58082,58084,58087],{"class":202,"line":14},[200,58081,973],{"class":206},[200,58083,242],{"class":214},[200,58085,58086],{"class":245},"quill\u002Fdist\u002Fquill.snow.css",[200,58088,992],{"class":214},[200,58090,58091],{"class":202,"line":255},[200,58092,529],{"emptyLinePlaceholder":20},[200,58094,58095,58097,58099,58101,58103,58105],{"class":202,"line":272},[200,58096,57399],{"class":223},[200,58098,227],{"class":210},[200,58100,3711],{"class":379},[200,58102,3714],{"class":214},[200,58104,393],{"class":379},[200,58106,280],{"class":214},[200,58108,58109,58111,58114,58116,58118,58120,58122,58124,58127,58129,58131,58133,58135,58137,58139,58141,58143,58145,58147],{"class":202,"line":283},[200,58110,3723],{"class":379},[200,58112,58113],{"class":210}," Quill",[200,58115,510],{"class":214},[200,58117,452],{"class":206},[200,58119,27725],{"class":214},[200,58121,227],{"class":235},[200,58123,249],{"class":214},[200,58125,58126],{"class":245},"quill",[200,58128,249],{"class":214},[200,58130,2089],{"class":235},[200,58132,215],{"class":214},[200,58134,10929],{"class":223},[200,58136,227],{"class":235},[200,58138,57452],{"class":386},[200,58140,393],{"class":379},[200,58142,57457],{"class":210},[200,58144,215],{"class":214},[200,58146,52073],{"class":210},[200,58148,416],{"class":235},[200,58150,58151,58154,58156,58158,58160,58162,58164,58166,58168,58171,58173,58175],{"class":202,"line":300},[200,58152,58153],{"class":210},"  quill",[200,58155,215],{"class":214},[200,58157,15837],{"class":210},[200,58159,510],{"class":214},[200,58161,12997],{"class":214},[200,58163,58113],{"class":223},[200,58165,227],{"class":235},[200,58167,249],{"class":214},[200,58169,58170],{"class":245},"#editor",[200,58172,249],{"class":214},[200,58174,550],{"class":214},[200,58176,280],{"class":214},[200,58178,58179,58182,58184,58186,58189,58191],{"class":202,"line":310},[200,58180,58181],{"class":235},"    theme",[200,58183,239],{"class":214},[200,58185,242],{"class":214},[200,58187,58188],{"class":245},"snow",[200,58190,249],{"class":214},[200,58192,252],{"class":214},[200,58194,58195,58197],{"class":202,"line":336},[200,58196,5080],{"class":214},[200,58198,416],{"class":235},[200,58200,58201,58203,58205,58207,58209,58211,58213,58215,58218,58220,58222,58224,58226,58228,58231,58233,58236,58238,58240],{"class":202,"line":342},[200,58202,58153],{"class":210},[200,58204,215],{"class":214},[200,58206,15837],{"class":210},[200,58208,6524],{"class":214},[200,58210,25370],{"class":223},[200,58212,227],{"class":235},[200,58214,249],{"class":214},[200,58216,58217],{"class":245},"text-change",[200,58219,249],{"class":214},[200,58221,550],{"class":214},[200,58223,1095],{"class":214},[200,58225,19200],{"class":386},[200,58227,550],{"class":214},[200,58229,58230],{"class":386}," __",[200,58232,550],{"class":214},[200,58234,58235],{"class":386}," source",[200,58237,2089],{"class":214},[200,58239,393],{"class":379},[200,58241,280],{"class":214},[200,58243,58244,58246,58248,58250,58252,58255,58257,58259,58261,58264,58266,58268,58270],{"class":202,"line":365},[200,58245,27430],{"class":210},[200,58247,215],{"class":214},[200,58249,3582],{"class":210},[200,58251,510],{"class":214},[200,58253,58254],{"class":210}," quill",[200,58256,215],{"class":214},[200,58258,15837],{"class":210},[200,58260,6524],{"class":214},[200,58262,58263],{"class":210},"root",[200,58265,215],{"class":214},[200,58267,15967],{"class":210},[200,58269,3891],{"class":214},[200,58271,18875],{"class":214},[200,58273,58274,58276],{"class":202,"line":371},[200,58275,5080],{"class":214},[200,58277,416],{"class":235},[200,58279,58280,58282],{"class":202,"line":398},[200,58281,413],{"class":214},[200,58283,416],{"class":210},[200,58285,58286,58288,58290],{"class":202,"line":405},[200,58287,681],{"class":214},[200,58289,14892],{"class":235},[200,58291,648],{"class":214},[200,58293,58294],{"class":202,"line":410},[200,58295,529],{"emptyLinePlaceholder":20},[200,58297,58298,58300,58302],{"class":202,"line":861},[200,58299,600],{"class":214},[200,58301,21428],{"class":235},[200,58303,648],{"class":214},[200,58305,58306,58308,58310,58312,58314,58316,58319,58321],{"class":202,"line":890},[200,58307,657],{"class":214},[200,58309,11386],{"class":235},[200,58311,3224],{"class":379},[200,58313,449],{"class":214},[200,58315,611],{"class":214},[200,58317,58318],{"class":245},"editor",[200,58320,611],{"class":214},[200,58322,21487],{"class":214},[200,58324,58325,58327,58329],{"class":202,"line":900},[200,58326,681],{"class":214},[200,58328,21428],{"class":235},[200,58330,648],{"class":214},[35,58332,58333],{},"Of course, I need to add some custom styles to the editor to make it more native into the design.",[35,58335,58336],{},"A last thing. I use this in a Nuxt UI form. In this form, every item is validated against the schema. This allows me to display an error message if the description is empty or too long. The problem is that it's not integrated in the Quill editor, it does not trigger any validation. Thanks to the Nuxt UI form API, I can easily validate the Quill editor content on every change.",[192,58338,58340],{"className":21417,"code":58339,"language":12060,"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,58341,58342,58362,58375,58379,58393,58433,58461,58498,58504,58510,58518,58522,58530,58537,58550,58562,58596,58600,58618,58626],{"__ignoreMap":13},[200,58343,58344,58346,58348,58350,58352,58354,58356,58358,58360],{"class":202,"line":203},[200,58345,600],{"class":214},[200,58347,14892],{"class":235},[200,58349,14590],{"class":379},[200,58351,449],{"class":214},[200,58353,611],{"class":214},[200,58355,1373],{"class":245},[200,58357,611],{"class":214},[200,58359,21781],{"class":379},[200,58361,648],{"class":214},[200,58363,58364,58366,58369,58371,58373],{"class":202,"line":14},[200,58365,443],{"class":379},[200,58367,58368],{"class":210}," form ",[200,58370,449],{"class":214},[200,58372,18685],{"class":223},[200,58374,466],{"class":210},[200,58376,58377],{"class":202,"line":255},[200,58378,529],{"emptyLinePlaceholder":20},[200,58380,58381,58383,58385,58387,58389,58391],{"class":202,"line":272},[200,58382,57399],{"class":223},[200,58384,227],{"class":210},[200,58386,3711],{"class":379},[200,58388,3714],{"class":214},[200,58390,393],{"class":379},[200,58392,280],{"class":214},[200,58394,58395,58397,58399,58401,58403,58405,58407,58409,58411,58413,58415,58417,58419,58421,58423,58425,58427,58429,58431],{"class":202,"line":283},[200,58396,58153],{"class":210},[200,58398,215],{"class":214},[200,58400,15837],{"class":210},[200,58402,6524],{"class":214},[200,58404,25370],{"class":223},[200,58406,227],{"class":235},[200,58408,249],{"class":214},[200,58410,58217],{"class":245},[200,58412,249],{"class":214},[200,58414,550],{"class":214},[200,58416,1095],{"class":214},[200,58418,19200],{"class":386},[200,58420,550],{"class":214},[200,58422,58230],{"class":386},[200,58424,550],{"class":214},[200,58426,58235],{"class":386},[200,58428,2089],{"class":214},[200,58430,393],{"class":379},[200,58432,280],{"class":214},[200,58434,58435,58437,58439,58441,58443,58445,58447,58449,58451,58453,58455,58457,58459],{"class":202,"line":300},[200,58436,27430],{"class":210},[200,58438,215],{"class":214},[200,58440,3582],{"class":210},[200,58442,510],{"class":214},[200,58444,58254],{"class":210},[200,58446,215],{"class":214},[200,58448,15837],{"class":210},[200,58450,6524],{"class":214},[200,58452,58263],{"class":210},[200,58454,215],{"class":214},[200,58456,15967],{"class":210},[200,58458,3891],{"class":214},[200,58460,18875],{"class":214},[200,58462,58463,58466,58468,58470,58472,58475,58477,58479,58481,58483,58485,58487,58490,58492,58494,58496],{"class":202,"line":310},[200,58464,58465],{"class":210},"    form",[200,58467,215],{"class":214},[200,58469,15837],{"class":210},[200,58471,215],{"class":214},[200,58473,58474],{"class":223},"validate",[200,58476,227],{"class":235},[200,58478,249],{"class":214},[200,58480,3582],{"class":245},[200,58482,249],{"class":214},[200,58484,550],{"class":214},[200,58486,318],{"class":214},[200,58488,58489],{"class":235}," silent",[200,58491,239],{"class":214},[200,58493,2424],{"class":2423},[200,58495,981],{"class":214},[200,58497,416],{"class":235},[200,58499,58500,58502],{"class":202,"line":336},[200,58501,5080],{"class":214},[200,58503,416],{"class":235},[200,58505,58506,58508],{"class":202,"line":342},[200,58507,413],{"class":214},[200,58509,416],{"class":210},[200,58511,58512,58514,58516],{"class":202,"line":365},[200,58513,681],{"class":214},[200,58515,14892],{"class":235},[200,58517,648],{"class":214},[200,58519,58520],{"class":202,"line":371},[200,58521,529],{"emptyLinePlaceholder":20},[200,58523,58524,58526,58528],{"class":202,"line":398},[200,58525,600],{"class":214},[200,58527,21428],{"class":235},[200,58529,648],{"class":214},[200,58531,58532,58534],{"class":202,"line":405},[200,58533,657],{"class":214},[200,58535,58536],{"class":235},"UFormGroup\n",[200,58538,58539,58542,58544,58546,58548],{"class":202,"line":410},[200,58540,58541],{"class":379},"    label",[200,58543,449],{"class":214},[200,58545,611],{"class":214},[200,58547,54564],{"class":245},[200,58549,617],{"class":214},[200,58551,58552,58554,58556,58558,58560],{"class":202,"line":861},[200,58553,11797],{"class":379},[200,58555,449],{"class":214},[200,58557,611],{"class":214},[200,58559,3582],{"class":245},[200,58561,617],{"class":214},[200,58563,58564,58566,58569,58571,58574,58576,58578,58580,58582,58584,58586,58588,58590,58593],{"class":202,"line":890},[200,58565,23821],{"class":214},[200,58567,58568],{"class":379},"hint",[200,58570,449],{"class":214},[200,58572,58573],{"class":214},"\"`${",[200,58575,26836],{"class":210},[200,58577,215],{"class":214},[200,58579,3582],{"class":210},[200,58581,6524],{"class":214},[200,58583,51343],{"class":210},[200,58585,14981],{"class":214},[200,58587,51368],{"class":2045},[200,58589,413],{"class":214},[200,58591,58592],{"class":245},"\u002F2000 characters",[200,58594,58595],{"class":214},"`\"\n",[200,58597,58598],{"class":202,"line":900},[200,58599,11536],{"class":214},[200,58601,58602,58604,58606,58608,58610,58612,58614,58616],{"class":202,"line":905},[200,58603,804],{"class":214},[200,58605,11386],{"class":235},[200,58607,3224],{"class":379},[200,58609,449],{"class":214},[200,58611,611],{"class":214},[200,58613,58318],{"class":245},[200,58615,611],{"class":214},[200,58617,21487],{"class":214},[200,58619,58620,58622,58624],{"class":202,"line":931},[200,58621,893],{"class":214},[200,58623,48706],{"class":235},[200,58625,648],{"class":214},[200,58627,58628,58630,58632],{"class":202,"line":3640},[200,58629,681],{"class":214},[200,58631,21428],{"class":235},[200,58633,648],{"class":214},[35,58635,58636],{},"Now, the Quill editor is fully integrated into the form and an error message will be displayed if the description is empty or too long.",[35,58638,58639],{},[1690,58640],{"alt":58641,"src":58642},"The rich text editor customized to fully integrate into the form.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Frich-text-editor.png",[1913,58644,30681],{"id":58645},"conclusion-3",[35,58647,58648],{},"I'm really happy with this change. Since years, I was wondering how to install and use a rich text editor and now I know. You can use markdown for technical users, parse it on the server, and store HTML to simplify a get, or use a rich text editor for non-technical users and sanitize the HTML on the server. It's a good lesson for me and I hope it will be useful for you.",[51,58650,58652],{"id":58651},"adding-modules-to-the-template-form","Adding modules to the template form",[35,58654,58655],{},[1690,58656],{"alt":58657,"src":58658},"Thumbnail of my ninth live stream.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-28.png",[35,58660,58661],{},[1217,58662,58663,46057,58666,46062,58669,46066,58672,215],{},[39,58664,38639],{"href":46025,"rel":58665},[77],[39,58667,46061],{"href":18567,"rel":58668},[77],[39,58670,19412],{"href":46016,"rel":58671},[77],[39,58673,11622],{"href":46025,"rel":58674},[77],[35,58676,58677],{},"During this live stream, I added the ability to add modules to a template in the form. This feature is important because it will allow users to filter templates by modules.",[35,58679,58680],{},"I chose to do it in a separate stream because it's a many-to-many relation and a little bit more complex than the other parts. I was expecting to do it in 1 hour, but it took me 2 hours with the help of the chat. Indeed, I was stuck on the SQL query to retrieve a template populated with its modules, and the Drizzle documentation was not clear about it.",[35,58682,58683],{},"Let's deep dive into the code and let's understand how the Drizzle ORM relations concept works.",[1913,58685,48572],{"id":48664},[35,58687,58688,58689],{},"The creator can add multiple modules to a template. At the same time, a module can be used by multiple templates. The relation is a many-to-many relation. To handle this, I need to create a new table, called a pivot table, to store the relation between the templates and the modules. It acts as a link between the two tables to connect them. ",[1217,58690,58691],{},"We use a pivot table instead of an array of modules in the template because it's more efficient and it's the SQL way to do it.",[35,58693,58694],{},[1690,58695],{"alt":58696,"src":58697},"Many-to-many relation between templates and modules","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fmany-to-many-relation.png",[35,58699,58700,58701,58703,58704,19487],{},"The schema is simplified to only show the relation between the templates and the modules. From the ",[156,58702,51759],{}," table, you can retrieve all the modules associated and inversely thanks to the ",[156,58705,58706],{},"templates_modules",[192,58708,58710],{"className":1370,"code":58709,"language":1373,"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,58711,58712,58736,58774,58800,58806,58810,58834,58872,58904,58910,58914,58940,58984,59028],{"__ignoreMap":13},[200,58713,58714,58716,58718,58720,58722,58724,58726,58728,58730,58732,58734],{"class":202,"line":203},[200,58715,1380],{"class":206},[200,58717,14301],{"class":379},[200,58719,52933],{"class":210},[200,58721,449],{"class":214},[200,58723,50977],{"class":223},[200,58725,227],{"class":210},[200,58727,249],{"class":214},[200,58729,51759],{"class":245},[200,58731,249],{"class":214},[200,58733,550],{"class":214},[200,58735,280],{"class":214},[200,58737,58738,58740,58742,58744,58746,58748,58750,58752,58754,58756,58758,58760,58762,58764,58766,58768,58770,58772],{"class":202,"line":14},[200,58739,49799],{"class":235},[200,58741,239],{"class":214},[200,58743,50998],{"class":223},[200,58745,227],{"class":210},[200,58747,249],{"class":214},[200,58749,3280],{"class":245},[200,58751,249],{"class":214},[200,58753,2089],{"class":210},[200,58755,215],{"class":214},[200,58757,51013],{"class":223},[200,58759,227],{"class":210},[200,58761,2752],{"class":214},[200,58763,51020],{"class":235},[200,58765,239],{"class":214},[200,58767,2424],{"class":2423},[200,58769,981],{"class":214},[200,58771,2089],{"class":210},[200,58773,252],{"class":214},[200,58775,58776,58778,58780,58782,58784,58786,58788,58790,58792,58794,58796,58798],{"class":202,"line":255},[200,58777,23659],{"class":235},[200,58779,239],{"class":214},[200,58781,51039],{"class":223},[200,58783,227],{"class":210},[200,58785,249],{"class":214},[200,58787,3566],{"class":245},[200,58789,249],{"class":214},[200,58791,2089],{"class":210},[200,58793,215],{"class":214},[200,58795,51054],{"class":223},[200,58797,1532],{"class":210},[200,58799,252],{"class":214},[200,58801,58802,58804],{"class":202,"line":272},[200,58803,413],{"class":214},[200,58805,416],{"class":210},[200,58807,58808],{"class":202,"line":283},[200,58809,529],{"emptyLinePlaceholder":20},[200,58811,58812,58814,58816,58818,58820,58822,58824,58826,58828,58830,58832],{"class":202,"line":300},[200,58813,1380],{"class":206},[200,58815,14301],{"class":379},[200,58817,50834],{"class":210},[200,58819,449],{"class":214},[200,58821,50977],{"class":223},[200,58823,227],{"class":210},[200,58825,249],{"class":214},[200,58827,2461],{"class":245},[200,58829,249],{"class":214},[200,58831,550],{"class":214},[200,58833,280],{"class":214},[200,58835,58836,58838,58840,58842,58844,58846,58848,58850,58852,58854,58856,58858,58860,58862,58864,58866,58868,58870],{"class":202,"line":310},[200,58837,49799],{"class":235},[200,58839,239],{"class":214},[200,58841,50998],{"class":223},[200,58843,227],{"class":210},[200,58845,249],{"class":214},[200,58847,3280],{"class":245},[200,58849,249],{"class":214},[200,58851,2089],{"class":210},[200,58853,215],{"class":214},[200,58855,51013],{"class":223},[200,58857,227],{"class":210},[200,58859,2752],{"class":214},[200,58861,51020],{"class":235},[200,58863,239],{"class":214},[200,58865,2424],{"class":2423},[200,58867,981],{"class":214},[200,58869,2089],{"class":210},[200,58871,252],{"class":214},[200,58873,58874,58876,58878,58880,58882,58884,58886,58888,58890,58892,58894,58896,58898,58900,58902],{"class":202,"line":336},[200,58875,236],{"class":235},[200,58877,239],{"class":214},[200,58879,51039],{"class":223},[200,58881,227],{"class":210},[200,58883,249],{"class":214},[200,58885,20089],{"class":245},[200,58887,249],{"class":214},[200,58889,2089],{"class":210},[200,58891,215],{"class":214},[200,58893,51054],{"class":223},[200,58895,1532],{"class":210},[200,58897,215],{"class":214},[200,58899,51061],{"class":223},[200,58901,1532],{"class":210},[200,58903,252],{"class":214},[200,58905,58906,58908],{"class":202,"line":342},[200,58907,413],{"class":214},[200,58909,416],{"class":210},[200,58911,58912],{"class":202,"line":365},[200,58913,529],{"emptyLinePlaceholder":20},[200,58915,58916,58918,58920,58923,58925,58927,58929,58931,58934,58936,58938],{"class":202,"line":371},[200,58917,1380],{"class":206},[200,58919,14301],{"class":379},[200,58921,58922],{"class":210}," modulesToTemplates ",[200,58924,449],{"class":214},[200,58926,50977],{"class":223},[200,58928,227],{"class":210},[200,58930,249],{"class":214},[200,58932,58933],{"class":245},"modulesToTemplates",[200,58935,249],{"class":214},[200,58937,550],{"class":214},[200,58939,280],{"class":214},[200,58941,58942,58945,58947,58949,58951,58953,58956,58958,58960,58962,58964,58966,58968,58970,58972,58974,58976,58978,58980,58982],{"class":202,"line":398},[200,58943,58944],{"class":235},"  moduleId",[200,58946,239],{"class":214},[200,58948,50998],{"class":223},[200,58950,227],{"class":210},[200,58952,249],{"class":214},[200,58954,58955],{"class":245},"module_id",[200,58957,249],{"class":214},[200,58959,2089],{"class":210},[200,58961,215],{"class":214},[200,58963,51054],{"class":223},[200,58965,1532],{"class":210},[200,58967,215],{"class":214},[200,58969,53331],{"class":223},[200,58971,227],{"class":210},[200,58973,1532],{"class":214},[200,58975,393],{"class":379},[200,58977,51401],{"class":210},[200,58979,215],{"class":214},[200,58981,53344],{"class":210},[200,58983,252],{"class":214},[200,58985,58986,58989,58991,58993,58995,58997,59000,59002,59004,59006,59008,59010,59012,59014,59016,59018,59020,59022,59024,59026],{"class":202,"line":405},[200,58987,58988],{"class":235},"  templateId",[200,58990,239],{"class":214},[200,58992,50998],{"class":223},[200,58994,227],{"class":210},[200,58996,249],{"class":214},[200,58998,58999],{"class":245},"template_id",[200,59001,249],{"class":214},[200,59003,2089],{"class":210},[200,59005,215],{"class":214},[200,59007,51054],{"class":223},[200,59009,1532],{"class":210},[200,59011,215],{"class":214},[200,59013,53331],{"class":223},[200,59015,227],{"class":210},[200,59017,1532],{"class":214},[200,59019,393],{"class":379},[200,59021,54086],{"class":210},[200,59023,215],{"class":214},[200,59025,53344],{"class":210},[200,59027,252],{"class":214},[200,59029,59030,59032],{"class":202,"line":410},[200,59031,413],{"class":214},[200,59033,416],{"class":210},[1913,59035,59037],{"id":59036},"modules-form","Modules form",[35,59039,59040],{},"In this part, I will add the modules to the form, from the client side to the server side.",[38677,59042,59044],{"id":59043},"adding-modules-to-the-template","Adding modules to the template",[35,59046,59047,59048,59051],{},"In the UI, handling the modules is pretty simple. I fetch the modules from the server and display them in a ",[156,59049,59050],{},"USelectMenu"," with a multiple prop.",[192,59053,59055],{"className":21417,"code":59054,"language":12060,"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,59056,59057,59077,59110,59120,59134,59140,59148,59152,59160,59167,59181,59186,59199,59212,59216],{"__ignoreMap":13},[200,59058,59059,59061,59063,59065,59067,59069,59071,59073,59075],{"class":202,"line":203},[200,59060,600],{"class":214},[200,59062,14892],{"class":235},[200,59064,14590],{"class":379},[200,59066,449],{"class":214},[200,59068,611],{"class":214},[200,59070,1373],{"class":245},[200,59072,611],{"class":214},[200,59074,21781],{"class":379},[200,59076,648],{"class":214},[200,59078,59079,59081,59083,59085,59087,59089,59091,59093,59095,59097,59099,59101,59104,59106,59108],{"class":202,"line":14},[200,59080,443],{"class":379},[200,59082,318],{"class":214},[200,59084,5041],{"class":235},[200,59086,239],{"class":214},[200,59088,50834],{"class":210},[200,59090,413],{"class":214},[200,59092,510],{"class":214},[200,59094,452],{"class":206},[200,59096,48105],{"class":223},[200,59098,227],{"class":210},[200,59100,249],{"class":214},[200,59102,59103],{"class":245},"\u002Fapi\u002Fmodules",[200,59105,249],{"class":214},[200,59107,550],{"class":214},[200,59109,280],{"class":214},[200,59111,59112,59114,59116,59118],{"class":202,"line":255},[200,59113,48123],{"class":235},[200,59115,239],{"class":214},[200,59117,16166],{"class":2423},[200,59119,252],{"class":214},[200,59121,59122,59124,59126,59128,59130,59132],{"class":202,"line":272},[200,59123,50621],{"class":223},[200,59125,239],{"class":214},[200,59127,3714],{"class":214},[200,59129,393],{"class":379},[200,59131,27960],{"class":210},[200,59133,252],{"class":214},[200,59135,59136,59138],{"class":202,"line":283},[200,59137,413],{"class":214},[200,59139,416],{"class":210},[200,59141,59142,59144,59146],{"class":202,"line":300},[200,59143,681],{"class":214},[200,59145,14892],{"class":235},[200,59147,648],{"class":214},[200,59149,59150],{"class":202,"line":310},[200,59151,529],{"emptyLinePlaceholder":20},[200,59153,59154,59156,59158],{"class":202,"line":336},[200,59155,600],{"class":214},[200,59157,21428],{"class":235},[200,59159,648],{"class":214},[200,59161,59162,59164],{"class":202,"line":342},[200,59163,657],{"class":214},[200,59165,59166],{"class":235},"USelectMenu\n",[200,59168,59169,59171,59173,59175,59177,59179],{"class":202,"line":365},[200,59170,23821],{"class":214},[200,59172,30152],{"class":379},[200,59174,449],{"class":214},[200,59176,611],{"class":214},[200,59178,2461],{"class":210},[200,59180,617],{"class":214},[200,59182,59183],{"class":202,"line":371},[200,59184,59185],{"class":379},"    multiple\n",[200,59187,59188,59191,59193,59195,59197],{"class":202,"line":398},[200,59189,59190],{"class":379},"    value-attribute",[200,59192,449],{"class":214},[200,59194,611],{"class":214},[200,59196,3280],{"class":245},[200,59198,617],{"class":214},[200,59200,59201,59204,59206,59208,59210],{"class":202,"line":405},[200,59202,59203],{"class":379},"    option-attribute",[200,59205,449],{"class":214},[200,59207,611],{"class":214},[200,59209,20089],{"class":245},[200,59211,617],{"class":214},[200,59213,59214],{"class":202,"line":410},[200,59215,23321],{"class":214},[200,59217,59218,59220,59222],{"class":202,"line":861},[200,59219,681],{"class":214},[200,59221,21428],{"class":235},[200,59223,648],{"class":214},[38677,59225,59227],{"id":59226},"validating-the-modules","Validating the modules",[35,59229,59230,59231,59234],{},"In the validator, I add a new field ",[156,59232,59233],{},"moduleIds"," that verifies that the modules are an array of integers.",[192,59236,59238],{"className":1370,"code":59237,"language":1373,"meta":13,"style":13},"import { array, number, object, string } from 'zod'\n\nexport const createTemplateValidator = object({\n  title: string({ message: 'Required' }),\n  moduleIds: array(number()).max(6, { message: 'Max 6 modules' }).optional(),\n})\n",[156,59239,59240,59271,59275,59292,59320,59373],{"__ignoreMap":13},[200,59241,59242,59244,59246,59249,59251,59253,59255,59257,59259,59261,59263,59265,59267,59269],{"class":202,"line":203},[200,59243,973],{"class":206},[200,59245,318],{"class":214},[200,59247,59248],{"class":210}," array",[200,59250,550],{"class":214},[200,59252,20132],{"class":210},[200,59254,550],{"class":214},[200,59256,42872],{"class":210},[200,59258,550],{"class":214},[200,59260,22708],{"class":210},[200,59262,981],{"class":214},[200,59264,984],{"class":206},[200,59266,242],{"class":214},[200,59268,19790],{"class":245},[200,59270,992],{"class":214},[200,59272,59273],{"class":202,"line":14},[200,59274,529],{"emptyLinePlaceholder":20},[200,59276,59277,59279,59281,59284,59286,59288,59290],{"class":202,"line":255},[200,59278,1380],{"class":206},[200,59280,14301],{"class":379},[200,59282,59283],{"class":210}," createTemplateValidator ",[200,59285,449],{"class":214},[200,59287,42872],{"class":223},[200,59289,227],{"class":210},[200,59291,230],{"class":214},[200,59293,59294,59296,59298,59300,59302,59304,59306,59308,59310,59312,59314,59316,59318],{"class":202,"line":272},[200,59295,23659],{"class":235},[200,59297,239],{"class":214},[200,59299,22708],{"class":223},[200,59301,227],{"class":210},[200,59303,2752],{"class":214},[200,59305,39347],{"class":235},[200,59307,239],{"class":214},[200,59309,242],{"class":214},[200,59311,41947],{"class":245},[200,59313,249],{"class":214},[200,59315,981],{"class":214},[200,59317,2089],{"class":210},[200,59319,252],{"class":214},[200,59321,59322,59325,59327,59329,59331,59334,59337,59339,59341,59343,59346,59348,59350,59352,59354,59356,59359,59361,59363,59365,59367,59369,59371],{"class":202,"line":283},[200,59323,59324],{"class":235},"  moduleIds",[200,59326,239],{"class":214},[200,59328,59248],{"class":223},[200,59330,227],{"class":210},[200,59332,59333],{"class":223},"number",[200,59335,59336],{"class":210},"())",[200,59338,215],{"class":214},[200,59340,4123],{"class":223},[200,59342,227],{"class":210},[200,59344,59345],{"class":2045},"6",[200,59347,550],{"class":214},[200,59349,318],{"class":214},[200,59351,39347],{"class":235},[200,59353,239],{"class":214},[200,59355,242],{"class":214},[200,59357,59358],{"class":245},"Max 6 modules",[200,59360,249],{"class":214},[200,59362,981],{"class":214},[200,59364,2089],{"class":210},[200,59366,215],{"class":214},[200,59368,53746],{"class":223},[200,59370,1532],{"class":210},[200,59372,252],{"class":214},[200,59374,59375,59377],{"class":202,"line":300},[200,59376,413],{"class":214},[200,59378,416],{"class":210},[35,59380,59381],{},"I arbitrarily set the maximum number of modules to 6. It's a number that I think is enough for a template.",[38677,59383,59385],{"id":59384},"saving-the-modules","Saving the modules",[35,59387,59388],{},"Finally, I update the endpoint to save the template and related modules. I first save the template and then save the modules in the pivot table.",[192,59390,59392],{"className":1370,"code":59391,"language":1373,"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,59393,59394,59413,59417,59439,59461,59465,59491,59495,59535,59549,59563,59581,59587,59591,59605,59684,59688,59704],{"__ignoreMap":13},[200,59395,59396,59398,59400,59403,59405,59407,59409,59411],{"class":202,"line":203},[200,59397,973],{"class":206},[200,59399,318],{"class":214},[200,59401,59402],{"class":210}," createTemplateValidator",[200,59404,981],{"class":214},[200,59406,984],{"class":206},[200,59408,242],{"class":214},[200,59410,42580],{"class":245},[200,59412,992],{"class":214},[200,59414,59415],{"class":202,"line":14},[200,59416,529],{"emptyLinePlaceholder":20},[200,59418,59419,59421,59423,59425,59427,59429,59431,59433,59435,59437],{"class":202,"line":255},[200,59420,1380],{"class":206},[200,59422,1383],{"class":206},[200,59424,38960],{"class":223},[200,59426,227],{"class":210},[200,59428,3711],{"class":379},[200,59430,1095],{"class":214},[200,59432,25386],{"class":386},[200,59434,2089],{"class":214},[200,59436,393],{"class":379},[200,59438,280],{"class":214},[200,59440,59441,59443,59445,59447,59449,59451,59453,59455,59457,59459],{"class":202,"line":272},[200,59442,3723],{"class":379},[200,59444,318],{"class":214},[200,59446,34776],{"class":210},[200,59448,981],{"class":214},[200,59450,510],{"class":214},[200,59452,452],{"class":206},[200,59454,39235],{"class":223},[200,59456,227],{"class":235},[200,59458,25386],{"class":210},[200,59460,416],{"class":235},[200,59462,59463],{"class":202,"line":283},[200,59464,529],{"emptyLinePlaceholder":20},[200,59466,59467,59469,59471,59473,59475,59477,59479,59481,59483,59485,59487,59489],{"class":202,"line":300},[200,59468,3723],{"class":379},[200,59470,41038],{"class":210},[200,59472,510],{"class":214},[200,59474,452],{"class":206},[200,59476,41180],{"class":223},[200,59478,227],{"class":235},[200,59480,25386],{"class":210},[200,59482,550],{"class":214},[200,59484,59402],{"class":210},[200,59486,215],{"class":214},[200,59488,41223],{"class":210},[200,59490,416],{"class":235},[200,59492,59493],{"class":202,"line":310},[200,59494,529],{"emptyLinePlaceholder":20},[200,59496,59497,59499,59501,59503,59505,59507,59509,59511,59513,59515,59517,59519,59521,59523,59525,59527,59529,59531,59533],{"class":202,"line":336},[200,59498,3723],{"class":379},[200,59500,350],{"class":214},[200,59502,21428],{"class":210},[200,59504,360],{"class":214},[200,59506,510],{"class":214},[200,59508,452],{"class":206},[200,59510,47508],{"class":223},[200,59512,1532],{"class":235},[200,59514,215],{"class":214},[200,59516,47696],{"class":223},[200,59518,227],{"class":235},[200,59520,47588],{"class":210},[200,59522,215],{"class":214},[200,59524,51759],{"class":210},[200,59526,2089],{"class":235},[200,59528,215],{"class":214},[200,59530,47711],{"class":223},[200,59532,227],{"class":235},[200,59534,230],{"class":214},[200,59536,59537,59539,59541,59543,59545,59547],{"class":202,"line":342},[200,59538,21442],{"class":235},[200,59540,239],{"class":214},[200,59542,41038],{"class":210},[200,59544,215],{"class":214},[200,59546,3566],{"class":210},[200,59548,252],{"class":214},[200,59550,59551,59553,59555,59557,59559,59561],{"class":202,"line":365},[200,59552,5080],{"class":214},[200,59554,2089],{"class":235},[200,59556,215],{"class":214},[200,59558,52685],{"class":223},[200,59560,227],{"class":235},[200,59562,230],{"class":214},[200,59564,59565,59567,59569,59571,59573,59575,59577,59579],{"class":202,"line":371},[200,59566,15858],{"class":235},[200,59568,239],{"class":214},[200,59570,47527],{"class":210},[200,59572,215],{"class":214},[200,59574,51759],{"class":210},[200,59576,215],{"class":214},[200,59578,3280],{"class":210},[200,59580,252],{"class":214},[200,59582,59583,59585],{"class":202,"line":398},[200,59584,5080],{"class":214},[200,59586,416],{"class":235},[200,59588,59589],{"class":202,"line":405},[200,59590,529],{"emptyLinePlaceholder":20},[200,59592,59593,59595,59597,59599,59601,59603],{"class":202,"line":410},[200,59594,20075],{"class":206},[200,59596,1095],{"class":235},[200,59598,23870],{"class":210},[200,59600,215],{"class":214},[200,59602,59233],{"class":210},[200,59604,416],{"class":235},[200,59606,59607,59609,59611,59613,59615,59617,59619,59621,59623,59625,59627,59629,59631,59633,59635,59637,59639,59641,59643,59645,59647,59649,59651,59653,59656,59658,59660,59662,59665,59667,59669,59671,59673,59675,59678,59680,59682],{"class":202,"line":861},[200,59608,19216],{"class":206},[200,59610,47508],{"class":223},[200,59612,1532],{"class":235},[200,59614,215],{"class":214},[200,59616,47696],{"class":223},[200,59618,227],{"class":235},[200,59620,47588],{"class":210},[200,59622,215],{"class":214},[200,59624,58933],{"class":210},[200,59626,2089],{"class":235},[200,59628,215],{"class":214},[200,59630,47711],{"class":223},[200,59632,227],{"class":235},[200,59634,23870],{"class":210},[200,59636,215],{"class":214},[200,59638,59233],{"class":210},[200,59640,215],{"class":214},[200,59642,3526],{"class":223},[200,59644,227],{"class":235},[200,59646,3280],{"class":386},[200,59648,393],{"class":379},[200,59650,1095],{"class":235},[200,59652,2752],{"class":214},[200,59654,59655],{"class":235}," moduleId",[200,59657,239],{"class":214},[200,59659,3224],{"class":210},[200,59661,550],{"class":214},[200,59663,59664],{"class":235}," templateId",[200,59666,239],{"class":214},[200,59668,56966],{"class":210},[200,59670,215],{"class":214},[200,59672,3280],{"class":210},[200,59674,981],{"class":214},[200,59676,59677],{"class":235},")))",[200,59679,215],{"class":214},[200,59681,47761],{"class":223},[200,59683,466],{"class":235},[200,59685,59686],{"class":202,"line":890},[200,59687,529],{"emptyLinePlaceholder":20},[200,59689,59690,59692,59694,59696,59698,59700,59702],{"class":202,"line":900},[200,59691,3743],{"class":206},[200,59693,41071],{"class":223},[200,59695,227],{"class":235},[200,59697,25386],{"class":210},[200,59699,550],{"class":214},[200,59701,41080],{"class":2045},[200,59703,416],{"class":235},[200,59705,59706,59708],{"class":202,"line":905},[200,59707,413],{"class":214},[200,59709,416],{"class":210},[35,59711,59712],{},[1217,59713,59714],{},"I have to admit that I'm not very confident with this part. I will need to check if there is a better way to do it (using a transaction or a built-in Drizzle feature).",[1913,59716,59718],{"id":59717},"populating-the-template-with-modules","Populating the template with modules",[35,59720,59721],{},[1217,59722,59723],{},"This is where the hard times begin.",[35,59725,59726,59727,59729,59730,59732,59733,59735,59736,215],{},"Initially, I had a custom SQL query that joined the ",[156,59728,51759],{}," table, the ",[156,59731,47325],{}," table, and the ",[156,59734,47331],{}," table. You can learn more about this in the ",[39,59737,59739],{"href":59738},"#creating-a-form-for-templates","previous part",[35,59741,59742,59743,59745,59746,59748,59749,59751],{},"To link the ",[156,59744,2461],{}," table, I tried to add a join with the ",[156,59747,58933],{}," table and then the ",[156,59750,2461],{}," table. The issue I encounter is how to group the modules by template to produce an array of modules for each template.",[35,59753,59754],{},"Something like:",[192,59756,59758],{"className":26986,"code":59757,"language":26988,"meta":13,"style":13},"{\n  \"id\": 1,\n  \"title\": \"My template\",\n  \"modules\": [\n    { \"id\": 1, \"name\": \"Module 1\" },\n    { \"id\": 2, \"name\": \"Module 2\" }\n  ]\n}\n",[156,59759,59760,59764,59778,59796,59808,59842,59876,59880],{"__ignoreMap":13},[200,59761,59762],{"class":202,"line":203},[200,59763,230],{"class":214},[200,59765,59766,59768,59770,59772,59774,59776],{"class":202,"line":14},[200,59767,26999],{"class":214},[200,59769,3280],{"class":379},[200,59771,611],{"class":214},[200,59773,239],{"class":214},[200,59775,3938],{"class":2045},[200,59777,252],{"class":214},[200,59779,59780,59782,59784,59786,59788,59790,59792,59794],{"class":202,"line":255},[200,59781,26999],{"class":214},[200,59783,3566],{"class":379},[200,59785,611],{"class":214},[200,59787,239],{"class":214},[200,59789,27008],{"class":214},[200,59791,55603],{"class":245},[200,59793,611],{"class":214},[200,59795,252],{"class":214},[200,59797,59798,59800,59802,59804,59806],{"class":202,"line":272},[200,59799,26999],{"class":214},[200,59801,2461],{"class":379},[200,59803,611],{"class":214},[200,59805,239],{"class":214},[200,59807,1400],{"class":214},[200,59809,59810,59813,59815,59817,59819,59821,59823,59825,59827,59829,59831,59833,59835,59838,59840],{"class":202,"line":283},[200,59811,59812],{"class":214},"    {",[200,59814,27008],{"class":214},[200,59816,3280],{"class":1978},[200,59818,611],{"class":214},[200,59820,239],{"class":214},[200,59822,3938],{"class":2045},[200,59824,550],{"class":214},[200,59826,27008],{"class":214},[200,59828,20089],{"class":1978},[200,59830,611],{"class":214},[200,59832,239],{"class":214},[200,59834,27008],{"class":214},[200,59836,59837],{"class":245},"Module 1",[200,59839,611],{"class":214},[200,59841,333],{"class":214},[200,59843,59844,59846,59848,59850,59852,59854,59857,59859,59861,59863,59865,59867,59869,59872,59874],{"class":202,"line":300},[200,59845,59812],{"class":214},[200,59847,27008],{"class":214},[200,59849,3280],{"class":1978},[200,59851,611],{"class":214},[200,59853,239],{"class":214},[200,59855,59856],{"class":2045}," 2",[200,59858,550],{"class":214},[200,59860,27008],{"class":214},[200,59862,20089],{"class":1978},[200,59864,611],{"class":214},[200,59866,239],{"class":214},[200,59868,27008],{"class":214},[200,59870,59871],{"class":245},"Module 2",[200,59873,611],{"class":214},[200,59875,34880],{"class":214},[200,59877,59878],{"class":202,"line":310},[200,59879,13674],{"class":214},[200,59881,59882],{"class":202,"line":336},[200,59883,1130],{"class":214},[35,59885,59886,59887,59890],{},"I found the SQLite function ",[156,59888,59889],{},"group_concat"," that can be used to concatenate the modules. I was a little disappointed to have to do this manually because Drizzle presents itself as an ORM. I was expecting to have a way to do this easily.",[35,59892,59893,59894,59904],{},"BUT, the Twitch chat came to the rescue and suggested me to use the ",[39,59895,59898,59901,59902,6577],{"href":59896,"rel":59897},"https:\u002F\u002Form.drizzle.team\u002Fdocs\u002Frqb#include-relations",[77],[156,59899,59900],{},"with"," key from the ",[156,59903,40340],{},". I tried, and it did not really work. Cloudflare was complaining about a missing relation. I was a little bit lost.",[35,59906,59907,59908,59910,59911,59913],{},"I end up by carefully reading the documentation about the many-to-many relation page, and I understood that I need to explicitly define every relation between every table. In other words, I have to tell Drizzle how to join the tables. A template has many modules by using the ",[156,59909,58933],{}," table. A module has many templates by using the ",[156,59912,58933],{}," table. This is the way to define the many-to-many relation.",[192,59915,59917],{"className":1370,"code":59916,"language":1373,"meta":13,"style":13},"\u002F\u002F A template has a many-to-many relation\nexport const templatesRelations = relations(templates, ({ one, many }) => ({\n  \u002F\u002F Linked to the pivot table and not directly to the modules table\n  modules: many(modulesToTemplates),\n}))\n\n\u002F\u002F The pivot table has 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 A module has a many-to-many relation\nexport const modulesRelations = relations(modules, ({ one, many }) => ({\n  \u002F\u002F Linked to the pivot table and not directly to the templates table\n  templates: many(modulesToTemplates),\n}))\n",[156,59918,59919,59924,59959,59964,59977,59983,59987,59992,60022,60037,60054,60071,60079,60093,60108,60123,60131,60137,60141,60146,60179,60184,60196],{"__ignoreMap":13},[200,59920,59921],{"class":202,"line":203},[200,59922,59923],{"class":401},"\u002F\u002F A template has a many-to-many relation\n",[200,59925,59926,59928,59930,59933,59935,59937,59940,59942,59944,59947,59949,59951,59953,59955,59957],{"class":202,"line":14},[200,59927,1380],{"class":206},[200,59929,14301],{"class":379},[200,59931,59932],{"class":210}," templatesRelations ",[200,59934,449],{"class":214},[200,59936,53456],{"class":223},[200,59938,59939],{"class":210},"(templates",[200,59941,550],{"class":214},[200,59943,383],{"class":214},[200,59945,59946],{"class":386}," one",[200,59948,550],{"class":214},[200,59950,53466],{"class":386},[200,59952,390],{"class":214},[200,59954,393],{"class":379},[200,59956,1095],{"class":210},[200,59958,230],{"class":214},[200,59960,59961],{"class":202,"line":255},[200,59962,59963],{"class":401},"  \u002F\u002F Linked to the pivot table and not directly to the modules table\n",[200,59965,59966,59968,59970,59972,59975],{"class":202,"line":272},[200,59967,1395],{"class":235},[200,59969,239],{"class":214},[200,59971,53466],{"class":223},[200,59973,59974],{"class":210},"(modulesToTemplates)",[200,59976,252],{"class":214},[200,59978,59979,59981],{"class":202,"line":283},[200,59980,413],{"class":214},[200,59982,585],{"class":210},[200,59984,59985],{"class":202,"line":300},[200,59986,529],{"emptyLinePlaceholder":20},[200,59988,59989],{"class":202,"line":310},[200,59990,59991],{"class":401},"\u002F\u002F The pivot table has 2 relations\n",[200,59993,59994,59996,59998,60001,60003,60005,60008,60010,60012,60014,60016,60018,60020],{"class":202,"line":336},[200,59995,1380],{"class":206},[200,59997,14301],{"class":379},[200,59999,60000],{"class":210}," modulesToTemplatesRelations ",[200,60002,449],{"class":214},[200,60004,53456],{"class":223},[200,60006,60007],{"class":210},"(modulesToTemplates",[200,60009,550],{"class":214},[200,60011,383],{"class":214},[200,60013,59946],{"class":386},[200,60015,390],{"class":214},[200,60017,393],{"class":379},[200,60019,1095],{"class":210},[200,60021,230],{"class":214},[200,60023,60024,60027,60029,60031,60033,60035],{"class":202,"line":342},[200,60025,60026],{"class":235},"  module",[200,60028,239],{"class":214},[200,60030,59946],{"class":223},[200,60032,51338],{"class":210},[200,60034,550],{"class":214},[200,60036,280],{"class":214},[200,60038,60039,60042,60044,60047,60049,60052],{"class":202,"line":365},[200,60040,60041],{"class":235},"    fields",[200,60043,239],{"class":214},[200,60045,60046],{"class":210}," [modulesToTemplates",[200,60048,215],{"class":214},[200,60050,60051],{"class":210},"moduleId]",[200,60053,252],{"class":214},[200,60055,60056,60059,60061,60064,60066,60069],{"class":202,"line":371},[200,60057,60058],{"class":235},"    references",[200,60060,239],{"class":214},[200,60062,60063],{"class":210}," [modules",[200,60065,215],{"class":214},[200,60067,60068],{"class":210},"id]",[200,60070,252],{"class":214},[200,60072,60073,60075,60077],{"class":202,"line":398},[200,60074,5080],{"class":214},[200,60076,2089],{"class":210},[200,60078,252],{"class":214},[200,60080,60081,60083,60085,60087,60089,60091],{"class":202,"line":405},[200,60082,15697],{"class":235},[200,60084,239],{"class":214},[200,60086,59946],{"class":223},[200,60088,59939],{"class":210},[200,60090,550],{"class":214},[200,60092,280],{"class":214},[200,60094,60095,60097,60099,60101,60103,60106],{"class":202,"line":410},[200,60096,60041],{"class":235},[200,60098,239],{"class":214},[200,60100,60046],{"class":210},[200,60102,215],{"class":214},[200,60104,60105],{"class":210},"templateId]",[200,60107,252],{"class":214},[200,60109,60110,60112,60114,60117,60119,60121],{"class":202,"line":861},[200,60111,60058],{"class":235},[200,60113,239],{"class":214},[200,60115,60116],{"class":210}," [templates",[200,60118,215],{"class":214},[200,60120,60068],{"class":210},[200,60122,252],{"class":214},[200,60124,60125,60127,60129],{"class":202,"line":890},[200,60126,5080],{"class":214},[200,60128,2089],{"class":210},[200,60130,252],{"class":214},[200,60132,60133,60135],{"class":202,"line":900},[200,60134,413],{"class":214},[200,60136,585],{"class":210},[200,60138,60139],{"class":202,"line":905},[200,60140,529],{"emptyLinePlaceholder":20},[200,60142,60143],{"class":202,"line":931},[200,60144,60145],{"class":401},"\u002F\u002F A module has a many-to-many relation\n",[200,60147,60148,60150,60152,60155,60157,60159,60161,60163,60165,60167,60169,60171,60173,60175,60177],{"class":202,"line":3640},[200,60149,1380],{"class":206},[200,60151,14301],{"class":379},[200,60153,60154],{"class":210}," modulesRelations ",[200,60156,449],{"class":214},[200,60158,53456],{"class":223},[200,60160,51338],{"class":210},[200,60162,550],{"class":214},[200,60164,383],{"class":214},[200,60166,59946],{"class":386},[200,60168,550],{"class":214},[200,60170,53466],{"class":386},[200,60172,390],{"class":214},[200,60174,393],{"class":379},[200,60176,1095],{"class":210},[200,60178,230],{"class":214},[200,60180,60181],{"class":202,"line":3645},[200,60182,60183],{"class":401},"  \u002F\u002F Linked to the pivot table and not directly to the templates table\n",[200,60185,60186,60188,60190,60192,60194],{"class":202,"line":4020},[200,60187,53479],{"class":235},[200,60189,239],{"class":214},[200,60191,53466],{"class":223},[200,60193,59974],{"class":210},[200,60195,252],{"class":214},[200,60197,60198,60200],{"class":202,"line":4475},[200,60199,413],{"class":214},[200,60201,585],{"class":210},[35,60203,60204,60205,60210],{},"This part is super important. In an ORM like ",[39,60206,60209],{"href":60207,"rel":60208},"https:\u002F\u002Flucid.adonisjs.com",[77],"Lucid",", the usage of a pivot table is transparent. With Drizzle, you have to define relation between every table. It's a little bit more verbose, but it's also more explicit.",[35,60212,60213],{},"Finally, I can update the endpoint to retrieve the templates with their modules:",[192,60215,60217],{"className":1370,"code":60216,"language":1373,"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,60218,60219,60237,60267,60276,60287,60298,60307,60315,60324,60328,60332,60336,60342,60346,60352],{"__ignoreMap":13},[200,60220,60221,60223,60225,60227,60229,60231,60233,60235],{"class":202,"line":203},[200,60222,1380],{"class":206},[200,60224,1383],{"class":206},[200,60226,38960],{"class":223},[200,60228,227],{"class":210},[200,60230,3711],{"class":379},[200,60232,3714],{"class":214},[200,60234,393],{"class":379},[200,60236,280],{"class":214},[200,60238,60239,60241,60243,60245,60247,60249,60251,60253,60255,60257,60259,60261,60263,60265],{"class":202,"line":14},[200,60240,3723],{"class":379},[200,60242,54086],{"class":210},[200,60244,510],{"class":214},[200,60246,452],{"class":206},[200,60248,47508],{"class":223},[200,60250,1532],{"class":235},[200,60252,215],{"class":214},[200,60254,3509],{"class":210},[200,60256,215],{"class":214},[200,60258,51759],{"class":210},[200,60260,215],{"class":214},[200,60262,40340],{"class":223},[200,60264,227],{"class":235},[200,60266,230],{"class":214},[200,60268,60269,60272,60274],{"class":202,"line":255},[200,60270,60271],{"class":235},"    with",[200,60273,239],{"class":214},[200,60275,280],{"class":214},[200,60277,60278,60281,60283,60285],{"class":202,"line":272},[200,60279,60280],{"class":235},"      category",[200,60282,239],{"class":214},[200,60284,2424],{"class":2423},[200,60286,252],{"class":214},[200,60288,60289,60292,60294,60296],{"class":202,"line":283},[200,60290,60291],{"class":235},"      creator",[200,60293,239],{"class":214},[200,60295,2424],{"class":2423},[200,60297,252],{"class":214},[200,60299,60300,60303,60305],{"class":202,"line":300},[200,60301,60302],{"class":235},"      modules",[200,60304,239],{"class":214},[200,60306,280],{"class":214},[200,60308,60309,60311,60313],{"class":202,"line":310},[200,60310,32732],{"class":235},[200,60312,239],{"class":214},[200,60314,280],{"class":214},[200,60316,60317,60320,60322],{"class":202,"line":336},[200,60318,60319],{"class":235},"          module",[200,60321,239],{"class":214},[200,60323,22192],{"class":2423},[200,60325,60326],{"class":202,"line":342},[200,60327,4950],{"class":214},[200,60329,60330],{"class":202,"line":365},[200,60331,12083],{"class":214},[200,60333,60334],{"class":202,"line":371},[200,60335,339],{"class":214},[200,60337,60338,60340],{"class":202,"line":398},[200,60339,5080],{"class":214},[200,60341,416],{"class":235},[200,60343,60344],{"class":202,"line":405},[200,60345,529],{"emptyLinePlaceholder":20},[200,60347,60348,60350],{"class":202,"line":410},[200,60349,3743],{"class":206},[200,60351,54219],{"class":210},[200,60353,60354,60356],{"class":202,"line":861},[200,60355,413],{"class":214},[200,60357,416],{"class":210},[35,60359,60360,60361,60363],{},"The usage of the ",[156,60362,59900],{},", once everything is correctly defined, makes things so much easier; I really love it.",[1913,60365,30681],{"id":60366},"conclusion-4",[35,60368,60369],{},"In my mind, this part was easy, and in 30 minutes, it was done. I already did a similar thing using Lucid from Adonis. In reality, it was not really hard, but finding the right part of the Drizzle documentation and then understanding it was way more difficult than expected. This is largely because Drizzle does not abstract SQL but acts as a little wrapper, and everything must be explicit.",[35,60371,60372],{},"I'm happy to have done it, I learned a lot, and a huge thanks to the chat for helping me.",[1171,60374,60375],{},"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":60377},[60378,60379],{"id":46042,"depth":14,"text":46043},{"id":46324,"depth":14,"text":46325},"2024-05-01","Since months I want to start streaming on Twitch for the pleasure of sharing things and live coding. I finally started my first stream.",{},"\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey","---\ncontentId: c21a371e-abdd-4325-b296-dadcb8deec25\ntitle: \"Create a Full-Stack Nuxt Application: A Twitch Journey\"\ndescription: Since months I want to start streaming on Twitch for the pleasure of sharing things and live coding. I finally started my first stream.\ndate: 2024-05-01\n---\n\n**I finally did it!**\n\nAfter months of thinking about it, I finally started my first stream on Twitch, the 28th of April 2024. It's a very important step for me because I love sharing my knowledge and talking about what I'm doing.\n\n\u003Cdiv\n  class=\"grid grid-cols-1 md:grid-cols-2 gap-4\"\n>\n  \u003CCalloutFollowMeOnTwitch \u002F>\n  \u003CCallout icon=\"i-simple-icons-youtube\" text=\"View Replays\" href=\"https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes\" \u002F>\n  \u003CCallout icon=\"i-simple-icons-x\" text=\"Don't Miss a Thing\" href=\"https:\u002F\u002Fx.com\u002Fsoubiran_\" \u002F>\n  \u003CCallout icon=\"i-simple-icons-github\" text=\"The Full-Stack Application\" href=\"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion\" \u002F>\n\u003C\u002Fdiv>\n\nWhen I was at school, I organized some lessons for my classmates to explain programming. I also joined a community to explain things about computers, I gave some talks, and recently, I started to write articles on my blog.\n\n**Twitch is just a new way to continue doing what I love!**\n\nFor the coming weeks, I'll share with you my journey to build a full-stack Nuxt application, deployed on Cloudflare, called Orion. I'll explain how I built it, the choices I made, and the problems I encountered. My goal through this article is to give you an honest and realistic view of the pros and cons of building a full-stack application on the edge with Nuxt in May 2024 through the process of building a real-world application.\n\nIn this article, I'll share with you every step of this first series of streams. If you don't speak French (because streams are in French), if you missed a stream, or if you simply prefer to read, this article is for you!\n\n## Discovering NuxtHub and starting the full-stack Nuxt application\n\n![Thumbnail of my first 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) is a full-stack application built with Nuxt and developed live [on Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). You can see the replay on [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) and the source code is open-source on [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nThis was my first stream, and to be honest, I was a bit stressed. The moment when you have to change the scene from the \"Starting Soon\" to the facecam is hard. But once I started to talk, everything went well. _However, that doesn't mean it's the best stream you'll ever see. We're going to improve!_\n\n![Myself, not very comfortable in front of the camera.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-04-28-me.png)\n\nFirst, I introduce [NuxtHub](https:\u002F\u002Fhub.nuxt.com). It is a platform built by the Nuxt team to facilitate the deployment and management of Nuxt applications deployed on Cloudflare. In a few clicks, you can deploy your Nuxt application on Cloudflare. That's really magical! Thanks to a Nuxt module, you will be able to develop your application locally and then deploy it without tweaking your code. Everything, from the database to the blog storage, works seamlessly in both environments.\n\nI tried to deploy a template from NuxtHub to show how easy it is to use the platform. I chose the Atinotes template. Unfortunately, this application produces a bundle bigger than 1MB, the limit for the free tier, so the deployment fails. Sad for a demonstration. _After this fail, I was thinking about a way to prevent this and to tell users whether a template requires a paid plan or not. [I raised an issue](https:\u002F\u002Fgithub.com\u002Fnuxt-hub\u002Ffeedback\u002Fissues\u002F13) that was quickly fixed!_. Demo will wait for later.\n\nAfter this beautiful demo effect, we start coding the full-stack Nuxt application by adding authentication. To do so, we use the module [`nuxt-auth-utils`](https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-auth-utils) from [Sébastien Chopin](https:\u002F\u002Fgithub.com\u002FAtinux). With a few lines of code, we add authentication. Here is the code we used:\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\nThat's really simple and it works like a charm, nicely integrated into a Nuxt application.\n\nTo finish the stream, I quickly set up NuxtHub into my application to show how easy it is to play with a database. I tried to use raw SQL, but the recipe to migrate the database was not working. _[I raised an issue](https:\u002F\u002Fgithub.com\u002Fnuxt-hub\u002Ffeedback\u002Fissues\u002F15)_ about it and it's now fixed! After this second fail, I decided to start using Drizzle ORM, a simple but powerful ORM compatible with the workers of Cloudflare. In all cases, I should have to use Drizzle because managing everything with raw SQL is too much work.\n\nDuring the live, someone asked how to apply migration to the production database. I have to admit that I didn't know the answer and nothing in the documentation was clear about it. _[I raised an issue](https:\u002F\u002Fgithub.com\u002Fnuxt-hub\u002Ffeedback\u002Fissues\u002F14)_ about it that received an answer. _You have to deploy your app and then start the local dev with a remote connection to run migrations_ Clearly, it's far from being good for the moment, but with the upcoming [Nitro tasks](https:\u002F\u002Fnitro.unjs.io\u002Fguide\u002Ftasks), it should be easier in the near future. _I really hope._\n\nGlobally, I'm so happy to have started this stream. Even if it's far from being good, but getting started is already a big step forward, and I know that future streams will only get easier to start.\n\n_I was so stressed that I went very fast in my explanations, so I decided that the next stream would be more talkative to re-explain different concepts._\n\nThe project is open-source and available on GitHub at \u003CGitHubLink repo=\"barbapapazes\u002Forion\" \u002F>! Give it a star if you like it! ⭐\n\n## Discussion on Cloudflare, NuxtHub, and GitHub OAuth\n\n![Thumbnail of my second 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) is a full-stack application built with Nuxt and developed live [on Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). You can see the replay on [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) and the source code is open-source on [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nI was frustrated by my first live stream. With the stress, I didn't explain things like what is Cloudflare, what is the edge and how NuxtHub is related to Cloudflare. But I know it's important to explain these concepts since it's new technologies and not everyone knows them.\n\nAt StrasbourgJS or Devoxx, I talked about UnJS and problems solved by the ecosystem. Many people, during or after, ask me about the edge. What is it? Why do we need it? Where does it come from?\n\n\u003CCalloutReadMore id=\"ba562d1f-534c-4cef-9004-2fefc09829a6\" \u002F>\n\n\u003CCalloutReadMore id=\"08852918-9829-4658-97b3-1aabede8e9fe\" \u002F>\n\nThe goal of my streams is to try this new technology, and with these feedbacks, it was a necessity to explain it.\n\n### Cloudflare at a glance\n\nCloudflare is a company that provides a lot of services to make the web faster and more secure like a zero-trust solution, a WAF, a CDN, a DNS, a DDoS protection, and more for both companies and individuals.\n\nTheir most known product is the reverse proxy. Cloudflare will act as the main and single entry point for your traffic. Then, it will route, internally, the traffic to the right server using the DNS. This allows you to hide your IP server, to cache static assets, and to protect your server from DDoS attacks.\n\n![Reverse proxy schema.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Freverse-proxy.png)\n\nIn order to provide these services, they built a network of data centers around the world to be as close as possible to the users to serve cached content faster and to be able to mitigate attacks. It's called the edge network. It is at the frontier between users and the web, for which it is the gateway.\n\nIn September 2017, [they announced Cloudflare Workers](https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-cloudflare-workers). To understand the objective at the time, let's quote the article:\n\n> What if you want to load balance with a custom affinity algorithm? What if standard HTTP caching rules aren't quite right, and you need some custom logic to boost your cache hit rate? What if you want to write custom WAF rules tailored for your application?\n>\n> **You want to write code**\n\nWorkers have been released to allow developers to customize the infrastructure to their needs. The Cloudflare Workers solution is built on top of the Service Worker API, a standard in the browser to intercept and modify requests and responses.\n\nSince then, they have built a full capabilities platform for developers around the workers:\n\n\u003C!-- TODO: create a component grid (and document it: heavily related items, better presentation than a simple list) -->\n\n- [Workers KV, serverless key-value store](https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-workers-kv)\n- [D1, serverless SQL database](https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-d1)\n- [R2, serverless blob storage](https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-r2-object-storage)\n- [Queues, serverless task queues](https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-cloudflare-queues)\n- [Workers AI, serverless GPU-powered inference](https:\u002F\u002Fblog.cloudflare.com\u002Fworkers-ai)\n\nWith all these services, it's now (theoretically and that's the purpose of this series to answer the question) possible to build an application entirely on the Cloudflare's global network.\n\n### NuxtHub\n\n![NuxtHub home page.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fnuxt-hub-home-page.png)\n\nSince the version 3 of Nuxt, the team is pushing the edge. Nitro, the server part of Nuxt, can be deployed everywhere, current and future platform, and is optimized for the edge. H3, the underlying HTTP server wakes up in less than 2ms, and the build output of Nitro is less than 1 MB, node modules included.\n\nNuxtHub is a continuation of this effort. First, it's a platform to deploy Nuxt applications on Cloudflare. It's a two-click deployment. You connect your Cloudflare account because it's built on top of Cloudflare, you choose a template or a repo, you click on deploy, and that's it. _Your application is live on the edge._ It's feeling like magic.\n\n![NuxtHub templates that can be deployed with two clicks.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fnuxt-hub-deploy.png)\n\nNuxtHub is also a module for your Nuxt app to add data access capabilities. Locally, you will be able to play instantly with a database, a KV store, and even a blob storage. In production, you do not need to change your code, it will work the same way. This is clearly the true magic of NuxtHub, powered with [`workerd`](https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fworker).\n\n![NuxtHub schema (from hub.nuxt.com).](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fnuxt-hub-schema.png)\n\nThere is also a feature to allow a local dev environment to interact with the remote data. Not sure about the use case, but it's definitely interesting.\n\nEven if the platform is still new, announced during Vue Amsterdam 2024, it's already very promising. The team is very reactive, and the platform is evolving quickly. I'm very excited to see what the future holds for NuxtHub but have some hints in documentation:\n\n> We plan to provide a complete backend experience for Nuxt apps through various @nuxthub packages.\n>\n> - @nuxthub\u002Fcore: Main package to provide storage features\n> - @nuxthub\u002Fauth: Add authentication for user management (soon)\n> - @nuxthub\u002Femail: Send transactional emails to your users (soon)\n> - @nuxthub\u002Fanalytics: Understand your traffic and track events within your application and API (soon)\n> - @nuxthub\u002F...: You name it!\n\n### GitHub OAuth\n\nDuring the previous live stream, I added authentication, using `nuxt-auth-utils` to Orion, the full-stack Nuxt application. I used GitHub OAuth, and I think it's important to see how it works under the hood.\n\nDuring the live, we jumped into the code, and I explained how it works. Here is a schema to help you understand the process:\n\n![GitHub OAuth schema.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Foauth-github.png)\n\n1. The user clicks on the login button, which points to `\u002Fauth\u002Fgithub`.\n2. The server sends a redirect to the GitHub OAuth page.\n3. The user accepts the authorization from GitHub.\n4. GitHub redirects the user back to the application callback URL with a code.\n5. The server exchanges the code for the tokens.\n6. The server uses the tokens to get the user.\n7. The server stores the user in a session.\n8. The server redirects the user to the home page.\n\nIn these steps, the server is Nitro, and we do not talk about the possible errors. There are many possible errors like the user refuses the authorization, the code is invalid, the tokens are invalid, the user is not found, etc. Each error must be handled to provide a good user experience.\n\n#### Code of the GitHub OAuth handler\n\nNow, let's dive into the code of `nuxt-auth-utils` to see how it works.\n\nTo simplify, I removed some parts of the code that are not necessary to understand the process, and I focused on the main steps. You can find the full code in the [GitHub repository](https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-auth-utils).\n\n```ts\n\u002F**\n * GitHub OAuth event handler.\n *\n * _The code is simplified for the article and doesn't work as is._\n *\u002F\nexport function githubEventHandler({ config, onSuccess }: OAuthConfig\u003COAuthGitHubConfig>) {\n  return eventHandler(async (event: H3Event) => {\n    config = { \u002F** ... *\u002F}\n\n    \u002F**\n     * Get the query from the event.\n     *\u002F\n    const query = getQuery(event)\n\n    if (query.error) {\n      \u002F\u002F Handle error\n      return\n    }\n\n    if (!query.code) {\n      \u002F**\n       * Redirect to GitHub OAuth page if there is no code in the query.\n       * If the user accepts the authorization, GitHub will redirect the user back to the application with the code.\n       * The code is the one-time code to exchange for the 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     * Use the code to get the 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     * Use the access token to get the user.\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     * Call the onSuccess callback with the user and the tokens.\n     *\n     * The OAuth is done, and the user can now be authenticated using our system.\n     * With Nitro, we store the user in a session within a cookie.\n     *\u002F\n    return onSuccess(event, {\n      user,\n      tokens,\n    })\n  })\n}\n```\n\nAs we can expect, the code is quite simple, and we easily recognize the steps we saw in the schema.\n\n### Conclusion\n\nI didn't code during this live. I wanted to explain many important concepts to help everyone understand the context of the project Orion. I hope it was clear, and that you learned something new.\n\nThe project is open-source and available on GitHub at \u003CGitHubLink repo=\"barbapapazes\u002Forion\" \u002F>! Give it a star if you like it! ⭐\n\n## SQL schema and first blocks of Orion's admin interface\n\n![Thumbnail of my third 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) is a full-stack application built with Nuxt and developed live [on Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). You can see the replay on [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) and the source code is open-source on [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nIn this third stream, I created the SQL schema for the database. I then set up Nuxt UI and Nuxt UI Pro to create the admin layout and the first form for categories. This article is a summary of the stream and the work done.\n\n### SQL schema\n\nWhat is a SQL schema and why did I create the SQL schema? These are two good questions.\n\nA SQL schema is a way to organize the database. It is a blueprint of how the database will be structured. So it's very useful to think about it before starting to code. It does not need to be perfect, but helps to iterate on the database structure, avoiding errors, and to have a clear vision of the project. _I personally think it's a must-have when starting a new project._\n\nSo, before writing any code for the project Orion, I created the SQL schema.\n\n![SQL Schema](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fsql-schema.png)\n\nTo build this schema, I used [drawSQL](https:\u002F\u002Fdrawsql.app\u002F). It's a web app to create SQL schema with a nice UI. It's very easy to use and free for small projects. I recommend it.\n\nThe database for the project Orion, the full-stack Nuxt application, is simple and common for this type of project.\n\n- A `users` table to store the users of the application. Storing users is useful for the admin interface, to ban a user, or to give admin rights.\n- A `categories` table to store the categories of the theme like blog, e-commerce, documentation, etc. It's a simple table with a name and a slug. The slug is used for the URL of the category.\n- A `modules` table to store the modules of the theme. The table will have a name, a slug, and a type (official, community). This will be useful to filter the themes by modules.\n- A `themes` table to store the main content of the application. It will have a lot of columns but we will see that in another stream.\n- A `feedbacks` table to store the feedbacks from admins about a theme. Indeed, a theme must be reviewed before being published and the feedback will be used to tell the creator what to improve.\n\nThis schema is a good start to clarify things and to know where to get started but I already know that it will evolve.\n\n### Nuxt UI and Nuxt UI Pro\n\nNuxt UI is a component library made by the Nuxt team. Initially, it was an internal tool for their own projects like [Volta](https:\u002F\u002Fvolta.net). In May 2023, they decided to [open-source it](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Freleases\u002Ftag\u002Fv2.0.0) and to make it available for everyone for free. _Today, Nuxt UI is my go-to for building user interfaces with Nuxt because it's simple, beautiful but yet highly customizable._\n\nNuxt UI Pro is built on top of Nuxt UI and provides layouts related components like a page, a hero, a header and complete layouts like a dashboard, a blog, a documentation, etc. It's a paid product, but it's worth it. _I'm using it for Orion, the full-stack application, because it's a time saver and it's beautiful._ The better part is that it's free during development (the build process will require a key), so you can try it before buying it.\n\nDuring the stream, I start by installing Nuxt UI and Nuxt UI Pro. I then create the layout for the admin interface and the page for the categories. Let's see the code.\n\n### Admin layout\n\nWith Nuxt UI Pro, this part was very easy. There is no navbar, nor sidebar for the moment. _The goal is to add features and components step by step, when needed, and to avoid to anticipate too much._ Here is the code for the layout `layouts\u002Fadmin.vue`.\n\n```vue\n\u003Ctemplate>\n  \u003CUDashboardLayout>\n    \u003Cslot \u002F>\n  \u003C\u002FUDashboardLayout>\n\u003C\u002Ftemplate>\n```\n\n### Categories page\n\nBefore developing the view, I create the API endpoint to get and update the categories.\n\n![Result of the Layout and Page for Categories.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Flayout-and-page-for-categories.png)\n\n#### Categories API\n\nThanks to Nitro and Drizzle (setup in the first stream), it is very easy. In the file `server\u002Fapi\u002Fcategories\u002Findex.get.ts` (the `.get.` is used to specify the HTTP verb), I wrote the following code:\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\nThe `useDrizzle` utils is a wrapper around the Drizzle ORM. This code will return all the categories with only the `id`, `slug`, and `name` fields. _It's a good practice to only return the fields you need to avoid sending too much data._\n\nThen, I created the endpoint to create a new category, and it's the same process. I created the file `server\u002Fapi\u002Fcategories\u002Findex.post.ts` with the following code:\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\nIn this code, there is no security check, and every user can create a category. It's clearly not a use case for our application, but the bouncer will be added later. In the same time, I did not verify the body. It's important to validate data received from a client to avoid security issues. **It will be the subject for the next stream**.\n\n#### Categories content\n\nThe page for the categories is built with Nuxt UI Pro, which makes the code very 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!-- Content -->\n    \u003C\u002FUDashboardPanel>\n  \u003C\u002FUDashboardPage>\n\u003C\u002Ftemplate>\n```\n\nWithin the `UDashboardPanel`, we can add a header with the page title and a button to create a new category and a table to display the categories.\n\nFirst, let's add the table in the `UDashboardPanel`:\n\n```vue\n\u003Cscript setup lang=\"ts\">\nconst columns = [{\n  key: 'id',\n  label: '#'\n}, {\n  key: 'name',\n  label: 'Name',\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\nIt's hard to be simpler! _Thanks Nuxt UI for the work!_\n\nThen, I add the header with the title and the button to create a new category:\n\n```vue\n\u003Ctemplate>\n  \u003CUDashboardNavbar\n    title=\"Categories\"\n  >\n    \u003Ctemplate #right>\n      \u003CUButton\n        label=\"New category\"\n        trailing-icon=\"i-heroicons-plus\"\n        color=\"gray\"\n      \u002F>\n    \u003C\u002Ftemplate>\n  \u003C\u002FUDashboardNavbar>\n\u003C\u002Ftemplate>\n```\n\nStill very easy. The button will be used to open a modal with a form to create a new category. I started to create the form but in a very simple way:\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: 'Required' }),\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=\"Name\" name=\"name\">\n      \u003CUInput v-model=\"state.name\" \u002F>\n    \u003C\u002FUFormGroup>\n\n    \u003CUButton type=\"submit\">\n      Submit\n    \u003C\u002FUButton>\n  \u003C\u002FUForm>\n\u003C\u002Ftemplate>\n```\n\nI use the form as explained in the Nuxt UI documentation. I do not really choose Yup for the validation but it was the first proposed. _We will see if it's a good choice in the future._ In the `onSubmit` method, I just do a fetch to the API to create a new category. In the next stream, we will improve it by handling the success and the error state.\n\nThis component is put into a modal using the component `UModal` and listening to the click event on the button to open a modal.\n\n_A point I'm not sure about is how to handle the response of the API. Should I return the new category and put it in the table, or should I refresh the table? For the moment, I opted for the second one._\n\nUsing Nuxt UI helps me to build the admin interface quickly, without any custom components.\n\n\u003CCallout icon=\"i-simple-icons-github\" text=\"View Source Code\" to=\"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion\" target=\"_blank\" \u002F>\n\n### End of the stream\n\nIt was definitely a good stream. The project is moving forward without a lot of difficulties and it is a pleasure to work on it. I am looking forward to the next stream to continue working on the admin interface and the forms. Nuxt UI and Nuxt UI Pro are game changers, simple code, easy to use, and I can focus on the features of the application.\n\nThe project is open-source and available on GitHub at \u003CGitHubLink repo=\"barbapapazes\u002Forion\" \u002F>! Give it a star if you like it! ⭐\n\n## Let's continue the admin interface\n\n![Thumbnail of my fourth 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) is a full-stack application built with Nuxt and developed live [on Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). You can see the replay on [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) and the source code is open-source on [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nDuring the previous stream, I started creating the categories page. I implemented the API endpoints to get and create categories, and I built the UI to display the categories and the form to create a new category. In this stream, I've continued working on the admin interface by adding the edit category form and creating a new page to manage users. I've also added the bouncer to secure the access to the admin part of the application, both for the frontend and the backend. Let's see the work done in more detail.\n\n### Edit category form\n\nThis part was very similar to the create category form. Instead of submitting the form data to the API to create a new category, I changed the endpoint to update an existing category.\n\nI created the form in a new component `components\u002Fcategories\u002FCategoryEditForm.vue` (and renamed the previous form to `CategoryCreateForm.vue`). I also created a new endpoint `server\u002Fapi\u002Fcategories\u002F[id].put.ts` to update a category. This endpoint updates a category using the `id` in the URL and the body of the request:\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_The 200 status code could not be the best choice (204 is better with a `PUT`), but I'm still thinking if I should return the updated category or not._\n\nI also tried to validate the body of the request using Yup because it was the tool used in the frontend. However, I quickly realized that it was not possible to cast a string to a number. I know myzod can do it so I tried it. I encountered [an issue](https:\u002F\u002Fgithub.com\u002Funjs\u002Fh3\u002Fissues\u002F745) and instead used Zod. _I moved the frontend to Zod off-stream._\n\n### Users page\n\nActually, when a user logs in, related data from GitHub are stored in the database (_while writing this part, I'm wondering if this is necessary because it means that a log in is a write which is more expensive than a read_) and in the session. This behavior is useful to promote the user to an admin or to ban a user. But for the moment, there is no way to manage users. So, I created a new page to manage users. Like the categories page, the page will display the users in a table. However, it will not be possible to create or update a user for the moment.\n\nIn order to retrieve the users from the database, I created the API endpoint `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\nTo show them, I use the `UTable` component from Nuxt UI Pro. The code is very similar to the categories page:\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: 'Username',\n  sortable: true\n}, {\n  key: 'roleType',\n  label: 'Role Type'\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\nAs expected, the code is very similar to the categories page.\n\nNow we have two pages, one for categories and one for users, so I added a sidebar to navigate between them. Nothing complicated here, I just used the `UDashboardSidebar` component from Nuxt UI Pro and added the links to the categories and users pages:\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nconst links = [{\n  id: 'categories',\n  label: 'Categories',\n  icon: 'i-heroicons-tag',\n  to: '\u002Fadmin\u002Fcategories',\n  tooltip: {\n    text: 'Categories',\n  }\n}, {\n  id: 'users',\n  label: 'Users',\n  icon: 'i-heroicons-user-group',\n  to: '\u002Fadmin\u002Fusers',\n  tooltip: {\n    text: 'Users',\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### Protecting the admin part\n\nWhen building the administration part of an application, it's important to secure and restrict access to it, for both the frontend and the backend. This means that only authorized users can view and manipulate the data.\n\nTo restrict access to the UI part of the admin, i.e., the frontend, I used a middleware. It's a function that runs before navigating to the page and can be used to check if the user is authenticated and has the right permissions. In this case, I created a middleware `middleware\u002Fadmin.ts` to check if the user is authenticated and has the role of an 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_The middleware must be used on pages that need to be protected using `definePageMeta`._\n\nI get the user from the session using `useUserSession` and check if the user has the role of an admin, if not, I redirect the user to the home page.\n\nThe backend is way more critical than the frontend but it's not more difficult than the frontend to secure. In order to do so, I create a util named `requireAdminUser` in the file `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\nThis function checks if the user is authenticated and has the role of an admin. If not, it throws an error with a 401 status code. This function is used in the API endpoints to secure them:\n\n```ts\nexport default defineEventHandler(async (event) => {\n  await requireAdminUser(event)\n\n  \u002F\u002F Code to handle the request\n})\n```\n\nWith these two functions, the frontend and the backend of the admin part are secured. Only authenticated users with the role of admin can access the admin part of the application.\n\n### Coming\n\nThis stream was really good and set important things for the future. The next stream will be about the modules and the theme creator. I really hope to be able to push in production soon. I'm really excited about it and to see the feedback from the community.\n\nThe project is open-source and available on GitHub at \u003CGitHubLink repo=\"barbapapazes\u002Forion\" \u002F>! Give it a star if you like it! ⭐\n\n## Fetch an external API for the modules\n\n![Thumbnail of my fifth live stream.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-18.png)\n\n_[Orion](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion) is a full-stack application built with Nuxt and developed live [on Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). You can see the replay on [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) and the source code is open-source on [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nBefore going further, and implementing the modules page, I decided to change the `useFetch` behavior from current Categories and Users page. Actually, the `useFetch` composable block the client-side navigation until the data is fetched. This gives the sensation of a slow application. To avoid this, I can use the flag `lazy`. On server-side, this does not change anything and the page will still be served with data. On the client-side, the navigation will be instant and I will show a skeleton while the data is fetching using the `pending` ref.\n\n```ts\nconst { data, pending } = await useFetch('\u002Fapi\u002Fcategories', {\n  deep: false,\n  lazy: true, \u002F\u002F \u003C= Do not block the navigation\n  default: () => []\n})\n```\n\nThe `pending` ref is a boolean that tells if the data is fetching or not. I simply pass it to the `loading` props of the `UTable` component from Nuxt UI Pro and _voilà_! The user will see a loader while the data is fetching and the navigation will be instant.\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=\"Without flag\" loading=\"lazy\"\u002F>\n    \u003Cfigcaption>Without lazy flag (blocking 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=\"With lazy flag\" loading=\"lazy\" \u002F>\n    \u003Cfigcaption>With lazy flag (instant navigation)\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n\u003CCallout icon=\"i-simple-icons-youtube\" text=\"Learn More about useFetch\" to=\"https:\u002F\u002Fyoutu.be\u002Fb1S5os65Urs\" target=\"_blank\" \u002F>\n\n### Modules page\n\nModules are [Nuxt modules](https:\u002F\u002Fnuxt.com\u002Fmodules). They will be linked to the themes and will be used to filter the themes. Imagine you're looking for a theme with good SEO and precisely a sitemap, you'll be able to filter the themes by the modules they use. This is the goal of the modules feature. It will also help the user to understand how the theme is built and what are the features.\n\nThe first thing I do is adding a new page `pages\u002Fadmin\u002Fmodules.vue`. Then, I added the table to display the modules and page meta to use the admin layout and admin middleware. It's similar to the categories and users pages so I will not detail it here.\n\n#### Filling the database\n\nThe main question is: how to fill the database with modules? For the categories, I create them manually. This is okay because there are not a lot of categories and no way to get a predefined list, but for the modules, it's different. Modules are made by the core team or the community and there are a lot of them. Also, the list is not static and can change over time. So I need to find an API to fetch modules from. Fortunately, Nuxt has an API to get the modules list: [`api.nuxt.com\u002Fmodules`](https:\u002F\u002Fapi.nuxt.com\u002Fmodules). This API is not documented but it's public and I can use it.\n\nThanks to this API, I will be able to fetch data, sanitize it to keep only the useful information, and store it in the database. I will create an endpoint, accessible only for the admin, to fetch the modules and store them in the database. A second endpoint will be available to retrieve the modules from the database. This will be useful for the frontend to display the modules.\n\n#### Fetching the external API\n\nI create an endpoint `server\u002Fapi\u002Fmodules\u002Ffetch.post.ts` (_off stream, I found that calling this endpoint `sync` was a better idea_) to fetch the modules from the external API and store them in the database.\n\nFirst, I add the utility `requireAdminUser` to secure the endpoint. Then, I fetch the modules from the external API using the global `$fetch` function:\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\nThe data returned by the API contains a lot of data that I don't need. I only need the `name`, `repo`, `type`, and `icon` of the modules. So I sanitize the data to keep only the useful information:\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\nI choose to not keep the type `third party` because it can be a module from anybody and I don't know the quality of them. The `repo` key was not planned to have it but I think it's a good idea to have it. I could use it to add a link to the GitHub repository of the module on a theme page.\n\nNow I have my modules, I need to store them in the database, and I also need to create the table to store them. In the file `server\u002Fdatabase\u002Fschema.ts`, I create the 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> Do not forget to run the drizzle command to generate the SQL migration.\n\nIn the endpoint, it's now possible to insert the modules into the database:\n\n```ts\nawait useDrizzle().insert(tables.modules).values(modules).execute()\n```\n\nBUT, surprise, it **does not work**. I did not know, but the [Cloudflare D1 has a (several) limit](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fd1\u002Fplatform\u002Flimits\u002F) on the bound parameters at 100. In reality, this limit exists because they use a SQLite under the hood which has this limit for security reasons.\n\nWhat is a bound parameter? It's a parameter in a SQL query that is replaced by a value when the query is executed. For example, in the query `SELECT * FROM users WHERE id = ?`, the `?` is a bound parameter. In the case of the insert query `INSERT INTO users (name, email) VALUES (?, ?)`, the bound parameters are the values to insert in the table. The limit is 100 bound parameters. Warning, **a row is not a bound parameter**, a bound parameter is a single value, meaning that if you insert a row with 10 columns, it will count as 10 bound parameters. There are 42 modules with 4 columns, so 168 bound parameters.\n\nThis is a real problem because I'll have to split the insert into multiple queries and a worker is time limited, so if it runs too long, the worker could fail.\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\nIn order to have a dynamic insert in case I change the number of columns, I calculate the maximum number of rows I can insert in a single query. Then, I loop over the modules and insert the maximum number of rows in each query. I also use the `onConflictDoNothing` method to avoid inserting the same module twice if I run the endpoint multiple times.\n\n_I tried this in a production environment, and everything is working fine._\n\nI choose to return a 204 status code when the modules are fetched. If everything is ok, I can refresh the modules list in the UI to view the new modules.\n\n#### Serving the modules\n\nThis part is exactly the same as the categories and users index endpoint. I create an endpoint `server\u002Fapi\u002Fmodules\u002Findex.get.ts` to get the modules from the database:\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\nNothing new here, I just select the modules from the database and return them.\n\n### Conclusion\n\nAll of this stuff gives us this result:\n\n![Modules Page](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fmodules-page.png)\n\nThis stream was different from the others. First, I now have a new mic and the sound is way better than before!\n\nSecond, I encountered a problem with the D1 limit (that could result in a need to rethink the project, hopefully not) on the bound parameters. This is a good lesson for me, and I hope it will be useful for you.\n\n## Refactoring the user management\n\n_This part was done off-stream because it was not interesting to see and I was not sure of the direction I wanted to take._\n\nWhile writing a part of this article, I ask myself if storing the user's data was a good idea. Couldn't it be better? This question comes from the way I handle the authentication endpoint.\n\nActually, when a user logged in, I always write to the database the user's data to be sure to be up-to-date. The main concern is that a write is way more expensive than a read. To find ideas, I read the [Atinux's Nuxt todo list project](https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-todos-edge). In this project, it does not store the user's data. When the user logs in, it only stores the user's ID in the cookie. When the user creates a todo, it simply has to read the user's ID from the cookie to know who is the user. It's very smart and simple. I will do the same. _First through._\n\nLet's analyze if this way is possible for the Orion project. To know if it's possible, I need to answer some questions:\n\n1. Can I ban or promote a user without storing the user's data?\n2. Can a user create data without storing the user's data?\n3. Can I show user's data without storing the user's data?\n\nFor the first question, it's yes. I can imagine a system where you enter the login of a user, and when the user logs in, I will search in the database if the entry exists and get relevant data. It's not the best way but it's possible.\n\nFor the second question, it's yes. Since data are in the cookie, I can easily know who is the user and create data using this information. _And that's how it works actually. I do not fetch the user for every request._\n\nFor the third question, it's no. Imagine, I want to show the template created by the user 'X'. In our SQL schema, we have a column `user_id` in the `templates` table. If I do not store the user's data, I will not be able to know who is the user 'X', associated with the `user_id`. I will have to store the user's data in the database. _It could be possible with a non-relational database but it's not the case here. We could also imagine a system where the user is saved when he creates a template. It's possible but it will add a lot of complexity. At the end, this answer and the one from the question one just over complicate the system for no real gain._\n\nNow we know that we have to store the users in the database, what can we improve?\n\n- Saving the user's data only if it's necessary. For example, I could first search for the user before saving its data when the user logs in.\n- Saving the user's mail. This will be useful for the feedback system.\n- Saving the user's name and renaming the `username` column to `login`.\n- Adding a `created_at` and an `updated_at` column.\n- Updating data stored in the cookie.\n\nWith all of these points, I also update the UI accordingly. There is nothing difficult here; it's mainly renaming and adding fields in the schema.\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\nFunctions `$defaultFn` and `$onUpdateFn` are used to set the default value of the column. In the case of `createdAt`, the default value is the current timestamp. In the case of `updatedAt`, the default value is the current timestamp, and the value is updated when the row is updated. It's all automatic and handled by Drizzle.\n\nI also update the `server\u002Froutes\u002Fauth\u002Fgithub.ts` to store the user's data only if it's necessary:\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     * If the user is not in the database or their data has changed, update the user data.\n     * A write is more expensive than a read, so we only write if necessary.\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     * Set only the necessary data in the 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\nWith this new way to handle the user's data, I reduce the number of writes to the database while still updating the user's data when necessary.\n\n## Creating a form for templates\n\n![Thumbnail of my sixth live stream.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-19.png)\n\n_[Orion](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion) is a full-stack application built with Nuxt and developed live [on Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). You can see the replay on [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) and the source code is open-source on [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nSince the beginning of the series, I've been working on the admin interface of the web application. Today, it's time to create the form to add a new template. This form will be used by the creators to add their templates or themes to the platform. As a reminder, Orion is a community-driven collection of templates for your next project, from landing pages to complete web applications.\n\n### Template schema\n\n_Remember that things evolve and the schema is not definitive._\n\nSince the template feature is a big one, I split it into multiple live streams. The first step is to create the simplest but working form possible and adding features over time. This explains why the schema above is not exactly the same as the one we drew in the SQL schema (no modules, no images, and no markdown, for the moment).\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\nThe `hash` column is a small ID I'll store in the database to create URL like Notion or Dev.to: `\u003Cslug>-\u003Chash>`. This is helpful to have a readable URL, for both humans and SEO, but because two templates can have the same name, or the name of a template can change, I need to have a unique ID.\n\nFor the first time, we create a relation between two tables: a one-to-many between the `users` table and the `templates` table. This relation is used to know who is the creator of the template. The `userId` column is a foreign key that references the `id` column of the `users` table.\n\nThe `categoryId` column is also a foreign key that references the `id` column of the `categories` table. This relation is used to know in which category the template is.\n\nIn order to be able to retrieve templates from a user or a category, I also need to define the relation the other way around. Thanks to Drizzle, it's pretty easy.\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### Template form\n\nDuring this phase, I create the form on the UI, exactly like I did for the categories. I define the state, the schema, and the action on submit!\n\nNow that a user is able to fill a form, I need an endpoint to save the data in the database. I create the endpoint `server\u002Fapi\u002Ftemplates\u002Findex.post.ts` to save the template in the database:\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\nPretty similar to the endpoint to create a new category. **One difference** is the usage of the util `requireUserSession` to ensure that the request is from an authenticated user and to get the user's data. I use the data to fill the `userId` column in the `templates` table. Of course, I validate the data received from the client using Zod.\n\nThe `useHash` util is a little wrapper around the package `nanoid`.\n\n### Templates in admin interface\n\n_During this part, I encountered an important bug. Read carefully the next part, it could save you a lot of time if you also plan to use Drizzle on Cloudflare._\n\nUsers are able to create templates, that's nice. In order to monitor and approve them, I need to create a page in the admin interface to manage the templates. This page will display the templates in a table and allow the admin to approve or refuse them. _I only implement the view, not the action._ The task is simple, it's like any other page in the admin interface. _If only I knew..._\n\nFirst, let's create the endpoint to get the templates from the database. I create the endpoint `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\nThis endpoint gets all the templates from the database and populates them using a join and the relation we define earlier in the schema.\n\nA `join` is a SQL operation that combines rows from two or more tables based on a related column between them. In this case, I join the `templates` table with the `users` table and the `categories` table. The `leftJoin` method is used to keep all the rows from the `templates` table, even if there is no match in the `users` or `categories` table.\n\n![A schematic explanation of the join operation.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fleft-join.png)\n\nNow that I have the data, I can display it on the UI. I use the `UTable` component from Nuxt UI Pro to display the templates in a table:\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**But it does not work.** Using a lot of Adonis, I was familiar to having an object like this:\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\nBut I received an object like this:\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\nand what a surprise it was to see that the keys and the values were exchanged between them. _Hard to explain but see the example._\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\nVery hard to play with data now! _The ID should be a number and the login should definitely not be a URL._\n\nAfter a long research, I found two issues: https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fworkers-sdk\u002Fissues\u002F3160 and https:\u002F\u002Fgithub.com\u002Fdrizzle-team\u002Fdrizzle-orm\u002Fissues\u002F555 explaining why it does not work. When two tables have the same column name, ~~Drizzle and~~ Cloudflare are unable to resolve them correctly and mix the data. The solution is to explicitly select the columns and rename similar columns.\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')` is the important code. I rename the column `name` to `c_name` to avoid the conflict. I also use this syntax to format the object with the key `user` and `category`. Now, everything works as expected.\n\n### Conclusion\n\nWorking on the client side, and not only on the admin part, makes the project even more concrete. There is still a lot of work on the template form like the markdown or the image, but it's a pretty good start.\n\nReally enjoy this project and I hope you too.\n\n## Continuing the template form\n\n![Thumbnail of my eighth live stream.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-20.png)\n\n_[Orion](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion) is a full-stack application built with Nuxt and developed live [on Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). You can see the replay on [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) and the source code is open-source on [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nThis stream was short so I try to make interesting stuff. I mainly do three things:\n\n- Create the show template page\n- Handle markdown on the server-side for the template description\n- Add a markdown editor for the template description\n\nLet's see how it goes. _During the stream, it goes well but since, I completely rewrite the markdown editor part._\n\n### Show template page\n\nThis item was more about the usage of the URL that the design of the page. Indeed, the URL will work like Notion or Dev.to: `\u002F\u003Cslug>-\u003Chash>` where the `slug` is the name of the template and the `hash` is a unique identifier. The `slug` is used for SEO and the `hash` is used to get the template. The `slug` is not unique and can change over time. The `hash` is unique and serves to get the template.\n\nIn the page, we first start by getting the params from the route:\n\n```ts\nconst route = useRoute()\nconst slug = route.params.slug \u002F\u002F \u003Cslug>-\u003Chash>\n```\n\nThe variable `slug` contains both the template `slug` and `hash`. We know the hash is exactly 12 characters long so we can split the `slug` to get the `slug` and the `hash`:\n\n```ts\nslug.slice(-12)\n```\n\nFinally, we can make a request to the backend to retrieve the template. All of this gives us this 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\nThe server part is not that complicated since we only get the hash from the router param and search for the template using a `where` clause:\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\nLater we will need to join the user's data and the category's data to have a complete template.\n\n### Server-side Markdown\n\nThe idea of receiving markdown from the client is to easily sanitize it to avoid HTML injection.\n\nImagine if the client send this HTML code:\n\n```html\n\u003Cp> Hello World!\u003C\u002Fp>\n\u003Cscript> console.log('hacked') \u003C\u002Fscript>\n```\n\nIf the server stores this content in the database and then display it using the directive `v-html`, every user that will see the page will execute the page. That's a real security issue.\n\nBy receiving markdown from the client, I can easily transform it using `markdown-it` and store the **sanitized HTML** in the database. The sanitized HTML is HTML that does not contain any script or dangerous tag.\n\nSo to do this, I need to install `markdown-it` and update the endpoint to create a template `server\u002Fapi\u002Ftemplates\u002Findex.post.ts`. I also add a new column `descriptionHTML` to the schema to store the sanitized HTML. Storing the HTML avoids to do it every time we render the 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\nThe `useMarkdown` function is a simple function that uses `markdown-it` to transform the markdown to HTML.\n\n### Markdown editor\n\n_This part is highly experimental and could change in the future. This is a first implementation._\n\n_While writing this part, I made some more research and everything will change. I think it's still interesting to see the process._\n\nBefore starting, I need to make a choice. Which markdown editor should I use? _Remember that we've just implemented a server endpoint to handle markdown._ I have many options:\n\n- SimpleMDE\n- EasyMDE\n- TinyMDE\n- Editor.js\n- Quill\n- Trix\n- CKEditor\n- TinyMCE\n\nThat's a lot of options. But, what are my needs? I need a dead simple editor that can return markdown. Sounds easy, right?\n\nEditor.js, CKEditor and TinyMCE are advanced editors so I will not use them. SimpleMDE is a good option but way too old. It has not received any updates since 6 years. SimpleMDE and TinyMDE are forks of SimpleMDE. EasyMDE is powerful, simple and returns markdown. It's a good option. TinyMDE does not have enough customization options. Trix and Quill are not markdown editors. They are rich text editors so they do not output markdown. I will go with EasyMDE. _For the moment._\n\nSo, we can now install the package and use it. The usage is a little tricky. Since a client only package, we cannot execute, nor import, it on the server. To use it, we will dynamically import it in a `onMounted` hook. _`onBeforeMount` is even better!_\n\n```ts\nonMounted(async () => {\n  const EasyMDE = await import('easymde').then(m => m.default)\n  const easymde = new EasyMDE()\n})\n```\n\nAnd it works? Yes but I think we completely forgot to import the style!\n\n![EasyMDE without style](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Feasymde-without-style.png)\n\nI think we forgot to add the style for EasyMDE. Thanks to Vite and its CSS loader, we can import the CSS file directly in our script.\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nimport 'easymde\u002Fdist\u002Feasymde.min.css'\n\u003C\u002Fscript>\n```\n\n![EasyMDE with style](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Feasymde-with-style.png)\n\nOk, it's not perfect and I will have some work to do, on both styles and icons. But it's a good start. The only part that I do not like is the markdown symbol. I would prefer to not have them because it's not user-friendly (anyone could have to create a template).\n\nBut anyway, it's a good start and I can now easily listen to the editor changes and update the 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\nNow, I can send the description to the server and it will be transformed to HTML!\n\n### What's next for the form?\n\nThe form is far from done but it's progressing well. I will add the support for the images and the modules in the next stream.\n\nThe Orion project is nearly ready for an alpha release and I'm excited to see how the community will use it and discover the content they will share.\n\n## Replacing the Markdown editor\n\n_This part was done off-stream because I was not satisfied with the EasyMDE editor but I did not know what to do._\n\nAfter thinking about it, I realized that using Markdown, for a simple description and that can be used by non-technical users, is not the best idea. Furthermore, the EasyMDE editor uses CodeMirror which is slow and way too heavy for a simple editor.\n\nNow we know that the EasyMDE editor is not the best solution for our needs, we need to find a new one. We need a simple Rich Text Editor and there are two main options: Quill and Trix. In that case, Quill seems to be simpler so I will go with it. _I will not detail the installation of Quill, it's pretty simple._\n\nBecause Quill is a rich text editor, it does not output markdown but HTML. So I need to update the endpoint to store the HTML instead of the markdown. I also need to update the schema to remove the `description` column and add a `descriptionHTML` column.\n\n### Updating the endpoint\n\nWhy do I need to update the endpoint? Quill returns HTML that is already sanitized so I could directly store it in the database. Yes but no. We are building an API and it's impossible to trust the client, even our own client. Anyone can change the HTML or use an HTTP client to send a request to the server with malicious HTML. So I need to sanitize the HTML on the server.\n\nFor example, here's some malicious HTML:\n\n```html\n\u003Cp> Hello World! \u003C\u002Fp>\n\u003Cscript> console.log('hacked') \u003C\u002Fscript>\n```\n\nIf you store this HTML in the database and display it using the directive `v-html`, every user that will see the page will execute the script. That's a real security issue. We can't use `{{ html }}` because Vue will escape the HTML so sanitizing our content is the only solution.\n\nI will use the `sanitize-html` package to sanitize the HTML. After sanitizing using this package, the HTML will be:\n\n```html\n\u003Cp> Hello World! \u003C\u002Fp>\n```\n\nThat's perfect!\n\n```ts\nawait useDrizzle().insert(tables.templates).values({\n  description: body.description ? sanitizeHtml(body.description) : null,\n})\n```\n\nThe `sanitizeHtml` function is the default export of the `sanitize-html` package. Easy right?\n\n### Updating the schema\n\nNow, I need to update the schema to remove the `descriptionHTML` column since the `description` is now HTML by default. It's important to know that if the user needs to edit his template, the HTML will be used by the Quill editor.\n\n```ts\nexport const templates = sqliteTable('templates', {\n  description: text('description'),\n  descriptionHTML: text('description_html'), \u002F\u002F [!code --]\n})\n```\n\n### Updating the UI\n\nThe usage of Quill is not that different from EasyMDE. I lazy load the Quill package, create a new instance of it, and listen for the changes. I do not forget to import the CSS file.\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\nOf course, I need to add some custom styles to the editor to make it more native into the design.\n\nA last thing. I use this in a Nuxt UI form. In this form, every item is validated against the schema. This allows me to display an error message if the description is empty or too long. The problem is that it's not integrated in the Quill editor, it does not trigger any validation. Thanks to the Nuxt UI form API, I can easily validate the Quill editor content on every change.\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\nNow, the Quill editor is fully integrated into the form and an error message will be displayed if the description is empty or too long.\n\n![The rich text editor customized to fully integrate into the form.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Frich-text-editor.png)\n\n### Conclusion\n\nI'm really happy with this change. Since years, I was wondering how to install and use a rich text editor and now I know. You can use markdown for technical users, parse it on the server, and store HTML to simplify a get, or use a rich text editor for non-technical users and sanitize the HTML on the server. It's a good lesson for me and I hope it will be useful for you.\n\n## Adding modules to the template form\n\n![Thumbnail of my ninth 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) is a full-stack application built with Nuxt and developed live [on Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). You can see the replay on [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) and the source code is open-source on [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nDuring this live stream, I added the ability to add modules to a template in the form. This feature is important because it will allow users to filter templates by modules.\n\nI chose to do it in a separate stream because it's a many-to-many relation and a little bit more complex than the other parts. I was expecting to do it in 1 hour, but it took me 2 hours with the help of the chat. Indeed, I was stuck on the SQL query to retrieve a template populated with its modules, and the Drizzle documentation was not clear about it.\n\nLet's deep dive into the code and let's understand how the Drizzle ORM relations concept works.\n\n### Schema\n\nThe creator can add multiple modules to a template. At the same time, a module can be used by multiple templates. The relation is a many-to-many relation. To handle this, I need to create a new table, called a pivot table, to store the relation between the templates and the modules. It acts as a link between the two tables to connect them. _We use a pivot table instead of an array of modules in the template because it's more efficient and it's the SQL way to do it._\n\n![Many-to-many relation between templates and modules](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fmany-to-many-relation.png)\n\nThe schema is simplified to only show the relation between the templates and the modules. From the `templates` table, you can retrieve all the modules associated and inversely thanks to the `templates_modules` table.\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### Modules form\n\nIn this part, I will add the modules to the form, from the client side to the server side.\n\n#### Adding modules to the template\n\nIn the UI, handling the modules is pretty simple. I fetch the modules from the server and display them in a `USelectMenu` with a multiple prop.\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#### Validating the modules\n\nIn the validator, I add a new field `moduleIds` that verifies that the modules are an array of integers.\n\n```ts\nimport { array, number, object, string } from 'zod'\n\nexport const createTemplateValidator = object({\n  title: string({ message: 'Required' }),\n  moduleIds: array(number()).max(6, { message: 'Max 6 modules' }).optional(),\n})\n```\n\nI arbitrarily set the maximum number of modules to 6. It's a number that I think is enough for a template.\n\n#### Saving the modules\n\nFinally, I update the endpoint to save the template and related modules. I first save the template and then save the modules in the pivot table.\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_I have to admit that I'm not very confident with this part. I will need to check if there is a better way to do it (using a transaction or a built-in Drizzle feature)._\n\n### Populating the template with modules\n\n_This is where the hard times begin._\n\nInitially, I had a custom SQL query that joined the `templates` table, the `users` table, and the `categories` table. You can learn more about this in the [previous part](#creating-a-form-for-templates).\n\nTo link the `modules` table, I tried to add a join with the `modulesToTemplates` table and then the `modules` table. The issue I encounter is how to group the modules by template to produce an array of modules for each template.\n\nSomething like:\n\n```json\n{\n  \"id\": 1,\n  \"title\": \"My template\",\n  \"modules\": [\n    { \"id\": 1, \"name\": \"Module 1\" },\n    { \"id\": 2, \"name\": \"Module 2\" }\n  ]\n}\n```\n\nI found the SQLite function `group_concat` that can be used to concatenate the modules. I was a little disappointed to have to do this manually because Drizzle presents itself as an ORM. I was expecting to have a way to do this easily.\n\nBUT, the Twitch chat came to the rescue and suggested me to use the [`with` key from the `findMany` method](https:\u002F\u002Form.drizzle.team\u002Fdocs\u002Frqb#include-relations). I tried, and it did not really work. Cloudflare was complaining about a missing relation. I was a little bit lost.\n\nI end up by carefully reading the documentation about the many-to-many relation page, and I understood that I need to explicitly define every relation between every table. In other words, I have to tell Drizzle how to join the tables. A template has many modules by using the `modulesToTemplates` table. A module has many templates by using the `modulesToTemplates` table. This is the way to define the many-to-many relation.\n\n```ts\n\u002F\u002F A template has a many-to-many relation\nexport const templatesRelations = relations(templates, ({ one, many }) => ({\n  \u002F\u002F Linked to the pivot table and not directly to the modules table\n  modules: many(modulesToTemplates),\n}))\n\n\u002F\u002F The pivot table has 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 A module has a many-to-many relation\nexport const modulesRelations = relations(modules, ({ one, many }) => ({\n  \u002F\u002F Linked to the pivot table and not directly to the templates table\n  templates: many(modulesToTemplates),\n}))\n```\n\nThis part is super important. In an ORM like [Lucid](https:\u002F\u002Flucid.adonisjs.com), the usage of a pivot table is transparent. With Drizzle, you have to define relation between every table. It's a little bit more verbose, but it's also more explicit.\n\nFinally, I can update the endpoint to retrieve the templates with their 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\nThe usage of the `with`, once everything is correctly defined, makes things so much easier; I really love it.\n\n### Conclusion\n\nIn my mind, this part was easy, and in 30 minutes, it was done. I already did a similar thing using Lucid from Adonis. In reality, it was not really hard, but finding the right part of the Drizzle documentation and then understanding it was way more difficult than expected. This is largely because Drizzle does not abstract SQL but acts as a little wrapper, and everything must be explicit.\n\nI'm happy to have done it, I learned a lot, and a huge thanks to the chat for helping me.\n",{"title":45992,"description":60381},{"loc":60383,"images":60387},[60388,60389,60390,60391,60392,60393,60394,60395,60396,60397,60398,60399,60400,60401,60402,60403,60404,60405,60406,60407,60408,60409,60410,60411],{"loc":46049},{"loc":46081},{"loc":46331},{"loc":46376},{"loc":46453},{"loc":46469},{"loc":46485},{"loc":46532},{"loc":47264},{"loc":47305},{"loc":47456},{"loc":48860},{"loc":50524},{"loc":50658},{"loc":50667},{"loc":51697},{"loc":52888},{"loc":54257},{"loc":56439},{"loc":57493},{"loc":57546},{"loc":58642},{"loc":58658},{"loc":58697},"posts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey","nQU22X3QR1Z9e5n0gLRc_sPIph31lDoByOH1mreNcTo",{"id":60415,"title":60416,"body":60417,"contentId":20826,"date":60845,"description":60846,"extension":18,"meta":60847,"navigation":20,"path":60848,"rawbody":60849,"robots":23,"schemaOrg":23,"seo":60850,"sitemap":60851,"stem":60859,"__hash__":60860},"posts\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights.md","Speaking at Devoxx France 2024: Insights and Highlights",{"type":7,"value":60418,"toc":60838},[60419,60429,60437,60443],[35,60420,60421,60422,60425,60426,215],{},"Devoxx France is a huge conference that takes place in Paris. This year, more than ",[6850,60423,60424],{},"4500 attendees"," were present with 70 exhibitors and ",[6850,60427,60428],{},"319 speakers",[35,60430,60431,60432,60436],{},"It's so huge that it's impossible to attend all the talks. Sometimes, there are more than 10 talks at the same time, but thanks to the ",[39,60433,60435],{"href":7143,"rel":60434},[77],"replay on YouTube",", it's possible to watch them later.",[35,60438,60439],{},[1690,60440],{"alt":60441,"src":60442},"Photo of the exhibition hall and my speaker badge.","\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Ffirst-day.jpg",[7139,60444,60447,60449,60456,60465,60475,60478,60639,60651,60654,60661,60668,60675,60681,60685,60688,60692,60695,60702,60708,60715,60721,60727,60734,60740,60747,60750,60756,60758,60761,60781,60784,60787,60790,60793,60796,60799],{"w-full":13,"icon":7141,"text":60445,"to":60446,"target":7144},"View my talk about UnJS","https:\u002F\u002Fyoutu.be\u002F0BApl8r1VWc",[51,60448,6822],{"id":6821},[939,60450,60451],{},[35,60452,60453,60455],{},[200,60454,945],{},"\nIt's not a recommendation, it's just talks I was able to see.",[35,60457,60458,60459,60464],{},"Before going to Devoxx France, I had a look at the ",[39,60460,60463],{"href":60461,"rel":60462},"https:\u002F\u002Fmobile.devoxx.com\u002F",[77],"schedule"," and I made a list of talks that I wanted to attend. There are so many people that some talks were already full when I arrived at the room, and I discovered some talks that I preferred during the event (and choosing between all of these talks is not easy).",[35,60466,60467,60468,1638,60471,60474],{},"This year, my goal was to see a maximum of talks about ",[6850,60469,60470],{},"DevOps",[6850,60472,60473],{},"clean code",". I also wanted to be surprised by going to talk that I never go to just because I know the speaker or because the title is interesting (and I was not disappointed).",[35,60476,60477],{},"Finally, I was able to see (talk titles are in French):",[1576,60479,60480,60527,60580],{},[71,60481,60482,60483],{},"Wednesday, 17 April\n",[1576,60484,60485,60491,60497,60503,60509,60515,60521],{},[71,60486,60487,60490],{},[6850,60488,60489],{},"IA en médecine : où en sommes-nous ?"," Presented by Jean-Emmanuel Bibault",[71,60492,60493,60496],{},[6850,60494,60495],{},"React Server Components : Effet de mode ou réel progrès ?"," Presented by Thomas Simonnet",[71,60498,60499,60502],{},[6850,60500,60501],{},"High-Speed DDD (revisited)."," Presented by Thomas Pierrain",[71,60504,60505,60508],{},[6850,60506,60507],{},"V, comme Vautledétour."," Presented by Emmanuel Remy",[71,60510,60511,60514],{},[6850,60512,60513],{},"Comment bien planter un side project ?"," Presented by David Laizé",[71,60516,60517,60520],{},[6850,60518,60519],{},"Toutes et tous Mercenaires de DevOps!"," Presented by Arnaud Héritier, Henri Gomez, Pierre-Antoine Grégoire, Dimitri Baeli, and Gildas Cuisinier",[71,60522,60523,60526],{},[6850,60524,60525],{},"La recherche sous stéroïdes - une histoire de sémantique."," Presented by Mathilde Rigabert and Martin Labenne",[71,60528,60529,60530],{},"Thursday, 18 April\n",[1576,60531,60532,60538,60544,60550,60556,60562,60568,60574],{},[71,60533,60534,60537],{},[6850,60535,60536],{},"Programming's Greatest Mistakes."," Presented by Mark Rendle",[71,60539,60540,60543],{},[6850,60541,60542],{},"Un monde shooté aux métaux."," Presented by Guillaume Pitron and Agnes Crepet",[71,60545,60546,60549],{},[6850,60547,60548],{},"GatewayAPI, 10 ans de maturation pour une nouvelle API Kubernetes."," Presented by Kevin Davin",[71,60551,60552,60555],{},[6850,60553,60554],{},"Comment K8S a changé le monde de l'Ops."," Presented by Aurélien Moreau and Loïc Ortola",[71,60557,60558,60561],{},[6850,60559,60560],{},"Architecture Hexagonale: Comment venir à bout du code spaghetti ?"," Presented by Kévin LLOPIS",[71,60563,60564,60567],{},[6850,60565,60566],{},"Java rencontre l'IA : Comment intégrer les LLMs dans vos applications avec LangChain4j."," Presented by Lize Raes",[71,60569,60570,60573],{},[6850,60571,60572],{},"Les nouveautés de Java 21."," Presented by Jean-Michel Doudoux",[71,60575,60576,60579],{},[6850,60577,60578],{},"Debugguez votre salaire ! Mes stratégies gagnantes pour réussir sa négociation salariale."," Presented by Shirley Almosni-Chiche",[71,60581,60582,60583],{},"Friday, 19 April\n",[1576,60584,60585,60591,60597,60603,60609,60615,60621,60627,60633],{},[71,60586,60587,60590],{},[6850,60588,60589],{},"Comment modéliser l'état du monde en 2100 ?"," Le Rapport Meadows. Presented by Anatole Chouard",[71,60592,60593,60596],{},[6850,60594,60595],{},"Cybersécurité et cyberdéfense : un sujet géopolitique."," Presented by Guillaume Poupard",[71,60598,60599,60602],{},[6850,60600,60601],{},"Model Mitosis : ne plus se tromper entre les microservices et le monolithe."," Presented by Julien Topçu and Josian Chevalier",[71,60604,60605,60608],{},[6850,60606,60607],{},"L'architecture micro frontend : comment transformer son site web en puzzle géant collaboratif."," Presented by Maxime Ribera and Ludovic Lagatie",[71,60610,60611,60614],{},[6850,60612,60613],{},"Unifié et Universel, le JavaScript avec UnJS."," Presented by Estéban Soubiran (it's me)",[71,60616,60617,60620],{},[6850,60618,60619],{},"Au-delà des heures : La semaine de 4 jours comme levier d'égalité."," Presented by Albane Veyron",[71,60622,60623,60626],{},[6850,60624,60625],{},"Créer sa propre VM avec compilation JIT pour les nuls."," Presented by Olivier Poncet",[71,60628,60629,60632],{},[6850,60630,60631],{},"L'Architecture Hexagonale par la pratique, le live coding qui rendra vos applications plus pérennes."," Presented by Julien Topçu",[71,60634,60635,60638],{},[6850,60636,60637],{},"Accessibilité du web : du design au code comment tendre vers davantage d'inclusion ?"," Presented by Marjorie Aubert",[939,60640,60641],{},[35,60642,60643,60645,60646,215],{},[200,60644,945],{},"\nThis is only talks I was able to see (a small part of all available talks). To view the full schedule, go to the ",[39,60647,60650],{"href":60648,"rel":60649},"https:\u002F\u002Fmobile.devoxx.com\u002Fevents\u002Fdevoxxfr2024\u002Fschedule",[77],"Devoxx France 2024 schedule",[35,60652,60653],{},"Yes, there is some Java (I'm a web dev doing JavaScript) because I love to discover other languages and how they solve problems, the way they think and the tools they use. At the same time, the more time I spend in the web world, the more I think I need to learn Java because it's very convenient for big projects with its strong typing and its powerful dependency injection.",[35,60655,60656,60657,60660],{},"This year, there is ",[6850,60658,60659],{},"a trend about LLM, AI, RAG",", and how to integrate them into your business. Since AI is available for everyone for cheap, there are more and more usages (and real usage) of them. It's not only for big companies but also for small companies and even for individuals. So many possibilities, it's very exciting and many talks show that.",[35,60662,60663,60664,60667],{},"At the end of the day, all of these talks are good and you will definitely learn something by watching them on the ",[39,60665,20998],{"href":7143,"rel":60666},[77],". Go ahead!",[35,60669,60670,60671,60674],{},"One of the interesting parts of Devoxx France is the ",[6850,60672,60673],{},"exhibitors",". There are many stands where you can meet partners, discover new tools, and have fun. This year, I discovered I didn't take a lot of time to visit them, but I discovered some new tools and projects. One of them is to help teams to agree on best practices and to follow them or another one which builds a data center using GCP stack but without Google in order to be GDPR compliant and to build a 'trusted cloud'.",[35,60676,60677],{},[1690,60678],{"alt":60679,"src":60680},"Photo of the JavaScript logo built with LEGO on one of the partner stands.","\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Flego.jpg",[1913,60682,60684],{"id":60683},"speakers-dinner","Speakers dinner",[35,60686,60687],{},"Wednesday evening, I had the chance, with every other speaker, to attend the speakers' dinner. It was a private moment to meet the other speakers, and to chill before the rest of the event. I have to admit that this is not really my cup of tea to talk to unknown people, but the food was great so it was a nice moment.",[1913,60689,60691],{"id":60690},"my-preferred-talks","My preferred talks",[35,60693,60694],{},"I really want to highlight some talks that I really enjoyed and that I recommend to everyone (some of them are truly mind-blowing).",[35,60696,60697,60698,60701],{},"The first one is the talk about ",[6850,60699,60700],{},"RAG"," (Retrieval-Augmented Generation) where I learned a lot about this concept, how to use it and frameworks that make it easy to use. I really think this technology is the future, but it will be the subject for another article (I need to learn more about it and to start using it).",[35,60703,60704],{},[1690,60705],{"alt":60706,"src":60707},"Photo of the talk about RAG.","\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Frag.jpg",[35,60709,60710,60711,60714],{},"The second one is the talk about ",[6850,60712,60713],{},"micro front-end",". On paper, the subject is interesting but too often, talks about micro front-end are very theoretical, not very practical, and usage, with benefits, is not clear. But, this talk was at the opposite of that. They explained, in detail, how they implemented and used it and more importantly, why they do it. At the end, the live coding was on the live website (yes, on leroymerlin.fr) and it was very impressive.",[35,60716,60717],{},[1690,60718],{"alt":60719,"src":60720},"Photo of Leroy Merlin's talk on micro front-ends.","\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Fmicro-front-end.jpg",[35,60722,60723,60724,60726],{},"The third one is the talk that retraced the history of ",[6850,60725,60470],{}," from 25 years ago. It was very interesting to see how successive technologies are linked to each other and to understand that the actual state of DevOps is the work of years, steps by steps. It's not a revolution but a progressive evolution, from VMs to containers, orchestrators, and IaC.",[35,60728,60729,60730,60733],{},"By hazard, but because I like the content of the person on X, I went to a talk about ",[6850,60731,60732],{},"JIT",". I knew about this term and the objective but not how it was implemented. In 45 minutes, the speaker coded a simple but functional JIT compiler in C++. It almost looks easy and I think it's important to know how these pieces of tech, that we use every day, work.",[35,60735,60736],{},[1690,60737],{"alt":60738,"src":60739},"Photo of the talk about JIT.","\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Fjit.jpg",[35,60741,60742,60743,60746],{},"The last one was about ",[6850,60744,60745],{},"hexagonal architecture",". I already know the concept, but I never implemented it. The speaker did a live coding session where he implemented a simple application with hexagonal architecture. It was very interesting to see how the code is organized and how it's easy to test and to maintain. The fact that he implemented the domain (business logic) first with tests and then connected it to the infrastructure (the framework) was very nice to discover. I will definitely deep dive into this concept and write articles about it. When you go to a talk like this, it's like going to the theater and seeing a movie, I love this experience.",[35,60748,60749],{},"Overall, you should really watch these talks. They are very interesting and you will learn a lot.",[35,60751,60752],{},[1690,60753],{"alt":60754,"src":60755},"Photo of the large Devoxx amphitheater.","\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Famphi.jpg",[51,60757,7206],{"id":7205},[35,60759,60760],{},"This year, I submitted 3 talks to the CFP (Call For Paper):",[1576,60762,60763,60769,60775],{},[71,60764,60765,60768],{},[6850,60766,60767],{},"Construire un méta framework front-end pour l'edge"," (in English: Building a meta front-end framework for the edge)",[71,60770,60771,60774],{},[6850,60772,60773],{},"Unifié et Universel, le JavaScript avec UnJS"," (in English: Unified and Universal, JavaScript with UnJS)",[71,60776,60777,60780],{},[6850,60778,60779],{},"Atteindre l'edge avec Nuxt"," (in English: Reaching the edge with Nuxt)",[35,60782,60783],{},"And one of them was accepted. Yippee! It's important to note that Devoxx France is, originally, a Java conference, and I'm pretty sure that the edge is unknown for most of the attendees, and this could explain why these talks were not accepted.",[35,60785,60786],{},"But one was accepted, and I have to prepare it!",[35,60788,60789],{},"I chose to talk about UnJS because I love the philosophy behind it, and I think it's a very good tool for a JavaScript developer. Talking for 15 minutes about UnJS is also a challenge because that's not a lot of time, and the quantity and diversity of packages that UnJS provides is huge.",[35,60791,60792],{},"The first step is to choose packages I wanted to present. Then, I have to select which part of the selected packages I want to present, the one that could be interesting for most of the attendees. It's way more difficult than having a 1-hour talk where I could go deep into the subject, but it's a good exercise to be concise.",[35,60794,60795],{},"Finally, I ended up talking about the history and philosophy of UnJS and presenting useful packages for most developers like unbuild, nitro, or magic-regexp and going straight to the point for each of them. My goal was to give a good overview of UnJS and to make people want to try it.",[35,60797,60798],{},"Fortunately, I was able to train my talk at StrasbourgJS. This helps me to refine some parts and to be more confident.",[7905,60800,60801,60807],{"id":30890},[35,60802,60803,60804,215],{},"My talk was on Friday at 12:35, and until 12:20, I was confident. I repeated my talk many times, and I know UnJS. But when I came into the room and stood behind the desk, the stress came. Then, I started talking, and everything was fine. I'm proud of my talk. I really hope you will enjoy the replay on the ",[39,60805,20998],{"href":7143,"rel":60806},[77],[7139,60808,60809,60814,60817],{"w-full":13,"icon":7141,"text":60445,"to":60446,"target":7144},[35,60810,60811],{},[1217,60812,60813],{},"Slides are coming soon.",[35,60815,60816],{},"A good thing done, and it's so good to share knowledge with others and talk with them. I feel that I still have a long way to go before making the perfect presentation, so I will continue to submit talks to conferences and to meetups.",[60818,60819,60820,60822,60825,60832,60835],"callout-available-for-giving-a-talk",{},[51,60821,19334],{"id":19333},[35,60823,60824],{},"Devoxx France is a unique event. There are so many talks, so much diversity in the subjects, and so many people that you will definitely learn something. This year was the biggest Devoxx France ever with more than 4000 attendees.",[35,60826,60827,60828,60831],{},"And even if the event is physically finished, it continues online with the ",[39,60829,60435],{"href":7143,"rel":60830},[77],". So, if you can't attend, you can still watch the talks!",[35,60833,60834],{},"Thanks to the organizers and the red coat team for this amazing event. I hope to see you next year (I'm already working on many talks for the next edition)!",[35,60836,60837],{},"Until then, take care and keep learning!",{"title":13,"searchDepth":14,"depth":14,"links":60839},[60840,60844],{"id":6821,"depth":14,"text":6822,"children":60841},[60842,60843],{"id":60683,"depth":255,"text":60684},{"id":60690,"depth":255,"text":60691},{"id":7205,"depth":14,"text":7206},"2024-04-22","Uncover key insights and talks from Devoxx France 2024, focusing on AI, LLM, RAG, and more, plus a my talk on UnJS!",{},"\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights","---\ncontentId: 08852918-9829-4658-97b3-1aabede8e9fe\ntitle: \"Speaking at Devoxx France 2024: Insights and Highlights\"\ndescription: Uncover key insights and talks from Devoxx France 2024, focusing on AI, LLM, RAG, and more, plus a my talk on UnJS!\ndate: 2024-04-22\n---\n\nDevoxx France is a huge conference that takes place in Paris. This year, more than **4500 attendees** were present with 70 exhibitors and **319 speakers**.\n\nIt's so huge that it's impossible to attend all the talks. Sometimes, there are more than 10 talks at the same time, but thanks to the [replay on YouTube](https:\u002F\u002Fwww.youtube.com\u002F@DevoxxFRvideos), it's possible to watch them later.\n\n![Photo of the exhibition hall and my speaker badge.](\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Ffirst-day.jpg)\n\n\u003CCallout w-full icon=\"i-simple-icons-youtube\" text=\"View my talk about UnJS\" to=\"https:\u002F\u002Fyoutu.be\u002F0BApl8r1VWc\" target=\"_blank\" \u002F>\n\n## My schedule\n\n> [!NOTE]\n> It's not a recommendation, it's just talks I was able to see.\n\nBefore going to Devoxx France, I had a look at the [schedule](https:\u002F\u002Fmobile.devoxx.com\u002F) and I made a list of talks that I wanted to attend. There are so many people that some talks were already full when I arrived at the room, and I discovered some talks that I preferred during the event (and choosing between all of these talks is not easy).\n\nThis year, my goal was to see a maximum of talks about **DevOps** and **clean code**. I also wanted to be surprised by going to talk that I never go to just because I know the speaker or because the title is interesting (and I was not disappointed).\n\nFinally, I was able to see (talk titles are in French):\n\n- Wednesday, 17 April\n  - **IA en médecine : où en sommes-nous ?** Presented by Jean-Emmanuel Bibault\n  - **React Server Components : Effet de mode ou réel progrès ?** Presented by Thomas Simonnet\n  - **High-Speed DDD (revisited).** Presented by Thomas Pierrain\n  - **V, comme Vautledétour.** Presented by Emmanuel Remy\n  - **Comment bien planter un side project ?** Presented by David Laizé\n  - **Toutes et tous Mercenaires de DevOps!** Presented by Arnaud Héritier, Henri Gomez, Pierre-Antoine Grégoire, Dimitri Baeli, and Gildas Cuisinier\n  - **La recherche sous stéroïdes - une histoire de sémantique.** Presented by Mathilde Rigabert and Martin Labenne\n- Thursday, 18 April\n  - **Programming's Greatest Mistakes.** Presented by Mark Rendle\n  - **Un monde shooté aux métaux.** Presented by Guillaume Pitron and Agnes Crepet\n  - **GatewayAPI, 10 ans de maturation pour une nouvelle API Kubernetes.** Presented by Kevin Davin\n  - **Comment K8S a changé le monde de l'Ops.** Presented by Aurélien Moreau and Loïc Ortola\n  - **Architecture Hexagonale: Comment venir à bout du code spaghetti ?** Presented by Kévin LLOPIS\n  - **Java rencontre l'IA : Comment intégrer les LLMs dans vos applications avec LangChain4j.** Presented by Lize Raes\n  - **Les nouveautés de Java 21.** Presented by Jean-Michel Doudoux\n  - **Debugguez votre salaire ! Mes stratégies gagnantes pour réussir sa négociation salariale.** Presented by Shirley Almosni-Chiche\n- Friday, 19 April\n  - **Comment modéliser l'état du monde en 2100 ?** Le Rapport Meadows. Presented by Anatole Chouard\n  - **Cybersécurité et cyberdéfense : un sujet géopolitique.** Presented by Guillaume Poupard\n  - **Model Mitosis : ne plus se tromper entre les microservices et le monolithe.** Presented by Julien Topçu and Josian Chevalier\n  - **L'architecture micro frontend : comment transformer son site web en puzzle géant collaboratif.** Presented by Maxime Ribera and Ludovic Lagatie\n  - **Unifié et Universel, le JavaScript avec UnJS.** Presented by Estéban Soubiran (it's me)\n  - **Au-delà des heures : La semaine de 4 jours comme levier d'égalité.** Presented by Albane Veyron\n  - **Créer sa propre VM avec compilation JIT pour les nuls.** Presented by Olivier Poncet\n  - **L'Architecture Hexagonale par la pratique, le live coding qui rendra vos applications plus pérennes.** Presented by Julien Topçu\n  - **Accessibilité du web : du design au code comment tendre vers davantage d'inclusion ?** Presented by Marjorie Aubert\n\n> [!NOTE]\n> This is only talks I was able to see (a small part of all available talks). To view the full schedule, go to the [Devoxx France 2024 schedule](https:\u002F\u002Fmobile.devoxx.com\u002Fevents\u002Fdevoxxfr2024\u002Fschedule).\n\nYes, there is some Java (I'm a web dev doing JavaScript) because I love to discover other languages and how they solve problems, the way they think and the tools they use. At the same time, the more time I spend in the web world, the more I think I need to learn Java because it's very convenient for big projects with its strong typing and its powerful dependency injection.\n\nThis year, there is **a trend about LLM, AI, RAG**, and how to integrate them into your business. Since AI is available for everyone for cheap, there are more and more usages (and real usage) of them. It's not only for big companies but also for small companies and even for individuals. So many possibilities, it's very exciting and many talks show that.\n\nAt the end of the day, all of these talks are good and you will definitely learn something by watching them on the [Devoxx France YouTube channel](https:\u002F\u002Fwww.youtube.com\u002F@DevoxxFRvideos). Go ahead!\n\nOne of the interesting parts of Devoxx France is the **exhibitors**. There are many stands where you can meet partners, discover new tools, and have fun. This year, I discovered I didn't take a lot of time to visit them, but I discovered some new tools and projects. One of them is to help teams to agree on best practices and to follow them or another one which builds a data center using GCP stack but without Google in order to be GDPR compliant and to build a 'trusted cloud'.\n\n![Photo of the JavaScript logo built with LEGO on one of the partner stands.](\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Flego.jpg)\n\n### Speakers dinner\n\nWednesday evening, I had the chance, with every other speaker, to attend the speakers' dinner. It was a private moment to meet the other speakers, and to chill before the rest of the event. I have to admit that this is not really my cup of tea to talk to unknown people, but the food was great so it was a nice moment.\n\n### My preferred talks\n\nI really want to highlight some talks that I really enjoyed and that I recommend to everyone (some of them are truly mind-blowing).\n\nThe first one is the talk about **RAG** (Retrieval-Augmented Generation) where I learned a lot about this concept, how to use it and frameworks that make it easy to use. I really think this technology is the future, but it will be the subject for another article (I need to learn more about it and to start using it).\n\n![Photo of the talk about RAG.](\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Frag.jpg)\n\nThe second one is the talk about **micro front-end**. On paper, the subject is interesting but too often, talks about micro front-end are very theoretical, not very practical, and usage, with benefits, is not clear. But, this talk was at the opposite of that. They explained, in detail, how they implemented and used it and more importantly, why they do it. At the end, the live coding was on the live website (yes, on leroymerlin.fr) and it was very impressive.\n\n![Photo of Leroy Merlin's talk on micro front-ends.](\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Fmicro-front-end.jpg)\n\nThe third one is the talk that retraced the history of **DevOps** from 25 years ago. It was very interesting to see how successive technologies are linked to each other and to understand that the actual state of DevOps is the work of years, steps by steps. It's not a revolution but a progressive evolution, from VMs to containers, orchestrators, and IaC.\n\nBy hazard, but because I like the content of the person on X, I went to a talk about **JIT**. I knew about this term and the objective but not how it was implemented. In 45 minutes, the speaker coded a simple but functional JIT compiler in C++. It almost looks easy and I think it's important to know how these pieces of tech, that we use every day, work.\n\n![Photo of the talk about JIT.](\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Fjit.jpg)\n\nThe last one was about **hexagonal architecture**. I already know the concept, but I never implemented it. The speaker did a live coding session where he implemented a simple application with hexagonal architecture. It was very interesting to see how the code is organized and how it's easy to test and to maintain. The fact that he implemented the domain (business logic) first with tests and then connected it to the infrastructure (the framework) was very nice to discover. I will definitely deep dive into this concept and write articles about it. When you go to a talk like this, it's like going to the theater and seeing a movie, I love this experience.\n\nOverall, you should really watch these talks. They are very interesting and you will learn a lot.\n\n![Photo of the large Devoxx amphitheater.](\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Famphi.jpg)\n\n## My talk\n\nThis year, I submitted 3 talks to the CFP (Call For Paper):\n\n- **Construire un méta framework front-end pour l'edge** (in English: Building a meta front-end framework for the edge)\n- **Unifié et Universel, le JavaScript avec UnJS** (in English: Unified and Universal, JavaScript with UnJS)\n- **Atteindre l'edge avec Nuxt** (in English: Reaching the edge with Nuxt)\n\nAnd one of them was accepted. Yippee! It's important to note that Devoxx France is, originally, a Java conference, and I'm pretty sure that the edge is unknown for most of the attendees, and this could explain why these talks were not accepted.\n\nBut one was accepted, and I have to prepare it!\n\nI chose to talk about UnJS because I love the philosophy behind it, and I think it's a very good tool for a JavaScript developer. Talking for 15 minutes about UnJS is also a challenge because that's not a lot of time, and the quantity and diversity of packages that UnJS provides is huge.\n\nThe first step is to choose packages I wanted to present. Then, I have to select which part of the selected packages I want to present, the one that could be interesting for most of the attendees. It's way more difficult than having a 1-hour talk where I could go deep into the subject, but it's a good exercise to be concise.\n\nFinally, I ended up talking about the history and philosophy of UnJS and presenting useful packages for most developers like unbuild, nitro, or magic-regexp and going straight to the point for each of them. My goal was to give a good overview of UnJS and to make people want to try it.\n\nFortunately, I was able to train my talk at StrasbourgJS. This helps me to refine some parts and to be more confident.\n\n\u003CCalloutReadMore id=\"ba562d1f-534c-4cef-9004-2fefc09829a6\" \u002F>\n\nMy talk was on Friday at 12:35, and until 12:20, I was confident. I repeated my talk many times, and I know UnJS. But when I came into the room and stood behind the desk, the stress came. Then, I started talking, and everything was fine. I'm proud of my talk. I really hope you will enjoy the replay on the [Devoxx France YouTube channel](https:\u002F\u002Fwww.youtube.com\u002F@DevoxxFRvideos).\n\n\u003CCallout w-full icon=\"i-simple-icons-youtube\" text=\"View my talk about UnJS\" to=\"https:\u002F\u002Fyoutu.be\u002F0BApl8r1VWc\" target=\"_blank\" \u002F>\n\n_Slides are coming soon._\n\nA good thing done, and it's so good to share knowledge with others and talk with them. I feel that I still have a long way to go before making the perfect presentation, so I will continue to submit talks to conferences and to meetups.\n\n\u003CCalloutAvailableForGivingATalk \u002F>\n\n## Finally\n\nDevoxx France is a unique event. There are so many talks, so much diversity in the subjects, and so many people that you will definitely learn something. This year was the biggest Devoxx France ever with more than 4000 attendees.\n\nAnd even if the event is physically finished, it continues online with the [replay on YouTube](https:\u002F\u002Fwww.youtube.com\u002F@DevoxxFRvideos). So, if you can't attend, you can still watch the talks!\n\nThanks to the organizers and the red coat team for this amazing event. I hope to see you next year (I'm already working on many talks for the next edition)!\n\nUntil then, take care and keep learning!\n",{"title":60416,"description":60846},{"loc":60848,"images":60852},[60853,60854,60855,60856,60857,60858],{"loc":60442},{"loc":60680},{"loc":60707},{"loc":60720},{"loc":60739},{"loc":60755},"posts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights","u5n4K5q-Zdb1cmC931CHIiMev75GcB5jAH4MvhUT0sw",{"id":60862,"title":60863,"body":60864,"contentId":30890,"date":61048,"description":61049,"extension":18,"meta":61050,"navigation":20,"path":61051,"rawbody":61052,"robots":23,"schemaOrg":23,"seo":61053,"sitemap":61054,"stem":61062,"__hash__":61063},"posts\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem.md","My First Meetup Talk: A Dive into the Large UnJS Ecosystem",{"type":7,"value":60865,"toc":61041},[60866,60869,60872,60878,60883,60886,60890,60893,60902,60911,60914,60918,60921,60929,60932,60940,60944,60947,60955,60966,60970,60973,60976,60982,60985,60991,60994,61000,61003,61009,61012,61015,61021,61023,61026,61029,61036],[35,60867,60868],{},"In April 2022, I moved to Paris for a 4-month internship in a consulting company. It was the first time for me to live in a city of this size.",[35,60870,60871],{},"One of the cool things about Paris is that you can easily do an event every evening. I took the opportunity to go to a lot of meetups. I went to ParisJS, TypeScript Paris, Vue.js Paris, and many others. After this internship, I continued in Paris for a gap year, and I continued to go to meetups. I met a lot of amazing people and I learned a lot of things from JavaScript to HTTP and security and entrepreneurship.",[35,60873,60874],{},[1690,60875],{"alt":60876,"src":60877},"GDI Live meetup","\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fgdi-live.jpg",[35,60879,60880],{},[1217,60881,60882],{},"A meetup I went to in Paris.",[35,60884,60885],{},"In September 2024, I had to go back to my school in the middle of France for 6 months of my last year of study. I was a little bit sad because I was not able to go to meetups anymore. I live in a small city where events are rare. But before leaving the city, I submitted two talks to the ParisJS meetup. I never had the opportunity to present them, unfortunately, but the idea of doing a talk was in my mind, and submitting talks was a first and concrete step.",[51,60887,60889],{"id":60888},"meetups-again","Meetups again",[35,60891,60892],{},"Patience is important, and I knew (I hoped very much) that I would have the opportunity to do a talk at a meetup one day.",[35,60894,60895,60896,60901],{},"In November 2023, ",[39,60897,60900],{"href":60898,"rel":60899},"https:\u002F\u002Ftwitter.com\u002Fedimitchel",[77],"Michel",", the organizer of StrasbourgJS, contacted me to propose a talk at his meetup. I was so happy. Going back to a meetup, and as a speaker, that is so cool!",[35,60903,60904,60905,60910],{},"Of course, I said yes! Then, he announced that ",[39,60906,60909],{"href":60907,"rel":60908},"https:\u002F\u002Ftwitter.com\u002Fromainlanz",[77],"Romain Lanz"," would also do a talk at the meetup. This meetup was going to be even more amazing because I love Romain's streams, that I've been following for a long time now, and his mindset about the JavaScript ecosystem.",[35,60912,60913],{},"The meetup will happen in April 2024.",[51,60915,60917],{"id":60916},"working-on-the-talk","Working on the talk",[35,60919,60920],{},"January 2024 to April 2024, 3 months to work on the talk, and that's not that much.",[35,60922,60923,60924,60928],{},"First, I needed to choose a subject. Because of my 6-month internship in Switzerland and my talk at Devoxx France I had to prepare, time was a real constraint. I decided to talk about the ",[39,60925,60927],{"href":35152,"rel":60926},[77],"UnJS ecosystem",". A similar subject to the one I will present at Devoxx France but with a different angle.",[35,60930,60931],{},"The thing is that the UnJS ecosystem is giant, with more than 63 packages, mainly for package developers, from Nitro, the server toolkit, to unbuild to help developers build their npm packages. What did I want to talk about? Let's discover it with a 20-minute talk showing the main packages and how they can help developers.",[35,60933,60934,60935,60939],{},"Then, let's work on it by opening my ",[39,60936,60938],{"href":7907,"rel":60937},[77],"talks repository"," and start writing the slides.",[51,60941,60943],{"id":60942},"making-my-talk-better","Making my talk better",[35,60945,60946],{},"From lecture courses to company presentations, I, unfortunately, see a lot of boring talks. When I was in students associations, I had the opportunity to learn how to make a talk and to train other students.",[35,60948,60949,60950,60954],{},"I got the idea after seeing the talk \"State of Nuxt 2024\" from ",[39,60951,16108],{"href":60952,"rel":60953},"https:\u002F\u002Ftwitter.com\u002Fdanielcroe",[77]," at Vue Amsterdam 2024. He asked people to go to a link, answer a question about Nuxt, and magically, answers appeared on the screen. Then, he clicked a button to generate with AI a summary. The State of Nuxt was done with the audience. More than a talk, it was a real experience.",[35,60956,60957,60958,60961,60962,60965],{},"I wanted to do the same thing with my talk, to make it interactive and audience-driven. \"Découvrons ",[1217,60959,60960],{},"ensemble"," l'écosystème UnJS\" (in English: \"Let's ",[1217,60963,60964],{},"together"," discover the UnJS ecosystem\") was born.",[51,60967,60969],{"id":60968},"_12-april-2024-strasbourg","12 April 2024, Strasbourg",[35,60971,60972],{},"It's D-day. After 4 weeks of building the interactive part of the talk (it will be the subject of another article), I was ready to present it. (I have to confess that I finished my slides 3 hours before the meetup 😅).",[35,60974,60975],{},"With Romain, who was also coming from Switzerland, we arrived at the venue and discovered the (big) amphitheatre. Unexpected because I'm more familiar with meetups in company open-spaces where tables are moved away and chairs against each other.",[35,60977,60978],{},[1690,60979],{"alt":60980,"src":60981},"StrasbourgJS Amphitheatre","\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Famphitheatre.jpg",[35,60983,60984],{},"The meetup started with Michel introducing the meetup and the speakers (us!).",[35,60986,60987],{},[1690,60988],{"alt":60989,"src":60990},"Michel on stage","\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fmichel-on-stage.jpg",[35,60992,60993],{},"My talk, supposed to be 20 minutes, was 30 minutes long and could have been much longer. The interactive part was a success because it was working, but also because people were participating. I was so happy after all the work done.",[35,60995,60996],{},[1690,60997],{"alt":60998,"src":60999},"Estéban Soubiran Talk","\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Festeban-soubiran-talk.jpg",[35,61001,61002],{},"I was followed by Romain who talked about his background and AdonisJS. 45 minutes of pure pleasure to listen to him and rediscovering the beauty of AdonisJS. I think he convinced some of the audience to try out the framework! 🥇",[35,61004,61005],{},[1690,61006],{"alt":61007,"src":61008},"Romain Lanz Talk","\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fromain-lanz-talk.jpg",[35,61010,61011],{},"After the talks, it was time to eat, drink, and discuss with the people, around 35 people, who came to the meetup.",[35,61013,61014],{},"In front of the picture, it's Romain and me. Behind, it's all the StrasbourgJS team that organized the meetup!",[35,61016,61017],{},[1690,61018],{"alt":61019,"src":61020},"StrasbourgJS Team and Speakers","\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fteam-and-speakers.jpeg",[51,61022,19334],{"id":19333},[35,61024,61025],{},"I missed sensations to go to a meetup, to meet people, to talk about tech with people working in a variety of companies around some food. StrasbourgJS was very good for that. Thanks for the organization! 💛",[35,61027,61028],{},"The journey continues at Devoxx France from 17th to 19th April 2024. I will present a talk about the UnJS ecosystem. I hope to see you there!",[35,61030,61031,61032,215],{},"I'm open to doing more talks at meetups! Do not hesitate to ",[39,61033,61035],{"href":36517,"rel":61034},[77],"contact me",[35,61037,61038],{},[1217,61039,61040],{},"Slides and replay are coming soon",{"title":13,"searchDepth":14,"depth":14,"links":61042},[61043,61044,61045,61046,61047],{"id":60888,"depth":14,"text":60889},{"id":60916,"depth":14,"text":60917},{"id":60942,"depth":14,"text":60943},{"id":60968,"depth":14,"text":60969},{"id":19333,"depth":14,"text":19334},"2024-04-12","Explore the expansive UnJS ecosystem during my first meetup talk at StrasbourgJS. My very personal experience at StrasbourgJS.",{},"\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem","---\ncontentId: ba562d1f-534c-4cef-9004-2fefc09829a6\ntitle: \"My First Meetup Talk: A Dive into the Large UnJS Ecosystem\"\ndescription: Explore the expansive UnJS ecosystem during my first meetup talk at StrasbourgJS. My very personal experience at StrasbourgJS.\ndate: 2024-04-12\n---\n\nIn April 2022, I moved to Paris for a 4-month internship in a consulting company. It was the first time for me to live in a city of this size.\n\nOne of the cool things about Paris is that you can easily do an event every evening. I took the opportunity to go to a lot of meetups. I went to ParisJS, TypeScript Paris, Vue.js Paris, and many others. After this internship, I continued in Paris for a gap year, and I continued to go to meetups. I met a lot of amazing people and I learned a lot of things from JavaScript to HTTP and security and entrepreneurship.\n\n![GDI Live meetup](\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fgdi-live.jpg)\n\n_A meetup I went to in Paris._\n\nIn September 2024, I had to go back to my school in the middle of France for 6 months of my last year of study. I was a little bit sad because I was not able to go to meetups anymore. I live in a small city where events are rare. But before leaving the city, I submitted two talks to the ParisJS meetup. I never had the opportunity to present them, unfortunately, but the idea of doing a talk was in my mind, and submitting talks was a first and concrete step.\n\n## Meetups again\n\nPatience is important, and I knew (I hoped very much) that I would have the opportunity to do a talk at a meetup one day.\n\nIn November 2023, [Michel](https:\u002F\u002Ftwitter.com\u002Fedimitchel), the organizer of StrasbourgJS, contacted me to propose a talk at his meetup. I was so happy. Going back to a meetup, and as a speaker, that is so cool!\n\nOf course, I said yes! Then, he announced that [Romain Lanz](https:\u002F\u002Ftwitter.com\u002Fromainlanz) would also do a talk at the meetup. This meetup was going to be even more amazing because I love Romain's streams, that I've been following for a long time now, and his mindset about the JavaScript ecosystem.\n\nThe meetup will happen in April 2024.\n\n## Working on the talk\n\nJanuary 2024 to April 2024, 3 months to work on the talk, and that's not that much.\n\nFirst, I needed to choose a subject. Because of my 6-month internship in Switzerland and my talk at Devoxx France I had to prepare, time was a real constraint. I decided to talk about the [UnJS ecosystem](https:\u002F\u002Funjs.io). A similar subject to the one I will present at Devoxx France but with a different angle.\n\nThe thing is that the UnJS ecosystem is giant, with more than 63 packages, mainly for package developers, from Nitro, the server toolkit, to unbuild to help developers build their npm packages. What did I want to talk about? Let's discover it with a 20-minute talk showing the main packages and how they can help developers.\n\nThen, let's work on it by opening my [talks repository](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Ftalks) and start writing the slides.\n\n## Making my talk better\n\nFrom lecture courses to company presentations, I, unfortunately, see a lot of boring talks. When I was in students associations, I had the opportunity to learn how to make a talk and to train other students.\n\nI got the idea after seeing the talk \"State of Nuxt 2024\" from [Daniel Roe](https:\u002F\u002Ftwitter.com\u002Fdanielcroe) at Vue Amsterdam 2024. He asked people to go to a link, answer a question about Nuxt, and magically, answers appeared on the screen. Then, he clicked a button to generate with AI a summary. The State of Nuxt was done with the audience. More than a talk, it was a real experience.\n\nI wanted to do the same thing with my talk, to make it interactive and audience-driven. \"Découvrons _ensemble_ l'écosystème UnJS\" (in English: \"Let's _together_ discover the UnJS ecosystem\") was born.\n\n## 12 April 2024, Strasbourg\n\nIt's D-day. After 4 weeks of building the interactive part of the talk (it will be the subject of another article), I was ready to present it. (I have to confess that I finished my slides 3 hours before the meetup 😅).\n\nWith Romain, who was also coming from Switzerland, we arrived at the venue and discovered the (big) amphitheatre. Unexpected because I'm more familiar with meetups in company open-spaces where tables are moved away and chairs against each other.\n\n![StrasbourgJS Amphitheatre](\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Famphitheatre.jpg)\n\nThe meetup started with Michel introducing the meetup and the speakers (us!).\n\n![Michel on stage](\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fmichel-on-stage.jpg)\n\nMy talk, supposed to be 20 minutes, was 30 minutes long and could have been much longer. The interactive part was a success because it was working, but also because people were participating. I was so happy after all the work done.\n\n![Estéban Soubiran Talk](\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Festeban-soubiran-talk.jpg)\n\nI was followed by Romain who talked about his background and AdonisJS. 45 minutes of pure pleasure to listen to him and rediscovering the beauty of AdonisJS. I think he convinced some of the audience to try out the framework! 🥇\n\n![Romain Lanz Talk](\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fromain-lanz-talk.jpg)\n\nAfter the talks, it was time to eat, drink, and discuss with the people, around 35 people, who came to the meetup.\n\nIn front of the picture, it's Romain and me. Behind, it's all the StrasbourgJS team that organized the meetup!\n\n![StrasbourgJS Team and Speakers](\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fteam-and-speakers.jpeg)\n\n## Finally\n\nI missed sensations to go to a meetup, to meet people, to talk about tech with people working in a variety of companies around some food. StrasbourgJS was very good for that. Thanks for the organization! 💛\n\nThe journey continues at Devoxx France from 17th to 19th April 2024. I will present a talk about the UnJS ecosystem. I hope to see you there!\n\nI'm open to doing more talks at meetups! Do not hesitate to [contact me](https:\u002F\u002Fx.com\u002Fsoubiran_).\n\n_Slides and replay are coming soon_\n",{"title":60863,"description":61049},{"loc":61051,"images":61055},[61056,61057,61058,61059,61060,61061],{"loc":60877},{"loc":60981},{"loc":60990},{"loc":60999},{"loc":61008},{"loc":61020},"posts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem","9xrr3-_cG0yHOEks7A32U8JWiOahpPtJMM28TYIDApQ",{"id":61065,"title":61066,"body":61067,"contentId":63599,"date":63600,"description":63601,"extension":18,"meta":63602,"navigation":20,"path":63603,"rawbody":63604,"robots":23,"schemaOrg":23,"seo":63605,"sitemap":63606,"stem":63610,"__hash__":63611},"posts\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events.md","Create Real-Time Node.js Apps with Server-Sent Events",{"type":7,"value":61068,"toc":63589},[61069,61076,61084,61087,61091,61094,61098,61105,61116,61122,61465,61474,61477,61489,61493,61498,61638,61641,61645,61648,61655,61945,61969,61973,61976,61979,62277,62286,62291,62517,62521,62524,62527,62533,62536,62539,63025,63035,63041,63050,63070,63080,63084,63089,63096,63099,63527,63536,63542,63548,63551,63557,63561,63564,63567,63580,63583,63586],[35,61070,61071,61072,61075],{},"I'm deeply convinced that to understand a concept or a technology, we all need to try ",[6850,61073,61074],{},"to cook something with it"," using the lowest level possible. This is not only related to programming but to everything in life.",[35,61077,61078,61079,61083],{},"In the continuation of the article ",[39,61080,61082],{"href":61081},"\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets","Real-time without WebSocket",", I think that building a little client-server application using Server-Sent Events (SSE) is a good way to understand how it works and to give us the confidence to use it in our projects.",[35,61085,61086],{},"This client-server application will be a little chat where the client will send a message to the server and the server will broadcast the message to all connected clients. No external library, no framework, just Node.js.",[51,61088,61090],{"id":61089},"requirements","Requirements",[35,61092,61093],{},"To follow this tutorial, we need to have Node.js installed on our machine. That's all.",[51,61095,61097],{"id":61096},"the-server","The server",[35,61099,61100,61101,61104],{},"Create a file named ",[156,61102,61103],{},"server.mjs",". This file will contain the server code to perform the following actions:",[1576,61106,61107,61110,61113],{},[71,61108,61109],{},"Serve the client-side code aka a simple HTML file",[71,61111,61112],{},"Receive messages from clients",[71,61114,61115],{},"Accept Server-Sent Events connections and broadcast messages",[35,61117,61118,61119,61121],{},"But for now, let's begin by serving the client-side code. Our server will be built using the native Node.js ",[156,61120,28444],{}," module.",[192,61123,61125],{"className":25029,"code":61124,"language":25031,"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 Serve the index.html file\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(`Server running at http:\u002F\u002F${hostname}:${port}\u002F`)\n})\n",[156,61126,61127,61147,61167,61185,61189,61205,61217,61221,61248,61253,61267,61296,61300,61333,61358,61362,61377,61387,61393,61397,61422,61459],{"__ignoreMap":13},[200,61128,61129,61131,61133,61136,61138,61140,61142,61145],{"class":202,"line":203},[200,61130,973],{"class":206},[200,61132,318],{"class":214},[200,61134,61135],{"class":210}," readFileSync",[200,61137,981],{"class":214},[200,61139,984],{"class":206},[200,61141,242],{"class":214},[200,61143,61144],{"class":245},"node:fs",[200,61146,992],{"class":214},[200,61148,61149,61151,61153,61156,61158,61160,61162,61165],{"class":202,"line":14},[200,61150,973],{"class":206},[200,61152,318],{"class":214},[200,61154,61155],{"class":210}," createServer",[200,61157,981],{"class":214},[200,61159,984],{"class":206},[200,61161,242],{"class":214},[200,61163,61164],{"class":245},"node:http",[200,61166,992],{"class":214},[200,61168,61169,61171,61173,61175,61177,61179,61181,61183],{"class":202,"line":255},[200,61170,973],{"class":206},[200,61172,318],{"class":214},[200,61174,12462],{"class":210},[200,61176,981],{"class":214},[200,61178,984],{"class":206},[200,61180,242],{"class":214},[200,61182,12471],{"class":245},[200,61184,992],{"class":214},[200,61186,61187],{"class":202,"line":272},[200,61188,529],{"emptyLinePlaceholder":20},[200,61190,61191,61193,61196,61198,61200,61203],{"class":202,"line":283},[200,61192,443],{"class":379},[200,61194,61195],{"class":210}," hostname ",[200,61197,449],{"class":214},[200,61199,242],{"class":214},[200,61201,61202],{"class":245},"127.0.0.1",[200,61204,992],{"class":214},[200,61206,61207,61209,61212,61214],{"class":202,"line":300},[200,61208,443],{"class":379},[200,61210,61211],{"class":210}," port ",[200,61213,449],{"class":214},[200,61215,61216],{"class":2045}," 3000\n",[200,61218,61219],{"class":202,"line":310},[200,61220,529],{"emptyLinePlaceholder":20},[200,61222,61223,61225,61227,61229,61231,61233,61235,61238,61240,61242,61244,61246],{"class":202,"line":336},[200,61224,443],{"class":379},[200,61226,19823],{"class":210},[200,61228,449],{"class":214},[200,61230,61155],{"class":223},[200,61232,227],{"class":210},[200,61234,227],{"class":214},[200,61236,61237],{"class":386},"req",[200,61239,550],{"class":214},[200,61241,10939],{"class":386},[200,61243,2089],{"class":214},[200,61245,393],{"class":379},[200,61247,280],{"class":214},[200,61249,61250],{"class":202,"line":342},[200,61251,61252],{"class":401},"  \u002F\u002F Serve the index.html file\n",[200,61254,61255,61258,61260,61262,61264],{"class":202,"line":365},[200,61256,61257],{"class":210},"  res",[200,61259,215],{"class":214},[200,61261,39459],{"class":210},[200,61263,510],{"class":214},[200,61265,61266],{"class":2045}," 200\n",[200,61268,61269,61271,61273,61276,61278,61280,61283,61285,61287,61289,61292,61294],{"class":202,"line":371},[200,61270,61257],{"class":210},[200,61272,215],{"class":214},[200,61274,61275],{"class":223},"setHeader",[200,61277,227],{"class":235},[200,61279,249],{"class":214},[200,61281,61282],{"class":245},"Content-Type",[200,61284,249],{"class":214},[200,61286,550],{"class":214},[200,61288,242],{"class":214},[200,61290,61291],{"class":245},"text\u002Fhtml",[200,61293,249],{"class":214},[200,61295,416],{"class":235},[200,61297,61298],{"class":202,"line":398},[200,61299,529],{"emptyLinePlaceholder":20},[200,61301,61302,61304,61307,61309,61311,61313,61315,61317,61320,61322,61324,61326,61329,61331],{"class":202,"line":405},[200,61303,3723],{"class":379},[200,61305,61306],{"class":210}," htmlFile",[200,61308,510],{"class":214},[200,61310,12462],{"class":223},[200,61312,227],{"class":235},[200,61314,15565],{"class":210},[200,61316,215],{"class":214},[200,61318,61319],{"class":223},"cwd",[200,61321,1532],{"class":235},[200,61323,550],{"class":214},[200,61325,242],{"class":214},[200,61327,61328],{"class":245},"index.html",[200,61330,249],{"class":214},[200,61332,416],{"class":235},[200,61334,61335,61337,61339,61341,61343,61345,61348,61350,61352,61354,61356],{"class":202,"line":410},[200,61336,3723],{"class":379},[200,61338,10276],{"class":210},[200,61340,510],{"class":214},[200,61342,61135],{"class":223},[200,61344,227],{"class":235},[200,61346,61347],{"class":210},"htmlFile",[200,61349,550],{"class":214},[200,61351,242],{"class":214},[200,61353,14746],{"class":245},[200,61355,249],{"class":214},[200,61357,416],{"class":235},[200,61359,61360],{"class":202,"line":861},[200,61361,529],{"emptyLinePlaceholder":20},[200,61363,61364,61366,61368,61371,61373,61375],{"class":202,"line":890},[200,61365,61257],{"class":210},[200,61367,215],{"class":214},[200,61369,61370],{"class":223},"write",[200,61372,227],{"class":235},[200,61374,593],{"class":210},[200,61376,416],{"class":235},[200,61378,61379,61381,61383,61385],{"class":202,"line":900},[200,61380,61257],{"class":210},[200,61382,215],{"class":214},[200,61384,28841],{"class":223},[200,61386,466],{"class":235},[200,61388,61389,61391],{"class":202,"line":905},[200,61390,413],{"class":214},[200,61392,416],{"class":210},[200,61394,61395],{"class":202,"line":931},[200,61396,529],{"emptyLinePlaceholder":20},[200,61398,61399,61401,61403,61406,61409,61411,61414,61416,61418,61420],{"class":202,"line":3640},[200,61400,3108],{"class":210},[200,61402,215],{"class":214},[200,61404,61405],{"class":223},"listen",[200,61407,61408],{"class":210},"(port",[200,61410,550],{"class":214},[200,61412,61413],{"class":210}," hostname",[200,61415,550],{"class":214},[200,61417,3714],{"class":214},[200,61419,393],{"class":379},[200,61421,280],{"class":214},[200,61423,61424,61426,61428,61430,61432,61434,61437,61439,61442,61444,61446,61448,61451,61453,61455,61457],{"class":202,"line":3645},[200,61425,1113],{"class":210},[200,61427,215],{"class":214},[200,61429,1118],{"class":223},[200,61431,227],{"class":235},[200,61433,3318],{"class":214},[200,61435,61436],{"class":245},"Server running at http:\u002F\u002F",[200,61438,4853],{"class":214},[200,61440,61441],{"class":210},"hostname",[200,61443,413],{"class":214},[200,61445,239],{"class":245},[200,61447,4853],{"class":214},[200,61449,61450],{"class":210},"port",[200,61452,413],{"class":214},[200,61454,2882],{"class":245},[200,61456,3318],{"class":214},[200,61458,416],{"class":235},[200,61460,61461,61463],{"class":202,"line":4020},[200,61462,413],{"class":214},[200,61464,416],{"class":210},[35,61466,61467,61468,61470,61471,215],{},"When a request hits the server, it will respond with the content of the ",[156,61469,61328],{}," file by reading it on the disk. The server is listening on the port ",[156,61472,61473],{},"3000",[35,61475,61476],{},"We can start our server with the following command:",[192,61478,61480],{"className":1355,"code":61479,"language":1358,"meta":13,"style":13},"node server.mjs\n",[156,61481,61482],{"__ignoreMap":13},[200,61483,61484,61486],{"class":202,"line":203},[200,61485,10026],{"class":1978},[200,61487,61488],{"class":245}," server.mjs\n",[51,61490,61492],{"id":61491},"the-client","The client",[35,61494,61100,61495,61497],{},[156,61496,61328],{}," at the root of the project. This file will contain the client-side code:",[192,61499,61501],{"className":591,"code":61500,"language":593,"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,61502,61503,61515,61533,61541,61560,61589,61606,61614,61622,61630],{"__ignoreMap":13},[200,61504,61505,61508,61511,61513],{"class":202,"line":203},[200,61506,61507],{"class":214},"\u003C!",[200,61509,61510],{"class":235},"doctype",[200,61512,10276],{"class":379},[200,61514,648],{"class":214},[200,61516,61517,61519,61521,61523,61525,61527,61529,61531],{"class":202,"line":14},[200,61518,600],{"class":214},[200,61520,593],{"class":235},[200,61522,14590],{"class":379},[200,61524,449],{"class":214},[200,61526,611],{"class":214},[200,61528,14597],{"class":245},[200,61530,611],{"class":214},[200,61532,648],{"class":214},[200,61534,61535,61537,61539],{"class":202,"line":255},[200,61536,657],{"class":214},[200,61538,14574],{"class":235},[200,61540,648],{"class":214},[200,61542,61543,61545,61547,61549,61551,61553,61556,61558],{"class":202,"line":272},[200,61544,804],{"class":214},[200,61546,2015],{"class":235},[200,61548,14739],{"class":379},[200,61550,449],{"class":214},[200,61552,611],{"class":214},[200,61554,61555],{"class":245},"UTF-8",[200,61557,611],{"class":214},[200,61559,21487],{"class":214},[200,61561,61562,61564,61566,61568,61570,61572,61574,61576,61578,61580,61582,61585,61587],{"class":202,"line":283},[200,61563,804],{"class":214},[200,61565,2015],{"class":235},[200,61567,701],{"class":379},[200,61569,449],{"class":214},[200,61571,611],{"class":214},[200,61573,14794],{"class":245},[200,61575,611],{"class":214},[200,61577,6041],{"class":379},[200,61579,449],{"class":214},[200,61581,611],{"class":214},[200,61583,61584],{"class":245},"width=device-width, initial-scale=1.0",[200,61586,611],{"class":214},[200,61588,21487],{"class":214},[200,61590,61591,61593,61595,61597,61600,61602,61604],{"class":202,"line":300},[200,61592,804],{"class":214},[200,61594,3566],{"class":235},[200,61596,675],{"class":214},[200,61598,61599],{"class":210},"Node SSE",[200,61601,681],{"class":214},[200,61603,3566],{"class":235},[200,61605,648],{"class":214},[200,61607,61608,61610,61612],{"class":202,"line":310},[200,61609,893],{"class":214},[200,61611,14574],{"class":235},[200,61613,648],{"class":214},[200,61615,61616,61618,61620],{"class":202,"line":336},[200,61617,657],{"class":214},[200,61619,23870],{"class":235},[200,61621,648],{"class":214},[200,61623,61624,61626,61628],{"class":202,"line":342},[200,61625,893],{"class":214},[200,61627,23870],{"class":235},[200,61629,648],{"class":214},[200,61631,61632,61634,61636],{"class":202,"line":365},[200,61633,681],{"class":214},[200,61635,593],{"class":235},[200,61637,648],{"class":214},[35,61639,61640],{},"Yeah, that's a very simple HTML file, nothing fancy for now.",[51,61642,61644],{"id":61643},"server-sent-events-connection","Server-sent events connection",[35,61646,61647],{},"Now, let's add the Server-Sent Events connection to the server. To handle SSE, we need to respond with some headers to keep the connection open and to send data. So the client will ask for a resource and the server will respond with a stream.",[35,61649,61650,61651,61654],{},"This resource will be ",[156,61652,61653],{},"\u002Fevents",". Let's update the server code:",[192,61656,61658],{"className":25029,"code":61657,"language":25031,"meta":13,"style":13},"\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n  \u002F\u002F `\u002Fevents` endpoint used to listen for 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: Handle the connection\n  }\n  else {\n    \u002F\u002F Serve the index.html file\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,61659,61660,61664,61668,61694,61699,61723,61751,61779,61783,61788,61792,61798,61803,61815,61841,61845,61875,61899,61903,61917,61927,61931,61937,61941],{"__ignoreMap":13},[200,61661,61662],{"class":202,"line":203},[200,61663,33144],{"class":401},[200,61665,61666],{"class":202,"line":14},[200,61667,529],{"emptyLinePlaceholder":20},[200,61669,61670,61672,61674,61676,61678,61680,61682,61684,61686,61688,61690,61692],{"class":202,"line":255},[200,61671,443],{"class":379},[200,61673,19823],{"class":210},[200,61675,449],{"class":214},[200,61677,61155],{"class":223},[200,61679,227],{"class":210},[200,61681,227],{"class":214},[200,61683,61237],{"class":386},[200,61685,550],{"class":214},[200,61687,10939],{"class":386},[200,61689,2089],{"class":214},[200,61691,393],{"class":379},[200,61693,280],{"class":214},[200,61695,61696],{"class":202,"line":272},[200,61697,61698],{"class":401},"  \u002F\u002F `\u002Fevents` endpoint used to listen for messages\n",[200,61700,61701,61703,61705,61707,61709,61711,61713,61715,61717,61719,61721],{"class":202,"line":283},[200,61702,20075],{"class":206},[200,61704,1095],{"class":235},[200,61706,61237],{"class":210},[200,61708,215],{"class":214},[200,61710,2020],{"class":210},[200,61712,3283],{"class":214},[200,61714,242],{"class":214},[200,61716,61653],{"class":245},[200,61718,249],{"class":214},[200,61720,5267],{"class":235},[200,61722,230],{"class":214},[200,61724,61725,61728,61730,61732,61734,61736,61738,61740,61742,61744,61747,61749],{"class":202,"line":300},[200,61726,61727],{"class":210},"    res",[200,61729,215],{"class":214},[200,61731,61275],{"class":223},[200,61733,227],{"class":235},[200,61735,249],{"class":214},[200,61737,61282],{"class":245},[200,61739,249],{"class":214},[200,61741,550],{"class":214},[200,61743,242],{"class":214},[200,61745,61746],{"class":245},"text\u002Fevent-stream",[200,61748,249],{"class":214},[200,61750,416],{"class":235},[200,61752,61753,61755,61757,61759,61761,61763,61766,61768,61770,61772,61775,61777],{"class":202,"line":310},[200,61754,61727],{"class":210},[200,61756,215],{"class":214},[200,61758,61275],{"class":223},[200,61760,227],{"class":235},[200,61762,249],{"class":214},[200,61764,61765],{"class":245},"Cache-Control",[200,61767,249],{"class":214},[200,61769,550],{"class":214},[200,61771,242],{"class":214},[200,61773,61774],{"class":245},"no-cache",[200,61776,249],{"class":214},[200,61778,416],{"class":235},[200,61780,61781],{"class":202,"line":336},[200,61782,529],{"emptyLinePlaceholder":20},[200,61784,61785],{"class":202,"line":342},[200,61786,61787],{"class":401},"    \u002F\u002F TODO: Handle the connection\n",[200,61789,61790],{"class":202,"line":365},[200,61791,4490],{"class":214},[200,61793,61794,61796],{"class":202,"line":371},[200,61795,45892],{"class":206},[200,61797,280],{"class":214},[200,61799,61800],{"class":202,"line":398},[200,61801,61802],{"class":401},"    \u002F\u002F Serve the index.html file\n",[200,61804,61805,61807,61809,61811,61813],{"class":202,"line":405},[200,61806,61727],{"class":210},[200,61808,215],{"class":214},[200,61810,39459],{"class":210},[200,61812,510],{"class":214},[200,61814,61266],{"class":2045},[200,61816,61817,61819,61821,61823,61825,61827,61829,61831,61833,61835,61837,61839],{"class":202,"line":410},[200,61818,61727],{"class":210},[200,61820,215],{"class":214},[200,61822,61275],{"class":223},[200,61824,227],{"class":235},[200,61826,249],{"class":214},[200,61828,61282],{"class":245},[200,61830,249],{"class":214},[200,61832,550],{"class":214},[200,61834,242],{"class":214},[200,61836,61291],{"class":245},[200,61838,249],{"class":214},[200,61840,416],{"class":235},[200,61842,61843],{"class":202,"line":861},[200,61844,529],{"emptyLinePlaceholder":20},[200,61846,61847,61849,61851,61853,61855,61857,61859,61861,61863,61865,61867,61869,61871,61873],{"class":202,"line":890},[200,61848,1999],{"class":379},[200,61850,61306],{"class":210},[200,61852,510],{"class":214},[200,61854,12462],{"class":223},[200,61856,227],{"class":235},[200,61858,15565],{"class":210},[200,61860,215],{"class":214},[200,61862,61319],{"class":223},[200,61864,1532],{"class":235},[200,61866,550],{"class":214},[200,61868,242],{"class":214},[200,61870,61328],{"class":245},[200,61872,249],{"class":214},[200,61874,416],{"class":235},[200,61876,61877,61879,61881,61883,61885,61887,61889,61891,61893,61895,61897],{"class":202,"line":900},[200,61878,1999],{"class":379},[200,61880,10276],{"class":210},[200,61882,510],{"class":214},[200,61884,61135],{"class":223},[200,61886,227],{"class":235},[200,61888,61347],{"class":210},[200,61890,550],{"class":214},[200,61892,242],{"class":214},[200,61894,14746],{"class":245},[200,61896,249],{"class":214},[200,61898,416],{"class":235},[200,61900,61901],{"class":202,"line":905},[200,61902,529],{"emptyLinePlaceholder":20},[200,61904,61905,61907,61909,61911,61913,61915],{"class":202,"line":931},[200,61906,61727],{"class":210},[200,61908,215],{"class":214},[200,61910,61370],{"class":223},[200,61912,227],{"class":235},[200,61914,593],{"class":210},[200,61916,416],{"class":235},[200,61918,61919,61921,61923,61925],{"class":202,"line":3640},[200,61920,61727],{"class":210},[200,61922,215],{"class":214},[200,61924,28841],{"class":223},[200,61926,466],{"class":235},[200,61928,61929],{"class":202,"line":3645},[200,61930,4490],{"class":214},[200,61932,61933,61935],{"class":202,"line":4020},[200,61934,413],{"class":214},[200,61936,416],{"class":210},[200,61938,61939],{"class":202,"line":4475},[200,61940,529],{"emptyLinePlaceholder":20},[200,61942,61943],{"class":202,"line":4481},[200,61944,33144],{"class":401},[35,61946,61947,61948,61950,61951,3080,61954,61957,61958,61961,61962,61965,61966,61968],{},"When the client asks for the resource ",[156,61949,61653],{},", the server will respond with the headers ",[156,61952,61953],{},"Content-Type: text\u002Fevent-stream",[156,61955,61956],{},"Cache-Control: no-cache",". This will tell the client that the connection is a stream, i.e. the server will send data to the client at any time. The connection must not be cached to avoid issues with the stream.\nIt's important to note that we never call the function ",[156,61959,61960],{},"res.end()",". This is because ",[6850,61963,61964],{},"the connection must stay open"," to allow the server to send data to the client. The function ",[156,61967,61960],{}," closes the connection.",[51,61970,61972],{"id":61971},"chat-logic","Chat logic",[35,61974,61975],{},"This is very simple and simplified to the maximum, but the logic behind remains the same as a more complex application.",[35,61977,61978],{},"In our server, we will have a logic to receive messages from clients.",[192,61980,61982],{"className":25029,"code":61981,"language":25031,"meta":13,"style":13},"\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n\u002F\u002F `\u002Fevents` endpoint used to listen for messages\n  if (req.url === '\u002Fevents') {\n    \u002F\u002F ...\n\n    \u002F\u002F `\u002Fmessages` endpoint used to post 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: broadcast the message\n\n      \u002F\u002F Redirect to the index.html file\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Serve the index.html file\n    \u002F\u002F ...\n  }\n})\n",[156,61983,61984,61988,61992,62018,62023,62047,62051,62055,62060,62064,62091,62101,62105,62134,62144,62150,62154,62178,62183,62187,62192,62206,62233,62243,62249,62253,62259,62263,62267,62271],{"__ignoreMap":13},[200,61985,61986],{"class":202,"line":203},[200,61987,33144],{"class":401},[200,61989,61990],{"class":202,"line":14},[200,61991,529],{"emptyLinePlaceholder":20},[200,61993,61994,61996,61998,62000,62002,62004,62006,62008,62010,62012,62014,62016],{"class":202,"line":255},[200,61995,443],{"class":379},[200,61997,19823],{"class":210},[200,61999,449],{"class":214},[200,62001,61155],{"class":223},[200,62003,227],{"class":210},[200,62005,227],{"class":214},[200,62007,61237],{"class":386},[200,62009,550],{"class":214},[200,62011,10939],{"class":386},[200,62013,2089],{"class":214},[200,62015,393],{"class":379},[200,62017,280],{"class":214},[200,62019,62020],{"class":202,"line":272},[200,62021,62022],{"class":401},"\u002F\u002F `\u002Fevents` endpoint used to listen for messages\n",[200,62024,62025,62027,62029,62031,62033,62035,62037,62039,62041,62043,62045],{"class":202,"line":283},[200,62026,20075],{"class":206},[200,62028,1095],{"class":235},[200,62030,61237],{"class":210},[200,62032,215],{"class":214},[200,62034,2020],{"class":210},[200,62036,3283],{"class":214},[200,62038,242],{"class":214},[200,62040,61653],{"class":245},[200,62042,249],{"class":214},[200,62044,5267],{"class":235},[200,62046,230],{"class":214},[200,62048,62049],{"class":202,"line":300},[200,62050,13669],{"class":401},[200,62052,62053],{"class":202,"line":310},[200,62054,529],{"emptyLinePlaceholder":20},[200,62056,62057],{"class":202,"line":336},[200,62058,62059],{"class":401},"    \u002F\u002F `\u002Fmessages` endpoint used to post messages\n",[200,62061,62062],{"class":202,"line":342},[200,62063,4490],{"class":214},[200,62065,62066,62068,62070,62072,62074,62076,62078,62080,62082,62085,62087,62089],{"class":202,"line":365},[200,62067,45892],{"class":206},[200,62069,45895],{"class":206},[200,62071,1095],{"class":235},[200,62073,61237],{"class":210},[200,62075,215],{"class":214},[200,62077,2020],{"class":210},[200,62079,3283],{"class":214},[200,62081,242],{"class":214},[200,62083,62084],{"class":245},"\u002Fmessage",[200,62086,249],{"class":214},[200,62088,5267],{"class":235},[200,62090,230],{"class":214},[200,62092,62093,62095,62097,62099],{"class":202,"line":371},[200,62094,52339],{"class":379},[200,62096,41038],{"class":210},[200,62098,510],{"class":214},[200,62100,18875],{"class":214},[200,62102,62103],{"class":202,"line":398},[200,62104,529],{"emptyLinePlaceholder":20},[200,62106,62107,62110,62112,62114,62116,62118,62120,62122,62124,62126,62128,62130,62132],{"class":202,"line":405},[200,62108,62109],{"class":210},"    req",[200,62111,215],{"class":214},[200,62113,25370],{"class":223},[200,62115,227],{"class":235},[200,62117,249],{"class":214},[200,62119,12726],{"class":245},[200,62121,249],{"class":214},[200,62123,550],{"class":214},[200,62125,1095],{"class":214},[200,62127,3598],{"class":386},[200,62129,2089],{"class":214},[200,62131,393],{"class":379},[200,62133,280],{"class":214},[200,62135,62136,62138,62141],{"class":202,"line":410},[200,62137,42082],{"class":210},[200,62139,62140],{"class":214}," +=",[200,62142,62143],{"class":210}," chunk\n",[200,62145,62146,62148],{"class":202,"line":861},[200,62147,2096],{"class":214},[200,62149,416],{"class":235},[200,62151,62152],{"class":202,"line":890},[200,62153,529],{"emptyLinePlaceholder":20},[200,62155,62156,62158,62160,62162,62164,62166,62168,62170,62172,62174,62176],{"class":202,"line":900},[200,62157,62109],{"class":210},[200,62159,215],{"class":214},[200,62161,25370],{"class":223},[200,62163,227],{"class":235},[200,62165,249],{"class":214},[200,62167,28841],{"class":245},[200,62169,249],{"class":214},[200,62171,550],{"class":214},[200,62173,3714],{"class":214},[200,62175,393],{"class":379},[200,62177,280],{"class":214},[200,62179,62180],{"class":202,"line":905},[200,62181,62182],{"class":401},"      \u002F\u002F TODO: broadcast the message\n",[200,62184,62185],{"class":202,"line":931},[200,62186,529],{"emptyLinePlaceholder":20},[200,62188,62189],{"class":202,"line":3640},[200,62190,62191],{"class":401},"      \u002F\u002F Redirect to the index.html file\n",[200,62193,62194,62197,62199,62201,62203],{"class":202,"line":3645},[200,62195,62196],{"class":210},"      res",[200,62198,215],{"class":214},[200,62200,39459],{"class":210},[200,62202,510],{"class":214},[200,62204,62205],{"class":2045}," 302\n",[200,62207,62208,62210,62212,62214,62216,62218,62221,62223,62225,62227,62229,62231],{"class":202,"line":4020},[200,62209,62196],{"class":210},[200,62211,215],{"class":214},[200,62213,61275],{"class":223},[200,62215,227],{"class":235},[200,62217,249],{"class":214},[200,62219,62220],{"class":245},"Location",[200,62222,249],{"class":214},[200,62224,550],{"class":214},[200,62226,242],{"class":214},[200,62228,2882],{"class":245},[200,62230,249],{"class":214},[200,62232,416],{"class":235},[200,62234,62235,62237,62239,62241],{"class":202,"line":4475},[200,62236,62196],{"class":210},[200,62238,215],{"class":214},[200,62240,28841],{"class":223},[200,62242,466],{"class":235},[200,62244,62245,62247],{"class":202,"line":4481},[200,62246,2096],{"class":214},[200,62248,416],{"class":235},[200,62250,62251],{"class":202,"line":4487},[200,62252,4490],{"class":214},[200,62254,62255,62257],{"class":202,"line":4493},[200,62256,45892],{"class":206},[200,62258,280],{"class":214},[200,62260,62261],{"class":202,"line":5454},[200,62262,61802],{"class":401},[200,62264,62265],{"class":202,"line":5459},[200,62266,13669],{"class":401},[200,62268,62269],{"class":202,"line":13227},[200,62270,4490],{"class":214},[200,62272,62273,62275],{"class":202,"line":13253},[200,62274,413],{"class":214},[200,62276,416],{"class":210},[35,62278,62279,62280,62282,62283,62285],{},"When the URL requested is ",[156,62281,62084],{},", the server will listen for the data sent by the client, which is the body of the request. When the request is finished, the server redirects the client to the ",[156,62284,61328],{}," file. This is a classic behavior when a form is submitted for a server-rendered application.",[35,62287,30272,62288,62290],{},[156,62289,61328],{}," file, add a form to send messages to the server:",[192,62292,62294],{"className":591,"code":62293,"language":593,"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\">Send\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[156,62295,62296,62306,62324,62332,62350,62378,62394,62402,62410,62438,62466,62493,62501,62509],{"__ignoreMap":13},[200,62297,62298,62300,62302,62304],{"class":202,"line":203},[200,62299,61507],{"class":214},[200,62301,61510],{"class":235},[200,62303,10276],{"class":379},[200,62305,648],{"class":214},[200,62307,62308,62310,62312,62314,62316,62318,62320,62322],{"class":202,"line":14},[200,62309,600],{"class":214},[200,62311,593],{"class":235},[200,62313,14590],{"class":379},[200,62315,449],{"class":214},[200,62317,611],{"class":214},[200,62319,14597],{"class":245},[200,62321,611],{"class":214},[200,62323,648],{"class":214},[200,62325,62326,62328,62330],{"class":202,"line":255},[200,62327,657],{"class":214},[200,62329,14574],{"class":235},[200,62331,648],{"class":214},[200,62333,62334,62336,62338,62340,62342,62344,62346,62348],{"class":202,"line":272},[200,62335,804],{"class":214},[200,62337,2015],{"class":235},[200,62339,14739],{"class":379},[200,62341,449],{"class":214},[200,62343,611],{"class":214},[200,62345,61555],{"class":245},[200,62347,611],{"class":214},[200,62349,21487],{"class":214},[200,62351,62352,62354,62356,62358,62360,62362,62364,62366,62368,62370,62372,62374,62376],{"class":202,"line":283},[200,62353,804],{"class":214},[200,62355,2015],{"class":235},[200,62357,701],{"class":379},[200,62359,449],{"class":214},[200,62361,611],{"class":214},[200,62363,14794],{"class":245},[200,62365,611],{"class":214},[200,62367,6041],{"class":379},[200,62369,449],{"class":214},[200,62371,611],{"class":214},[200,62373,61584],{"class":245},[200,62375,611],{"class":214},[200,62377,21487],{"class":214},[200,62379,62380,62382,62384,62386,62388,62390,62392],{"class":202,"line":300},[200,62381,804],{"class":214},[200,62383,3566],{"class":235},[200,62385,675],{"class":214},[200,62387,61599],{"class":210},[200,62389,681],{"class":214},[200,62391,3566],{"class":235},[200,62393,648],{"class":214},[200,62395,62396,62398,62400],{"class":202,"line":310},[200,62397,893],{"class":214},[200,62399,14574],{"class":235},[200,62401,648],{"class":214},[200,62403,62404,62406,62408],{"class":202,"line":336},[200,62405,657],{"class":214},[200,62407,23870],{"class":235},[200,62409,648],{"class":214},[200,62411,62412,62414,62416,62418,62420,62422,62424,62426,62428,62430,62432,62434,62436],{"class":202,"line":342},[200,62413,804],{"class":214},[200,62415,603],{"class":235},[200,62417,40910],{"class":379},[200,62419,449],{"class":214},[200,62421,611],{"class":214},[200,62423,62084],{"class":245},[200,62425,611],{"class":214},[200,62427,6577],{"class":379},[200,62429,449],{"class":214},[200,62431,611],{"class":214},[200,62433,3778],{"class":245},[200,62435,611],{"class":214},[200,62437,648],{"class":214},[200,62439,62440,62442,62444,62446,62448,62450,62452,62454,62456,62458,62460,62462,62464],{"class":202,"line":365},[200,62441,22027],{"class":214},[200,62443,355],{"class":235},[200,62445,321],{"class":379},[200,62447,449],{"class":214},[200,62449,611],{"class":214},[200,62451,698],{"class":245},[200,62453,611],{"class":214},[200,62455,701],{"class":379},[200,62457,449],{"class":214},[200,62459,611],{"class":214},[200,62461,29574],{"class":245},[200,62463,611],{"class":214},[200,62465,21487],{"class":214},[200,62467,62468,62470,62472,62474,62476,62478,62480,62482,62484,62487,62489,62491],{"class":202,"line":371},[200,62469,22027],{"class":214},[200,62471,910],{"class":235},[200,62473,321],{"class":379},[200,62475,449],{"class":214},[200,62477,611],{"class":214},[200,62479,917],{"class":245},[200,62481,611],{"class":214},[200,62483,675],{"class":214},[200,62485,62486],{"class":210},"Send",[200,62488,681],{"class":214},[200,62490,910],{"class":235},[200,62492,648],{"class":214},[200,62494,62495,62497,62499],{"class":202,"line":398},[200,62496,15074],{"class":214},[200,62498,603],{"class":235},[200,62500,648],{"class":214},[200,62502,62503,62505,62507],{"class":202,"line":405},[200,62504,893],{"class":214},[200,62506,23870],{"class":235},[200,62508,648],{"class":214},[200,62510,62511,62513,62515],{"class":202,"line":410},[200,62512,681],{"class":214},[200,62514,593],{"class":235},[200,62516,648],{"class":214},[51,62518,62520],{"id":62519},"broadcast-the-message","Broadcast the message",[35,62522,62523],{},"The missing feature is the broadcast of the message. How do we broadcast the message and what does it mean?",[35,62525,62526],{},"A broadcast is when a message is sent to everyone. Otherwise, it's a unicast, when a message is sent to a specific person.",[35,62528,62529],{},[1690,62530],{"alt":62531,"src":62532},"Broadcast vs Unicast","\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fbroadcast-vs-unicast.png",[35,62534,62535],{},"In our case, the client A will send a message to the server (unicast). Then, the server will send this message to every client (broadcast).",[35,62537,62538],{},"The unicast has been implemented in the previous section. For the broadcast, we need to keep track of the connected clients to loop over them when a message is received.",[192,62540,62542],{"className":25029,"code":62541,"language":25031,"meta":13,"style":13},"\u002F\u002F ...\n\n\u002F\u002F Keep track of the connected clients\nconst connection = new Set()\n\nconst server = createServer((req, res) => {\n  \u002F\u002F `\u002Fevents` endpoint used to listen for 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 `\u002Fmessages` endpoint used to post 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 Broadcast the message to all connected clients\n      for (const client of connection)\n        client.write(`data: ${body}\\n\\n`)\n\n      \u002F\u002F Redirect to the index.html file\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Serve the index.html file\n    \u002F\u002F ...\n  }\n})\n\n\u002F\u002F ...\n",[156,62543,62544,62548,62552,62557,62572,62576,62602,62606,62630,62656,62682,62686,62702,62706,62730,62746,62752,62756,62760,62764,62790,62800,62804,62832,62840,62846,62850,62874,62879,62898,62927,62931,62935,62947,62973,62983,62989,62993,62999,63003,63007,63011,63017,63021],{"__ignoreMap":13},[200,62545,62546],{"class":202,"line":203},[200,62547,33144],{"class":401},[200,62549,62550],{"class":202,"line":14},[200,62551,529],{"emptyLinePlaceholder":20},[200,62553,62554],{"class":202,"line":255},[200,62555,62556],{"class":401},"\u002F\u002F Keep track of the connected clients\n",[200,62558,62559,62561,62564,62566,62568,62570],{"class":202,"line":272},[200,62560,443],{"class":379},[200,62562,62563],{"class":210}," connection ",[200,62565,449],{"class":214},[200,62567,12997],{"class":214},[200,62569,15418],{"class":223},[200,62571,466],{"class":210},[200,62573,62574],{"class":202,"line":283},[200,62575,529],{"emptyLinePlaceholder":20},[200,62577,62578,62580,62582,62584,62586,62588,62590,62592,62594,62596,62598,62600],{"class":202,"line":300},[200,62579,443],{"class":379},[200,62581,19823],{"class":210},[200,62583,449],{"class":214},[200,62585,61155],{"class":223},[200,62587,227],{"class":210},[200,62589,227],{"class":214},[200,62591,61237],{"class":386},[200,62593,550],{"class":214},[200,62595,10939],{"class":386},[200,62597,2089],{"class":214},[200,62599,393],{"class":379},[200,62601,280],{"class":214},[200,62603,62604],{"class":202,"line":310},[200,62605,61698],{"class":401},[200,62607,62608,62610,62612,62614,62616,62618,62620,62622,62624,62626,62628],{"class":202,"line":336},[200,62609,20075],{"class":206},[200,62611,1095],{"class":235},[200,62613,61237],{"class":210},[200,62615,215],{"class":214},[200,62617,2020],{"class":210},[200,62619,3283],{"class":214},[200,62621,242],{"class":214},[200,62623,61653],{"class":245},[200,62625,249],{"class":214},[200,62627,5267],{"class":235},[200,62629,230],{"class":214},[200,62631,62632,62634,62636,62638,62640,62642,62644,62646,62648,62650,62652,62654],{"class":202,"line":342},[200,62633,61727],{"class":210},[200,62635,215],{"class":214},[200,62637,61275],{"class":223},[200,62639,227],{"class":235},[200,62641,249],{"class":214},[200,62643,61282],{"class":245},[200,62645,249],{"class":214},[200,62647,550],{"class":214},[200,62649,242],{"class":214},[200,62651,61746],{"class":245},[200,62653,249],{"class":214},[200,62655,416],{"class":235},[200,62657,62658,62660,62662,62664,62666,62668,62670,62672,62674,62676,62678,62680],{"class":202,"line":365},[200,62659,61727],{"class":210},[200,62661,215],{"class":214},[200,62663,61275],{"class":223},[200,62665,227],{"class":235},[200,62667,249],{"class":214},[200,62669,61765],{"class":245},[200,62671,249],{"class":214},[200,62673,550],{"class":214},[200,62675,242],{"class":214},[200,62677,61774],{"class":245},[200,62679,249],{"class":214},[200,62681,416],{"class":235},[200,62683,62684],{"class":202,"line":371},[200,62685,529],{"emptyLinePlaceholder":20},[200,62687,62688,62691,62693,62696,62698,62700],{"class":202,"line":398},[200,62689,62690],{"class":210},"    connection",[200,62692,215],{"class":214},[200,62694,62695],{"class":223},"add",[200,62697,227],{"class":235},[200,62699,10934],{"class":210},[200,62701,416],{"class":235},[200,62703,62704],{"class":202,"line":405},[200,62705,529],{"emptyLinePlaceholder":20},[200,62707,62708,62710,62712,62714,62716,62718,62720,62722,62724,62726,62728],{"class":202,"line":410},[200,62709,62109],{"class":210},[200,62711,215],{"class":214},[200,62713,25370],{"class":223},[200,62715,227],{"class":235},[200,62717,249],{"class":214},[200,62719,22565],{"class":245},[200,62721,249],{"class":214},[200,62723,550],{"class":214},[200,62725,3714],{"class":214},[200,62727,393],{"class":379},[200,62729,280],{"class":214},[200,62731,62732,62735,62737,62740,62742,62744],{"class":202,"line":861},[200,62733,62734],{"class":210},"      connection",[200,62736,215],{"class":214},[200,62738,62739],{"class":223},"delete",[200,62741,227],{"class":235},[200,62743,10934],{"class":210},[200,62745,416],{"class":235},[200,62747,62748,62750],{"class":202,"line":890},[200,62749,2096],{"class":214},[200,62751,416],{"class":235},[200,62753,62754],{"class":202,"line":900},[200,62755,529],{"emptyLinePlaceholder":20},[200,62757,62758],{"class":202,"line":905},[200,62759,62059],{"class":401},[200,62761,62762],{"class":202,"line":931},[200,62763,4490],{"class":214},[200,62765,62766,62768,62770,62772,62774,62776,62778,62780,62782,62784,62786,62788],{"class":202,"line":3640},[200,62767,45892],{"class":206},[200,62769,45895],{"class":206},[200,62771,1095],{"class":235},[200,62773,61237],{"class":210},[200,62775,215],{"class":214},[200,62777,2020],{"class":210},[200,62779,3283],{"class":214},[200,62781,242],{"class":214},[200,62783,62084],{"class":245},[200,62785,249],{"class":214},[200,62787,5267],{"class":235},[200,62789,230],{"class":214},[200,62791,62792,62794,62796,62798],{"class":202,"line":3645},[200,62793,52339],{"class":379},[200,62795,41038],{"class":210},[200,62797,510],{"class":214},[200,62799,18875],{"class":214},[200,62801,62802],{"class":202,"line":4020},[200,62803,529],{"emptyLinePlaceholder":20},[200,62805,62806,62808,62810,62812,62814,62816,62818,62820,62822,62824,62826,62828,62830],{"class":202,"line":4475},[200,62807,62109],{"class":210},[200,62809,215],{"class":214},[200,62811,25370],{"class":223},[200,62813,227],{"class":235},[200,62815,249],{"class":214},[200,62817,12726],{"class":245},[200,62819,249],{"class":214},[200,62821,550],{"class":214},[200,62823,1095],{"class":214},[200,62825,3598],{"class":386},[200,62827,2089],{"class":214},[200,62829,393],{"class":379},[200,62831,280],{"class":214},[200,62833,62834,62836,62838],{"class":202,"line":4481},[200,62835,42082],{"class":210},[200,62837,62140],{"class":214},[200,62839,62143],{"class":210},[200,62841,62842,62844],{"class":202,"line":4487},[200,62843,2096],{"class":214},[200,62845,416],{"class":235},[200,62847,62848],{"class":202,"line":4493},[200,62849,529],{"emptyLinePlaceholder":20},[200,62851,62852,62854,62856,62858,62860,62862,62864,62866,62868,62870,62872],{"class":202,"line":5454},[200,62853,62109],{"class":210},[200,62855,215],{"class":214},[200,62857,25370],{"class":223},[200,62859,227],{"class":235},[200,62861,249],{"class":214},[200,62863,28841],{"class":245},[200,62865,249],{"class":214},[200,62867,550],{"class":214},[200,62869,3714],{"class":214},[200,62871,393],{"class":379},[200,62873,280],{"class":214},[200,62875,62876],{"class":202,"line":5459},[200,62877,62878],{"class":401},"      \u002F\u002F Broadcast the message to all connected clients\n",[200,62880,62881,62884,62886,62888,62891,62893,62896],{"class":202,"line":13227},[200,62882,62883],{"class":206},"      for",[200,62885,1095],{"class":235},[200,62887,443],{"class":379},[200,62889,62890],{"class":210}," client",[200,62892,6445],{"class":214},[200,62894,62895],{"class":210}," connection",[200,62897,416],{"class":235},[200,62899,62900,62903,62905,62907,62909,62911,62914,62916,62918,62920,62923,62925],{"class":202,"line":13253},[200,62901,62902],{"class":210},"        client",[200,62904,215],{"class":214},[200,62906,61370],{"class":223},[200,62908,227],{"class":235},[200,62910,3318],{"class":214},[200,62912,62913],{"class":245},"data: ",[200,62915,4853],{"class":214},[200,62917,23870],{"class":210},[200,62919,413],{"class":214},[200,62921,62922],{"class":210},"\\n\\n",[200,62924,3318],{"class":214},[200,62926,416],{"class":235},[200,62928,62929],{"class":202,"line":13269},[200,62930,529],{"emptyLinePlaceholder":20},[200,62932,62933],{"class":202,"line":13313},[200,62934,62191],{"class":401},[200,62936,62937,62939,62941,62943,62945],{"class":202,"line":13319},[200,62938,62196],{"class":210},[200,62940,215],{"class":214},[200,62942,39459],{"class":210},[200,62944,510],{"class":214},[200,62946,62205],{"class":2045},[200,62948,62949,62951,62953,62955,62957,62959,62961,62963,62965,62967,62969,62971],{"class":202,"line":13324},[200,62950,62196],{"class":210},[200,62952,215],{"class":214},[200,62954,61275],{"class":223},[200,62956,227],{"class":235},[200,62958,249],{"class":214},[200,62960,62220],{"class":245},[200,62962,249],{"class":214},[200,62964,550],{"class":214},[200,62966,242],{"class":214},[200,62968,2882],{"class":245},[200,62970,249],{"class":214},[200,62972,416],{"class":235},[200,62974,62975,62977,62979,62981],{"class":202,"line":13350},[200,62976,62196],{"class":210},[200,62978,215],{"class":214},[200,62980,28841],{"class":223},[200,62982,466],{"class":235},[200,62984,62985,62987],{"class":202,"line":13355},[200,62986,2096],{"class":214},[200,62988,416],{"class":235},[200,62990,62991],{"class":202,"line":13375},[200,62992,4490],{"class":214},[200,62994,62995,62997],{"class":202,"line":13395},[200,62996,45892],{"class":206},[200,62998,280],{"class":214},[200,63000,63001],{"class":202,"line":13400},[200,63002,61802],{"class":401},[200,63004,63005],{"class":202,"line":13408},[200,63006,13669],{"class":401},[200,63008,63009],{"class":202,"line":13426},[200,63010,4490],{"class":214},[200,63012,63013,63015],{"class":202,"line":13456},[200,63014,413],{"class":214},[200,63016,416],{"class":210},[200,63018,63019],{"class":202,"line":13462},[200,63020,529],{"emptyLinePlaceholder":20},[200,63022,63023],{"class":202,"line":13467},[200,63024,33144],{"class":401},[35,63026,63027,63028,63030,63031,63034],{},"When a client connects to the ",[156,63029,61653],{}," endpoint, the server adds the response object to the ",[156,63032,63033],{},"connection"," set. When the connection is closed, the response object is removed from the set. Easy!",[35,63036,63037,63038,63040],{},"We keep this object to be able to send data to the client using the method ",[156,63039,61370],{},". It's the heart of the system.",[35,63042,63043,63044,63046,63047,63049],{},"When a message is received on the ",[156,63045,62084],{}," endpoint, the server loops over the ",[156,63048,63033],{}," previously saved to send the message to every client.",[35,63051,63052,63053,63055,63056,63058,63059,63061,63062,63064,63065,63067,63068,215],{},"We can observe the ",[156,63054,12726],{}," keyword in the message and the double ",[156,63057,6086],{}," at the end. This is the format of the Server-Sent Events. The ",[156,63060,12726],{}," keyword is used to send data to the client, and the double ",[156,63063,6086],{}," is used to tell the client that the message is finished. The client will only expose the content after the ",[156,63066,12726],{}," keyword and before the double ",[156,63069,6086],{},[35,63071,63072,63073,63076,63077,215],{},"For example, if the server sends ",[156,63074,63075],{},"data: Hello\\n\\n",", the client will see ",[156,63078,63079],{},"Hello",[51,63081,63083],{"id":63082},"eventsource-api","EventSource API",[35,63085,63086,63087,215],{},"Our server handles the connection and broadcasts messages, but the client is still not able to connect to the SSE endpoint, resource ",[156,63088,61653],{},[35,63090,63091,63092,63095],{},"In order to connect to the server, we will use the ",[156,63093,63094],{},"EventSource"," API. This API is a native JavaScript API to handle Server-Sent Events, and it's very straightforward.",[35,63097,63098],{},"In the beginning of our file, we need to have a little script to connect and to handle messages:",[192,63100,63102],{"className":591,"code":63101,"language":593,"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 Connect to the server\n      const eventSource = new EventSource(\"\u002Fevents\");\n\n      const ul = document.querySelector(\"ul\");\n\n      \u002F\u002F Listen for message\n      eventSource.onmessage = (event) => {\n        \u002F\u002F Add the message to the list\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\">Send\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[156,63103,63104,63114,63132,63140,63158,63186,63202,63210,63215,63241,63245,63273,63277,63282,63303,63308,63335,63355,63359,63376,63381,63389,63397,63405,63417,63421,63449,63477,63503,63511,63519],{"__ignoreMap":13},[200,63105,63106,63108,63110,63112],{"class":202,"line":203},[200,63107,61507],{"class":214},[200,63109,61510],{"class":235},[200,63111,10276],{"class":379},[200,63113,648],{"class":214},[200,63115,63116,63118,63120,63122,63124,63126,63128,63130],{"class":202,"line":14},[200,63117,600],{"class":214},[200,63119,593],{"class":235},[200,63121,14590],{"class":379},[200,63123,449],{"class":214},[200,63125,611],{"class":214},[200,63127,14597],{"class":245},[200,63129,611],{"class":214},[200,63131,648],{"class":214},[200,63133,63134,63136,63138],{"class":202,"line":255},[200,63135,657],{"class":214},[200,63137,14574],{"class":235},[200,63139,648],{"class":214},[200,63141,63142,63144,63146,63148,63150,63152,63154,63156],{"class":202,"line":272},[200,63143,804],{"class":214},[200,63145,2015],{"class":235},[200,63147,14739],{"class":379},[200,63149,449],{"class":214},[200,63151,611],{"class":214},[200,63153,61555],{"class":245},[200,63155,611],{"class":214},[200,63157,21487],{"class":214},[200,63159,63160,63162,63164,63166,63168,63170,63172,63174,63176,63178,63180,63182,63184],{"class":202,"line":283},[200,63161,804],{"class":214},[200,63163,2015],{"class":235},[200,63165,701],{"class":379},[200,63167,449],{"class":214},[200,63169,611],{"class":214},[200,63171,14794],{"class":245},[200,63173,611],{"class":214},[200,63175,6041],{"class":379},[200,63177,449],{"class":214},[200,63179,611],{"class":214},[200,63181,61584],{"class":245},[200,63183,611],{"class":214},[200,63185,21487],{"class":214},[200,63187,63188,63190,63192,63194,63196,63198,63200],{"class":202,"line":300},[200,63189,804],{"class":214},[200,63191,3566],{"class":235},[200,63193,675],{"class":214},[200,63195,61599],{"class":210},[200,63197,681],{"class":214},[200,63199,3566],{"class":235},[200,63201,648],{"class":214},[200,63203,63204,63206,63208],{"class":202,"line":310},[200,63205,804],{"class":214},[200,63207,14892],{"class":235},[200,63209,648],{"class":214},[200,63211,63212],{"class":202,"line":336},[200,63213,63214],{"class":401},"      \u002F\u002F Connect to the server\n",[200,63216,63217,63219,63222,63224,63226,63229,63231,63233,63235,63237,63239],{"class":202,"line":342},[200,63218,20113],{"class":379},[200,63220,63221],{"class":210}," eventSource ",[200,63223,449],{"class":214},[200,63225,12997],{"class":214},[200,63227,63228],{"class":223}," EventSource",[200,63230,227],{"class":210},[200,63232,611],{"class":214},[200,63234,61653],{"class":245},[200,63236,611],{"class":214},[200,63238,2089],{"class":210},[200,63240,5932],{"class":214},[200,63242,63243],{"class":202,"line":365},[200,63244,529],{"emptyLinePlaceholder":20},[200,63246,63247,63249,63252,63254,63256,63258,63261,63263,63265,63267,63269,63271],{"class":202,"line":371},[200,63248,20113],{"class":379},[200,63250,63251],{"class":210}," ul ",[200,63253,449],{"class":214},[200,63255,211],{"class":210},[200,63257,215],{"class":214},[200,63259,63260],{"class":223},"querySelector",[200,63262,227],{"class":210},[200,63264,611],{"class":214},[200,63266,1576],{"class":245},[200,63268,611],{"class":214},[200,63270,2089],{"class":210},[200,63272,5932],{"class":214},[200,63274,63275],{"class":202,"line":398},[200,63276,529],{"emptyLinePlaceholder":20},[200,63278,63279],{"class":202,"line":405},[200,63280,63281],{"class":401},"      \u002F\u002F Listen for message\n",[200,63283,63284,63287,63289,63291,63293,63295,63297,63299,63301],{"class":202,"line":410},[200,63285,63286],{"class":210},"      eventSource",[200,63288,215],{"class":214},[200,63290,29409],{"class":223},[200,63292,510],{"class":214},[200,63294,1095],{"class":214},[200,63296,25386],{"class":386},[200,63298,2089],{"class":214},[200,63300,393],{"class":379},[200,63302,280],{"class":214},[200,63304,63305],{"class":202,"line":861},[200,63306,63307],{"class":401},"        \u002F\u002F Add the message to the list\n",[200,63309,63310,63312,63315,63317,63319,63321,63323,63325,63327,63329,63331,63333],{"class":202,"line":890},[200,63311,12989],{"class":379},[200,63313,63314],{"class":210}," li",[200,63316,510],{"class":214},[200,63318,211],{"class":210},[200,63320,215],{"class":214},[200,63322,15944],{"class":223},[200,63324,227],{"class":235},[200,63326,611],{"class":214},[200,63328,71],{"class":245},[200,63330,611],{"class":214},[200,63332,2089],{"class":235},[200,63334,5932],{"class":214},[200,63336,63337,63340,63342,63345,63347,63349,63351,63353],{"class":202,"line":900},[200,63338,63339],{"class":210},"        li",[200,63341,215],{"class":214},[200,63343,63344],{"class":210},"innerText",[200,63346,510],{"class":214},[200,63348,43652],{"class":210},[200,63350,215],{"class":214},[200,63352,12726],{"class":210},[200,63354,5932],{"class":214},[200,63356,63357],{"class":202,"line":905},[200,63358,529],{"emptyLinePlaceholder":20},[200,63360,63361,63364,63366,63368,63370,63372,63374],{"class":202,"line":931},[200,63362,63363],{"class":210},"        ul",[200,63365,215],{"class":214},[200,63367,16014],{"class":223},[200,63369,227],{"class":235},[200,63371,71],{"class":210},[200,63373,2089],{"class":235},[200,63375,5932],{"class":214},[200,63377,63378],{"class":202,"line":3640},[200,63379,63380],{"class":214},"      };\n",[200,63382,63383,63385,63387],{"class":202,"line":3645},[200,63384,15074],{"class":214},[200,63386,14892],{"class":235},[200,63388,648],{"class":214},[200,63390,63391,63393,63395],{"class":202,"line":4020},[200,63392,893],{"class":214},[200,63394,14574],{"class":235},[200,63396,648],{"class":214},[200,63398,63399,63401,63403],{"class":202,"line":4475},[200,63400,657],{"class":214},[200,63402,23870],{"class":235},[200,63404,648],{"class":214},[200,63406,63407,63409,63411,63413,63415],{"class":202,"line":4481},[200,63408,804],{"class":214},[200,63410,1576],{"class":235},[200,63412,10192],{"class":214},[200,63414,1576],{"class":235},[200,63416,648],{"class":214},[200,63418,63419],{"class":202,"line":4487},[200,63420,529],{"emptyLinePlaceholder":20},[200,63422,63423,63425,63427,63429,63431,63433,63435,63437,63439,63441,63443,63445,63447],{"class":202,"line":4493},[200,63424,804],{"class":214},[200,63426,603],{"class":235},[200,63428,40910],{"class":379},[200,63430,449],{"class":214},[200,63432,611],{"class":214},[200,63434,62084],{"class":245},[200,63436,611],{"class":214},[200,63438,6577],{"class":379},[200,63440,449],{"class":214},[200,63442,611],{"class":214},[200,63444,3778],{"class":245},[200,63446,611],{"class":214},[200,63448,648],{"class":214},[200,63450,63451,63453,63455,63457,63459,63461,63463,63465,63467,63469,63471,63473,63475],{"class":202,"line":5454},[200,63452,22027],{"class":214},[200,63454,355],{"class":235},[200,63456,321],{"class":379},[200,63458,449],{"class":214},[200,63460,611],{"class":214},[200,63462,698],{"class":245},[200,63464,611],{"class":214},[200,63466,701],{"class":379},[200,63468,449],{"class":214},[200,63470,611],{"class":214},[200,63472,29574],{"class":245},[200,63474,611],{"class":214},[200,63476,21487],{"class":214},[200,63478,63479,63481,63483,63485,63487,63489,63491,63493,63495,63497,63499,63501],{"class":202,"line":5459},[200,63480,22027],{"class":214},[200,63482,910],{"class":235},[200,63484,321],{"class":379},[200,63486,449],{"class":214},[200,63488,611],{"class":214},[200,63490,917],{"class":245},[200,63492,611],{"class":214},[200,63494,675],{"class":214},[200,63496,62486],{"class":210},[200,63498,681],{"class":214},[200,63500,910],{"class":235},[200,63502,648],{"class":214},[200,63504,63505,63507,63509],{"class":202,"line":13227},[200,63506,15074],{"class":214},[200,63508,603],{"class":235},[200,63510,648],{"class":214},[200,63512,63513,63515,63517],{"class":202,"line":13253},[200,63514,893],{"class":214},[200,63516,23870],{"class":235},[200,63518,648],{"class":214},[200,63520,63521,63523,63525],{"class":202,"line":13269},[200,63522,681],{"class":214},[200,63524,593],{"class":235},[200,63526,648],{"class":214},[35,63528,63529,63530,63532,63533,63535],{},"The script starts by asking for the resource ",[156,63531,61653],{}," from the server using the ",[156,63534,63094],{}," API. This will automatically handle the reconnection.",[35,63537,63538,63539,63541],{},"Then, we register a method ",[156,63540,29409],{}," that will be triggered every time a message is received by the client from the server.",[35,63543,63544,63545,63547],{},"To have something more like a chat, we will add a list to display the messages. When a message is received, we will create a ",[156,63546,71],{}," element and append it to the list.",[35,63549,63550],{},"Now, we can open two tabs in our browser and send messages. It will appear in the other tab. :magic:",[35,63552,63553],{},[1690,63554],{"alt":63555,"src":63556},"Node SSE Chat","\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fnode-sse-chat.gif",[51,63558,63560],{"id":63559},"final-words","Final words",[35,63562,63563],{},"Even if this is a very simple example, remember that the logic of storing clients and broadcasting with a loop is the same as a more complex application.",[35,63565,63566],{},"There are two things to remember with Server-Sent Events:",[1576,63568,63569,63574],{},[71,63570,12142,63571,63573],{},[156,63572,63094],{}," API to connect to the server",[71,63575,63576,63577,63579],{},"Respond with ",[156,63578,61953],{}," from the server",[35,63581,63582],{},"Nothing more. It's simple, it's HTTP, and it's powerful.",[35,63584,63585],{},"I hope this article will help you to understand how Server-Sent Events work and to give you the confidence to use it in your projects. It's a very powerful tool to build real-time applications without the complexity of WebSockets.",[1171,63587,63588],{},"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 .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 .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}",{"title":13,"searchDepth":14,"depth":14,"links":63590},[63591,63592,63593,63594,63595,63596,63597,63598],{"id":61089,"depth":14,"text":61090},{"id":61096,"depth":14,"text":61097},{"id":61491,"depth":14,"text":61492},{"id":61643,"depth":14,"text":61644},{"id":61971,"depth":14,"text":61972},{"id":62519,"depth":14,"text":62520},{"id":63082,"depth":14,"text":63083},{"id":63559,"depth":14,"text":63560},"c2fe30ca-712c-40d6-93d3-7f4dead62a2f","2024-04-03","Create real-time Node.js apps using Server-Sent Events for effective server-client communication without the complexity of WebSockets.",{},"\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events","---\ncontentId: c2fe30ca-712c-40d6-93d3-7f4dead62a2f\ntitle: Create Real-Time Node.js Apps with Server-Sent Events\ndescription: Create real-time Node.js apps using Server-Sent Events for effective server-client communication without the complexity of WebSockets.\ndate: 2024-04-03\n---\n\nI'm deeply convinced that to understand a concept or a technology, we all need to try **to cook something with it** using the lowest level possible. This is not only related to programming but to everything in life.\n\nIn the continuation of the article [Real-time without WebSocket](\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets), I think that building a little client-server application using Server-Sent Events (SSE) is a good way to understand how it works and to give us the confidence to use it in our projects.\n\nThis client-server application will be a little chat where the client will send a message to the server and the server will broadcast the message to all connected clients. No external library, no framework, just Node.js.\n\n## Requirements\n\nTo follow this tutorial, we need to have Node.js installed on our machine. That's all.\n\n## The server\n\nCreate a file named `server.mjs`. This file will contain the server code to perform the following actions:\n\n- Serve the client-side code aka a simple HTML file\n- Receive messages from clients\n- Accept Server-Sent Events connections and broadcast messages\n\nBut for now, let's begin by serving the client-side code. Our server will be built using the native Node.js `http` module.\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 Serve the index.html file\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(`Server running at http:\u002F\u002F${hostname}:${port}\u002F`)\n})\n```\n\nWhen a request hits the server, it will respond with the content of the `index.html` file by reading it on the disk. The server is listening on the port `3000`.\n\nWe can start our server with the following command:\n\n```sh\nnode server.mjs\n```\n\n## The client\n\nCreate a file named `index.html` at the root of the project. This file will contain the client-side code:\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\nYeah, that's a very simple HTML file, nothing fancy for now.\n\n## Server-sent events connection\n\nNow, let's add the Server-Sent Events connection to the server. To handle SSE, we need to respond with some headers to keep the connection open and to send data. So the client will ask for a resource and the server will respond with a stream.\n\nThis resource will be `\u002Fevents`. Let's update the server code:\n\n```js\n\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n  \u002F\u002F `\u002Fevents` endpoint used to listen for 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: Handle the connection\n  }\n  else {\n    \u002F\u002F Serve the index.html file\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\nWhen the client asks for the resource `\u002Fevents`, the server will respond with the headers `Content-Type: text\u002Fevent-stream`, and `Cache-Control: no-cache`. This will tell the client that the connection is a stream, i.e. the server will send data to the client at any time. The connection must not be cached to avoid issues with the stream.\nIt's important to note that we never call the function `res.end()`. This is because **the connection must stay open** to allow the server to send data to the client. The function `res.end()` closes the connection.\n\n## Chat logic\n\nThis is very simple and simplified to the maximum, but the logic behind remains the same as a more complex application.\n\nIn our server, we will have a logic to receive messages from clients.\n\n```js\n\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n\u002F\u002F `\u002Fevents` endpoint used to listen for messages\n  if (req.url === '\u002Fevents') {\n    \u002F\u002F ...\n\n    \u002F\u002F `\u002Fmessages` endpoint used to post 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: broadcast the message\n\n      \u002F\u002F Redirect to the index.html file\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Serve the index.html file\n    \u002F\u002F ...\n  }\n})\n```\n\nWhen the URL requested is `\u002Fmessage`, the server will listen for the data sent by the client, which is the body of the request. When the request is finished, the server redirects the client to the `index.html` file. This is a classic behavior when a form is submitted for a server-rendered application.\n\nIn our `index.html` file, add a form to send messages to the server:\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\">Send\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\n## Broadcast the message\n\nThe missing feature is the broadcast of the message. How do we broadcast the message and what does it mean?\n\nA broadcast is when a message is sent to everyone. Otherwise, it's a unicast, when a message is sent to a specific person.\n\n![Broadcast vs Unicast](\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fbroadcast-vs-unicast.png)\n\nIn our case, the client A will send a message to the server (unicast). Then, the server will send this message to every client (broadcast).\n\nThe unicast has been implemented in the previous section. For the broadcast, we need to keep track of the connected clients to loop over them when a message is received.\n\n```js\n\u002F\u002F ...\n\n\u002F\u002F Keep track of the connected clients\nconst connection = new Set()\n\nconst server = createServer((req, res) => {\n  \u002F\u002F `\u002Fevents` endpoint used to listen for 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 `\u002Fmessages` endpoint used to post 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 Broadcast the message to all connected clients\n      for (const client of connection)\n        client.write(`data: ${body}\\n\\n`)\n\n      \u002F\u002F Redirect to the index.html file\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Serve the index.html file\n    \u002F\u002F ...\n  }\n})\n\n\u002F\u002F ...\n```\n\nWhen a client connects to the `\u002Fevents` endpoint, the server adds the response object to the `connection` set. When the connection is closed, the response object is removed from the set. Easy!\n\nWe keep this object to be able to send data to the client using the method `write`. It's the heart of the system.\n\nWhen a message is received on the `\u002Fmessage` endpoint, the server loops over the `connection` previously saved to send the message to every client.\n\nWe can observe the `data` keyword in the message and the double `\\n` at the end. This is the format of the Server-Sent Events. The `data` keyword is used to send data to the client, and the double `\\n` is used to tell the client that the message is finished. The client will only expose the content after the `data` keyword and before the double `\\n`.\n\nFor example, if the server sends `data: Hello\\n\\n`, the client will see `Hello`.\n\n## EventSource API\n\nOur server handles the connection and broadcasts messages, but the client is still not able to connect to the SSE endpoint, resource `\u002Fevents`.\n\nIn order to connect to the server, we will use the `EventSource` API. This API is a native JavaScript API to handle Server-Sent Events, and it's very straightforward.\n\nIn the beginning of our file, we need to have a little script to connect and to handle 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 Connect to the server\n      const eventSource = new EventSource(\"\u002Fevents\");\n\n      const ul = document.querySelector(\"ul\");\n\n      \u002F\u002F Listen for message\n      eventSource.onmessage = (event) => {\n        \u002F\u002F Add the message to the list\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\">Send\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\nThe script starts by asking for the resource `\u002Fevents` from the server using the `EventSource` API. This will automatically handle the reconnection.\n\nThen, we register a method `onmessage` that will be triggered every time a message is received by the client from the server.\n\nTo have something more like a chat, we will add a list to display the messages. When a message is received, we will create a `li` element and append it to the list.\n\nNow, we can open two tabs in our browser and send messages. It will appear in the other tab. :magic:\n\n![Node SSE Chat](\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fnode-sse-chat.gif)\n\n## Final words\n\nEven if this is a very simple example, remember that the logic of storing clients and broadcasting with a loop is the same as a more complex application.\n\nThere are two things to remember with Server-Sent Events:\n\n- Use the `EventSource` API to connect to the server\n- Respond with `Content-Type: text\u002Fevent-stream` from the server\n\nNothing more. It's simple, it's HTTP, and it's powerful.\n\nI hope this article will help you to understand how Server-Sent Events work and to give you the confidence to use it in your projects. It's a very powerful tool to build real-time applications without the complexity of WebSockets.\n",{"title":61066,"description":63601},{"loc":63603,"images":63607},[63608,63609],{"loc":62532},{"loc":63556},"posts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events","eYqDrkiPCAnwO49gIgBRIWIGR8rtpHsaGrGtOLNP-fc",{"id":63613,"title":63614,"body":63615,"contentId":63890,"date":63891,"description":63892,"extension":18,"meta":63893,"navigation":20,"path":61081,"rawbody":63894,"robots":23,"schemaOrg":23,"seo":63895,"sitemap":63896,"stem":63901,"__hash__":63902},"posts\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets.md","Simplify Real-Time with Server-Sent Events Over WebSockets",{"type":7,"value":63616,"toc":63881},[63617,63628,63633,63643,63647,63654,63668,63673,63677,63680,63705,63708,63713,63717,63724,63729,63734,63739,63745,63752,63759,63764,63768,63781,63795,63801,63805,63811,63817,63823,63829,63839,63844,63850],[35,63618,63619,63620,63623,63624,63627],{},"When I started building with the ",[6850,63621,63622],{},"web"," in 2018, I was fascinated by the ",[6850,63625,63626],{},"real-time"," feature. Imagine, someone changes something on a page and the change is instantly visible for everyone. It was like magic for me.",[35,63629,63630],{},[1217,63631,63632],{},"I later learned that this magic comes with the cost of complexity.",[35,63634,63635,63636,63638,63639,63642],{},"At that time, I was learning ",[6850,63637,33634],{},", and building a simple ",[6850,63640,63641],{},"web server"," was already a challenge.",[51,63644,63646],{"id":63645},"the-first-time","The first time",[35,63648,63649,63650,63653],{},"Later, for a school project, I had to build a very simple dashboard to manage ",[6850,63651,63652],{},"IoT devices",", everything from scratch. Real-time was a requirement to broadcast the state of the devices from the server to every connected client.",[35,63655,63656,63657,63660,63661,63664,63665],{},"I searched on the internet and every blog post, every tutorial was talking about ",[6850,63658,63659],{},"WebSocket",". So, maybe that's the way to go. For this project, I learned and used ",[6850,63662,63663],{},"Socket.IO",". The project is on GitHub at ",[1582,63666,215],{"repo":63667},"barbapapazes\u002Fraspiot",[35,63669,63670],{},[1217,63671,63672],{},"During the presentation, the demo effect touched me and everything was not working as expected.",[51,63674,63676],{"id":63675},"with-a-framework","With a framework",[35,63678,63679],{},"I also tried to build a web application for students of my school to manage mentoring between students. I was still a beginner but projects are the best way to learn so I jumped into it.",[35,63681,63682,63683,63690,63691,63694,63695,63697,63698,215],{},"I used ",[6850,63684,63685],{},[39,63686,63689],{"href":63687,"rel":63688},"https:\u002F\u002Ffeathersjs.com\u002F",[77],"Feathers",", the API and real-time ",[6850,63692,63693],{},"application framework",". It was probably too easy, so I decided to use ",[6850,63696,17986],{}," for the frontend with ",[6850,63699,63700],{},[39,63701,63704],{"href":63702,"rel":63703},"https:\u002F\u002Fvuex.feathersjs.com\u002F",[77],"Feathers-Vuex",[35,63706,63707],{},"I never experienced something so hard for absolutely no value to my project. In retrospect, I think I wanted real time because it was fashionable (clearly, not a good reason).",[35,63709,63710],{},[1217,63711,63712],{},"It was so complex that I never finished it.",[51,63714,63716],{"id":63715},"websocket-is-complex","WebSocket is complex",[35,63718,63719,63720,63723],{},"On the web, we use a protocol called ",[6850,63721,63722],{},"HTTP",". The request from a browser to a server is using HTTP.",[35,63725,63726],{},[1690,63727],{"alt":63722,"src":63728},"\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets\u002Fhttp.png",[35,63730,63731,63733],{},[6850,63732,63659],{}," is not HTTP. WebSocket is a protocol on its own. This is very important to know because this has consequences on how we can use it.",[35,63735,63736],{},[1690,63737],{"alt":63659,"src":63738},"\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets\u002Fwebsocket.png",[35,63740,63741,63742,63744],{},"First, WebSocket is not native on ",[6850,63743,33630],{},". We have to use an external library to use it. This could become a big deal if we have to integrate it into a complex project.",[35,63746,63747,63748,63751],{},"Second, WebSocket does not handle ",[6850,63749,63750],{},"reconnection",". If the connection is lost, there is no built-in mechanism to reconnect. We have to handle it ourselves. In another way and despite its native support in browsers, we have to use it with a library to handle reconnection. This starts to be a lot of complexity and code to maintain.",[35,63753,63754,63755,63758],{},"Finally, WebSocket is not HTTP, and in your web server, you will need to handle two protocols and create a bridge between them. The bigger part is about ",[6850,63756,63757],{},"authentication and authorization",". This could result in a lot of logic to create and maintain despite the fact that we could already have an authentication logic for HTTP. This also implies having a custom logic on the client to handle authentication and authorization. This is because HTTP is stateless and WebSocket is not.",[35,63760,63761],{},[1217,63762,63763],{},"The thing I would love to hear when I was starting with WebSocket is that not every data needs to be real-time and simpler things are better than nothing.",[51,63765,63767],{"id":63766},"the-other-way","The other way",[35,63769,63770,63771,63775,63776,63780],{},"The first time I heard about this tech was on a stream of ",[39,63772,60909],{"href":63773,"rel":63774},"https:\u002F\u002Fwww.twitch.tv\u002Fromainlanz",[77],". He is a maintainer of ",[39,63777,30093],{"href":63778,"rel":63779},"https:\u002F\u002Fadonisjs.com",[77]," and masters the subject.",[35,63782,63783,63784,3076,63787,63790,63791,63794],{},"Sometimes, real-time can have value for a project that has ",[6850,63785,63786],{},"charts",[6850,63788,63789],{},"data visualization",", information, asynchronous tasks, or even a ",[6850,63792,63793],{},"chat",". But do you see the thing that all of these features have in common?",[35,63796,63797,63798,215],{},"The client does not send, or few, information. Furthermore, the query time, between the client and the server, is not critical. So why would we need a bidirectional communication? And that does not interop well with HTTP? In 2024, ",[1217,63799,63800],{},"I do not know",[1913,63802,63804],{"id":63803},"server-sent-events","Server-sent events",[35,63806,63807,63810],{},[6850,63808,63809],{},"Server-Sent Events"," (SSE) is a technology that allows a server to send events to a client. It is a unidirectional communication from the server to the client.",[35,63812,63813],{},[1690,63814],{"alt":63815,"src":63816},"SSE","\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets\u002Fsse.png",[35,63818,63819,63820,63822],{},"The client asks for a resource using the ",[156,63821,63094],{}," API and the server responds with a stream. This is an HTTP connection but that stays open to allow the server to send data to the client. Simple, right?",[35,63824,63825,63826,63828],{},"Bonus, this handle automatically ",[6850,63827,63750],{}," without extra code.",[35,63830,63831,63832,13731,63835,63838],{},"On the internet, we can often read that SSE is bad, the client can't send data to the server. But do these people forget how internet works? Do they forget ",[156,63833,63834],{},"\u003Cform>",[156,63836,63837],{},"XMLHttpRequest","? Of course, the client can still send data to the server. Today, SSE does not have huge drawbacks.",[35,63840,63841],{},[1217,63842,63843],{},"One day, we will build a simple chat using SSE and a form to send messages to demonstrate how simple and enjoyable the web can be.",[35,63845,63846,63847,215],{},"In reality, there is a historical reason why WebSocket was better than SSE but that's not more the case in 2024 thanks to ",[6850,63848,63849],{},"HTTP\u002F2",[7905,63851,63854,63859,63861,63867,63870],{"text":63852,"href":63853},"Using Server-Sent Events","https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FServer-sent_events\u002FUsing_server-sent_events",[35,63855,63856],{},[1217,63857,63858],{},"For a game, WebSocket could be the best option to avoid the handshake between the client and the server.",[51,63860,19334],{"id":19333},[35,63862,63863,63864],{},"I think that we should always ask ourselves if we really need an instant bidirectional communication. If we do not need it, we should use SSE. It's simpler, it's HTTP, and it's powerful. ",[1217,63865,63866],{},"Always weigh pros and cons before choosing, even if it's the most popular choice.",[35,63868,63869],{},"So, will you use SSE in your next project that needs real-time?",[35,63871,63872],{},[1217,63873,63874,63875,63880],{},"It's possible to use SSE directly within our application since it's just HTTP. It's also interesting to know that ",[39,63876,63879],{"href":63877,"rel":63878},"https:\u002F\u002Fmercure.rocks\u002F",[77],"Mercure"," exists and can be useful for more complex projects.",{"title":13,"searchDepth":14,"depth":14,"links":63882},[63883,63884,63885,63886,63889],{"id":63645,"depth":14,"text":63646},{"id":63675,"depth":14,"text":63676},{"id":63715,"depth":14,"text":63716},{"id":63766,"depth":14,"text":63767,"children":63887},[63888],{"id":63803,"depth":255,"text":63804},{"id":19333,"depth":14,"text":19334},"c0a8f7ea-0bb1-4374-ad67-bf0d70c60eb0","2024-03-29","Discover powerful real-time web alternatives to WebSockets using Server-Sent Events (SSE) for simpler, more efficient solutions.",{},"---\ncontentId: c0a8f7ea-0bb1-4374-ad67-bf0d70c60eb0\ntitle: Simplify Real-Time with Server-Sent Events Over WebSockets\ndescription: Discover powerful real-time web alternatives to WebSockets using Server-Sent Events (SSE) for simpler, more efficient solutions.\ndate: 2024-03-29\n---\n\nWhen I started building with the **web** in 2018, I was fascinated by the **real-time** feature. Imagine, someone changes something on a page and the change is instantly visible for everyone. It was like magic for me.\n\n_I later learned that this magic comes with the cost of complexity._\n\nAt that time, I was learning **Express**, and building a simple **web server** was already a challenge.\n\n## The first time\n\nLater, for a school project, I had to build a very simple dashboard to manage **IoT devices**, everything from scratch. Real-time was a requirement to broadcast the state of the devices from the server to every connected client.\n\nI searched on the internet and every blog post, every tutorial was talking about **WebSocket**. So, maybe that's the way to go. For this project, I learned and used **Socket.IO**. The project is on GitHub at \u003CGitHubLink repo=\"barbapapazes\u002Fraspiot\" \u002F>.\n\n_During the presentation, the demo effect touched me and everything was not working as expected._\n\n## With a framework\n\nI also tried to build a web application for students of my school to manage mentoring between students. I was still a beginner but projects are the best way to learn so I jumped into it.\n\nI used **[Feathers](https:\u002F\u002Ffeathersjs.com\u002F)**, the API and real-time **application framework**. It was probably too easy, so I decided to use **Vue** for the frontend with **[Feathers-Vuex](https:\u002F\u002Fvuex.feathersjs.com\u002F)**.\n\nI never experienced something so hard for absolutely no value to my project. In retrospect, I think I wanted real time because it was fashionable (clearly, not a good reason).\n\n_It was so complex that I never finished it._\n\n## WebSocket is complex\n\nOn the web, we use a protocol called **HTTP**. The request from a browser to a server is using HTTP.\n\n![HTTP](\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets\u002Fhttp.png)\n\n**WebSocket** is not HTTP. WebSocket is a protocol on its own. This is very important to know because this has consequences on how we can use it.\n\n![WebSocket](\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets\u002Fwebsocket.png)\n\nFirst, WebSocket is not native on **Node.js**. We have to use an external library to use it. This could become a big deal if we have to integrate it into a complex project.\n\nSecond, WebSocket does not handle **reconnection**. If the connection is lost, there is no built-in mechanism to reconnect. We have to handle it ourselves. In another way and despite its native support in browsers, we have to use it with a library to handle reconnection. This starts to be a lot of complexity and code to maintain.\n\nFinally, WebSocket is not HTTP, and in your web server, you will need to handle two protocols and create a bridge between them. The bigger part is about **authentication and authorization**. This could result in a lot of logic to create and maintain despite the fact that we could already have an authentication logic for HTTP. This also implies having a custom logic on the client to handle authentication and authorization. This is because HTTP is stateless and WebSocket is not.\n\n_The thing I would love to hear when I was starting with WebSocket is that not every data needs to be real-time and simpler things are better than nothing._\n\n## The other way\n\nThe first time I heard about this tech was on a stream of [Romain Lanz](https:\u002F\u002Fwww.twitch.tv\u002Fromainlanz). He is a maintainer of [Adonis](https:\u002F\u002Fadonisjs.com) and masters the subject.\n\nSometimes, real-time can have value for a project that has **charts**, **data visualization**, information, asynchronous tasks, or even a **chat**. But do you see the thing that all of these features have in common?\n\nThe client does not send, or few, information. Furthermore, the query time, between the client and the server, is not critical. So why would we need a bidirectional communication? And that does not interop well with HTTP? In 2024, _I do not know_.\n\n### Server-sent events\n\n**Server-Sent Events** (SSE) is a technology that allows a server to send events to a client. It is a unidirectional communication from the server to the client.\n\n![SSE](\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets\u002Fsse.png)\n\nThe client asks for a resource using the `EventSource` API and the server responds with a stream. This is an HTTP connection but that stays open to allow the server to send data to the client. Simple, right?\n\nBonus, this handle automatically **reconnection** without extra code.\n\nOn the internet, we can often read that SSE is bad, the client can't send data to the server. But do these people forget how internet works? Do they forget `\u003Cform>` or `XMLHttpRequest`? Of course, the client can still send data to the server. Today, SSE does not have huge drawbacks.\n\n_One day, we will build a simple chat using SSE and a form to send messages to demonstrate how simple and enjoyable the web can be._\n\nIn reality, there is a historical reason why WebSocket was better than SSE but that's not more the case in 2024 thanks to **HTTP\u002F2**.\n\n\u003CCalloutReadMore text=\"Using Server-Sent Events\" href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FServer-sent_events\u002FUsing_server-sent_events\" \u002F>\n\n_For a game, WebSocket could be the best option to avoid the handshake between the client and the server._\n\n## Finally\n\nI think that we should always ask ourselves if we really need an instant bidirectional communication. If we do not need it, we should use SSE. It's simpler, it's HTTP, and it's powerful. _Always weigh pros and cons before choosing, even if it's the most popular choice._\n\nSo, will you use SSE in your next project that needs real-time?\n\n_It's possible to use SSE directly within our application since it's just HTTP. It's also interesting to know that [Mercure](https:\u002F\u002Fmercure.rocks\u002F) exists and can be useful for more complex projects._\n",{"title":63614,"description":63892},{"loc":61081,"images":63897},[63898,63899,63900],{"loc":63728},{"loc":63738},{"loc":63816},"posts\u002Fsimplify-real-time-with-server-sent-events-over-websockets","CEHLIMXsbhLLw688IpmXpIdeIaNxVx-dHCnI0Fh56jU",{"id":63904,"title":63905,"body":63906,"contentId":30815,"date":64120,"description":64121,"extension":18,"meta":64122,"navigation":20,"path":64123,"rawbody":64124,"robots":23,"schemaOrg":23,"seo":64125,"sitemap":64126,"stem":64127,"__hash__":64128},"posts\u002Fposts\u002Fvitepress-for-a-personal-website-is-that-possible.md","VitePress for a Personal Website, Is That Possible?",{"type":7,"value":63907,"toc":64112},[63908,63911,63915,63926,63940,63951,63955,63960,63963,63969,63972,63981,63984,63990,63995,63999,64002,64005,64008,64011,64017,64021,64024,64027,64030,64033,64041,64048,64054,64058,64061,64070,64075,64078,64082,64093,64100,64106,64109],[35,63909,63910],{},"For many years, I've attempted to create a personal website that reflects my style. Until now, I haven't been successful. Perhaps this effort will also fail, but I have never felt more confident in my choice. Let me explain why!",[51,63912,63914],{"id":63913},"the-personal-website","The personal website",[35,63916,63917,63918],{},"At first glance, building a personal website seems straightforward. So simple that it was my first significant project 5 years ago, ",[1582,63919,63921,63922,215],{"repo":63920},"barbapapazes\u002Festeban-s-website",", with Express.js while learning ",[39,63923,33630],{"href":63924,"rel":63925},"https:\u002F\u002Fnodejs.org",[77],[35,63927,63928,63929],{},"Afterward, I experimented with Nuxt 2, ",[1582,63930,63932,63933],{"repo":63931},"barbapapazes\u002Festeban-soubiran.site\u002Ftree\u002Fv2",", and then Nuxt 3, ",[1582,63934,63936,63937],{"repo":63935},"barbapapazes\u002Festeban-soubiran.site",", but something always felt missing. I was never satisfied with the outcome, and using it was not enjoyable. ",[1217,63938,63939],{},"If there's no enjoyment, there's no drive to maintain it. So what's the point of having a personal website?",[35,63941,63942,63943,63946,63947,63950],{},"I want to ",[6850,63944,63945],{},"take pride in my website"," and share it with everyone, but these attempts were clearly ",[6850,63948,63949],{},"the opposite"," of what I envisioned.",[51,63952,63954],{"id":63953},"history","History",[35,63956,63957],{},[1217,63958,63959],{},"Crafting a personal website is trickier than I initially thought.",[35,63961,63962],{},"Throughout the process of learning and creating websites, I've experienced significant growth, both in my needs and skills and in my appreciation for different approaches.",[35,63964,63965,63966],{},"Initially, I aimed to build a website with a database, a markdown editor, and authentication, as seen in tutorials and numerous sites. But why did I seek something so intricate? To learn and write articles. Ultimately, I gained a lot of knowledge but never published any articles, as deploying something of this complexity was daunting for a beginner. ",[1217,63967,63968],{},"And I was a beginner.",[35,63970,63971],{},"For the next blog website, I questioned my intent in building a personal site. It's beneficial for the CV and serves as a platform to test new technologies. That's exactly what I did. I explored Nuxt 2 when I was learning about it. For this second attempt, I never considered writing articles.",[35,63973,63974,63975,63980],{},"Eventually, I intended to write a tutorial on Adonis 5 but had nowhere to share it. I ended up posting it on ",[39,63976,63979],{"href":63977,"rel":63978},"https:\u002F\u002Fdev.to\u002Fbarbapapazes\u002Fobjectifs-et-mise-en-route-creer-un-blog-avec-adonis-20lg",[77],"dev.to",". I wasn't satisfied. The experience was unsatisfactory, and I lacked a backup of my work.",[35,63982,63983],{},"Several months later, I decided to revamp my website from scratch using Nuxt 3. Including an article section was planned, but experimenting with new features remained the primary focus.",[35,63985,63986,63987,215],{},"And today, I'm back at it, reconstructing my personal website once more. Time passes, but now, ",[6850,63988,63989],{},"I know exactly what I want",[35,63991,63992],{},[1217,63993,63994],{},"Even if this is the same project, the context and my needs have evolved so much that it feels like a new beginning.",[51,63996,63998],{"id":63997},"needs","Needs",[35,64000,64001],{},"The foremost necessity is a blog. I want the capability to craft and publish content effortlessly. My preferred method is writing in markdown within my text editor. I cherish this experience due to plugins that enhance my writing skills.",[35,64003,64004],{},"This markdown must be customizable to incorporate unique components and extended syntax, similar to GitHub alerts.",[35,64006,64007],{},"I desire a blog that requires minimal maintenance. The less effort needed to publish an article, the better. I don't want to continuously update my website due to security vulnerabilities or package updates. Fewer dependencies grant me more time to focus on writing.",[35,64009,64010],{},"I want to generate an RSS feed and an Open Graph image for each article. I want this procedure to be simple and automatic upon publishing an article. The tool should be extensible for future enhancements.",[35,64012,64013,64014,215],{},"The crucial takeaway is that ",[6850,64015,64016],{},"I want to concentrate on content, not on coding",[51,64018,64020],{"id":64019},"tools","Tools",[35,64022,64023],{},"I am familiar with creating websites using WordPress, Adonis, Astro, Nuxt, and Vite with Vue.",[35,64025,64026],{},"Considering my needs, WordPress and Adonis are not optimal choices. I do not wish to maintain a server and a database. Although Adonis offers a way to generate a static website, it's undocumented. I don't want to invest time in this.",[35,64028,64029],{},"Nuxt with the Content plugin is a viable option, but over the past year, I've developed many websites with that stack. The developer experience of the Content plugin is unsatisfactory. It significantly slows down the dev server (up to 30 seconds to start) and build time (up to 2 minutes), and requires too much maintenance. I no longer wish to use it for this type of project.",[35,64031,64032],{},"Astro is a suitable choice. It's quick and ready for use. The only concern is that it might offer too many features. I fear it's easy to lose track, and configuring the content module is something I prefer to avoid.",[35,64034,64035,64036,64040],{},"Creating a custom project with Vite and Vue is another solid option, allowing me to delve into Vite's plugins. This technology powers ",[39,64037,64039],{"href":31330,"rel":64038},[77],"Anthony Fu's website",", providing a reliable example to follow.",[35,64042,64043,64044,64047],{},"I've decided to construct my next website using ",[6850,64045,64046],{},"a custom project with Vite and Vue",". This allows me to have the most control over the project, to keep it minimal, and to have the full Vue ecosystem at my disposal.",[35,64049,64050,64051],{},"That's the plan. ",[1217,64052,64053],{},"Or so I thought.",[51,64055,64057],{"id":64056},"final-choice","Final choice",[35,64059,64060],{},"Three days after selecting and commencing the development of my website, I utilized VitePress at work to document a set of functions. It was a pleasure to use, but the default template is documentation-oriented. I didn't want my personal website to resemble a documentation site.",[35,64062,64063,64064,64069],{},"Two days later, ",[39,64065,64068],{"href":64066,"rel":64067},"https:\u002F\u002Fx.com\u002Fyouyuxi\u002Fstatus\u002F1770837536509661339",[77],"Evan You announced VitePress 1.0"," and stated:",[939,64071,64072],{},[35,64073,64074],{},"Discover why we consider it one of the best ways to build static sites (not just for documentation!)",[35,64076,64077],{},"This piqued my curiosity. I read the entire documentation, and realized that it perfectly aligns with my requirements.",[51,64079,64081],{"id":64080},"the-reality-behind-vitepress","The reality behind VitePress",[35,64083,64084,64085,64088,64089,64092],{},"In essence, ",[6850,64086,64087],{},"VitePress is a tool that processes Markdown files to create routes and render them as HTML."," Nothing more. The documentation aspect is merely a theme, a layer atop this powerful core with file-based routing, markdown parsing, hooks, build-time data loading, and SSG. ",[6850,64090,64091],{},"You can easily create your own theme"," on top of this powerful and maintainable core.",[35,64094,64095,64096,64099],{},"In summary, I can leverage this robust core to build my personal website with a custom theme crafted in ",[39,64097,18624],{"href":34562,"rel":64098},[77],". Perfect for extensibility and customization while the having a solid foundation around the content.",[35,64101,64102,64103],{},"That's precisely what I plan to do. Essentially, this core is what I aimed to create with the Vite and Vue stack. With VitePress, it's a single dependency, ready to use, well-documented, maintained, and with a community, allowing me to focus on content from the outset. ",[1217,64104,64105],{},"Crafting a theme is uncomplicated as it's purely aesthetic. The core functionality is already established. I can start writing now while taking the time to craft a theme and adding features as needed.",[35,64107,64108],{},"I intend to keep this blog as minimal as possible, and with VitePress, composing content and deploying is now streamlined.",[35,64110,64111],{},"I highly recommend giving VitePress a try, despite its marketing as a documentation builder. It's genuinely impressive and surpasses expectations. 📖",{"title":13,"searchDepth":14,"depth":14,"links":64113},[64114,64115,64116,64117,64118,64119],{"id":63913,"depth":14,"text":63914},{"id":63953,"depth":14,"text":63954},{"id":63997,"depth":14,"text":63998},{"id":64019,"depth":14,"text":64020},{"id":64056,"depth":14,"text":64057},{"id":64080,"depth":14,"text":64081},"2024-03-22","I chose VitePress to build my personal website. You may know it as a documentation tool, but it's more than that.",{},"\u002Fposts\u002Fvitepress-for-a-personal-website-is-that-possible","---\ncontentId: 674a3932-aaba-445c-82bb-0896ce3a8e7e\ntitle: VitePress for a Personal Website, Is That Possible?\ndescription: I chose VitePress to build my personal website. You may know it as a documentation tool, but it's more than that.\ndate: 2024-03-22\n---\n\nFor many years, I've attempted to create a personal website that reflects my style. Until now, I haven't been successful. Perhaps this effort will also fail, but I have never felt more confident in my choice. Let me explain why!\n\n## The personal website\n\nAt first glance, building a personal website seems straightforward. So simple that it was my first significant project 5 years ago, \u003CGitHubLink repo=\"barbapapazes\u002Festeban-s-website\" \u002F>, with Express.js while learning [Node.js](https:\u002F\u002Fnodejs.org).\n\nAfterward, I experimented with Nuxt 2, \u003CGitHubLink repo=\"barbapapazes\u002Festeban-soubiran.site\u002Ftree\u002Fv2\" \u002F>, and then Nuxt 3, \u003CGitHubLink repo=\"barbapapazes\u002Festeban-soubiran.site\" \u002F>, but something always felt missing. I was never satisfied with the outcome, and using it was not enjoyable. _If there's no enjoyment, there's no drive to maintain it. So what's the point of having a personal website?_\n\nI want to **take pride in my website** and share it with everyone, but these attempts were clearly **the opposite** of what I envisioned.\n\n## History\n\n_Crafting a personal website is trickier than I initially thought._\n\nThroughout the process of learning and creating websites, I've experienced significant growth, both in my needs and skills and in my appreciation for different approaches.\n\nInitially, I aimed to build a website with a database, a markdown editor, and authentication, as seen in tutorials and numerous sites. But why did I seek something so intricate? To learn and write articles. Ultimately, I gained a lot of knowledge but never published any articles, as deploying something of this complexity was daunting for a beginner. _And I was a beginner._\n\nFor the next blog website, I questioned my intent in building a personal site. It's beneficial for the CV and serves as a platform to test new technologies. That's exactly what I did. I explored Nuxt 2 when I was learning about it. For this second attempt, I never considered writing articles.\n\nEventually, I intended to write a tutorial on Adonis 5 but had nowhere to share it. I ended up posting it on [dev.to](https:\u002F\u002Fdev.to\u002Fbarbapapazes\u002Fobjectifs-et-mise-en-route-creer-un-blog-avec-adonis-20lg). I wasn't satisfied. The experience was unsatisfactory, and I lacked a backup of my work.\n\nSeveral months later, I decided to revamp my website from scratch using Nuxt 3. Including an article section was planned, but experimenting with new features remained the primary focus.\n\nAnd today, I'm back at it, reconstructing my personal website once more. Time passes, but now, **I know exactly what I want**.\n\n_Even if this is the same project, the context and my needs have evolved so much that it feels like a new beginning._\n\n## Needs\n\nThe foremost necessity is a blog. I want the capability to craft and publish content effortlessly. My preferred method is writing in markdown within my text editor. I cherish this experience due to plugins that enhance my writing skills.\n\nThis markdown must be customizable to incorporate unique components and extended syntax, similar to GitHub alerts.\n\nI desire a blog that requires minimal maintenance. The less effort needed to publish an article, the better. I don't want to continuously update my website due to security vulnerabilities or package updates. Fewer dependencies grant me more time to focus on writing.\n\nI want to generate an RSS feed and an Open Graph image for each article. I want this procedure to be simple and automatic upon publishing an article. The tool should be extensible for future enhancements.\n\nThe crucial takeaway is that **I want to concentrate on content, not on coding**.\n\n## Tools\n\nI am familiar with creating websites using WordPress, Adonis, Astro, Nuxt, and Vite with Vue.\n\nConsidering my needs, WordPress and Adonis are not optimal choices. I do not wish to maintain a server and a database. Although Adonis offers a way to generate a static website, it's undocumented. I don't want to invest time in this.\n\nNuxt with the Content plugin is a viable option, but over the past year, I've developed many websites with that stack. The developer experience of the Content plugin is unsatisfactory. It significantly slows down the dev server (up to 30 seconds to start) and build time (up to 2 minutes), and requires too much maintenance. I no longer wish to use it for this type of project.\n\nAstro is a suitable choice. It's quick and ready for use. The only concern is that it might offer too many features. I fear it's easy to lose track, and configuring the content module is something I prefer to avoid.\n\nCreating a custom project with Vite and Vue is another solid option, allowing me to delve into Vite's plugins. This technology powers [Anthony Fu's website](https:\u002F\u002Fantfu.me), providing a reliable example to follow.\n\nI've decided to construct my next website using **a custom project with Vite and Vue**. This allows me to have the most control over the project, to keep it minimal, and to have the full Vue ecosystem at my disposal.\n\nThat's the plan. _Or so I thought._\n\n## Final choice\n\nThree days after selecting and commencing the development of my website, I utilized VitePress at work to document a set of functions. It was a pleasure to use, but the default template is documentation-oriented. I didn't want my personal website to resemble a documentation site.\n\nTwo days later, [Evan You announced VitePress 1.0](https:\u002F\u002Fx.com\u002Fyouyuxi\u002Fstatus\u002F1770837536509661339) and stated:\n\n> Discover why we consider it one of the best ways to build static sites (not just for documentation!)\n\nThis piqued my curiosity. I read the entire documentation, and realized that it perfectly aligns with my requirements.\n\n## The reality behind VitePress\n\nIn essence, **VitePress is a tool that processes Markdown files to create routes and render them as HTML.** Nothing more. The documentation aspect is merely a theme, a layer atop this powerful core with file-based routing, markdown parsing, hooks, build-time data loading, and SSG. **You can easily create your own theme** on top of this powerful and maintainable core.\n\nIn summary, I can leverage this robust core to build my personal website with a custom theme crafted in [Vue.js](https:\u002F\u002Fvuejs.org). Perfect for extensibility and customization while the having a solid foundation around the content.\n\nThat's precisely what I plan to do. Essentially, this core is what I aimed to create with the Vite and Vue stack. With VitePress, it's a single dependency, ready to use, well-documented, maintained, and with a community, allowing me to focus on content from the outset. _Crafting a theme is uncomplicated as it's purely aesthetic. The core functionality is already established. I can start writing now while taking the time to craft a theme and adding features as needed._\n\nI intend to keep this blog as minimal as possible, and with VitePress, composing content and deploying is now streamlined.\n\nI highly recommend giving VitePress a try, despite its marketing as a documentation builder. It's genuinely impressive and surpasses expectations. 📖\n",{"title":63905,"description":64121},{"loc":64123},"posts\u002Fvitepress-for-a-personal-website-is-that-possible","mnNhesoP7NbBkcCtWxLKN2KqTyWlR3l5fKsybnVHKN4",{"id":64130,"title":64131,"body":64132,"contentId":30821,"date":66934,"description":66935,"extension":18,"meta":66936,"navigation":20,"path":66937,"rawbody":66938,"robots":23,"schemaOrg":23,"seo":66939,"sitemap":66940,"stem":66941,"__hash__":66942},"posts\u002Fposts\u002Fbuild-a-url-shortener-with-nitro-on-cloudflare-pages.md","Build a URL Shortener with Nitro on Cloudflare Pages",{"type":7,"value":64133,"toc":66927},[64134,64154,64160,64169,64181,64194,64197,64254,64258,64264,64282,64288,64305,64312,64325,64333,64337,64340,64357,64361,64378,64796,64806,64816,64822,65040,65049,65053,65059,65073,65092,65624,65637,65649,65659,65673,66924],[35,64135,64136,64137,64140,64141,64147,64148,215],{},"In this article, we will develop a ",[6850,64138,64139],{},"URL shortener"," utilizing ",[39,64142,64145],{"href":64143,"rel":64144},"https:\u002F\u002Fnitro.unjs.io",[77],[6850,64146,36816],{}," and deploy it on ",[39,64149,64152],{"href":64150,"rel":64151},"https:\u002F\u002Fpages.cloudflare.com\u002F",[77],[6850,64153,38775],{},[35,64155,64156,64157],{},"The source code is accessible on ",[1582,64158,215],{"repo":64159},"barbapapazes\u002Furl-shortener",[35,64161,64162,64164,64165,64168],{},[6850,64163,36816],{}," represents a new generation of ",[6850,64166,64167],{},"server toolkit",". It empowers us to construct web servers with all necessary functionalities and deploy them at our convenience.",[35,64170,64171,64173,64174,64180],{},[6850,64172,38775],{}," is a platform designed to build and host websites on the edge. It supports services like ",[39,64175,64177],{"href":38788,"rel":64176},[77],[6850,64178,64179],{},"KV"," to create comprehensive full-stack stateful applications.",[35,64182,64183,64184,64186,64187,64189,64190,64193],{},"Our project is a straightforward ",[6850,64185,64139],{}," that facilitates converting a long URL into a shortened version. We will leverage the ",[6850,64188,38790],{}," to store the URLs and the ",[6850,64191,64192],{},"Nitro server"," to manage requests.",[35,64195,64196],{},"We will utilize:",[1576,64198,64199,64215,64228,64244],{},[71,64200,64201,64208,64209,64211,64212,215],{},[39,64202,64205],{"href":64203,"rel":64204},"https:\u002F\u002Funstorage.unjs.io",[77],[6850,64206,64207],{},"unstorage"," to streamline the development process by abstracting the ",[6850,64210,64179],{}," layer, eliminating the need for the ",[6850,64213,64214],{},"Cloudflare Wrangler CLI",[71,64216,64217,64224,64225,64227],{},[39,64218,64221],{"href":64219,"rel":64220},"https:\u002F\u002Fohash.unjs.io",[77],[6850,64222,64223],{},"ohash"," to generate a ",[6850,64226,53003],{}," from the URL to prevent collisions.",[71,64229,64230,64237,64238,35697,64241,215],{},[39,64231,64234],{"href":64232,"rel":64233},"https:\u002F\u002Fnanojsx.io\u002F",[77],[6850,64235,64236],{},"nanojsx"," to build the ",[6850,64239,64240],{},"HTML pages",[6850,64242,64243],{},"TSX",[71,64245,64246,64253],{},[39,64247,64250],{"href":64248,"rel":64249},"https:\u002F\u002Fpicocss.com",[77],[6850,64251,64252],{},"pico.css"," for styling the application.",[51,64255,64257],{"id":64256},"project-initialization","Project initialization",[35,64259,64260,64261,239],{},"First, create a new ",[6850,64262,64263],{},"Nitro project",[192,64265,64267],{"className":2151,"code":64266,"language":2153,"meta":13,"style":13},"npx giget@latest nitro url-shortener\n",[156,64268,64269],{"__ignoreMap":13},[200,64270,64271,64273,64276,64279],{"class":202,"line":203},[200,64272,31244],{"class":1978},[200,64274,64275],{"class":245}," giget@latest",[200,64277,64278],{"class":245}," nitro",[200,64280,64281],{"class":245}," url-shortener\n",[35,64283,64284,64285,239],{},"Subsequently, navigate to the project and install the required ",[6850,64286,64287],{},"dependencies",[192,64289,64291],{"className":2151,"code":64290,"language":2153,"meta":13,"style":13},"cd url-shortener\nnpm install\n",[156,64292,64293,64299],{"__ignoreMap":13},[200,64294,64295,64297],{"class":202,"line":203},[200,64296,36292],{"class":223},[200,64298,64281],{"class":245},[200,64300,64301,64303],{"class":202,"line":14},[200,64302,36332],{"class":1978},[200,64304,36172],{"class":245},[35,64306,64307,64308,64311],{},"Start the ",[6850,64309,64310],{},"development server"," to view the default Nitro page:",[192,64313,64315],{"className":2151,"code":64314,"language":2153,"meta":13,"style":13},"npm run dev\n",[156,64316,64317],{"__ignoreMap":13},[200,64318,64319,64321,64323],{"class":202,"line":203},[200,64320,36332],{"class":1978},[200,64322,32709],{"class":245},[200,64324,13695],{"class":245},[35,64326,64327,64328,64332],{},"Open your browser and visit ",[39,64329,64330],{"href":64330,"rel":64331},"http:\u002F\u002Flocalhost:3000",[77]," to verify functionality.",[51,64334,64336],{"id":64335},"constructing-the-url-shortener","Constructing the URL shortener",[35,64338,64339],{},"Initially, install the necessary packages:",[192,64341,64343],{"className":2151,"code":64342,"language":2153,"meta":13,"style":13},"npm install ohash nano-jsx\n",[156,64344,64345],{"__ignoreMap":13},[200,64346,64347,64349,64351,64354],{"class":202,"line":203},[200,64348,36332],{"class":1978},[200,64350,12288],{"class":245},[200,64352,64353],{"class":245}," ohash",[200,64355,64356],{"class":245}," nano-jsx\n",[1913,64358,64360],{"id":64359},"generate-a-short-url","Generate a short URL",[35,64362,32355,64363,64365,64366,64369,64370,64373,64374,64377],{},[6850,64364,56660],{}," named ",[156,64367,64368],{},"index.get.tsx"," within the ",[156,64371,64372],{},"server\u002Froutes"," directory. This will serve as the ",[6850,64375,64376],{},"home page"," of our URL shortener where users can generate a shortened URL from a long one.",[192,64379,64384],{"className":64380,"code":64381,"filename":64382,"language":64383,"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 the `h` is critical here\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineLazyEventHandler(() => {\n  const App = () => {\n    return (\n      \u003Cdiv>\n        \u003CHelmet>\n          \u003Ctitle>URL Shortener with Nitro\u003C\u002Ftitle>\n        \u003C\u002FHelmet>\n        \u003Ch2>Shorten a URL\u003C\u002Fh2>\n        \u003Cform action=\"\u002Fcreate\" method=\"POST\">\n          \u003Cinput type=\"url\" name=\"url\" placeholder=\"URL to shorten\" autocomplete=\"off\" \u002F>\n          \u003Cbutton type=\"submit\">Create\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,64385,64386,64419,64439,64443,64460,64475,64482,64490,64499,64516,64524,64541,64570,64621,64648,64656,64664,64668,64672,64693,64722,64726,64740,64746,64753,64759,64763,64777,64784,64790],{"__ignoreMap":13},[200,64387,64388,64390,64392,64395,64397,64400,64402,64405,64407,64409,64411,64414,64416],{"class":202,"line":203},[200,64389,973],{"class":206},[200,64391,318],{"class":214},[200,64393,64394],{"class":210}," h",[200,64396,550],{"class":214},[200,64398,64399],{"class":210}," Helmet",[200,64401,550],{"class":214},[200,64403,64404],{"class":210}," renderSSR",[200,64406,981],{"class":214},[200,64408,984],{"class":206},[200,64410,242],{"class":214},[200,64412,64413],{"class":245},"nano-jsx",[200,64415,249],{"class":214},[200,64417,64418],{"class":401}," \u002F\u002F the `h` is critical here\n",[200,64420,64421,64423,64425,64428,64430,64432,64434,64437],{"class":202,"line":14},[200,64422,973],{"class":206},[200,64424,318],{"class":214},[200,64426,64427],{"class":210}," withTemplate",[200,64429,981],{"class":214},[200,64431,984],{"class":206},[200,64433,242],{"class":214},[200,64435,64436],{"class":245},"..\u002Fresources\u002Ftemplate",[200,64438,992],{"class":214},[200,64440,64441],{"class":202,"line":255},[200,64442,529],{"emptyLinePlaceholder":20},[200,64444,64445,64447,64449,64452,64454,64456,64458],{"class":202,"line":272},[200,64446,1380],{"class":206},[200,64448,1383],{"class":206},[200,64450,64451],{"class":223}," defineLazyEventHandler",[200,64453,227],{"class":210},[200,64455,1532],{"class":214},[200,64457,393],{"class":379},[200,64459,280],{"class":214},[200,64461,64462,64464,64467,64469,64471,64473],{"class":202,"line":283},[200,64463,3723],{"class":379},[200,64465,64466],{"class":210}," App",[200,64468,510],{"class":214},[200,64470,3714],{"class":214},[200,64472,393],{"class":379},[200,64474,280],{"class":214},[200,64476,64477,64479],{"class":202,"line":300},[200,64478,3296],{"class":206},[200,64480,64481],{"class":235}," (\n",[200,64483,64484,64486,64488],{"class":202,"line":310},[200,64485,22027],{"class":214},[200,64487,11386],{"class":235},[200,64489,648],{"class":214},[200,64491,64492,64494,64497],{"class":202,"line":336},[200,64493,49602],{"class":214},[200,64495,64496],{"class":1978},"Helmet",[200,64498,648],{"class":214},[200,64500,64501,64503,64505,64507,64510,64512,64514],{"class":202,"line":342},[200,64502,49629],{"class":214},[200,64504,3566],{"class":235},[200,64506,675],{"class":214},[200,64508,64509],{"class":210},"URL Shortener with Nitro",[200,64511,681],{"class":214},[200,64513,3566],{"class":235},[200,64515,648],{"class":214},[200,64517,64518,64520,64522],{"class":202,"line":365},[200,64519,49710],{"class":214},[200,64521,64496],{"class":1978},[200,64523,648],{"class":214},[200,64525,64526,64528,64530,64532,64535,64537,64539],{"class":202,"line":371},[200,64527,49602],{"class":214},[200,64529,51],{"class":235},[200,64531,675],{"class":214},[200,64533,64534],{"class":210},"Shorten a URL",[200,64536,681],{"class":214},[200,64538,51],{"class":235},[200,64540,648],{"class":214},[200,64542,64543,64545,64547,64549,64551,64553,64556,64558,64560,64562,64564,64566,64568],{"class":202,"line":398},[200,64544,49602],{"class":214},[200,64546,603],{"class":235},[200,64548,40910],{"class":379},[200,64550,449],{"class":214},[200,64552,611],{"class":214},[200,64554,64555],{"class":245},"\u002Fcreate",[200,64557,611],{"class":214},[200,64559,6577],{"class":379},[200,64561,449],{"class":214},[200,64563,611],{"class":214},[200,64565,18766],{"class":245},[200,64567,611],{"class":214},[200,64569,648],{"class":214},[200,64571,64572,64574,64576,64578,64580,64582,64584,64586,64588,64590,64592,64594,64596,64598,64600,64602,64605,64607,64610,64612,64614,64617,64619],{"class":202,"line":405},[200,64573,49629],{"class":214},[200,64575,355],{"class":235},[200,64577,321],{"class":379},[200,64579,449],{"class":214},[200,64581,611],{"class":214},[200,64583,2020],{"class":245},[200,64585,611],{"class":214},[200,64587,701],{"class":379},[200,64589,449],{"class":214},[200,64591,611],{"class":214},[200,64593,2020],{"class":245},[200,64595,611],{"class":214},[200,64597,40940],{"class":379},[200,64599,449],{"class":214},[200,64601,611],{"class":214},[200,64603,64604],{"class":245},"URL to shorten",[200,64606,611],{"class":214},[200,64608,64609],{"class":379}," autocomplete",[200,64611,449],{"class":214},[200,64613,611],{"class":214},[200,64615,64616],{"class":245},"off",[200,64618,611],{"class":214},[200,64620,21487],{"class":214},[200,64622,64623,64625,64627,64629,64631,64633,64635,64637,64639,64642,64644,64646],{"class":202,"line":410},[200,64624,49629],{"class":214},[200,64626,910],{"class":235},[200,64628,321],{"class":379},[200,64630,449],{"class":214},[200,64632,611],{"class":214},[200,64634,917],{"class":245},[200,64636,611],{"class":214},[200,64638,675],{"class":214},[200,64640,64641],{"class":210},"Create",[200,64643,681],{"class":214},[200,64645,910],{"class":235},[200,64647,648],{"class":214},[200,64649,64650,64652,64654],{"class":202,"line":861},[200,64651,49710],{"class":214},[200,64653,603],{"class":235},[200,64655,648],{"class":214},[200,64657,64658,64660,64662],{"class":202,"line":890},[200,64659,43992],{"class":214},[200,64661,11386],{"class":235},[200,64663,648],{"class":214},[200,64665,64666],{"class":202,"line":900},[200,64667,3854],{"class":235},[200,64669,64670],{"class":202,"line":905},[200,64671,4490],{"class":214},[200,64673,64674,64676,64678,64680,64682,64684,64686,64689,64691],{"class":202,"line":931},[200,64675,3723],{"class":379},[200,64677,14330],{"class":210},[200,64679,510],{"class":214},[200,64681,64404],{"class":223},[200,64683,227],{"class":235},[200,64685,600],{"class":214},[200,64687,64688],{"class":1978},"App",[200,64690,22055],{"class":214},[200,64692,416],{"class":235},[200,64694,64695,64697,64699,64701,64703,64706,64708,64710,64712,64714,64716,64718,64720],{"class":202,"line":3640},[200,64696,3723],{"class":379},[200,64698,318],{"class":214},[200,64700,41038],{"class":210},[200,64702,550],{"class":214},[200,64704,64705],{"class":210}," head",[200,64707,981],{"class":214},[200,64709,510],{"class":214},[200,64711,64399],{"class":210},[200,64713,215],{"class":214},[200,64715,14492],{"class":223},[200,64717,227],{"class":235},[200,64719,26746],{"class":210},[200,64721,416],{"class":235},[200,64723,64724],{"class":202,"line":3645},[200,64725,529],{"emptyLinePlaceholder":20},[200,64727,64728,64730,64732,64734,64736,64738],{"class":202,"line":4020},[200,64729,3723],{"class":379},[200,64731,3257],{"class":210},[200,64733,510],{"class":214},[200,64735,64427],{"class":223},[200,64737,227],{"class":235},[200,64739,230],{"class":214},[200,64741,64742,64744],{"class":202,"line":4475},[200,64743,18775],{"class":210},[200,64745,252],{"class":214},[200,64747,64748,64751],{"class":202,"line":4481},[200,64749,64750],{"class":210},"    head",[200,64752,252],{"class":214},[200,64754,64755,64757],{"class":202,"line":4487},[200,64756,5080],{"class":214},[200,64758,416],{"class":235},[200,64760,64761],{"class":202,"line":4493},[200,64762,529],{"emptyLinePlaceholder":20},[200,64764,64765,64767,64769,64771,64773,64775],{"class":202,"line":5454},[200,64766,3743],{"class":206},[200,64768,38960],{"class":223},[200,64770,227],{"class":235},[200,64772,1532],{"class":214},[200,64774,393],{"class":379},[200,64776,280],{"class":214},[200,64778,64779,64781],{"class":202,"line":5459},[200,64780,3296],{"class":206},[200,64782,64783],{"class":210}," page\n",[200,64785,64786,64788],{"class":202,"line":13227},[200,64787,5080],{"class":214},[200,64789,416],{"class":235},[200,64791,64792,64794],{"class":202,"line":13253},[200,64793,413],{"class":214},[200,64795,416],{"class":210},[35,64797,64798,64799,64802,64803,64805],{},"This route will present a form enabling users to input a URL to shorten. Upon form submission, a ",[6850,64800,64801],{},"POST request"," is sent to the ",[156,64804,64555],{}," route.",[35,64807,47322,64808,64811,64812,64815],{},[6850,64809,64810],{},"lazy event handler"," is utilized to generate the view only once, when a request reaches the server. The response is then ",[6850,64813,64814],{},"cached in-memory"," and reused for future requests, reducing the computational load.",[35,64817,419,64818,64821],{},[156,64819,64820],{},"withTemplate"," function serves as a utility that we must construct.",[192,64823,64826],{"className":1370,"code":64824,"filename":64825,"language":1373,"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\">URL Shortener with 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,64827,64828,64838,64847,64859,64863,64867,64887,64906,64910,64922,64927,64932,64937,64942,64947,64970,64975,64980,64985,64990,64995,65000,65005,65010,65019,65024,65029,65036],{"__ignoreMap":13},[200,64829,64830,64833,64836],{"class":202,"line":203},[200,64831,64832],{"class":379},"interface",[200,64834,64835],{"class":1978}," LayoutProps",[200,64837,280],{"class":214},[200,64839,64840,64843,64845],{"class":202,"line":14},[200,64841,64842],{"class":235},"  body",[200,64844,239],{"class":214},[200,64846,23664],{"class":1978},[200,64848,64849,64852,64854,64856],{"class":202,"line":255},[200,64850,64851],{"class":235},"  head",[200,64853,239],{"class":214},[200,64855,22708],{"class":1978},[200,64857,64858],{"class":210},"[]\n",[200,64860,64861],{"class":202,"line":272},[200,64862,1130],{"class":214},[200,64864,64865],{"class":202,"line":283},[200,64866,529],{"emptyLinePlaceholder":20},[200,64868,64869,64871,64873,64875,64877,64879,64881,64883,64885],{"class":202,"line":300},[200,64870,1380],{"class":206},[200,64872,4987],{"class":379},[200,64874,64427],{"class":223},[200,64876,227],{"class":214},[200,64878,23830],{"class":386},[200,64880,239],{"class":214},[200,64882,64835],{"class":1978},[200,64884,2089],{"class":214},[200,64886,280],{"class":214},[200,64888,64889,64891,64893,64895,64897,64899,64901,64903],{"class":202,"line":310},[200,64890,3723],{"class":379},[200,64892,318],{"class":214},[200,64894,64705],{"class":210},[200,64896,550],{"class":214},[200,64898,41038],{"class":210},[200,64900,981],{"class":214},[200,64902,510],{"class":214},[200,64904,64905],{"class":210}," props\n",[200,64907,64908],{"class":202,"line":336},[200,64909,529],{"emptyLinePlaceholder":20},[200,64911,64912,64914,64917,64919],{"class":202,"line":342},[200,64913,3743],{"class":206},[200,64915,64916],{"class":401}," \u002F* html *\u002F",[200,64918,3318],{"class":214},[200,64920,64921],{"class":245},"\u003Chtml>\n",[200,64923,64924],{"class":202,"line":365},[200,64925,64926],{"class":245},"      \u003Chead>\n",[200,64928,64929],{"class":202,"line":371},[200,64930,64931],{"class":245},"       \u003Clink\n",[200,64933,64934],{"class":202,"line":398},[200,64935,64936],{"class":245},"          rel=\"stylesheet\"\n",[200,64938,64939],{"class":202,"line":405},[200,64940,64941],{"class":245},"          href=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002F@picocss\u002Fpico@2\u002Fcss\u002Fpico.min.css\"\n",[200,64943,64944],{"class":202,"line":410},[200,64945,64946],{"class":245},"        \u002F>\n",[200,64948,64949,64952,64954,64956,64958,64960,64962,64964,64966,64968],{"class":202,"line":861},[200,64950,64951],{"class":214},"        ${",[200,64953,14574],{"class":210},[200,64955,215],{"class":214},[200,64957,6190],{"class":223},[200,64959,227],{"class":210},[200,64961,249],{"class":214},[200,64963,6086],{"class":210},[200,64965,249],{"class":214},[200,64967,2089],{"class":210},[200,64969,1130],{"class":214},[200,64971,64972],{"class":202,"line":890},[200,64973,64974],{"class":245},"      \u003C\u002Fhead>\n",[200,64976,64977],{"class":202,"line":900},[200,64978,64979],{"class":245},"      \u003Cbody>\n",[200,64981,64982],{"class":202,"line":905},[200,64983,64984],{"class":245},"        \u003Cheader class=\"container\">\n",[200,64986,64987],{"class":202,"line":931},[200,64988,64989],{"class":245},"          \u003Ch1>\n",[200,64991,64992],{"class":202,"line":3640},[200,64993,64994],{"class":245},"            \u003Ca href=\"\u002F\">URL Shortener with Nitro\u003C\u002Fa>\n",[200,64996,64997],{"class":202,"line":3645},[200,64998,64999],{"class":245},"          \u003C\u002Fh1>\n",[200,65001,65002],{"class":202,"line":4020},[200,65003,65004],{"class":245},"        \u003C\u002Fheader>\n",[200,65006,65007],{"class":202,"line":4475},[200,65008,65009],{"class":245},"        \u003Cmain class=\"container\">\n",[200,65011,65012,65015,65017],{"class":202,"line":4481},[200,65013,65014],{"class":214},"          ${",[200,65016,23870],{"class":210},[200,65018,1130],{"class":214},[200,65020,65021],{"class":202,"line":4487},[200,65022,65023],{"class":245},"        \u003C\u002Fmain>\n",[200,65025,65026],{"class":202,"line":4493},[200,65027,65028],{"class":245},"      \u003C\u002Fbody>\n",[200,65030,65031,65034],{"class":202,"line":5454},[200,65032,65033],{"class":245},"    \u003C\u002Fhtml>",[200,65035,12762],{"class":214},[200,65037,65038],{"class":202,"line":5459},[200,65039,1130],{"class":214},[35,65041,65042,65043,65046,65047,215],{},"This is a simple ",[6850,65044,65045],{},"string template"," where we incorporate the head and body content generated by ",[6850,65048,64413],{},[1913,65050,65052],{"id":65051},"url-storage","URL storage",[35,65054,65055,65056,65058],{},"Before proceeding, install ",[156,65057,19790],{}," to validate the request body.",[192,65060,65062],{"className":2151,"code":65061,"language":2153,"meta":13,"style":13},"npm install zod\n",[156,65063,65064],{"__ignoreMap":13},[200,65065,65066,65068,65070],{"class":202,"line":203},[200,65067,36332],{"class":1978},[200,65069,12288],{"class":245},[200,65071,65072],{"class":245}," zod\n",[35,65074,65075,65076,65078,65079,65081,65082,65084,65085,65088,65089,65091],{},"Create the ",[156,65077,64555],{}," route to manage the ",[6850,65080,64801],{}," and store the URL in the ",[6850,65083,64179],{},". This route is labeled ",[156,65086,65087],{},"create.post.tsx"," and resides in the ",[156,65090,64372],{}," directory.",[192,65093,65096],{"className":64380,"code":65094,"filename":65095,"language":64383,"meta":13,"style":13},"import { h, Helmet, renderSSR } from 'nano-jsx'\nimport { hash } from 'ohash'\nimport { z } from 'zod' \u002F\u002F the `h` is essential here\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>Created\u003C\u002Ftitle>\n        \u003C\u002FHelmet>\n        \u003Ch2>Created and Ready\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,65097,65098,65124,65142,65163,65181,65185,65207,65235,65257,65269,65273,65290,65310,65346,65350,65386,65390,65404,65410,65418,65426,65443,65451,65468,65475,65487,65500,65505,65509,65517,65521,65525,65529,65549,65582,65586,65596,65606,65612,65618],{"__ignoreMap":13},[200,65099,65100,65102,65104,65106,65108,65110,65112,65114,65116,65118,65120,65122],{"class":202,"line":203},[200,65101,973],{"class":206},[200,65103,318],{"class":214},[200,65105,64394],{"class":210},[200,65107,550],{"class":214},[200,65109,64399],{"class":210},[200,65111,550],{"class":214},[200,65113,64404],{"class":210},[200,65115,981],{"class":214},[200,65117,984],{"class":206},[200,65119,242],{"class":214},[200,65121,64413],{"class":245},[200,65123,992],{"class":214},[200,65125,65126,65128,65130,65132,65134,65136,65138,65140],{"class":202,"line":14},[200,65127,973],{"class":206},[200,65129,318],{"class":214},[200,65131,56948],{"class":210},[200,65133,981],{"class":214},[200,65135,984],{"class":206},[200,65137,242],{"class":214},[200,65139,64223],{"class":245},[200,65141,992],{"class":214},[200,65143,65144,65146,65148,65150,65152,65154,65156,65158,65160],{"class":202,"line":255},[200,65145,973],{"class":206},[200,65147,318],{"class":214},[200,65149,3165],{"class":210},[200,65151,981],{"class":214},[200,65153,984],{"class":206},[200,65155,242],{"class":214},[200,65157,19790],{"class":245},[200,65159,249],{"class":214},[200,65161,65162],{"class":401}," \u002F\u002F the `h` is essential here\n",[200,65164,65165,65167,65169,65171,65173,65175,65177,65179],{"class":202,"line":272},[200,65166,973],{"class":206},[200,65168,318],{"class":214},[200,65170,64427],{"class":210},[200,65172,981],{"class":214},[200,65174,984],{"class":206},[200,65176,242],{"class":214},[200,65178,64436],{"class":245},[200,65180,992],{"class":214},[200,65182,65183],{"class":202,"line":283},[200,65184,529],{"emptyLinePlaceholder":20},[200,65186,65187,65189,65191,65193,65195,65197,65199,65201,65203,65205],{"class":202,"line":300},[200,65188,1380],{"class":206},[200,65190,1383],{"class":206},[200,65192,38960],{"class":223},[200,65194,227],{"class":210},[200,65196,3711],{"class":379},[200,65198,1095],{"class":214},[200,65200,25386],{"class":386},[200,65202,2089],{"class":214},[200,65204,393],{"class":379},[200,65206,280],{"class":214},[200,65208,65209,65211,65213,65215,65217,65219,65221,65223,65225,65227,65229,65231,65233],{"class":202,"line":310},[200,65210,3723],{"class":379},[200,65212,41038],{"class":210},[200,65214,510],{"class":214},[200,65216,452],{"class":206},[200,65218,41180],{"class":223},[200,65220,227],{"class":235},[200,65222,25386],{"class":210},[200,65224,550],{"class":214},[200,65226,3165],{"class":210},[200,65228,215],{"class":214},[200,65230,293],{"class":223},[200,65232,227],{"class":235},[200,65234,230],{"class":214},[200,65236,65237,65239,65241,65243,65245,65247,65249,65251,65253,65255],{"class":202,"line":336},[200,65238,5192],{"class":235},[200,65240,239],{"class":214},[200,65242,3165],{"class":210},[200,65244,215],{"class":214},[200,65246,328],{"class":223},[200,65248,1532],{"class":235},[200,65250,215],{"class":214},[200,65252,2020],{"class":223},[200,65254,1532],{"class":235},[200,65256,252],{"class":214},[200,65258,65259,65261,65263,65265,65267],{"class":202,"line":342},[200,65260,5080],{"class":214},[200,65262,2089],{"class":235},[200,65264,215],{"class":214},[200,65266,41223],{"class":210},[200,65268,416],{"class":235},[200,65270,65271],{"class":202,"line":365},[200,65272,529],{"emptyLinePlaceholder":20},[200,65274,65275,65277,65280,65282,65284,65286,65288],{"class":202,"line":371},[200,65276,3723],{"class":379},[200,65278,65279],{"class":210}," requestURL",[200,65281,510],{"class":214},[200,65283,46807],{"class":223},[200,65285,227],{"class":235},[200,65287,25386],{"class":210},[200,65289,416],{"class":235},[200,65291,65292,65294,65296,65298,65300,65302,65304,65306,65308],{"class":202,"line":398},[200,65293,3723],{"class":379},[200,65295,3224],{"class":210},[200,65297,510],{"class":214},[200,65299,56948],{"class":223},[200,65301,227],{"class":235},[200,65303,23870],{"class":210},[200,65305,215],{"class":214},[200,65307,2020],{"class":210},[200,65309,416],{"class":235},[200,65311,65312,65314,65317,65319,65321,65324,65326,65328,65330,65332,65334,65336,65338,65340,65342,65344],{"class":202,"line":405},[200,65313,3723],{"class":379},[200,65315,65316],{"class":210}," shortenURL",[200,65318,510],{"class":214},[200,65320,12997],{"class":214},[200,65322,65323],{"class":223}," URL",[200,65325,227],{"class":235},[200,65327,3318],{"class":214},[200,65329,2882],{"class":245},[200,65331,4853],{"class":214},[200,65333,3280],{"class":210},[200,65335,23001],{"class":214},[200,65337,550],{"class":214},[200,65339,65279],{"class":210},[200,65341,2089],{"class":235},[200,65343,215],{"class":214},[200,65345,46818],{"class":210},[200,65347,65348],{"class":202,"line":410},[200,65349,529],{"emptyLinePlaceholder":20},[200,65351,65352,65354,65357,65359,65361,65363,65365,65367,65369,65372,65374,65376,65378,65380,65382,65384],{"class":202,"line":861},[200,65353,6165],{"class":206},[200,65355,65356],{"class":223}," useStorage",[200,65358,227],{"class":235},[200,65360,249],{"class":214},[200,65362,12726],{"class":245},[200,65364,249],{"class":214},[200,65366,2089],{"class":235},[200,65368,215],{"class":214},[200,65370,65371],{"class":223},"setItem",[200,65373,227],{"class":235},[200,65375,3280],{"class":210},[200,65377,550],{"class":214},[200,65379,41038],{"class":210},[200,65381,215],{"class":214},[200,65383,2020],{"class":210},[200,65385,416],{"class":235},[200,65387,65388],{"class":202,"line":890},[200,65389,529],{"emptyLinePlaceholder":20},[200,65391,65392,65394,65396,65398,65400,65402],{"class":202,"line":900},[200,65393,3723],{"class":379},[200,65395,64466],{"class":210},[200,65397,510],{"class":214},[200,65399,3714],{"class":214},[200,65401,393],{"class":379},[200,65403,280],{"class":214},[200,65405,65406,65408],{"class":202,"line":905},[200,65407,3296],{"class":206},[200,65409,64481],{"class":235},[200,65411,65412,65414,65416],{"class":202,"line":931},[200,65413,22027],{"class":214},[200,65415,11386],{"class":235},[200,65417,648],{"class":214},[200,65419,65420,65422,65424],{"class":202,"line":3640},[200,65421,49602],{"class":214},[200,65423,64496],{"class":1978},[200,65425,648],{"class":214},[200,65427,65428,65430,65432,65434,65437,65439,65441],{"class":202,"line":3645},[200,65429,49629],{"class":214},[200,65431,3566],{"class":235},[200,65433,675],{"class":214},[200,65435,65436],{"class":210},"Created",[200,65438,681],{"class":214},[200,65440,3566],{"class":235},[200,65442,648],{"class":214},[200,65444,65445,65447,65449],{"class":202,"line":4020},[200,65446,49710],{"class":214},[200,65448,64496],{"class":1978},[200,65450,648],{"class":214},[200,65452,65453,65455,65457,65459,65462,65464,65466],{"class":202,"line":4475},[200,65454,49602],{"class":214},[200,65456,51],{"class":235},[200,65458,675],{"class":214},[200,65460,65461],{"class":210},"Created and Ready",[200,65463,681],{"class":214},[200,65465,51],{"class":235},[200,65467,648],{"class":214},[200,65469,65470,65472],{"class":202,"line":4481},[200,65471,49602],{"class":214},[200,65473,65474],{"class":235},"input\n",[200,65476,65477,65479,65481,65483,65485],{"class":202,"line":4487},[200,65478,4914],{"class":379},[200,65480,449],{"class":214},[200,65482,611],{"class":214},[200,65484,698],{"class":245},[200,65486,617],{"class":214},[200,65488,65489,65492,65495,65498],{"class":202,"line":4493},[200,65490,65491],{"class":379},"          value",[200,65493,65494],{"class":214},"={",[200,65496,65497],{"class":210},"shortenURL",[200,65499,1130],{"class":214},[200,65501,65502],{"class":202,"line":5454},[200,65503,65504],{"class":379},"          autofocus\n",[200,65506,65507],{"class":202,"line":5459},[200,65508,64946],{"class":214},[200,65510,65511,65513,65515],{"class":202,"line":13227},[200,65512,43992],{"class":214},[200,65514,11386],{"class":235},[200,65516,648],{"class":214},[200,65518,65519],{"class":202,"line":13253},[200,65520,3854],{"class":235},[200,65522,65523],{"class":202,"line":13269},[200,65524,4490],{"class":214},[200,65526,65527],{"class":202,"line":13313},[200,65528,529],{"emptyLinePlaceholder":20},[200,65530,65531,65533,65535,65537,65539,65541,65543,65545,65547],{"class":202,"line":13319},[200,65532,3723],{"class":379},[200,65534,14330],{"class":210},[200,65536,510],{"class":214},[200,65538,64404],{"class":223},[200,65540,227],{"class":235},[200,65542,600],{"class":214},[200,65544,64688],{"class":1978},[200,65546,22055],{"class":214},[200,65548,416],{"class":235},[200,65550,65551,65553,65555,65557,65559,65562,65564,65566,65568,65570,65572,65574,65576,65578,65580],{"class":202,"line":13324},[200,65552,3723],{"class":379},[200,65554,318],{"class":214},[200,65556,41038],{"class":235},[200,65558,239],{"class":214},[200,65560,65561],{"class":210}," nanoBody",[200,65563,550],{"class":214},[200,65565,64705],{"class":210},[200,65567,981],{"class":214},[200,65569,510],{"class":214},[200,65571,64399],{"class":210},[200,65573,215],{"class":214},[200,65575,14492],{"class":223},[200,65577,227],{"class":235},[200,65579,26746],{"class":210},[200,65581,416],{"class":235},[200,65583,65584],{"class":202,"line":13350},[200,65585,529],{"emptyLinePlaceholder":20},[200,65587,65588,65590,65592,65594],{"class":202,"line":13355},[200,65589,3743],{"class":206},[200,65591,64427],{"class":223},[200,65593,227],{"class":235},[200,65595,230],{"class":214},[200,65597,65598,65600,65602,65604],{"class":202,"line":13375},[200,65599,18775],{"class":235},[200,65601,239],{"class":214},[200,65603,65561],{"class":210},[200,65605,252],{"class":214},[200,65607,65608,65610],{"class":202,"line":13395},[200,65609,64750],{"class":210},[200,65611,252],{"class":214},[200,65613,65614,65616],{"class":202,"line":13400},[200,65615,5080],{"class":214},[200,65617,416],{"class":235},[200,65619,65620,65622],{"class":202,"line":13408},[200,65621,413],{"class":214},[200,65623,416],{"class":210},[35,65625,65626,65627,65629,65630,65633,65634,65636],{},"In this segment, we utilize the ",[156,65628,41118],{}," function to ",[6850,65631,65632],{},"validate the request body",". This assures that the ",[156,65635,2020],{}," field is a legitimate URL, throwing an error otherwise.",[35,65638,65639,65640,42844,65643,65646,65647,215],{},"We retrieve the ",[6850,65641,65642],{},"request URL",[156,65644,65645],{},"getRequestURL"," function from ",[156,65648,1913],{},[35,65650,419,65651,65653,65654,65646,65656,65658],{},[6850,65652,53003],{}," is created from the body URL using the ",[156,65655,53003],{},[6850,65657,64223],{},", ensuring consistent hash generation to avoid collisions.",[35,65660,65661,65662,42844,65664,65646,65667,65669,65670,65672],{},"The URL is stored in the ",[6850,65663,64179],{},[156,65665,65666],{},"useStorage",[6850,65668,64207],{},", employing the ",[156,65671,12726],{}," namespace to store the URLs, pre-configured for our use.",[7905,65674,65677,65680,65687,65784,65806],{"text":65675,"href":65676},"KV Storage","https:\u002F\u002Fnitro.unjs.io\u002Fguide\u002Fstorage#usage",[35,65678,65679],{},"At the end of this event handler, a route is returned with the shortened URL that users can copy and utilize.",[35,65681,65682,65683,65686],{},"For development, everything functions correctly, but we must update this configuration, ",[156,65684,65685],{},"nitro.config.ts",", for the production environment.",[192,65688,65690],{"className":1370,"code":65689,"filename":65685,"language":1373,"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,65691,65692,65705,65720,65729,65774,65778],{"__ignoreMap":13},[200,65693,65694,65696,65698,65701,65703],{"class":202,"line":203},[200,65695,1380],{"class":206},[200,65697,1383],{"class":206},[200,65699,65700],{"class":223}," defineNitroConfig",[200,65702,227],{"class":210},[200,65704,230],{"class":214},[200,65706,65707,65710,65712,65714,65716,65718],{"class":202,"line":14},[200,65708,65709],{"class":235},"  srcDir",[200,65711,239],{"class":214},[200,65713,242],{"class":214},[200,65715,3108],{"class":245},[200,65717,249],{"class":214},[200,65719,252],{"class":214},[200,65721,65722,65725,65727],{"class":202,"line":255},[200,65723,65724],{"class":235},"  $production",[200,65726,239],{"class":214},[200,65728,280],{"class":214},[200,65730,65731,65734,65736,65738,65740,65742,65744,65747,65749,65751,65754,65756,65758,65761,65763,65765,65768,65770,65772],{"class":202,"line":272},[200,65732,65733],{"class":235},"    storage",[200,65735,239],{"class":214},[200,65737,318],{"class":214},[200,65739,5041],{"class":235},[200,65741,239],{"class":214},[200,65743,318],{"class":214},[200,65745,65746],{"class":235}," driver",[200,65748,239],{"class":214},[200,65750,242],{"class":214},[200,65752,65753],{"class":245},"cloudflare-kv-binding",[200,65755,249],{"class":214},[200,65757,550],{"class":214},[200,65759,65760],{"class":235}," binding",[200,65762,239],{"class":214},[200,65764,242],{"class":214},[200,65766,65767],{"class":245},"url-shortener",[200,65769,249],{"class":214},[200,65771,981],{"class":214},[200,65773,333],{"class":214},[200,65775,65776],{"class":202,"line":283},[200,65777,368],{"class":214},[200,65779,65780,65782],{"class":202,"line":300},[200,65781,413],{"class":214},[200,65783,416],{"class":210},[35,65785,65786,65787,65789,65790,65792,65793,65795,65796,65799,65800,65803,65804,215],{},"In this configuration, we define the ",[156,65788,12726],{}," namespace, as in development, to use the ",[6850,65791,65753],{}," driver and the ",[156,65794,65767],{}," binding, specified under the key ",[156,65797,65798],{},"$production",". This indicates that the configuration applies solely to the ",[6850,65801,65802],{},"production environment"," for accessing the ",[6850,65805,38790],{},[7905,65807,65810,65814,65823,66008,66027,66031,66041],{"text":65808,"href":65809},"Environment-specific configuration","https:\u002F\u002Fgithub.com\u002Funjs\u002Fc12#environment-specific-configuration",[1913,65811,65813],{"id":65812},"redirecting-to-the-original-url","Redirecting to the original URL",[35,65815,65816,65817,65820,65821,65091],{},"Finally, we need to establish a route to handle shortened URLs and redirect users to the primary URL. This route is named ",[156,65818,65819],{},"[id].get.ts"," and is situated in the ",[156,65822,64372],{},[192,65824,65827],{"className":1370,"code":65825,"filename":65826,"language":1373,"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: 'Not Found',\n    })\n  }\n\n  return sendRedirect(event, value)\n})\n","server\u002Froutes\u002F[id].get.ts",[156,65828,65829,65851,65877,65881,65919,65923,65937,65947,65957,65972,65978,65982,65986,66002],{"__ignoreMap":13},[200,65830,65831,65833,65835,65837,65839,65841,65843,65845,65847,65849],{"class":202,"line":203},[200,65832,1380],{"class":206},[200,65834,1383],{"class":206},[200,65836,38960],{"class":223},[200,65838,227],{"class":210},[200,65840,3711],{"class":379},[200,65842,1095],{"class":214},[200,65844,25386],{"class":386},[200,65846,2089],{"class":214},[200,65848,393],{"class":379},[200,65850,280],{"class":214},[200,65852,65853,65855,65857,65859,65862,65864,65866,65868,65870,65873,65875],{"class":202,"line":14},[200,65854,3723],{"class":379},[200,65856,3224],{"class":210},[200,65858,510],{"class":214},[200,65860,65861],{"class":223}," getRouterParam",[200,65863,227],{"class":235},[200,65865,25386],{"class":210},[200,65867,550],{"class":214},[200,65869,242],{"class":214},[200,65871,65872],{"class":245},"short",[200,65874,249],{"class":214},[200,65876,416],{"class":235},[200,65878,65879],{"class":202,"line":255},[200,65880,529],{"emptyLinePlaceholder":20},[200,65882,65883,65885,65887,65889,65891,65893,65895,65897,65899,65901,65903,65905,65907,65909,65911,65913,65915,65917],{"class":202,"line":272},[200,65884,3723],{"class":379},[200,65886,810],{"class":210},[200,65888,510],{"class":214},[200,65890,452],{"class":206},[200,65892,65356],{"class":223},[200,65894,600],{"class":214},[200,65896,328],{"class":1978},[200,65898,675],{"class":214},[200,65900,227],{"class":235},[200,65902,249],{"class":214},[200,65904,12726],{"class":245},[200,65906,249],{"class":214},[200,65908,2089],{"class":235},[200,65910,215],{"class":214},[200,65912,14967],{"class":223},[200,65914,227],{"class":235},[200,65916,3280],{"class":210},[200,65918,416],{"class":235},[200,65920,65921],{"class":202,"line":283},[200,65922,529],{"emptyLinePlaceholder":20},[200,65924,65925,65927,65929,65931,65933,65935],{"class":202,"line":300},[200,65926,20075],{"class":206},[200,65928,1095],{"class":235},[200,65930,11837],{"class":214},[200,65932,15837],{"class":210},[200,65934,5267],{"class":235},[200,65936,230],{"class":214},[200,65938,65939,65941,65943,65945],{"class":202,"line":310},[200,65940,39438],{"class":206},[200,65942,39441],{"class":223},[200,65944,227],{"class":235},[200,65946,230],{"class":214},[200,65948,65949,65951,65953,65955],{"class":202,"line":336},[200,65950,39450],{"class":235},[200,65952,239],{"class":214},[200,65954,40582],{"class":2045},[200,65956,252],{"class":214},[200,65958,65959,65962,65964,65966,65968,65970],{"class":202,"line":342},[200,65960,65961],{"class":235},"      statusMessage",[200,65963,239],{"class":214},[200,65965,242],{"class":214},[200,65967,40595],{"class":245},[200,65969,249],{"class":214},[200,65971,252],{"class":214},[200,65973,65974,65976],{"class":202,"line":365},[200,65975,2096],{"class":214},[200,65977,416],{"class":235},[200,65979,65980],{"class":202,"line":371},[200,65981,4490],{"class":214},[200,65983,65984],{"class":202,"line":398},[200,65985,529],{"emptyLinePlaceholder":20},[200,65987,65988,65990,65992,65994,65996,65998,66000],{"class":202,"line":405},[200,65989,3743],{"class":206},[200,65991,46247],{"class":223},[200,65993,227],{"class":235},[200,65995,25386],{"class":210},[200,65997,550],{"class":214},[200,65999,810],{"class":210},[200,66001,416],{"class":235},[200,66003,66004,66006],{"class":202,"line":410},[200,66005,413],{"class":214},[200,66007,416],{"class":210},[35,66009,66010,66011,66013,66014,66016,66017,35149,66020,66022,66023,66026],{},"Here, we simply obtain the ",[156,66012,3280],{}," from the router parameters and use ",[156,66015,65666],{}," to fetch the ",[6850,66018,66019],{},"URL",[6850,66021,64179],{},". If the URL does not exist, a ",[6850,66024,66025],{},"404 error"," is thrown. Otherwise, the user is redirected to the original URL.",[51,66028,66030],{"id":66029},"deploying-the-url-shortener","Deploying the URL shortener",[35,66032,66033,66034,66037,66038,215],{},"Congratulations! We have successfully created a basic URL shortener using Nitro. The next step is to ",[6850,66035,66036],{},"deploy"," it on Cloudflare Pages. Rest assured, it's straightforward thanks to the ",[6850,66039,66040],{},"zero-config deployment providers",[7905,66042,66045,66052,66070,66075,66092,66106,66113,66117,66127,66860,66866,66889,66891,66904,66912],{"text":66043,"href":66044},"Zero Config Providers","https:\u002F\u002Fnitro.unjs.io\u002Fdeploy#zero-config-providers",[939,66046,66047],{},[35,66048,66049,66051],{},[200,66050,29700],{},"\nA Cloudflare account and a GitHub repository are required to continue with the deployment.\nInitially, log in to the Cloudflare dashboard and select an account. Navigate to Account Home, choose Workers & Pages, and create a new application using the button at the top right. Select the Pages tab and follow the instructions to establish a GitHub connection.",[35,66053,66054,66055,66058,66059,66061,66062,66065,66066,66069],{},"Select the repository, add the build command ",[156,66056,66057],{},"npm run build",", specify the output directory ",[156,66060,5139],{},", include an environment variable ",[156,66063,66064],{},"NODE_ENV"," with the value ",[156,66067,66068],{},"production",", and save and deploy.",[35,66071,66072,66073,215],{},"Now, let's wait for the deployment to complete. Once done, you can access the URL shortener using the URL provided by ",[6850,66074,38775],{},[35,66076,66077,66080,66081,66084,66085,66088,66089,66091],{},[6850,66078,66079],{},"However",", it may not function adequately initially, as a ",[6850,66082,66083],{},"KV namespace"," has not been bound. This requires manual binding. Go to the project settings, select functions, and scroll until you find ",[6850,66086,66087],{},"KV namespace bindings",". Add a binding with the name ",[156,66090,65767],{}," and choose the desired namespace.",[939,66093,66094],{},[35,66095,66096,66098,66099,66101,66102,66105],{},[200,66097,945],{},"\nCreating a ",[6850,66100,66083],{}," is achievable in the ",[6850,66103,66104],{},"Workers & Pages"," section.\nRe-deploy your application to account for the new KV namespace binding. Once completed, the URL shortener will be fully functional. ✨",[939,66107,66108],{},[35,66109,66110,66112],{},[200,66111,945],{},"\nIf you do not intend to maintain the project, remember to delete it from the Cloudflare Pages dashboard to avoid unnecessary expenses.",[51,66114,66116],{"id":66115},"enhancements-and-future-considerations","Enhancements and future considerations",[35,66118,66119,66120,66123,66124,66126],{},"To enhance security, consider adding simple ",[6850,66121,66122],{},"CSRF protection"," using the handler object syntax. Modify the ",[156,66125,65087],{}," file to include a pre-request handler for origin header verification.",[192,66128,66130],{"className":64380,"code":66129,"filename":65095,"language":64383,"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: 'Bad Request',\n      })\n    }\n\n    if (origin !== requestURL) {\n      throw createError({\n        statusCode: 403,\n        statusMessage: 'Forbidden',\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>Created\u003C\u002Ftitle>\n          \u003C\u002FHelmet>\n          \u003Ch2>Created and Ready\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,66131,66132,66158,66176,66194,66212,66216,66228,66247,66268,66294,66298,66312,66322,66333,66349,66355,66359,66363,66379,66389,66399,66413,66419,66423,66427,66446,66474,66496,66508,66512,66528,66548,66582,66586,66620,66624,66638,66644,66652,66660,66677,66685,66701,66707,66720,66731,66736,66741,66749,66753,66757,66761,66781,66813,66817,66827,66837,66844,66850,66854],{"__ignoreMap":13},[200,66133,66134,66136,66138,66140,66142,66144,66146,66148,66150,66152,66154,66156],{"class":202,"line":203},[200,66135,973],{"class":206},[200,66137,318],{"class":214},[200,66139,64394],{"class":210},[200,66141,550],{"class":214},[200,66143,64399],{"class":210},[200,66145,550],{"class":214},[200,66147,64404],{"class":210},[200,66149,981],{"class":214},[200,66151,984],{"class":206},[200,66153,242],{"class":214},[200,66155,64413],{"class":245},[200,66157,992],{"class":214},[200,66159,66160,66162,66164,66166,66168,66170,66172,66174],{"class":202,"line":14},[200,66161,973],{"class":206},[200,66163,318],{"class":214},[200,66165,56948],{"class":210},[200,66167,981],{"class":214},[200,66169,984],{"class":206},[200,66171,242],{"class":214},[200,66173,64223],{"class":245},[200,66175,992],{"class":214},[200,66177,66178,66180,66182,66184,66186,66188,66190,66192],{"class":202,"line":255},[200,66179,973],{"class":206},[200,66181,318],{"class":214},[200,66183,3165],{"class":210},[200,66185,981],{"class":214},[200,66187,984],{"class":206},[200,66189,242],{"class":214},[200,66191,19790],{"class":245},[200,66193,992],{"class":214},[200,66195,66196,66198,66200,66202,66204,66206,66208,66210],{"class":202,"line":272},[200,66197,973],{"class":206},[200,66199,318],{"class":214},[200,66201,64427],{"class":210},[200,66203,981],{"class":214},[200,66205,984],{"class":206},[200,66207,242],{"class":214},[200,66209,64436],{"class":245},[200,66211,992],{"class":214},[200,66213,66214],{"class":202,"line":283},[200,66215,529],{"emptyLinePlaceholder":20},[200,66217,66218,66220,66222,66224,66226],{"class":202,"line":300},[200,66219,1380],{"class":206},[200,66221,1383],{"class":206},[200,66223,38960],{"class":223},[200,66225,227],{"class":210},[200,66227,230],{"class":214},[200,66229,66230,66233,66235,66237,66239,66241,66243,66245],{"class":202,"line":310},[200,66231,66232],{"class":223},"  onBeforeResponse",[200,66234,239],{"class":214},[200,66236,380],{"class":379},[200,66238,1095],{"class":214},[200,66240,25386],{"class":386},[200,66242,2089],{"class":214},[200,66244,393],{"class":379},[200,66246,280],{"class":214},[200,66248,66249,66251,66253,66255,66257,66259,66261,66263,66265],{"class":202,"line":336},[200,66250,1999],{"class":379},[200,66252,65279],{"class":210},[200,66254,510],{"class":214},[200,66256,46807],{"class":223},[200,66258,227],{"class":235},[200,66260,25386],{"class":210},[200,66262,2089],{"class":235},[200,66264,215],{"class":214},[200,66266,66267],{"class":210},"origin\n",[200,66269,66270,66272,66274,66276,66279,66281,66283,66285,66287,66290,66292],{"class":202,"line":342},[200,66271,1999],{"class":379},[200,66273,29888],{"class":210},[200,66275,510],{"class":214},[200,66277,66278],{"class":223}," getRequestHeader",[200,66280,227],{"class":235},[200,66282,25386],{"class":210},[200,66284,550],{"class":214},[200,66286,242],{"class":214},[200,66288,66289],{"class":245},"origin",[200,66291,249],{"class":214},[200,66293,416],{"class":235},[200,66295,66296],{"class":202,"line":365},[200,66297,529],{"emptyLinePlaceholder":20},[200,66299,66300,66302,66304,66306,66308,66310],{"class":202,"line":371},[200,66301,5252],{"class":206},[200,66303,1095],{"class":235},[200,66305,11837],{"class":214},[200,66307,66289],{"class":210},[200,66309,5267],{"class":235},[200,66311,230],{"class":214},[200,66313,66314,66316,66318,66320],{"class":202,"line":398},[200,66315,20227],{"class":206},[200,66317,39441],{"class":223},[200,66319,227],{"class":235},[200,66321,230],{"class":214},[200,66323,66324,66327,66329,66331],{"class":202,"line":405},[200,66325,66326],{"class":235},"        statusCode",[200,66328,239],{"class":214},[200,66330,41427],{"class":2045},[200,66332,252],{"class":214},[200,66334,66335,66338,66340,66342,66345,66347],{"class":202,"line":410},[200,66336,66337],{"class":235},"        statusMessage",[200,66339,239],{"class":214},[200,66341,242],{"class":214},[200,66343,66344],{"class":245},"Bad Request",[200,66346,249],{"class":214},[200,66348,252],{"class":214},[200,66350,66351,66353],{"class":202,"line":861},[200,66352,2632],{"class":214},[200,66354,416],{"class":235},[200,66356,66357],{"class":202,"line":890},[200,66358,4962],{"class":214},[200,66360,66361],{"class":202,"line":900},[200,66362,529],{"emptyLinePlaceholder":20},[200,66364,66365,66367,66369,66371,66373,66375,66377],{"class":202,"line":905},[200,66366,5252],{"class":206},[200,66368,1095],{"class":235},[200,66370,66289],{"class":210},[200,66372,13388],{"class":214},[200,66374,65279],{"class":210},[200,66376,5267],{"class":235},[200,66378,230],{"class":214},[200,66380,66381,66383,66385,66387],{"class":202,"line":931},[200,66382,20227],{"class":206},[200,66384,39441],{"class":223},[200,66386,227],{"class":235},[200,66388,230],{"class":214},[200,66390,66391,66393,66395,66397],{"class":202,"line":3640},[200,66392,66326],{"class":235},[200,66394,239],{"class":214},[200,66396,39464],{"class":2045},[200,66398,252],{"class":214},[200,66400,66401,66403,66405,66407,66409,66411],{"class":202,"line":3645},[200,66402,66337],{"class":235},[200,66404,239],{"class":214},[200,66406,242],{"class":214},[200,66408,39965],{"class":245},[200,66410,249],{"class":214},[200,66412,252],{"class":214},[200,66414,66415,66417],{"class":202,"line":4020},[200,66416,2632],{"class":214},[200,66418,416],{"class":235},[200,66420,66421],{"class":202,"line":4475},[200,66422,4962],{"class":214},[200,66424,66425],{"class":202,"line":4481},[200,66426,368],{"class":214},[200,66428,66429,66432,66434,66436,66438,66440,66442,66444],{"class":202,"line":4487},[200,66430,66431],{"class":223},"  handler",[200,66433,239],{"class":214},[200,66435,380],{"class":379},[200,66437,1095],{"class":214},[200,66439,25386],{"class":386},[200,66441,2089],{"class":214},[200,66443,393],{"class":379},[200,66445,280],{"class":214},[200,66447,66448,66450,66452,66454,66456,66458,66460,66462,66464,66466,66468,66470,66472],{"class":202,"line":4493},[200,66449,1999],{"class":379},[200,66451,41038],{"class":210},[200,66453,510],{"class":214},[200,66455,452],{"class":206},[200,66457,41180],{"class":223},[200,66459,227],{"class":235},[200,66461,25386],{"class":210},[200,66463,550],{"class":214},[200,66465,3165],{"class":210},[200,66467,215],{"class":214},[200,66469,293],{"class":223},[200,66471,227],{"class":235},[200,66473,230],{"class":214},[200,66475,66476,66478,66480,66482,66484,66486,66488,66490,66492,66494],{"class":202,"line":5454},[200,66477,3605],{"class":235},[200,66479,239],{"class":214},[200,66481,3165],{"class":210},[200,66483,215],{"class":214},[200,66485,328],{"class":223},[200,66487,1532],{"class":235},[200,66489,215],{"class":214},[200,66491,2020],{"class":223},[200,66493,1532],{"class":235},[200,66495,252],{"class":214},[200,66497,66498,66500,66502,66504,66506],{"class":202,"line":5459},[200,66499,2096],{"class":214},[200,66501,2089],{"class":235},[200,66503,215],{"class":214},[200,66505,41223],{"class":210},[200,66507,416],{"class":235},[200,66509,66510],{"class":202,"line":13227},[200,66511,529],{"emptyLinePlaceholder":20},[200,66513,66514,66516,66518,66520,66522,66524,66526],{"class":202,"line":13253},[200,66515,1999],{"class":379},[200,66517,65279],{"class":210},[200,66519,510],{"class":214},[200,66521,46807],{"class":223},[200,66523,227],{"class":235},[200,66525,25386],{"class":210},[200,66527,416],{"class":235},[200,66529,66530,66532,66534,66536,66538,66540,66542,66544,66546],{"class":202,"line":13269},[200,66531,1999],{"class":379},[200,66533,3224],{"class":210},[200,66535,510],{"class":214},[200,66537,56948],{"class":223},[200,66539,227],{"class":235},[200,66541,23870],{"class":210},[200,66543,215],{"class":214},[200,66545,2020],{"class":210},[200,66547,416],{"class":235},[200,66549,66550,66552,66554,66556,66558,66560,66562,66564,66566,66568,66570,66572,66574,66576,66578,66580],{"class":202,"line":13313},[200,66551,1999],{"class":379},[200,66553,65316],{"class":210},[200,66555,510],{"class":214},[200,66557,12997],{"class":214},[200,66559,65323],{"class":223},[200,66561,227],{"class":235},[200,66563,3318],{"class":214},[200,66565,2882],{"class":245},[200,66567,4853],{"class":214},[200,66569,3280],{"class":210},[200,66571,23001],{"class":214},[200,66573,550],{"class":214},[200,66575,65279],{"class":210},[200,66577,2089],{"class":235},[200,66579,215],{"class":214},[200,66581,46818],{"class":210},[200,66583,66584],{"class":202,"line":13319},[200,66585,529],{"emptyLinePlaceholder":20},[200,66587,66588,66590,66592,66594,66596,66598,66600,66602,66604,66606,66608,66610,66612,66614,66616,66618],{"class":202,"line":13324},[200,66589,19216],{"class":206},[200,66591,65356],{"class":223},[200,66593,227],{"class":235},[200,66595,249],{"class":214},[200,66597,12726],{"class":245},[200,66599,249],{"class":214},[200,66601,2089],{"class":235},[200,66603,215],{"class":214},[200,66605,65371],{"class":223},[200,66607,227],{"class":235},[200,66609,3280],{"class":210},[200,66611,550],{"class":214},[200,66613,41038],{"class":210},[200,66615,215],{"class":214},[200,66617,2020],{"class":210},[200,66619,416],{"class":235},[200,66621,66622],{"class":202,"line":13350},[200,66623,529],{"emptyLinePlaceholder":20},[200,66625,66626,66628,66630,66632,66634,66636],{"class":202,"line":13355},[200,66627,1999],{"class":379},[200,66629,64466],{"class":210},[200,66631,510],{"class":214},[200,66633,3714],{"class":214},[200,66635,393],{"class":379},[200,66637,280],{"class":214},[200,66639,66640,66642],{"class":202,"line":13375},[200,66641,11715],{"class":206},[200,66643,64481],{"class":235},[200,66645,66646,66648,66650],{"class":202,"line":13395},[200,66647,49602],{"class":214},[200,66649,11386],{"class":235},[200,66651,648],{"class":214},[200,66653,66654,66656,66658],{"class":202,"line":13400},[200,66655,49629],{"class":214},[200,66657,64496],{"class":1978},[200,66659,648],{"class":214},[200,66661,66662,66665,66667,66669,66671,66673,66675],{"class":202,"line":13408},[200,66663,66664],{"class":214},"            \u003C",[200,66666,3566],{"class":235},[200,66668,675],{"class":214},[200,66670,65436],{"class":210},[200,66672,681],{"class":214},[200,66674,3566],{"class":235},[200,66676,648],{"class":214},[200,66678,66679,66681,66683],{"class":202,"line":13426},[200,66680,49701],{"class":214},[200,66682,64496],{"class":1978},[200,66684,648],{"class":214},[200,66686,66687,66689,66691,66693,66695,66697,66699],{"class":202,"line":13456},[200,66688,49629],{"class":214},[200,66690,51],{"class":235},[200,66692,675],{"class":214},[200,66694,65461],{"class":210},[200,66696,681],{"class":214},[200,66698,51],{"class":235},[200,66700,648],{"class":214},[200,66702,66703,66705],{"class":202,"line":13462},[200,66704,49629],{"class":214},[200,66706,65474],{"class":235},[200,66708,66709,66712,66714,66716,66718],{"class":202,"line":13467},[200,66710,66711],{"class":379},"            type",[200,66713,449],{"class":214},[200,66715,611],{"class":214},[200,66717,698],{"class":245},[200,66719,617],{"class":214},[200,66721,66722,66725,66727,66729],{"class":202,"line":13472},[200,66723,66724],{"class":379},"            value",[200,66726,65494],{"class":214},[200,66728,65497],{"class":210},[200,66730,1130],{"class":214},[200,66732,66733],{"class":202,"line":13487},[200,66734,66735],{"class":379},"            autofocus\n",[200,66737,66738],{"class":202,"line":13498},[200,66739,66740],{"class":214},"          \u002F>\n",[200,66742,66743,66745,66747],{"class":202,"line":13503},[200,66744,49710],{"class":214},[200,66746,11386],{"class":235},[200,66748,648],{"class":214},[200,66750,66751],{"class":202,"line":13508},[200,66752,46937],{"class":235},[200,66754,66755],{"class":202,"line":20224},[200,66756,4962],{"class":214},[200,66758,66759],{"class":202,"line":20246},[200,66760,529],{"emptyLinePlaceholder":20},[200,66762,66763,66765,66767,66769,66771,66773,66775,66777,66779],{"class":202,"line":20251},[200,66764,1999],{"class":379},[200,66766,14330],{"class":210},[200,66768,510],{"class":214},[200,66770,64404],{"class":223},[200,66772,227],{"class":235},[200,66774,600],{"class":214},[200,66776,64688],{"class":1978},[200,66778,22055],{"class":214},[200,66780,416],{"class":235},[200,66782,66783,66785,66787,66789,66791,66793,66795,66797,66799,66801,66803,66805,66807,66809,66811],{"class":202,"line":20256},[200,66784,1999],{"class":379},[200,66786,318],{"class":214},[200,66788,41038],{"class":235},[200,66790,239],{"class":214},[200,66792,65561],{"class":210},[200,66794,550],{"class":214},[200,66796,64705],{"class":210},[200,66798,981],{"class":214},[200,66800,510],{"class":214},[200,66802,64399],{"class":210},[200,66804,215],{"class":214},[200,66806,14492],{"class":223},[200,66808,227],{"class":235},[200,66810,26746],{"class":210},[200,66812,416],{"class":235},[200,66814,66815],{"class":202,"line":20261},[200,66816,529],{"emptyLinePlaceholder":20},[200,66818,66819,66821,66823,66825],{"class":202,"line":20282},[200,66820,3296],{"class":206},[200,66822,64427],{"class":223},[200,66824,227],{"class":235},[200,66826,230],{"class":214},[200,66828,66829,66831,66833,66835],{"class":202,"line":20289},[200,66830,42082],{"class":235},[200,66832,239],{"class":214},[200,66834,65561],{"class":210},[200,66836,252],{"class":214},[200,66838,66839,66842],{"class":202,"line":20294},[200,66840,66841],{"class":210},"      head",[200,66843,252],{"class":214},[200,66845,66846,66848],{"class":202,"line":20310},[200,66847,2096],{"class":214},[200,66849,416],{"class":235},[200,66851,66852],{"class":202,"line":47145},[200,66853,368],{"class":214},[200,66855,66856,66858],{"class":202,"line":47150},[200,66857,413],{"class":214},[200,66859,416],{"class":210},[35,66861,66862,66863,66865],{},"We set up a pre-request handler to check the ",[6850,66864,66289],{}," header. If there is a mismatch, an error is raised; otherwise, the request proceeds to the main handler.",[939,66867,66868],{},[35,66869,66870,66872,66873,66875,66876,66881,66882,66884,66885,66888],{},[200,66871,945],{},"\nFor comprehensive information on ",[6850,66874,66122],{},", refer to the ",[39,66877,66880],{"href":66878,"rel":66879},"https:\u002F\u002Fcheatsheetseries.owasp.org\u002Fcheatsheets\u002FCross-Site_Request_Forgery_Prevention_Cheat_Sheet.html",[77],"Cross-Site Request Forgery Prevention Cheat Sheet"," by OWASP. Validating the ",[6850,66883,66289],{}," header is a basic defense approach for ",[6850,66886,66887],{},"CSRF",". However, it is advisable to implement a more robust protection mechanism such as a token.",[51,66890,30681],{"id":30680},[35,66892,66893,66894,1638,66896,66898,66899,66901,66902,215],{},"Developing with ",[6850,66895,36816],{},[6850,66897,38775],{}," is straightforward and provides an exceptional developer experience due to the ability to utilize ",[6850,66900,64243],{}," and development storage, eliminating the need for the ",[6850,66903,64214],{},[35,66905,66906,66907,1638,66909,66911],{},"Enjoy your development journey with ",[6850,66908,36816],{},[6850,66910,38775],{},"! 🚀",[939,66913,66914],{},[35,66915,66916,66918,66919,215],{},[200,66917,945],{},"\nThe URL shortener is inspired by ",[39,66920,66923],{"href":66921,"rel":66922},"https:\u002F\u002Fgithub.com\u002Fyusukebe\u002Furl-shortener",[77],"Yusuke Wada's URL Shortener",[1171,66925,66926],{},"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 .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 .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 .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 .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}",{"title":13,"searchDepth":14,"depth":14,"links":66928},[66929,66930],{"id":64256,"depth":14,"text":64257},{"id":64335,"depth":14,"text":64336,"children":66931},[66932,66933],{"id":64359,"depth":255,"text":64360},{"id":65051,"depth":255,"text":65052},"2024-03-19","Learn how to create a URL shortener using Nitro and deploy it on Cloudflare Pages, enabling effortless edge app development.",{},"\u002Fposts\u002Fbuild-a-url-shortener-with-nitro-on-cloudflare-pages","---\ncontentId: 5b969fcd-0a41-4bc1-ae7e-3aca6d1f48a5\ntitle: Build a URL Shortener with Nitro on Cloudflare Pages\ndescription: Learn how to create a URL shortener using Nitro and deploy it on Cloudflare Pages, enabling effortless edge app development.\ndate: 2024-03-19\n---\n\nIn this article, we will develop a **URL shortener** utilizing [**Nitro**](https:\u002F\u002Fnitro.unjs.io) and deploy it on [**Cloudflare Pages**](https:\u002F\u002Fpages.cloudflare.com\u002F).\n\nThe source code is accessible on \u003CGitHubLink repo=\"barbapapazes\u002Furl-shortener\" \u002F>.\n\n**Nitro** represents a new generation of **server toolkit**. It empowers us to construct web servers with all necessary functionalities and deploy them at our convenience.\n\n**Cloudflare Pages** is a platform designed to build and host websites on the edge. It supports services like [**KV**](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fkv\u002F) to create comprehensive full-stack stateful applications.\n\nOur project is a straightforward **URL shortener** that facilitates converting a long URL into a shortened version. We will leverage the **Cloudflare KV** to store the URLs and the **Nitro server** to manage requests.\n\nWe will utilize:\n\n- [**unstorage**](https:\u002F\u002Funstorage.unjs.io) to streamline the development process by abstracting the **KV** layer, eliminating the need for the **Cloudflare Wrangler CLI**.\n- [**ohash**](https:\u002F\u002Fohash.unjs.io) to generate a **hash** from the URL to prevent collisions.\n- [**nanojsx**](https:\u002F\u002Fnanojsx.io\u002F) to build the **HTML pages** using **TSX**.\n- [**pico.css**](https:\u002F\u002Fpicocss.com) for styling the application.\n\n## Project initialization\n\nFirst, create a new **Nitro project**:\n\n```bash\nnpx giget@latest nitro url-shortener\n```\n\nSubsequently, navigate to the project and install the required **dependencies**:\n\n```bash\ncd url-shortener\nnpm install\n```\n\nStart the **development server** to view the default Nitro page:\n\n```bash\nnpm run dev\n```\n\nOpen your browser and visit [http:\u002F\u002Flocalhost:3000](http:\u002F\u002Flocalhost:3000) to verify functionality.\n\n## Constructing the URL shortener\n\nInitially, install the necessary packages:\n\n```bash\nnpm install ohash nano-jsx\n```\n\n### Generate a short URL\n\nCreate a **route** named `index.get.tsx` within the `server\u002Froutes` directory. This will serve as the **home page** of our URL shortener where users can generate a shortened URL from a long one.\n\n```tsx [server\u002Froutes\u002Findex.get.tsx]\nimport { h, Helmet, renderSSR } from 'nano-jsx' \u002F\u002F the `h` is critical here\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineLazyEventHandler(() => {\n  const App = () => {\n    return (\n      \u003Cdiv>\n        \u003CHelmet>\n          \u003Ctitle>URL Shortener with Nitro\u003C\u002Ftitle>\n        \u003C\u002FHelmet>\n        \u003Ch2>Shorten a URL\u003C\u002Fh2>\n        \u003Cform action=\"\u002Fcreate\" method=\"POST\">\n          \u003Cinput type=\"url\" name=\"url\" placeholder=\"URL to shorten\" autocomplete=\"off\" \u002F>\n          \u003Cbutton type=\"submit\">Create\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\nThis route will present a form enabling users to input a URL to shorten. Upon form submission, a **POST request** is sent to the `\u002Fcreate` route.\n\nA **lazy event handler** is utilized to generate the view only once, when a request reaches the server. The response is then **cached in-memory** and reused for future requests, reducing the computational load.\n\nThe `withTemplate` function serves as a utility that we must construct.\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\">URL Shortener with 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\nThis is a simple **string template** where we incorporate the head and body content generated by **nano-jsx**.\n\n### URL storage\n\nBefore proceeding, install `zod` to validate the request body.\n\n```bash\nnpm install zod\n```\n\nCreate the `\u002Fcreate` route to manage the **POST request** and store the URL in the **KV**. This route is labeled `create.post.tsx` and resides in the `server\u002Froutes` directory.\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 the `h` is essential here\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>Created\u003C\u002Ftitle>\n        \u003C\u002FHelmet>\n        \u003Ch2>Created and Ready\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\nIn this segment, we utilize the `readValidatedBody` function to **validate the request body**. This assures that the `url` field is a legitimate URL, throwing an error otherwise.\n\nWe retrieve the **request URL** using the `getRequestURL` function from `h3`.\n\nThe **hash** is created from the body URL using the `hash` function from **ohash**, ensuring consistent hash generation to avoid collisions.\n\nThe URL is stored in the **KV** using the `useStorage` function from **unstorage**, employing the `data` namespace to store the URLs, pre-configured for our use.\n\n\u003CCalloutReadMore text=\"KV Storage\" href=\"https:\u002F\u002Fnitro.unjs.io\u002Fguide\u002Fstorage#usage\" \u002F>\n\nAt the end of this event handler, a route is returned with the shortened URL that users can copy and utilize.\n\nFor development, everything functions correctly, but we must update this configuration, `nitro.config.ts`, for the production environment.\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\nIn this configuration, we define the `data` namespace, as in development, to use the **cloudflare-kv-binding** driver and the `url-shortener` binding, specified under the key `$production`. This indicates that the configuration applies solely to the **production environment** for accessing the **Cloudflare KV**.\n\n\u003CCalloutReadMore text=\"Environment-specific configuration\" href=\"https:\u002F\u002Fgithub.com\u002Funjs\u002Fc12#environment-specific-configuration\" \u002F>\n\n### Redirecting to the original URL\n\nFinally, we need to establish a route to handle shortened URLs and redirect users to the primary URL. This route is named `[id].get.ts` and is situated in the `server\u002Froutes` directory.\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: 'Not Found',\n    })\n  }\n\n  return sendRedirect(event, value)\n})\n```\n\nHere, we simply obtain the `id` from the router parameters and use `useStorage` to fetch the **URL** from the **KV**. If the URL does not exist, a **404 error** is thrown. Otherwise, the user is redirected to the original URL.\n\n## Deploying the URL shortener\n\nCongratulations! We have successfully created a basic URL shortener using Nitro. The next step is to **deploy** it on Cloudflare Pages. Rest assured, it's straightforward thanks to the **zero-config deployment providers**.\n\n\u003CCalloutReadMore text=\"Zero Config Providers\" href=\"https:\u002F\u002Fnitro.unjs.io\u002Fdeploy#zero-config-providers\" \u002F>\n\n> [!WARNING]\n> A Cloudflare account and a GitHub repository are required to continue with the deployment.\nInitially, log in to the Cloudflare dashboard and select an account. Navigate to Account Home, choose Workers & Pages, and create a new application using the button at the top right. Select the Pages tab and follow the instructions to establish a GitHub connection.\n\nSelect the repository, add the build command `npm run build`, specify the output directory `dist`, include an environment variable `NODE_ENV` with the value `production`, and save and deploy.\n\nNow, let's wait for the deployment to complete. Once done, you can access the URL shortener using the URL provided by **Cloudflare Pages**.\n\n**However**, it may not function adequately initially, as a **KV namespace** has not been bound. This requires manual binding. Go to the project settings, select functions, and scroll until you find **KV namespace bindings**. Add a binding with the name `url-shortener` and choose the desired namespace.\n\n> [!NOTE]\n> Creating a **KV namespace** is achievable in the **Workers & Pages** section.\nRe-deploy your application to account for the new KV namespace binding. Once completed, the URL shortener will be fully functional. :sparkles:\n\n> [!NOTE]\n> If you do not intend to maintain the project, remember to delete it from the Cloudflare Pages dashboard to avoid unnecessary expenses.\n\n## Enhancements and future considerations\n\nTo enhance security, consider adding simple **CSRF protection** using the handler object syntax. Modify the `create.post.tsx` file to include a pre-request handler for origin header verification.\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: 'Bad Request',\n      })\n    }\n\n    if (origin !== requestURL) {\n      throw createError({\n        statusCode: 403,\n        statusMessage: 'Forbidden',\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>Created\u003C\u002Ftitle>\n          \u003C\u002FHelmet>\n          \u003Ch2>Created and Ready\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\nWe set up a pre-request handler to check the **origin** header. If there is a mismatch, an error is raised; otherwise, the request proceeds to the main handler.\n\n> [!NOTE]\n> For comprehensive information on **CSRF protection**, refer to the [Cross-Site Request Forgery Prevention Cheat Sheet](https:\u002F\u002Fcheatsheetseries.owasp.org\u002Fcheatsheets\u002FCross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) by OWASP. Validating the **origin** header is a basic defense approach for **CSRF**. However, it is advisable to implement a more robust protection mechanism such as a token.\n\n## Conclusion\n\nDeveloping with **Nitro** and **Cloudflare Pages** is straightforward and provides an exceptional developer experience due to the ability to utilize **TSX** and development storage, eliminating the need for the **Cloudflare Wrangler CLI**.\n\nEnjoy your development journey with **Nitro** and **Cloudflare Pages**! 🚀\n\n> [!NOTE]\n> The URL shortener is inspired by [Yusuke Wada's URL Shortener](https:\u002F\u002Fgithub.com\u002Fyusukebe\u002Furl-shortener).\n",{"title":64131,"description":66935},{"loc":66937},"posts\u002Fbuild-a-url-shortener-with-nitro-on-cloudflare-pages","CZ53xZ2ywdlK7SVMWbwtizjz4fdUj1L4aFYMJjxKTmU",[66944,67831,68130,68278,69181,71445,71512,72350,72816,73308,73685,74123,74270,75682,77846,78074,79956,80295,80515,80721,80853,80923,81137,81432,82091,82959,83253,83616,86163,90315,91577,91893,93792,94058,94176,94318,94468,96121,96375,97356,97511,97628,97869,99540,104451,117587,117889,118021,120368,120556,120697],{"id":30,"title":31,"body":66945,"contentId":1181,"date":1182,"description":1183,"extension":18,"meta":67828,"navigation":20,"path":1185,"rawbody":1186,"robots":23,"schemaOrg":23,"seo":67829,"sitemap":67830,"stem":1189,"__hash__":1190},{"type":7,"value":66946,"toc":67821},[66947,66951,66953,66955,66957,66959,66961,66963,66965,66977,66979,66981,66983,66991,66993,66995,67000,67008,67010,67015,67017,67019,67021,67023,67025,67030,67036,67038,67196,67206,67232,67241,67247,67329,67331,67633,67642,67644,67649,67793,67802,67804,67806,67808,67810,67812,67814,67819],[35,66948,37,66949,43],{},[39,66950,42],{"href":41},[35,66952,46],{},[35,66954,49],{},[51,66956,54],{"id":53},[35,66958,57],{},[35,66960,60],{},[35,66962,63],{},[35,66964,66],{},[68,66966,66967,66972],{},[71,66968,66969,79],{},[39,66970,78],{"href":75,"rel":66971},[77],[71,66973,66974,87],{},[39,66975,86],{"href":84,"rel":66976},[77],[35,66978,90],{},[51,66980,94],{"id":93},[35,66982,97],{},[35,66984,100,66985,106,66988,112],{},[39,66986,105],{"href":103,"rel":66987},[77],[39,66989,111],{"href":109,"rel":66990},[77],[35,66992,115],{},[51,66994,119],{"id":118},[35,66996,122,66997,128],{},[39,66998,127],{"href":125,"rel":66999},[77],[130,67001,132,67002,132,67006],{},[134,67003,136,67004,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,67005],{"src":140,"type":141},[143,67007,145],{},[35,67009,148],{},[35,67011,151,67012,157],{},[39,67013,156],{"href":154,"rel":67014},[77],[35,67016,160],{},[35,67018,163],{},[35,67020,166],{},[51,67022,170],{"id":169},[35,67024,173],{},[35,67026,67027,179],{},[39,67028,78],{"href":75,"rel":67029},[77],[68,67031,67032,67034],{},[71,67033,184],{},[71,67035,187],{},[35,67037,190],{},[192,67039,67040],{"className":194,"code":195,"language":196,"meta":13,"style":13},[156,67041,67042,67060,67074,67088,67096,67110,67118,67138,67142,67160,67164,67182,67186,67190],{"__ignoreMap":13},[200,67043,67044,67046,67048,67050,67052,67054,67056,67058],{"class":202,"line":203},[200,67045,207],{"class":206},[200,67047,211],{"class":210},[200,67049,215],{"class":214},[200,67051,218],{"class":210},[200,67053,215],{"class":214},[200,67055,224],{"class":223},[200,67057,227],{"class":210},[200,67059,230],{"class":214},[200,67061,67062,67064,67066,67068,67070,67072],{"class":202,"line":14},[200,67063,236],{"class":235},[200,67065,239],{"class":214},[200,67067,242],{"class":214},[200,67069,246],{"class":245},[200,67071,249],{"class":214},[200,67073,252],{"class":214},[200,67075,67076,67078,67080,67082,67084,67086],{"class":202,"line":255},[200,67077,258],{"class":235},[200,67079,239],{"class":214},[200,67081,242],{"class":214},[200,67083,265],{"class":245},[200,67085,249],{"class":214},[200,67087,252],{"class":214},[200,67089,67090,67092,67094],{"class":202,"line":272},[200,67091,275],{"class":235},[200,67093,239],{"class":214},[200,67095,280],{"class":214},[200,67097,67098,67100,67102,67104,67106,67108],{"class":202,"line":283},[200,67099,286],{"class":235},[200,67101,239],{"class":214},[200,67103,242],{"class":214},[200,67105,293],{"class":245},[200,67107,249],{"class":214},[200,67109,252],{"class":214},[200,67111,67112,67114,67116],{"class":202,"line":300},[200,67113,303],{"class":235},[200,67115,239],{"class":214},[200,67117,280],{"class":214},[200,67119,67120,67122,67124,67126,67128,67130,67132,67134,67136],{"class":202,"line":310},[200,67121,313],{"class":235},[200,67123,239],{"class":214},[200,67125,318],{"class":214},[200,67127,321],{"class":235},[200,67129,239],{"class":214},[200,67131,242],{"class":214},[200,67133,328],{"class":245},[200,67135,249],{"class":214},[200,67137,333],{"class":214},[200,67139,67140],{"class":202,"line":336},[200,67141,339],{"class":214},[200,67143,67144,67146,67148,67150,67152,67154,67156,67158],{"class":202,"line":342},[200,67145,345],{"class":235},[200,67147,239],{"class":214},[200,67149,350],{"class":210},[200,67151,249],{"class":214},[200,67153,355],{"class":245},[200,67155,249],{"class":214},[200,67157,360],{"class":210},[200,67159,252],{"class":214},[200,67161,67162],{"class":202,"line":365},[200,67163,368],{"class":214},[200,67165,67166,67168,67170,67172,67174,67176,67178,67180],{"class":202,"line":371},[200,67167,374],{"class":223},[200,67169,239],{"class":214},[200,67171,380],{"class":379},[200,67173,383],{"class":214},[200,67175,387],{"class":386},[200,67177,390],{"class":214},[200,67179,393],{"class":379},[200,67181,280],{"class":214},[200,67183,67184],{"class":202,"line":398},[200,67185,402],{"class":401},[200,67187,67188],{"class":202,"line":405},[200,67189,368],{"class":214},[200,67191,67192,67194],{"class":202,"line":410},[200,67193,413],{"class":214},[200,67195,416],{"class":210},[35,67197,419,67198,423,67200,426,67202,429,67204,433],{},[156,67199,422],{},[156,67201,218],{},[156,67203,218],{},[156,67205,432],{},[192,67207,67208],{"className":194,"code":436,"language":196,"meta":13,"style":13},[156,67209,67210],{"__ignoreMap":13},[200,67211,67212,67214,67216,67218,67220,67222,67224,67226,67228,67230],{"class":202,"line":203},[200,67213,443],{"class":379},[200,67215,446],{"class":210},[200,67217,449],{"class":214},[200,67219,452],{"class":206},[200,67221,211],{"class":210},[200,67223,215],{"class":214},[200,67225,218],{"class":210},[200,67227,215],{"class":214},[200,67229,463],{"class":223},[200,67231,466],{"class":210},[35,67233,469,67234,473,67236,481],{},[156,67235,472],{},[39,67237,67239],{"href":476,"rel":67238},[77],[156,67240,480],{},[35,67242,484,67243,488,67245,491],{},[156,67244,487],{},[156,67246,218],{},[192,67248,67249],{"className":194,"code":494,"language":196,"meta":13,"style":13},[156,67250,67251,67277,67281,67309,67323],{"__ignoreMap":13},[200,67252,67253,67255,67257,67259,67261,67263,67265,67267,67269,67271,67273,67275],{"class":202,"line":203},[200,67254,443],{"class":379},[200,67256,350],{"class":214},[200,67258,505],{"class":210},[200,67260,360],{"class":214},[200,67262,510],{"class":214},[200,67264,452],{"class":206},[200,67266,211],{"class":210},[200,67268,215],{"class":214},[200,67270,218],{"class":210},[200,67272,215],{"class":214},[200,67274,463],{"class":223},[200,67276,466],{"class":210},[200,67278,67279],{"class":202,"line":14},[200,67280,529],{"emptyLinePlaceholder":20},[200,67282,67283,67285,67287,67289,67291,67293,67295,67297,67299,67301,67303,67305,67307],{"class":202,"line":255},[200,67284,207],{"class":206},[200,67286,211],{"class":210},[200,67288,215],{"class":214},[200,67290,218],{"class":210},[200,67292,215],{"class":214},[200,67294,544],{"class":223},[200,67296,547],{"class":210},[200,67298,550],{"class":214},[200,67300,553],{"class":210},[200,67302,215],{"class":214},[200,67304,558],{"class":223},[200,67306,227],{"class":210},[200,67308,230],{"class":214},[200,67310,67311,67313,67315,67317,67319,67321],{"class":202,"line":272},[200,67312,567],{"class":235},[200,67314,239],{"class":214},[200,67316,242],{"class":214},[200,67318,574],{"class":245},[200,67320,249],{"class":214},[200,67322,252],{"class":214},[200,67324,67325,67327],{"class":202,"line":283},[200,67326,413],{"class":214},[200,67328,585],{"class":210},[35,67330,588],{},[192,67332,67333],{"className":591,"code":592,"language":593,"meta":13,"style":13},[156,67334,67335,67351,67363,67377,67381,67407,67427,67431,67457,67477,67481,67499,67513,67539,67565,67591,67599,67603,67625],{"__ignoreMap":13},[200,67336,67337,67339,67341,67343,67345,67347,67349],{"class":202,"line":203},[200,67338,600],{"class":214},[200,67340,603],{"class":235},[200,67342,606],{"class":379},[200,67344,449],{"class":214},[200,67346,611],{"class":214},[200,67348,614],{"class":245},[200,67350,617],{"class":214},[200,67352,67353,67355,67357,67359,67361],{"class":202,"line":14},[200,67354,622],{"class":379},[200,67356,449],{"class":214},[200,67358,611],{"class":214},[200,67360,629],{"class":245},[200,67362,617],{"class":214},[200,67364,67365,67367,67369,67371,67373,67375],{"class":202,"line":255},[200,67366,636],{"class":379},[200,67368,449],{"class":214},[200,67370,611],{"class":214},[200,67372,643],{"class":245},[200,67374,611],{"class":214},[200,67376,648],{"class":214},[200,67378,67379],{"class":202,"line":272},[200,67380,529],{"emptyLinePlaceholder":20},[200,67382,67383,67385,67387,67389,67391,67393,67395,67397,67399,67401,67403,67405],{"class":202,"line":283},[200,67384,657],{"class":214},[200,67386,660],{"class":235},[200,67388,663],{"class":379},[200,67390,449],{"class":214},[200,67392,611],{"class":214},[200,67394,670],{"class":245},[200,67396,611],{"class":214},[200,67398,675],{"class":214},[200,67400,678],{"class":210},[200,67402,681],{"class":214},[200,67404,660],{"class":235},[200,67406,648],{"class":214},[200,67408,67409,67411,67413,67415,67417,67419,67421,67423,67425],{"class":202,"line":300},[200,67410,657],{"class":214},[200,67412,355],{"class":235},[200,67414,321],{"class":379},[200,67416,449],{"class":214},[200,67418,698],{"class":245},[200,67420,701],{"class":379},[200,67422,449],{"class":214},[200,67424,670],{"class":245},[200,67426,648],{"class":214},[200,67428,67429],{"class":202,"line":310},[200,67430,529],{"emptyLinePlaceholder":20},[200,67432,67433,67435,67437,67439,67441,67443,67445,67447,67449,67451,67453,67455],{"class":202,"line":336},[200,67434,657],{"class":214},[200,67436,660],{"class":235},[200,67438,663],{"class":379},[200,67440,449],{"class":214},[200,67442,611],{"class":214},[200,67444,726],{"class":245},[200,67446,611],{"class":214},[200,67448,675],{"class":214},[200,67450,733],{"class":210},[200,67452,681],{"class":214},[200,67454,660],{"class":235},[200,67456,648],{"class":214},[200,67458,67459,67461,67463,67465,67467,67469,67471,67473,67475],{"class":202,"line":342},[200,67460,657],{"class":214},[200,67462,355],{"class":235},[200,67464,321],{"class":379},[200,67466,449],{"class":214},[200,67468,698],{"class":245},[200,67470,701],{"class":379},[200,67472,449],{"class":214},[200,67474,726],{"class":245},[200,67476,648],{"class":214},[200,67478,67479],{"class":202,"line":365},[200,67480,529],{"emptyLinePlaceholder":20},[200,67482,67483,67485,67487,67489,67491,67493,67495,67497],{"class":202,"line":371},[200,67484,657],{"class":214},[200,67486,770],{"class":235},[200,67488,701],{"class":379},[200,67490,449],{"class":214},[200,67492,611],{"class":214},[200,67494,770],{"class":245},[200,67496,611],{"class":214},[200,67498,783],{"class":379},[200,67500,67501,67503,67505,67507,67509,67511],{"class":202,"line":398},[200,67502,788],{"class":379},[200,67504,449],{"class":214},[200,67506,611],{"class":214},[200,67508,795],{"class":245},[200,67510,611],{"class":214},[200,67512,648],{"class":214},[200,67514,67515,67517,67519,67521,67523,67525,67527,67529,67531,67533,67535,67537],{"class":202,"line":405},[200,67516,804],{"class":214},[200,67518,807],{"class":235},[200,67520,810],{"class":379},[200,67522,449],{"class":214},[200,67524,611],{"class":214},[200,67526,817],{"class":245},[200,67528,611],{"class":214},[200,67530,675],{"class":214},[200,67532,824],{"class":210},[200,67534,681],{"class":214},[200,67536,807],{"class":235},[200,67538,648],{"class":214},[200,67540,67541,67543,67545,67547,67549,67551,67553,67555,67557,67559,67561,67563],{"class":202,"line":410},[200,67542,804],{"class":214},[200,67544,807],{"class":235},[200,67546,810],{"class":379},[200,67548,449],{"class":214},[200,67550,611],{"class":214},[200,67552,845],{"class":245},[200,67554,611],{"class":214},[200,67556,675],{"class":214},[200,67558,852],{"class":210},[200,67560,681],{"class":214},[200,67562,807],{"class":235},[200,67564,648],{"class":214},[200,67566,67567,67569,67571,67573,67575,67577,67579,67581,67583,67585,67587,67589],{"class":202,"line":861},[200,67568,804],{"class":214},[200,67570,807],{"class":235},[200,67572,810],{"class":379},[200,67574,449],{"class":214},[200,67576,611],{"class":214},[200,67578,874],{"class":245},[200,67580,611],{"class":214},[200,67582,675],{"class":214},[200,67584,881],{"class":210},[200,67586,681],{"class":214},[200,67588,807],{"class":235},[200,67590,648],{"class":214},[200,67592,67593,67595,67597],{"class":202,"line":890},[200,67594,893],{"class":214},[200,67596,770],{"class":235},[200,67598,648],{"class":214},[200,67600,67601],{"class":202,"line":900},[200,67602,529],{"emptyLinePlaceholder":20},[200,67604,67605,67607,67609,67611,67613,67615,67617,67619,67621,67623],{"class":202,"line":905},[200,67606,657],{"class":214},[200,67608,910],{"class":235},[200,67610,321],{"class":379},[200,67612,449],{"class":214},[200,67614,917],{"class":245},[200,67616,675],{"class":214},[200,67618,922],{"class":210},[200,67620,681],{"class":214},[200,67622,910],{"class":235},[200,67624,648],{"class":214},[200,67626,67627,67629,67631],{"class":202,"line":931},[200,67628,681],{"class":214},[200,67630,603],{"class":235},[200,67632,648],{"class":214},[939,67634,67635],{},[35,67636,67637,946,67639,215],{},[200,67638,945],{},[39,67640,951],{"href":949,"rel":67641},[77],[35,67643,954],{},[35,67645,957,67646,963],{},[39,67647,962],{"href":960,"rel":67648},[77],[192,67650,67651],{"className":194,"code":966,"language":196,"meta":13,"style":13},[156,67652,67653,67671,67689,67693,67705,67709,67727,67733,67747,67753,67757,67775,67789],{"__ignoreMap":13},[200,67654,67655,67657,67659,67661,67663,67665,67667,67669],{"class":202,"line":203},[200,67656,973],{"class":206},[200,67658,318],{"class":214},[200,67660,978],{"class":210},[200,67662,981],{"class":214},[200,67664,984],{"class":206},[200,67666,242],{"class":214},[200,67668,989],{"class":245},[200,67670,992],{"class":214},[200,67672,67673,67675,67677,67679,67681,67683,67685,67687],{"class":202,"line":14},[200,67674,973],{"class":206},[200,67676,318],{"class":214},[200,67678,1001],{"class":210},[200,67680,981],{"class":214},[200,67682,984],{"class":206},[200,67684,242],{"class":214},[200,67686,1010],{"class":245},[200,67688,992],{"class":214},[200,67690,67691],{"class":202,"line":255},[200,67692,529],{"emptyLinePlaceholder":20},[200,67694,67695,67697,67699,67701,67703],{"class":202,"line":272},[200,67696,443],{"class":379},[200,67698,1023],{"class":210},[200,67700,449],{"class":214},[200,67702,978],{"class":223},[200,67704,466],{"class":210},[200,67706,67707],{"class":202,"line":283},[200,67708,529],{"emptyLinePlaceholder":20},[200,67710,67711,67713,67715,67717,67719,67721,67723,67725],{"class":202,"line":300},[200,67712,443],{"class":379},[200,67714,318],{"class":214},[200,67716,1042],{"class":210},[200,67718,413],{"class":214},[200,67720,510],{"class":214},[200,67722,1001],{"class":223},[200,67724,227],{"class":210},[200,67726,230],{"class":214},[200,67728,67729,67731],{"class":202,"line":310},[200,67730,1057],{"class":210},[200,67732,252],{"class":214},[200,67734,67735,67737,67739,67741,67743,67745],{"class":202,"line":336},[200,67736,1064],{"class":235},[200,67738,239],{"class":214},[200,67740,242],{"class":214},[200,67742,1071],{"class":245},[200,67744,249],{"class":214},[200,67746,252],{"class":214},[200,67748,67749,67751],{"class":202,"line":342},[200,67750,413],{"class":214},[200,67752,416],{"class":210},[200,67754,67755],{"class":202,"line":365},[200,67756,529],{"emptyLinePlaceholder":20},[200,67758,67759,67761,67763,67765,67767,67769,67771,67773],{"class":202,"line":371},[200,67760,1090],{"class":206},[200,67762,452],{"class":206},[200,67764,1095],{"class":210},[200,67766,443],{"class":379},[200,67768,1100],{"class":210},[200,67770,1103],{"class":214},[200,67772,1106],{"class":210},[200,67774,230],{"class":214},[200,67776,67777,67779,67781,67783,67785,67787],{"class":202,"line":398},[200,67778,1113],{"class":210},[200,67780,215],{"class":214},[200,67782,1118],{"class":223},[200,67784,227],{"class":235},[200,67786,1123],{"class":210},[200,67788,416],{"class":235},[200,67790,67791],{"class":202,"line":405},[200,67792,1130],{"class":214},[939,67794,67795],{},[35,67796,67797,1137,67799,215],{},[200,67798,945],{},[39,67800,1141],{"href":103,"rel":67801},[77],[51,67803,1145],{"id":1144},[35,67805,1148],{},[35,67807,1151],{},[35,67809,1154],{},[35,67811,1157],{},[1159,67813],{},[35,67815,1163,67816,1169],{},[39,67817,1168],{"href":1166,"rel":67818},[77],[1171,67820,1173],{},{"title":13,"searchDepth":14,"depth":14,"links":67822},[67823,67824,67825,67826,67827],{"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":1144,"depth":14,"text":1145},{},{"title":31,"description":1183},{"loc":1185},{"id":1192,"title":1193,"body":67832,"contentId":1606,"date":1607,"description":1608,"extension":18,"meta":68127,"navigation":20,"path":1610,"rawbody":1611,"robots":23,"schemaOrg":23,"seo":68128,"sitemap":68129,"stem":1614,"__hash__":1615},{"type":7,"value":67833,"toc":68121},[67834,67836,67838,67840,67842,67844,67846,67850,67852,67854,67856,67863,67865,67870,67872,67876,67878,67882,67884,67886,67888,67890,67892,67894,67896,67903,67910,67912,67917,67919,67921,67923,67929,67931,67939,67941,67987,67989,67995,67997,67999,68007,68009,68089,68093,68095,68103,68105,68115,68119],[35,67835,1198],{},[35,67837,1201],{},[35,67839,1204],{},[35,67841,1207],{},[35,67843,1210],{},[35,67845,1213],{},[35,67847,67848],{},[1217,67849,1219],{},[51,67851,1223],{"id":1222},[35,67853,1226],{},[35,67855,1229],{},[35,67857,1232,67858],{},[1217,67859,1235,67860],{},[39,67861,1240],{"href":1238,"rel":67862},[77],[35,67864,1243],{},[35,67866,1246,67867,1252],{},[39,67868,1251],{"href":1249,"rel":67869},[77],[35,67871,1255],{},[35,67873,1258,67874,1262],{},[156,67875,1261],{},[35,67877,1265],{},[35,67879,1268,67880,1272],{},[1217,67881,1271],{},[35,67883,1275],{},[35,67885,1278],{},[51,67887,1282],{"id":1281},[35,67889,1285],{},[35,67891,1288],{},[35,67893,1291],{},[35,67895,1294],{},[35,67897,1297,67898,1303,67901,1307],{},[39,67899,1302],{"href":1300,"rel":67900},[77],[156,67902,1306],{},[35,67904,1310,67905,1315,67908,1319],{},[39,67906,1271],{"href":1313,"rel":67907},[77],[156,67909,1318],{},[35,67911,1322],{},[35,67913,1325,67914,215],{},[39,67915,1330],{"href":1328,"rel":67916},[77],[35,67918,1333],{},[51,67920,1337],{"id":1336},[35,67922,1340],{},[35,67924,1343,67925,1346,67927,215],{},[156,67926,1271],{},[156,67928,1349],{},[35,67930,1352],{},[192,67932,67933],{"className":1355,"code":1356,"filename":1357,"language":1358,"meta":13,"style":13},[156,67934,67935],{"__ignoreMap":13},[200,67936,67937],{"class":202,"line":203},[200,67938,1356],{},[35,67940,1367],{},[192,67942,67943],{"className":1370,"code":1371,"filename":1372,"language":1373,"meta":13,"style":13},[156,67944,67945,67957,67965,67975,67981],{"__ignoreMap":13},[200,67946,67947,67949,67951,67953,67955],{"class":202,"line":203},[200,67948,1380],{"class":206},[200,67950,1383],{"class":206},[200,67952,1386],{"class":223},[200,67954,227],{"class":210},[200,67956,230],{"class":214},[200,67958,67959,67961,67963],{"class":202,"line":14},[200,67960,1395],{"class":235},[200,67962,239],{"class":214},[200,67964,1400],{"class":210},[200,67966,67967,67969,67971,67973],{"class":202,"line":255},[200,67968,1405],{"class":214},[200,67970,1408],{"class":245},[200,67972,249],{"class":214},[200,67974,252],{"class":214},[200,67976,67977,67979],{"class":202,"line":272},[200,67978,1417],{"class":210},[200,67980,252],{"class":214},[200,67982,67983,67985],{"class":202,"line":283},[200,67984,413],{"class":214},[200,67986,416],{"class":210},[35,67988,1428],{},[35,67990,1431,67991,1435,67993,1438],{},[156,67992,1434],{},[156,67994,1318],{},[35,67996,1441],{},[35,67998,1444],{},[192,68000,68001],{"className":1355,"code":1447,"filename":1357,"language":1358,"meta":13,"style":13},[156,68002,68003],{"__ignoreMap":13},[200,68004,68005],{"class":202,"line":203},[200,68006,1447],{},[35,68008,1456],{},[192,68010,68011],{"className":1370,"code":1459,"filename":1460,"language":1373,"meta":13,"style":13},[156,68012,68013,68027,68045,68049,68061,68069,68077,68083],{"__ignoreMap":13},[200,68014,68015,68017,68019,68021,68023,68025],{"class":202,"line":203},[200,68016,973],{"class":206},[200,68018,1469],{"class":210},[200,68020,1472],{"class":206},[200,68022,242],{"class":214},[200,68024,1477],{"class":245},[200,68026,992],{"class":214},[200,68028,68029,68031,68033,68035,68037,68039,68041,68043],{"class":202,"line":14},[200,68030,973],{"class":206},[200,68032,318],{"class":214},[200,68034,1488],{"class":210},[200,68036,981],{"class":214},[200,68038,984],{"class":206},[200,68040,242],{"class":214},[200,68042,1497],{"class":245},[200,68044,992],{"class":214},[200,68046,68047],{"class":202,"line":255},[200,68048,529],{"emptyLinePlaceholder":20},[200,68050,68051,68053,68055,68057,68059],{"class":202,"line":272},[200,68052,1380],{"class":206},[200,68054,1383],{"class":206},[200,68056,1488],{"class":223},[200,68058,227],{"class":210},[200,68060,230],{"class":214},[200,68062,68063,68065,68067],{"class":202,"line":283},[200,68064,1520],{"class":235},[200,68066,239],{"class":214},[200,68068,1400],{"class":210},[200,68070,68071,68073,68075],{"class":202,"line":300},[200,68072,1529],{"class":223},[200,68074,1532],{"class":210},[200,68076,252],{"class":214},[200,68078,68079,68081],{"class":202,"line":310},[200,68080,1417],{"class":210},[200,68082,252],{"class":214},[200,68084,68085,68087],{"class":202,"line":336},[200,68086,413],{"class":214},[200,68088,416],{"class":210},[35,68090,1549,68091,1552],{},[156,68092,1349],{},[51,68094,1556],{"id":1555},[35,68096,1559,68097,1565,68100,1571],{},[39,68098,1564],{"href":1562,"rel":68099},[77],[39,68101,1570],{"href":1568,"rel":68102},[77],[35,68104,1574],{},[1576,68106,68107,68111],{},[71,68108,1580,68109],{},[1582,68110],{"repo":1584},[71,68112,1587,68113],{},[1582,68114],{"repo":1590},[35,68116,1593,68117],{},[1217,68118,1596],{},[1171,68120,1599],{},{"title":13,"searchDepth":14,"depth":14,"links":68122},[68123,68124,68125,68126],{"id":1222,"depth":14,"text":1223},{"id":1281,"depth":14,"text":1282},{"id":1336,"depth":14,"text":1337},{"id":1555,"depth":14,"text":1556},{},{"title":1193,"description":1608},{"loc":1610},{"id":1617,"title":1618,"body":68131,"contentId":1835,"date":1607,"description":1836,"extension":18,"meta":68268,"navigation":20,"path":1838,"rawbody":1839,"robots":23,"schemaOrg":23,"seo":68269,"sitemap":68270,"stem":1849,"__hash__":1850},{"type":7,"value":68132,"toc":68263},[68133,68135,68137,68139,68147,68149,68151,68153,68155,68157,68159,68169,68171,68173,68175,68179,68181,68183,68185,68187,68192,68194,68196,68198,68200,68207,68211,68216,68220,68224,68229,68233,68237,68239,68241,68243,68245,68247,68249,68251,68255,68257,68259,68261],[35,68134,1623],{},[35,68136,1626],{},[35,68138,1629],{},[35,68140,1632,68141,1638,68144,1644],{},[39,68142,1637],{"href":1635,"rel":68143},[77],[39,68145,1643],{"href":1641,"rel":68146},[77],[35,68148,1647],{},[51,68150,1651],{"id":1650},[35,68152,1654],{},[35,68154,1657],{},[35,68156,1660],{},[35,68158,1663],{},[1576,68160,68161,68163,68165,68167],{},[71,68162,1668],{},[71,68164,1671],{},[71,68166,1674],{},[71,68168,1677],{},[35,68170,1680],{},[35,68172,1683],{},[35,68174,1686],{},[35,68176,68177],{},[1690,68178],{"alt":1692,"src":1693},[51,68180,1697],{"id":1696},[35,68182,1700],{},[35,68184,1703],{},[35,68186,1706],{},[35,68188,1709,68189,1715],{},[39,68190,1714],{"href":1712,"rel":68191},[77],[35,68193,1718],{},[35,68195,1721],{},[35,68197,1724],{},[35,68199,1727],{},[35,68201,1730,68202,1736,68205,215],{},[39,68203,1735],{"href":1733,"rel":68204},[77],[39,68206,1740],{"href":1739},[35,68208,68209],{},[1690,68210],{"alt":1745,"src":1746},[35,68212,1749,68213,1755],{},[39,68214,1754],{"href":1752,"rel":68215},[77],[35,68217,68218],{},[1690,68219],{"alt":1760,"src":1761},[35,68221,68222],{},[1690,68223],{"alt":1766,"src":1767},[35,68225,1770,68226,1776],{},[39,68227,1775],{"href":1773,"rel":68228},[77],[35,68230,68231],{},[1690,68232],{"alt":1781,"src":1782},[35,68234,68235],{},[1690,68236],{"alt":1787,"src":1788},[51,68238,1792],{"id":1791},[35,68240,1795],{},[35,68242,1798],{},[35,68244,1801],{},[35,68246,1804],{},[35,68248,1807],{},[35,68250,1810],{},[35,68252,1813,68253,1818],{},[39,68254,1817],{"href":1816},[35,68256,1821],{},[35,68258,1824],{},[1159,68260],{},[35,68262,1829],{},{"title":13,"searchDepth":14,"depth":14,"links":68264},[68265,68266,68267],{"id":1650,"depth":14,"text":1651},{"id":1696,"depth":14,"text":1697},{"id":1791,"depth":14,"text":1792},{},{"title":1618,"description":1836},{"loc":1838,"images":68271},[68272,68273,68274,68275,68276,68277],{"loc":1693},{"loc":1746},{"loc":1761},{"loc":1767},{"loc":1782},{"loc":1788},{"id":1852,"title":1853,"body":68279,"contentId":2993,"date":2994,"description":2995,"extension":18,"meta":69178,"navigation":20,"path":2997,"rawbody":2998,"robots":23,"schemaOrg":23,"seo":69179,"sitemap":69180,"stem":3001,"__hash__":3002},{"type":7,"value":68280,"toc":69167},[68281,68288,68290,68298,68300,68304,68306,68308,68310,68312,68314,68316,68318,68322,68482,68488,68490,68498,68506,68508,68520,68524,68624,68626,68633,68635,68637,68645,68649,68651,68656,68658,68670,68674,68752,68754,68756,68758,68763,68767,68769,68771,68773,68775,68777,68784,68788,68790,68802,68806,68928,68933,68935,68947,68951,69037,69045,69047,69055,69057,69062,69064,69072,69082,69087,69089,69101,69109,69111,69113,69165],[35,68282,1858,68283,1864,68286],{},[39,68284,1863],{"href":1861,"rel":68285},[77],[1217,68287,1867],{},[51,68289,1871],{"id":1870},[35,68291,1874,68292,1880,68295,1886],{},[39,68293,1879],{"href":1877,"rel":68294},[77],[39,68296,1885],{"href":1883,"rel":68297},[77],[35,68299,1889],{},[35,68301,1892,68302],{},[1217,68303,1895],{},[51,68305,1899],{"id":1898},[35,68307,1902],{},[35,68309,1905],{},[35,68311,1908],{},[35,68313,1911],{},[1913,68315,1916],{"id":1915},[35,68317,1919],{},[35,68319,1922,68320,1926],{},[156,68321,1925],{},[192,68323,68324],{"className":1370,"code":1929,"language":1373,"meta":13,"style":13},[156,68325,68326,68352,68356,68374,68382,68406,68410,68418,68428,68442,68466,68472,68476],{"__ignoreMap":13},[200,68327,68328,68330,68332,68334,68336,68338,68340,68342,68344,68346,68348,68350],{"class":202,"line":203},[200,68329,973],{"class":206},[200,68331,318],{"class":214},[200,68333,1940],{"class":210},[200,68335,550],{"class":214},[200,68337,1945],{"class":210},[200,68339,550],{"class":214},[200,68341,1950],{"class":210},[200,68343,981],{"class":214},[200,68345,984],{"class":206},[200,68347,242],{"class":214},[200,68349,1959],{"class":245},[200,68351,992],{"class":214},[200,68353,68354],{"class":202,"line":14},[200,68355,529],{"emptyLinePlaceholder":20},[200,68357,68358,68360,68362,68364,68366,68368,68370,68372],{"class":202,"line":255},[200,68359,1380],{"class":206},[200,68361,1383],{"class":206},[200,68363,1950],{"class":223},[200,68365,600],{"class":214},[200,68367,1979],{"class":1978},[200,68369,675],{"class":214},[200,68371,227],{"class":210},[200,68373,230],{"class":214},[200,68375,68376,68378,68380],{"class":202,"line":272},[200,68377,1990],{"class":235},[200,68379,1532],{"class":214},[200,68381,280],{"class":214},[200,68383,68384,68386,68388,68390,68392,68394,68396,68398,68400,68402,68404],{"class":202,"line":283},[200,68385,1999],{"class":379},[200,68387,2002],{"class":210},[200,68389,510],{"class":214},[200,68391,1945],{"class":223},[200,68393,227],{"class":235},[200,68395,973],{"class":206},[200,68397,215],{"class":214},[200,68399,2015],{"class":210},[200,68401,215],{"class":214},[200,68403,2020],{"class":210},[200,68405,416],{"class":235},[200,68407,68408],{"class":202,"line":300},[200,68409,529],{"emptyLinePlaceholder":20},[200,68411,68412,68414,68416],{"class":202,"line":310},[200,68413,2031],{"class":223},[200,68415,227],{"class":235},[200,68417,230],{"class":214},[200,68419,68420,68422,68424,68426],{"class":202,"line":336},[200,68421,2040],{"class":235},[200,68423,239],{"class":214},[200,68425,2046],{"class":2045},[200,68427,252],{"class":214},[200,68429,68430,68432,68434,68436,68438,68440],{"class":202,"line":342},[200,68431,2053],{"class":235},[200,68433,239],{"class":214},[200,68435,242],{"class":214},[200,68437,1925],{"class":245},[200,68439,249],{"class":214},[200,68441,252],{"class":214},[200,68443,68444,68446,68448,68450,68452,68454,68456,68458,68460,68462,68464],{"class":202,"line":365},[200,68445,2068],{"class":235},[200,68447,239],{"class":214},[200,68449,2002],{"class":210},[200,68451,215],{"class":214},[200,68453,2077],{"class":223},[200,68455,227],{"class":235},[200,68457,249],{"class":214},[200,68459,2084],{"class":245},[200,68461,249],{"class":214},[200,68463,2089],{"class":235},[200,68465,252],{"class":214},[200,68467,68468,68470],{"class":202,"line":371},[200,68469,2096],{"class":214},[200,68471,416],{"class":235},[200,68473,68474],{"class":202,"line":398},[200,68475,368],{"class":214},[200,68477,68478,68480],{"class":202,"line":405},[200,68479,413],{"class":214},[200,68481,416],{"class":210},[35,68483,2111,68484,2114,68486,2118],{},[156,68485,1925],{},[156,68487,2117],{},[35,68489,2121],{},[130,68491,132,68492,132,68496],{},[134,68493,136,68494,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,68495],{"src":2128,"type":141},[143,68497,2131],{},[35,68499,2134,68500,2140,68503,215],{},[39,68501,2139],{"href":2137,"rel":68502},[77],[39,68504,2145],{"href":2143,"rel":68505},[77],[35,68507,2148],{},[192,68509,68510],{"className":2151,"code":2152,"language":2153,"meta":13,"style":13},[156,68511,68512],{"__ignoreMap":13},[200,68513,68514,68516,68518],{"class":202,"line":203},[200,68515,2160],{"class":1978},[200,68517,2163],{"class":245},[200,68519,2166],{"class":245},[35,68521,2169,68522,239],{},[156,68523,1372],{},[192,68525,68526],{"className":1370,"code":2174,"language":1373,"meta":13,"style":13},[156,68527,68528,68546,68550,68562,68580,68588,68596,68610,68614,68618],{"__ignoreMap":13},[200,68529,68530,68532,68534,68536,68538,68540,68542,68544],{"class":202,"line":203},[200,68531,973],{"class":206},[200,68533,318],{"class":214},[200,68535,1386],{"class":210},[200,68537,981],{"class":214},[200,68539,984],{"class":206},[200,68541,242],{"class":214},[200,68543,2193],{"class":245},[200,68545,992],{"class":214},[200,68547,68548],{"class":202,"line":14},[200,68549,529],{"emptyLinePlaceholder":20},[200,68551,68552,68554,68556,68558,68560],{"class":202,"line":255},[200,68553,1380],{"class":206},[200,68555,1383],{"class":206},[200,68557,1386],{"class":223},[200,68559,227],{"class":210},[200,68561,230],{"class":214},[200,68563,68564,68566,68568,68570,68572,68574,68576,68578],{"class":202,"line":272},[200,68565,1395],{"class":235},[200,68567,239],{"class":214},[200,68569,350],{"class":210},[200,68571,249],{"class":214},[200,68573,2224],{"class":245},[200,68575,249],{"class":214},[200,68577,360],{"class":210},[200,68579,252],{"class":214},[200,68581,68582,68584,68586],{"class":202,"line":283},[200,68583,2235],{"class":235},[200,68585,239],{"class":214},[200,68587,280],{"class":214},[200,68589,68590,68592,68594],{"class":202,"line":300},[200,68591,2244],{"class":235},[200,68593,239],{"class":214},[200,68595,280],{"class":214},[200,68597,68598,68600,68602,68604,68606,68608],{"class":202,"line":310},[200,68599,2253],{"class":235},[200,68601,239],{"class":214},[200,68603,242],{"class":214},[200,68605,2260],{"class":245},[200,68607,249],{"class":214},[200,68609,252],{"class":214},[200,68611,68612],{"class":202,"line":336},[200,68613,339],{"class":214},[200,68615,68616],{"class":202,"line":342},[200,68617,368],{"class":214},[200,68619,68620,68622],{"class":202,"line":365},[200,68621,413],{"class":214},[200,68623,416],{"class":210},[35,68625,2281],{},[35,68627,2284,68628,2290,68631],{},[39,68629,2289],{"href":2287,"rel":68630},[77],[1217,68632,2293],{},[51,68634,2297],{"id":2296},[35,68636,2300],{},[68,68638,68639,68641,68643],{},[71,68640,2305],{},[71,68642,2308],{},[71,68644,2311],{},[35,68646,2314,68647,2318],{},[156,68648,2317],{},[35,68650,2321],{},[35,68652,2324,68653,239],{},[39,68654,2329],{"href":2327,"rel":68655},[77],[35,68657,2332],{},[192,68659,68660],{"className":2151,"code":2335,"language":2153,"meta":13,"style":13},[156,68661,68662],{"__ignoreMap":13},[200,68663,68664,68666,68668],{"class":202,"line":203},[200,68665,2160],{"class":1978},[200,68667,2163],{"class":245},[200,68669,2346],{"class":245},[35,68671,2349,68672,239],{},[156,68673,1372],{},[192,68675,68676],{"className":1370,"code":2354,"language":1373,"meta":13,"style":13},[156,68677,68678,68690,68698,68708,68718,68724,68732,68742,68746],{"__ignoreMap":13},[200,68679,68680,68682,68684,68686,68688],{"class":202,"line":203},[200,68681,1380],{"class":206},[200,68683,1383],{"class":206},[200,68685,1386],{"class":223},[200,68687,227],{"class":210},[200,68689,230],{"class":214},[200,68691,68692,68694,68696],{"class":202,"line":14},[200,68693,1395],{"class":235},[200,68695,239],{"class":214},[200,68697,1400],{"class":210},[200,68699,68700,68702,68704,68706],{"class":202,"line":255},[200,68701,1405],{"class":214},[200,68703,2383],{"class":245},[200,68705,249],{"class":214},[200,68707,252],{"class":214},[200,68709,68710,68712,68714,68716],{"class":202,"line":272},[200,68711,1405],{"class":214},[200,68713,2394],{"class":245},[200,68715,249],{"class":214},[200,68717,252],{"class":214},[200,68719,68720,68722],{"class":202,"line":283},[200,68721,1417],{"class":210},[200,68723,252],{"class":214},[200,68725,68726,68728,68730],{"class":202,"line":300},[200,68727,2409],{"class":235},[200,68729,239],{"class":214},[200,68731,280],{"class":214},[200,68733,68734,68736,68738,68740],{"class":202,"line":310},[200,68735,2418],{"class":235},[200,68737,239],{"class":214},[200,68739,2424],{"class":2423},[200,68741,252],{"class":214},[200,68743,68744],{"class":202,"line":336},[200,68745,368],{"class":214},[200,68747,68748,68750],{"class":202,"line":342},[200,68749,413],{"class":214},[200,68751,416],{"class":210},[35,68753,2439],{},[35,68755,2442],{},[51,68757,2446],{"id":2445},[35,68759,2449,68760,2455],{},[39,68761,2454],{"href":2452,"rel":68762},[77],[35,68764,2458,68765,2462],{},[156,68766,2461],{},[35,68768,2465],{},[35,68770,2468],{},[1913,68772,2472],{"id":2471},[35,68774,2475],{},[35,68776,2478],{},[35,68778,2481,68779,2487,68782],{},[39,68780,2486],{"href":2484,"rel":68781},[77],[1582,68783,215],{"repo":2490},[35,68785,2493,68786,2497],{},[156,68787,2496],{},[35,68789,2500],{},[192,68791,68792],{"className":2151,"code":2503,"language":2153,"meta":13,"style":13},[156,68793,68794],{"__ignoreMap":13},[200,68795,68796,68798,68800],{"class":202,"line":203},[200,68797,2160],{"class":1978},[200,68799,2163],{"class":245},[200,68801,2514],{"class":245},[35,68803,2517,68804,239],{},[156,68805,2520],{},[192,68807,68808],{"className":1370,"code":2523,"language":1373,"meta":13,"style":13},[156,68809,68810,68814,68832,68850,68854,68866,68874,68882,68890,68904,68912,68918,68922],{"__ignoreMap":13},[200,68811,68812],{"class":202,"line":203},[200,68813,2530],{"class":401},[200,68815,68816,68818,68820,68822,68824,68826,68828,68830],{"class":202,"line":14},[200,68817,973],{"class":206},[200,68819,318],{"class":214},[200,68821,1488],{"class":210},[200,68823,981],{"class":214},[200,68825,984],{"class":206},[200,68827,242],{"class":214},[200,68829,2547],{"class":245},[200,68831,992],{"class":214},[200,68833,68834,68836,68838,68840,68842,68844,68846,68848],{"class":202,"line":255},[200,68835,973],{"class":206},[200,68837,318],{"class":214},[200,68839,2558],{"class":210},[200,68841,981],{"class":214},[200,68843,984],{"class":206},[200,68845,242],{"class":214},[200,68847,2567],{"class":245},[200,68849,992],{"class":214},[200,68851,68852],{"class":202,"line":272},[200,68853,529],{"emptyLinePlaceholder":20},[200,68855,68856,68858,68860,68862,68864],{"class":202,"line":283},[200,68857,1380],{"class":206},[200,68859,1383],{"class":206},[200,68861,1488],{"class":223},[200,68863,227],{"class":210},[200,68865,230],{"class":214},[200,68867,68868,68870,68872],{"class":202,"line":300},[200,68869,2590],{"class":235},[200,68871,239],{"class":214},[200,68873,280],{"class":214},[200,68875,68876,68878,68880],{"class":202,"line":310},[200,68877,2599],{"class":235},[200,68879,239],{"class":214},[200,68881,1400],{"class":210},[200,68883,68884,68886,68888],{"class":202,"line":336},[200,68885,2608],{"class":223},[200,68887,227],{"class":210},[200,68889,230],{"class":214},[200,68891,68892,68894,68896,68898,68900,68902],{"class":202,"line":342},[200,68893,2617],{"class":235},[200,68895,239],{"class":214},[200,68897,242],{"class":214},[200,68899,2260],{"class":245},[200,68901,249],{"class":214},[200,68903,252],{"class":214},[200,68905,68906,68908,68910],{"class":202,"line":365},[200,68907,2632],{"class":214},[200,68909,2089],{"class":210},[200,68911,252],{"class":214},[200,68913,68914,68916],{"class":202,"line":371},[200,68915,2641],{"class":210},[200,68917,252],{"class":214},[200,68919,68920],{"class":202,"line":398},[200,68921,368],{"class":214},[200,68923,68924,68926],{"class":202,"line":405},[200,68925,413],{"class":214},[200,68927,416],{"class":210},[35,68929,2656,68930,2660],{},[39,68931,2329],{"href":2327,"rel":68932},[77],[35,68934,2332],{},[192,68936,68937],{"className":2151,"code":2335,"language":2153,"meta":13,"style":13},[156,68938,68939],{"__ignoreMap":13},[200,68940,68941,68943,68945],{"class":202,"line":203},[200,68942,2160],{"class":1978},[200,68944,2163],{"class":245},[200,68946,2346],{"class":245},[35,68948,2677,68949,239],{},[156,68950,2520],{},[192,68952,68953],{"className":1370,"code":2682,"language":1373,"meta":13,"style":13},[156,68954,68955,68973,68991,68995,69007,69031],{"__ignoreMap":13},[200,68956,68957,68959,68961,68963,68965,68967,68969,68971],{"class":202,"line":203},[200,68958,973],{"class":206},[200,68960,318],{"class":214},[200,68962,2693],{"class":210},[200,68964,981],{"class":214},[200,68966,984],{"class":206},[200,68968,242],{"class":214},[200,68970,2702],{"class":245},[200,68972,992],{"class":214},[200,68974,68975,68977,68979,68981,68983,68985,68987,68989],{"class":202,"line":14},[200,68976,973],{"class":206},[200,68978,318],{"class":214},[200,68980,1488],{"class":210},[200,68982,981],{"class":214},[200,68984,984],{"class":206},[200,68986,242],{"class":214},[200,68988,2547],{"class":245},[200,68990,992],{"class":214},[200,68992,68993],{"class":202,"line":255},[200,68994,529],{"emptyLinePlaceholder":20},[200,68996,68997,68999,69001,69003,69005],{"class":202,"line":272},[200,68998,1380],{"class":206},[200,69000,1383],{"class":206},[200,69002,1488],{"class":223},[200,69004,227],{"class":210},[200,69006,230],{"class":214},[200,69008,69009,69011,69013,69015,69017,69019,69021,69023,69025,69027,69029],{"class":202,"line":283},[200,69010,2743],{"class":235},[200,69012,239],{"class":214},[200,69014,2693],{"class":223},[200,69016,227],{"class":210},[200,69018,2752],{"class":214},[200,69020,2755],{"class":235},[200,69022,239],{"class":214},[200,69024,2424],{"class":2423},[200,69026,981],{"class":214},[200,69028,2089],{"class":210},[200,69030,252],{"class":214},[200,69032,69033,69035],{"class":202,"line":300},[200,69034,413],{"class":214},[200,69036,416],{"class":210},[130,69038,132,69039,132,69043],{},[134,69040,136,69041,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,69042],{"src":2778,"type":141},[143,69044,2781],{},[35,69046,2784],{},[35,69048,2787,69049,2140,69052,215],{},[39,69050,2792],{"href":2790,"rel":69051},[77],[39,69053,2145],{"href":2795,"rel":69054},[77],[51,69056,2800],{"id":2799},[35,69058,2803,69059,2809],{},[39,69060,2808],{"href":2806,"rel":69061},[77],[35,69063,2812],{},[68,69065,69066,69068,69070],{},[71,69067,2817],{},[71,69069,2308],{},[71,69071,2822],{},[35,69073,2825,69074,1638,69077,106,69080],{},[39,69075,2830],{"href":2828,"rel":69076},[77],[39,69078,2835],{"href":2833,"rel":69079},[77],[1217,69081,2838],{},[35,69083,2841,69084,2847],{},[39,69085,2846],{"href":2844,"rel":69086},[77],[35,69088,2850],{},[35,69090,2853,69091,2857,69093,2861,69095,2865,69097,2869,69099,215],{},[156,69092,2856],{},[156,69094,2860],{},[156,69096,2864],{},[156,69098,2868],{},[156,69100,2872],{},[35,69102,2875,69103,2879,69105,2883,69107,2887],{},[156,69104,2878],{},[156,69106,2882],{},[156,69108,2886],{},[35,69110,2890],{},[35,69112,2893],{},[2895,69114,69115,69117],{"title":2897,"href":2898},[35,69116,2901],{},[2895,69118,69119,69127,69132,69134,69136,69143,69151,69153,69155,69157,69161,69163],{"title":2904,"href":2905},[35,69120,2908,69121,1638,69124,2919],{},[39,69122,2913],{"href":2911,"rel":69123},[77],[39,69125,2918],{"href":2916,"rel":69126},[77],[35,69128,2922,69129,2927],{},[39,69130,2926],{"href":2327,"rel":69131},[77],[35,69133,2930],{},[35,69135,2933],{},[35,69137,2936,69138,2941,69141,2945],{},[39,69139,2940],{"href":2898,"rel":69140},[77],[156,69142,2944],{},[35,69144,2948,69145,2952,69148,215],{},[39,69146,2792],{"href":2790,"rel":69147},[77],[39,69149,2139],{"href":2137,"rel":69150},[77],[35,69152,2958],{},[35,69154,2961],{},[51,69156,2965],{"id":2964},[35,69158,2968,69159,215],{},[39,69160,2972],{"href":2971},[35,69162,2975],{},[35,69164,2978],{},[1171,69166,2981],{},{"title":13,"searchDepth":14,"depth":14,"links":69168},[69169,69170,69173,69174,69177],{"id":1870,"depth":14,"text":1871},{"id":1898,"depth":14,"text":1899,"children":69171},[69172],{"id":1915,"depth":255,"text":1916},{"id":2296,"depth":14,"text":2297},{"id":2445,"depth":14,"text":2446,"children":69175},[69176],{"id":2471,"depth":255,"text":2472},{"id":2799,"depth":14,"text":2800},{},{"title":1853,"description":2995},{"loc":2997},{"id":3004,"title":3005,"body":69182,"contentId":5619,"date":2994,"description":5620,"extension":18,"meta":71442,"navigation":20,"path":5622,"rawbody":5623,"robots":23,"schemaOrg":23,"seo":71443,"sitemap":71444,"stem":5626,"__hash__":5627},{"type":7,"value":69183,"toc":71436},[69184,69186,69196,69200,69202,69204,69206,69208,69220,69222,69224,69232,69236,69242,69444,69454,69456,69464,69468,69475,69731,69741,69743,69751,69755,69757,69759,69763,69770,69772,70056,70064,70066,70230,70240,70248,70250,70252,70264,70266,70268,70272,70274,70624,71434],[35,69185,3010],{},[1576,69187,69188,69190,69192,69194],{},[71,69189,3015],{},[71,69191,3018],{},[71,69193,3021],{},[71,69195,3024],{},[35,69197,3027,69198],{},[3029,69199,3032],{"id":3031},[35,69201,3035],{},[51,69203,3039],{"id":3038},[35,69205,3042],{},[35,69207,3045],{},[68,69209,69210,69212,69214,69216,69218],{},[71,69211,3050],{},[71,69213,3053],{},[71,69215,3056],{},[71,69217,3059],{},[71,69219,3062],{},[35,69221,3065],{},[51,69223,3069],{"id":3068},[35,69225,3072,69226,3076,69228,3080,69230,3084],{},[156,69227,3075],{},[156,69229,3079],{},[156,69231,3083],{},[939,69233,69234],{},[35,69235,3089],{},[35,69237,3092,69238,3095,69240,3098],{},[156,69239,3075],{},[156,69241,3075],{},[192,69243,69244],{"className":194,"code":3101,"language":196,"meta":13,"style":13},[156,69245,69246,69256,69266,69270,69284,69292,69336,69340,69344,69358,69372,69376,69408,69412,69436,69440],{"__ignoreMap":13},[200,69247,69248,69250,69252,69254],{"class":202,"line":203},[200,69249,3108],{"class":210},[200,69251,215],{"class":214},[200,69253,224],{"class":223},[200,69255,3115],{"class":210},[200,69257,69258,69260,69262,69264],{"class":202,"line":14},[200,69259,3120],{"class":214},[200,69261,3075],{"class":245},[200,69263,249],{"class":214},[200,69265,252],{"class":214},[200,69267,69268],{"class":202,"line":255},[200,69269,3131],{"class":214},[200,69271,69272,69274,69276,69278,69280,69282],{"class":202,"line":272},[200,69273,3136],{"class":235},[200,69275,239],{"class":214},[200,69277,242],{"class":214},[200,69279,574],{"class":245},[200,69281,249],{"class":214},[200,69283,252],{"class":214},[200,69285,69286,69288,69290],{"class":202,"line":283},[200,69287,3151],{"class":235},[200,69289,239],{"class":214},[200,69291,280],{"class":214},[200,69293,69294,69296,69298,69300,69302,69304,69306,69308,69310,69312,69314,69316,69318,69320,69322,69324,69326,69328,69330,69332,69334],{"class":202,"line":300},[200,69295,3160],{"class":235},[200,69297,239],{"class":214},[200,69299,3165],{"class":210},[200,69301,215],{"class":214},[200,69303,328],{"class":223},[200,69305,1532],{"class":210},[200,69307,215],{"class":214},[200,69309,3176],{"class":223},[200,69311,1532],{"class":210},[200,69313,215],{"class":214},[200,69315,3183],{"class":223},[200,69317,227],{"class":210},[200,69319,3188],{"class":2045},[200,69321,2089],{"class":210},[200,69323,215],{"class":214},[200,69325,3195],{"class":223},[200,69327,227],{"class":210},[200,69329,249],{"class":214},[200,69331,3202],{"class":245},[200,69333,249],{"class":214},[200,69335,416],{"class":210},[200,69337,69338],{"class":202,"line":310},[200,69339,339],{"class":214},[200,69341,69342],{"class":202,"line":336},[200,69343,368],{"class":214},[200,69345,69346,69348,69350,69352,69354,69356],{"class":202,"line":342},[200,69347,3219],{"class":379},[200,69349,383],{"class":214},[200,69351,3224],{"class":386},[200,69353,390],{"class":214},[200,69355,393],{"class":379},[200,69357,280],{"class":214},[200,69359,69360,69362,69364,69366,69368,69370],{"class":202,"line":365},[200,69361,1999],{"class":379},[200,69363,3237],{"class":210},[200,69365,510],{"class":214},[200,69367,452],{"class":206},[200,69369,3244],{"class":223},[200,69371,466],{"class":235},[200,69373,69374],{"class":202,"line":371},[200,69375,529],{"emptyLinePlaceholder":20},[200,69377,69378,69380,69382,69384,69386,69388,69390,69392,69394,69396,69398,69400,69402,69404,69406],{"class":202,"line":398},[200,69379,1999],{"class":379},[200,69381,3257],{"class":210},[200,69383,510],{"class":214},[200,69385,3237],{"class":210},[200,69387,215],{"class":214},[200,69389,3266],{"class":223},[200,69391,227],{"class":235},[200,69393,3271],{"class":386},[200,69395,393],{"class":379},[200,69397,3257],{"class":210},[200,69399,215],{"class":214},[200,69401,3280],{"class":210},[200,69403,3283],{"class":214},[200,69405,3224],{"class":210},[200,69407,416],{"class":235},[200,69409,69410],{"class":202,"line":405},[200,69411,529],{"emptyLinePlaceholder":20},[200,69413,69414,69416,69418,69420,69422,69424,69426,69428,69430,69432,69434],{"class":202,"line":410},[200,69415,3296],{"class":206},[200,69417,3299],{"class":223},[200,69419,227],{"class":235},[200,69421,3304],{"class":214},[200,69423,3271],{"class":210},[200,69425,215],{"class":214},[200,69427,2020],{"class":210},[200,69429,413],{"class":214},[200,69431,3315],{"class":245},[200,69433,3318],{"class":214},[200,69435,416],{"class":235},[200,69437,69438],{"class":202,"line":861},[200,69439,368],{"class":214},[200,69441,69442],{"class":202,"line":890},[200,69443,416],{"class":210},[939,69445,69446],{},[35,69447,69448,3335,69450,3338,69452],{},[200,69449,945],{},[156,69451,3075],{},[1582,69453,215],{"repo":3341},[35,69455,3344],{},[130,69457,132,69458,132,69462],{},[134,69459,136,69460,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,69461],{"src":3351,"type":141},[143,69463,3354],{},[939,69465,69466],{},[35,69467,3359],{},[35,69469,3362,69470,3365,69472,215],{},[156,69471,3079],{},[39,69473,3370],{"href":3368,"rel":69474},[77],[192,69476,69477],{"className":194,"code":3373,"language":196,"meta":13,"style":13},[156,69478,69479,69489,69499,69503,69517,69525,69569,69573,69577,69591,69609,69613,69633,69647,69661,69675,69689,69703,69717,69723,69727],{"__ignoreMap":13},[200,69480,69481,69483,69485,69487],{"class":202,"line":203},[200,69482,3108],{"class":210},[200,69484,215],{"class":214},[200,69486,224],{"class":223},[200,69488,3115],{"class":210},[200,69490,69491,69493,69495,69497],{"class":202,"line":14},[200,69492,3120],{"class":214},[200,69494,3079],{"class":245},[200,69496,249],{"class":214},[200,69498,252],{"class":214},[200,69500,69501],{"class":202,"line":255},[200,69502,3131],{"class":214},[200,69504,69505,69507,69509,69511,69513,69515],{"class":202,"line":272},[200,69506,3136],{"class":235},[200,69508,239],{"class":214},[200,69510,242],{"class":214},[200,69512,574],{"class":245},[200,69514,249],{"class":214},[200,69516,252],{"class":214},[200,69518,69519,69521,69523],{"class":202,"line":283},[200,69520,3151],{"class":235},[200,69522,239],{"class":214},[200,69524,280],{"class":214},[200,69526,69527,69529,69531,69533,69535,69537,69539,69541,69543,69545,69547,69549,69551,69553,69555,69557,69559,69561,69563,69565,69567],{"class":202,"line":300},[200,69528,3426],{"class":235},[200,69530,239],{"class":214},[200,69532,3165],{"class":210},[200,69534,215],{"class":214},[200,69536,328],{"class":223},[200,69538,1532],{"class":210},[200,69540,215],{"class":214},[200,69542,3176],{"class":223},[200,69544,1532],{"class":210},[200,69546,215],{"class":214},[200,69548,3183],{"class":223},[200,69550,227],{"class":210},[200,69552,3188],{"class":2045},[200,69554,2089],{"class":210},[200,69556,215],{"class":214},[200,69558,3195],{"class":223},[200,69560,227],{"class":210},[200,69562,249],{"class":214},[200,69564,3463],{"class":245},[200,69566,249],{"class":214},[200,69568,416],{"class":210},[200,69570,69571],{"class":202,"line":310},[200,69572,339],{"class":214},[200,69574,69575],{"class":202,"line":336},[200,69576,368],{"class":214},[200,69578,69579,69581,69583,69585,69587,69589],{"class":202,"line":342},[200,69580,3219],{"class":379},[200,69582,383],{"class":214},[200,69584,3484],{"class":386},[200,69586,390],{"class":214},[200,69588,393],{"class":379},[200,69590,280],{"class":214},[200,69592,69593,69595,69597,69599,69601,69603,69605,69607],{"class":202,"line":365},[200,69594,1999],{"class":379},[200,69596,3497],{"class":210},[200,69598,510],{"class":214},[200,69600,452],{"class":206},[200,69602,3504],{"class":223},[200,69604,227],{"class":235},[200,69606,3509],{"class":210},[200,69608,416],{"class":235},[200,69610,69611],{"class":202,"line":371},[200,69612,529],{"emptyLinePlaceholder":20},[200,69614,69615,69617,69619,69621,69623,69625,69627,69629,69631],{"class":202,"line":398},[200,69616,3296],{"class":206},[200,69618,3497],{"class":210},[200,69620,215],{"class":214},[200,69622,3526],{"class":223},[200,69624,227],{"class":235},[200,69626,3531],{"class":386},[200,69628,393],{"class":379},[200,69630,1095],{"class":235},[200,69632,230],{"class":214},[200,69634,69635,69637,69639,69641,69643,69645],{"class":202,"line":405},[200,69636,3160],{"class":235},[200,69638,239],{"class":214},[200,69640,3546],{"class":210},[200,69642,215],{"class":214},[200,69644,3280],{"class":210},[200,69646,252],{"class":214},[200,69648,69649,69651,69653,69655,69657,69659],{"class":202,"line":410},[200,69650,3557],{"class":235},[200,69652,239],{"class":214},[200,69654,3546],{"class":210},[200,69656,215],{"class":214},[200,69658,3566],{"class":210},[200,69660,252],{"class":214},[200,69662,69663,69665,69667,69669,69671,69673],{"class":202,"line":861},[200,69664,3573],{"class":235},[200,69666,239],{"class":214},[200,69668,3546],{"class":210},[200,69670,215],{"class":214},[200,69672,3582],{"class":210},[200,69674,252],{"class":214},[200,69676,69677,69679,69681,69683,69685,69687],{"class":202,"line":890},[200,69678,3589],{"class":235},[200,69680,239],{"class":214},[200,69682,3546],{"class":210},[200,69684,215],{"class":214},[200,69686,3598],{"class":210},[200,69688,252],{"class":214},[200,69690,69691,69693,69695,69697,69699,69701],{"class":202,"line":900},[200,69692,3605],{"class":235},[200,69694,239],{"class":214},[200,69696,3546],{"class":210},[200,69698,215],{"class":214},[200,69700,2020],{"class":210},[200,69702,252],{"class":214},[200,69704,69705,69707,69709,69711,69713,69715],{"class":202,"line":905},[200,69706,3620],{"class":235},[200,69708,239],{"class":214},[200,69710,3546],{"class":210},[200,69712,215],{"class":214},[200,69714,3629],{"class":210},[200,69716,252],{"class":214},[200,69718,69719,69721],{"class":202,"line":931},[200,69720,2096],{"class":214},[200,69722,585],{"class":235},[200,69724,69725],{"class":202,"line":3640},[200,69726,368],{"class":214},[200,69728,69729],{"class":202,"line":3645},[200,69730,416],{"class":210},[939,69732,69733],{},[35,69734,69735,3335,69737,3338,69739],{},[200,69736,945],{},[156,69738,3079],{},[1582,69740,215],{"repo":3341},[35,69742,3660],{},[130,69744,132,69745,132,69749],{},[134,69746,136,69747,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,69748],{"src":3667,"type":141},[143,69750,3021],{},[939,69752,69753],{},[35,69754,3674],{},[35,69756,3677],{},[35,69758,3680],{},[35,69760,3683,69761,3686],{},[156,69762,3083],{},[35,69764,3689,69765,3692,69767,3698],{},[156,69766,156],{},[39,69768,3697],{"href":3695,"rel":69769},[77],[35,69771,3701],{},[192,69773,69774],{"className":194,"code":3704,"language":196,"meta":13,"style":13},[156,69775,69776,69786,69800,69804,69814,69842,69854,69878,69886,69900,69904,69924,69960,69964,69980,70016,70022,70028,70034,70040,70046,70052],{"__ignoreMap":13},[200,69777,69778,69780,69782,69784],{"class":202,"line":203},[200,69779,3711],{"class":379},[200,69781,3714],{"class":214},[200,69783,393],{"class":379},[200,69785,280],{"class":214},[200,69787,69788,69790,69792,69794,69796,69798],{"class":202,"line":14},[200,69789,3723],{"class":379},[200,69791,3484],{"class":210},[200,69793,510],{"class":214},[200,69795,242],{"class":214},[200,69797,3732],{"class":245},[200,69799,992],{"class":214},[200,69801,69802],{"class":202,"line":255},[200,69803,529],{"emptyLinePlaceholder":20},[200,69805,69806,69808,69810,69812],{"class":202,"line":272},[200,69807,3743],{"class":206},[200,69809,3237],{"class":210},[200,69811,215],{"class":214},[200,69813,3750],{"class":210},[200,69815,69816,69818,69820,69822,69824,69826,69828,69830,69832,69834,69836,69838,69840],{"class":202,"line":283},[200,69817,3755],{"class":214},[200,69819,3758],{"class":223},[200,69821,227],{"class":235},[200,69823,3271],{"class":386},[200,69825,393],{"class":379},[200,69827,3257],{"class":210},[200,69829,215],{"class":214},[200,69831,3771],{"class":210},[200,69833,3283],{"class":214},[200,69835,242],{"class":214},[200,69837,3778],{"class":245},[200,69839,249],{"class":214},[200,69841,416],{"class":235},[200,69843,69844,69846,69848,69850,69852],{"class":202,"line":300},[200,69845,3755],{"class":214},[200,69847,3758],{"class":223},[200,69849,227],{"class":235},[200,69851,3271],{"class":386},[200,69853,3795],{"class":379},[200,69855,69856,69858,69860,69862,69864,69866,69868,69870,69872,69874,69876],{"class":202,"line":310},[200,69857,3800],{"class":214},[200,69859,3271],{"class":210},[200,69861,215],{"class":214},[200,69863,3566],{"class":210},[200,69865,413],{"class":214},[200,69867,3811],{"class":214},[200,69869,3271],{"class":210},[200,69871,215],{"class":214},[200,69873,3818],{"class":210},[200,69875,3821],{"class":214},[200,69877,3824],{"class":214},[200,69879,69880,69882,69884],{"class":202,"line":336},[200,69881,3829],{"class":214},[200,69883,3832],{"class":223},[200,69885,466],{"class":235},[200,69887,69888,69890,69892,69894,69896,69898],{"class":202,"line":342},[200,69889,3829],{"class":214},[200,69891,3841],{"class":223},[200,69893,227],{"class":235},[200,69895,3509],{"class":210},[200,69897,2089],{"class":235},[200,69899,252],{"class":214},[200,69901,69902],{"class":202,"line":365},[200,69903,3854],{"class":235},[200,69905,69906,69908,69910,69912,69914,69916,69918,69920,69922],{"class":202,"line":371},[200,69907,3755],{"class":214},[200,69909,3861],{"class":223},[200,69911,227],{"class":235},[200,69913,227],{"class":214},[200,69915,39],{"class":386},[200,69917,550],{"class":214},[200,69919,3872],{"class":386},[200,69921,2089],{"class":214},[200,69923,3795],{"class":379},[200,69925,69926,69928,69930,69932,69934,69936,69938,69940,69942,69944,69946,69948,69950,69952,69954,69956,69958],{"class":202,"line":398},[200,69927,3881],{"class":235},[200,69929,3884],{"class":210},[200,69931,215],{"class":214},[200,69933,3629],{"class":210},[200,69935,3891],{"class":214},[200,69937,3894],{"class":214},[200,69939,2089],{"class":235},[200,69941,215],{"class":214},[200,69943,3901],{"class":223},[200,69945,227],{"class":235},[200,69947,39],{"class":210},[200,69949,215],{"class":214},[200,69951,3629],{"class":210},[200,69953,3891],{"class":214},[200,69955,3894],{"class":214},[200,69957,2089],{"class":235},[200,69959,252],{"class":214},[200,69961,69962],{"class":202,"line":405},[200,69963,3854],{"class":235},[200,69965,69966,69968,69970,69972,69974,69976,69978],{"class":202,"line":410},[200,69967,3755],{"class":214},[200,69969,3928],{"class":223},[200,69971,227],{"class":235},[200,69973,3933],{"class":2045},[200,69975,550],{"class":214},[200,69977,3938],{"class":2045},[200,69979,416],{"class":235},[200,69981,69982,69984,69986,69988,69990,69992,69994,69996,69998,70000,70002,70004,70006,70008,70010,70012,70014],{"class":202,"line":861},[200,69983,3755],{"class":214},[200,69985,3526],{"class":223},[200,69987,227],{"class":235},[200,69989,3951],{"class":214},[200,69991,3224],{"class":386},[200,69993,550],{"class":214},[200,69995,3958],{"class":386},[200,69997,550],{"class":214},[200,69999,3963],{"class":386},[200,70001,550],{"class":214},[200,70003,3968],{"class":386},[200,70005,550],{"class":214},[200,70007,3973],{"class":386},[200,70009,390],{"class":214},[200,70011,393],{"class":379},[200,70013,1095],{"class":235},[200,70015,230],{"class":214},[200,70017,70018,70020],{"class":202,"line":890},[200,70019,3160],{"class":210},[200,70021,252],{"class":214},[200,70023,70024,70026],{"class":202,"line":900},[200,70025,3557],{"class":210},[200,70027,252],{"class":214},[200,70029,70030,70032],{"class":202,"line":905},[200,70031,3573],{"class":210},[200,70033,252],{"class":214},[200,70035,70036,70038],{"class":202,"line":931},[200,70037,3620],{"class":210},[200,70039,252],{"class":214},[200,70041,70042,70044],{"class":202,"line":3640},[200,70043,3605],{"class":210},[200,70045,252],{"class":214},[200,70047,70048,70050],{"class":202,"line":3645},[200,70049,2096],{"class":214},[200,70051,585],{"class":235},[200,70053,70054],{"class":202,"line":4020},[200,70055,1130],{"class":214},[939,70057,70058],{},[35,70059,70060,4029,70062,215],{},[200,70061,945],{},[39,70063,4033],{"href":4032},[35,70065,4036],{},[192,70067,70068],{"className":194,"code":4039,"language":196,"meta":13,"style":13},[156,70069,70070,70080,70090,70094,70108,70116,70172,70176,70180,70194,70212,70216,70222,70226],{"__ignoreMap":13},[200,70071,70072,70074,70076,70078],{"class":202,"line":203},[200,70073,3108],{"class":210},[200,70075,215],{"class":214},[200,70077,224],{"class":223},[200,70079,3115],{"class":210},[200,70081,70082,70084,70086,70088],{"class":202,"line":14},[200,70083,3120],{"class":214},[200,70085,3083],{"class":245},[200,70087,249],{"class":214},[200,70089,252],{"class":214},[200,70091,70092],{"class":202,"line":255},[200,70093,3131],{"class":214},[200,70095,70096,70098,70100,70102,70104,70106],{"class":202,"line":272},[200,70097,3136],{"class":235},[200,70099,239],{"class":214},[200,70101,242],{"class":214},[200,70103,574],{"class":245},[200,70105,249],{"class":214},[200,70107,252],{"class":214},[200,70109,70110,70112,70114],{"class":202,"line":283},[200,70111,3151],{"class":235},[200,70113,239],{"class":214},[200,70115,280],{"class":214},[200,70117,70118,70120,70122,70124,70126,70128,70130,70132,70134,70136,70138,70140,70142,70144,70146,70148,70150,70152,70154,70156,70158,70160,70162,70164,70166,70168,70170],{"class":202,"line":300},[200,70119,4092],{"class":235},[200,70121,239],{"class":214},[200,70123,3165],{"class":210},[200,70125,215],{"class":214},[200,70127,328],{"class":223},[200,70129,1532],{"class":210},[200,70131,215],{"class":214},[200,70133,3176],{"class":223},[200,70135,1532],{"class":210},[200,70137,215],{"class":214},[200,70139,3183],{"class":223},[200,70141,227],{"class":210},[200,70143,3188],{"class":2045},[200,70145,2089],{"class":210},[200,70147,215],{"class":214},[200,70149,4123],{"class":223},[200,70151,227],{"class":210},[200,70153,4128],{"class":2045},[200,70155,2089],{"class":210},[200,70157,215],{"class":214},[200,70159,3195],{"class":223},[200,70161,227],{"class":210},[200,70163,249],{"class":214},[200,70165,4141],{"class":245},[200,70167,249],{"class":214},[200,70169,2089],{"class":210},[200,70171,252],{"class":214},[200,70173,70174],{"class":202,"line":310},[200,70175,339],{"class":214},[200,70177,70178],{"class":202,"line":336},[200,70179,368],{"class":214},[200,70181,70182,70184,70186,70188,70190,70192],{"class":202,"line":342},[200,70183,3219],{"class":379},[200,70185,383],{"class":214},[200,70187,4164],{"class":386},[200,70189,390],{"class":214},[200,70191,393],{"class":379},[200,70193,280],{"class":214},[200,70195,70196,70198,70200,70202,70204,70206,70208,70210],{"class":202,"line":365},[200,70197,1999],{"class":379},[200,70199,3546],{"class":210},[200,70201,510],{"class":214},[200,70203,452],{"class":206},[200,70205,4183],{"class":223},[200,70207,227],{"class":235},[200,70209,156],{"class":210},[200,70211,416],{"class":235},[200,70213,70214],{"class":202,"line":371},[200,70215,529],{"emptyLinePlaceholder":20},[200,70217,70218,70220],{"class":202,"line":398},[200,70219,3296],{"class":206},[200,70221,4200],{"class":210},[200,70223,70224],{"class":202,"line":405},[200,70225,368],{"class":214},[200,70227,70228],{"class":202,"line":410},[200,70229,416],{"class":210},[939,70231,70232],{},[35,70233,70234,3335,70236,3338,70238],{},[200,70235,945],{},[156,70237,3083],{},[1582,70239,215],{"repo":3341},[130,70241,132,70242,132,70246],{},[134,70243,136,70244,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,70245],{"src":4225,"type":141},[143,70247,3024],{},[35,70249,4230],{},[35,70251,4233],{},[1576,70253,70254,70256,70258,70260,70262],{},[71,70255,4238],{},[71,70257,4241],{},[71,70259,4244],{},[71,70261,4247],{},[71,70263,4250],{},[35,70265,4253],{},[51,70267,4257],{"id":4256},[35,70269,4260,70270],{},[3029,70271,4264],{"id":4263},[35,70273,4267],{},[1576,70275,70276,70288,70300,70312,70324,70336,70348,70460,70564,70612],{},[71,70277,70278,70280],{},[156,70279,4274],{},[192,70281,70282],{"className":4277,"code":4278,"language":4279,"meta":13,"style":13},[156,70283,70284],{"__ignoreMap":13},[200,70285,70286],{"class":202,"line":203},[200,70287,4278],{},[71,70289,70290,70292],{},[156,70291,4290],{},[192,70293,70294],{"className":4277,"code":4293,"language":4279,"meta":13,"style":13},[156,70295,70296],{"__ignoreMap":13},[200,70297,70298],{"class":202,"line":203},[200,70299,4293],{},[71,70301,70302,70304],{},[156,70303,3083],{},[192,70305,70306],{"className":4277,"code":4306,"language":4279,"meta":13,"style":13},[156,70307,70308],{"__ignoreMap":13},[200,70309,70310],{"class":202,"line":203},[200,70311,4306],{},[71,70313,70314,70316],{},[156,70315,4317],{},[192,70317,70318],{"className":4277,"code":4320,"language":4279,"meta":13,"style":13},[156,70319,70320],{"__ignoreMap":13},[200,70321,70322],{"class":202,"line":203},[200,70323,4320],{},[71,70325,70326,70328],{},[156,70327,4331],{},[192,70329,70330],{"className":4277,"code":4334,"language":4279,"meta":13,"style":13},[156,70331,70332],{"__ignoreMap":13},[200,70333,70334],{"class":202,"line":203},[200,70335,4334],{},[71,70337,70338,70340],{},[156,70339,4345],{},[192,70341,70342],{"className":4277,"code":4348,"language":4279,"meta":13,"style":13},[156,70343,70344],{"__ignoreMap":13},[200,70345,70346],{"class":202,"line":203},[200,70347,4348],{},[71,70349,70350,70352],{},[156,70351,4359],{},[192,70353,70354],{"className":4277,"code":4362,"language":4279,"meta":13,"style":13},[156,70355,70356,70360,70364,70368,70372,70376,70380,70384,70388,70392,70396,70400,70404,70408,70412,70416,70420,70424,70428,70432,70436,70440,70444,70448,70452,70456],{"__ignoreMap":13},[200,70357,70358],{"class":202,"line":203},[200,70359,4369],{},[200,70361,70362],{"class":202,"line":14},[200,70363,4374],{},[200,70365,70366],{"class":202,"line":255},[200,70367,4379],{},[200,70369,70370],{"class":202,"line":272},[200,70371,4384],{},[200,70373,70374],{"class":202,"line":283},[200,70375,4389],{},[200,70377,70378],{"class":202,"line":300},[200,70379,4394],{},[200,70381,70382],{"class":202,"line":310},[200,70383,4399],{},[200,70385,70386],{"class":202,"line":336},[200,70387,4404],{},[200,70389,70390],{"class":202,"line":342},[200,70391,4409],{},[200,70393,70394],{"class":202,"line":365},[200,70395,529],{"emptyLinePlaceholder":20},[200,70397,70398],{"class":202,"line":371},[200,70399,4418],{},[200,70401,70402],{"class":202,"line":398},[200,70403,4423],{},[200,70405,70406],{"class":202,"line":405},[200,70407,3131],{},[200,70409,70410],{"class":202,"line":410},[200,70411,4432],{},[200,70413,70414],{"class":202,"line":861},[200,70415,4437],{},[200,70417,70418],{"class":202,"line":890},[200,70419,4442],{},[200,70421,70422],{"class":202,"line":900},[200,70423,4447],{},[200,70425,70426],{"class":202,"line":905},[200,70427,4452],{},[200,70429,70430],{"class":202,"line":931},[200,70431,4457],{},[200,70433,70434],{"class":202,"line":3640},[200,70435,4462],{},[200,70437,70438],{"class":202,"line":3645},[200,70439,4467],{},[200,70441,70442],{"class":202,"line":4020},[200,70443,4472],{},[200,70445,70446],{"class":202,"line":4475},[200,70447,4478],{},[200,70449,70450],{"class":202,"line":4481},[200,70451,4484],{},[200,70453,70454],{"class":202,"line":4487},[200,70455,4490],{},[200,70457,70458],{"class":202,"line":4493},[200,70459,4496],{},[71,70461,70462,70464],{},[156,70463,4501],{},[192,70465,70466],{"className":4277,"code":4504,"language":4279,"meta":13,"style":13},[156,70467,70468,70472,70476,70480,70484,70488,70492,70496,70500,70504,70508,70512,70516,70520,70524,70528,70532,70536,70540,70544,70548,70552,70556,70560],{"__ignoreMap":13},[200,70469,70470],{"class":202,"line":203},[200,70471,4511],{},[200,70473,70474],{"class":202,"line":14},[200,70475,4516],{},[200,70477,70478],{"class":202,"line":255},[200,70479,4521],{},[200,70481,70482],{"class":202,"line":272},[200,70483,4526],{},[200,70485,70486],{"class":202,"line":283},[200,70487,4531],{},[200,70489,70490],{"class":202,"line":300},[200,70491,4536],{},[200,70493,70494],{"class":202,"line":310},[200,70495,4541],{},[200,70497,70498],{"class":202,"line":336},[200,70499,4546],{},[200,70501,70502],{"class":202,"line":342},[200,70503,4551],{},[200,70505,70506],{"class":202,"line":365},[200,70507,4556],{},[200,70509,70510],{"class":202,"line":371},[200,70511,529],{"emptyLinePlaceholder":20},[200,70513,70514],{"class":202,"line":398},[200,70515,4418],{},[200,70517,70518],{"class":202,"line":405},[200,70519,4423],{},[200,70521,70522],{"class":202,"line":410},[200,70523,3131],{},[200,70525,70526],{"class":202,"line":861},[200,70527,4577],{},[200,70529,70530],{"class":202,"line":890},[200,70531,4582],{},[200,70533,70534],{"class":202,"line":900},[200,70535,4587],{},[200,70537,70538],{"class":202,"line":905},[200,70539,4592],{},[200,70541,70542],{"class":202,"line":931},[200,70543,4597],{},[200,70545,70546],{"class":202,"line":3640},[200,70547,4602],{},[200,70549,70550],{"class":202,"line":3645},[200,70551,4607],{},[200,70553,70554],{"class":202,"line":4020},[200,70555,4612],{},[200,70557,70558],{"class":202,"line":4475},[200,70559,4490],{},[200,70561,70562],{"class":202,"line":4481},[200,70563,4496],{},[71,70565,70566,70568],{},[156,70567,4625],{},[192,70569,70570],{"className":4277,"code":4628,"language":4279,"meta":13,"style":13},[156,70571,70572,70576,70580,70584,70588,70592,70596,70600,70604,70608],{"__ignoreMap":13},[200,70573,70574],{"class":202,"line":203},[200,70575,4635],{},[200,70577,70578],{"class":202,"line":14},[200,70579,4640],{},[200,70581,70582],{"class":202,"line":255},[200,70583,4645],{},[200,70585,70586],{"class":202,"line":272},[200,70587,4650],{},[200,70589,70590],{"class":202,"line":283},[200,70591,529],{"emptyLinePlaceholder":20},[200,70593,70594],{"class":202,"line":300},[200,70595,4418],{},[200,70597,70598],{"class":202,"line":310},[200,70599,4423],{},[200,70601,70602],{"class":202,"line":336},[200,70603,4667],{},[200,70605,70606],{"class":202,"line":342},[200,70607,4672],{},[200,70609,70610],{"class":202,"line":365},[200,70611,4496],{},[71,70613,70614,70616],{},[156,70615,3075],{},[192,70617,70618],{"className":4277,"code":4683,"language":4279,"meta":13,"style":13},[156,70619,70620],{"__ignoreMap":13},[200,70621,70622],{"class":202,"line":203},[200,70623,4683],{},[1582,70625,70626,70628,70632,70640,70874,70876,71018,71022,71026,71330,71332,71338,71340,71342,71348,71350,71360,71373,71377,71379,71381,71383,71385,71392,71402,71404,71406,71420,71422,71424,71426,71428,71430],{"repo":4692},[35,70627,4695],{},[35,70629,70630,4702],{},[39,70631,4701],{"href":4700},[35,70633,4705,70634,4708,70636,4712,70638,4716],{},[156,70635,3083],{},[156,70637,4711],{},[156,70639,4715],{},[192,70641,70642],{"className":194,"code":4719,"language":196,"meta":13,"style":13},[156,70643,70644,70656,70666,70676,70680,70730,70734,70748,70780,70794,70800,70806,70814,70818,70832,70852,70856,70862,70866,70870],{"__ignoreMap":13},[200,70645,70646,70648,70650,70652,70654],{"class":202,"line":203},[200,70647,4726],{"class":214},[200,70649,3108],{"class":210},[200,70651,215],{"class":214},[200,70653,505],{"class":223},[200,70655,3115],{"class":210},[200,70657,70658,70660,70662,70664],{"class":202,"line":14},[200,70659,3120],{"class":214},[200,70661,3083],{"class":245},[200,70663,249],{"class":214},[200,70665,252],{"class":214},[200,70667,70668,70670,70672,70674],{"class":202,"line":255},[200,70669,3120],{"class":214},[200,70671,574],{"class":245},[200,70673,249],{"class":214},[200,70675,252],{"class":214},[200,70677,70678],{"class":202,"line":272},[200,70679,3131],{"class":214},[200,70681,70682,70684,70686,70688,70690,70692,70694,70696,70698,70700,70702,70704,70706,70708,70710,70712,70714,70716,70718,70720,70722,70724,70726,70728],{"class":202,"line":283},[200,70683,4763],{"class":235},[200,70685,239],{"class":214},[200,70687,3165],{"class":210},[200,70689,215],{"class":214},[200,70691,328],{"class":223},[200,70693,1532],{"class":210},[200,70695,215],{"class":214},[200,70697,3183],{"class":223},[200,70699,227],{"class":210},[200,70701,4782],{"class":2045},[200,70703,2089],{"class":210},[200,70705,215],{"class":214},[200,70707,4123],{"class":223},[200,70709,227],{"class":210},[200,70711,4782],{"class":2045},[200,70713,2089],{"class":210},[200,70715,215],{"class":214},[200,70717,3195],{"class":223},[200,70719,227],{"class":210},[200,70721,249],{"class":214},[200,70723,4805],{"class":245},[200,70725,249],{"class":214},[200,70727,2089],{"class":210},[200,70729,252],{"class":214},[200,70731,70732],{"class":202,"line":300},[200,70733,368],{"class":214},[200,70735,70736,70738,70740,70742,70744,70746],{"class":202,"line":310},[200,70737,3219],{"class":379},[200,70739,383],{"class":214},[200,70741,4824],{"class":386},[200,70743,390],{"class":214},[200,70745,393],{"class":379},[200,70747,280],{"class":214},[200,70749,70750,70752,70754,70756,70758,70760,70762,70764,70766,70768,70770,70772,70774,70776,70778],{"class":202,"line":336},[200,70751,1999],{"class":379},[200,70753,3546],{"class":210},[200,70755,510],{"class":214},[200,70757,452],{"class":206},[200,70759,4843],{"class":223},[200,70761,227],{"class":235},[200,70763,3318],{"class":214},[200,70765,4850],{"class":245},[200,70767,4853],{"class":214},[200,70769,4856],{"class":210},[200,70771,413],{"class":214},[200,70773,4861],{"class":245},[200,70775,3318],{"class":214},[200,70777,550],{"class":214},[200,70779,280],{"class":214},[200,70781,70782,70784,70786,70788,70790,70792],{"class":202,"line":342},[200,70783,4872],{"class":235},[200,70785,239],{"class":214},[200,70787,4877],{"class":210},[200,70789,215],{"class":214},[200,70791,4882],{"class":210},[200,70793,252],{"class":214},[200,70795,70796,70798],{"class":202,"line":365},[200,70797,2096],{"class":214},[200,70799,416],{"class":235},[200,70801,70802,70804],{"class":202,"line":371},[200,70803,3296],{"class":206},[200,70805,280],{"class":214},[200,70807,70808,70810,70812],{"class":202,"line":398},[200,70809,3589],{"class":235},[200,70811,239],{"class":214},[200,70813,1400],{"class":235},[200,70815,70816],{"class":202,"line":405},[200,70817,4909],{"class":214},[200,70819,70820,70822,70824,70826,70828,70830],{"class":202,"line":410},[200,70821,4914],{"class":235},[200,70823,239],{"class":214},[200,70825,242],{"class":214},[200,70827,698],{"class":245},[200,70829,249],{"class":214},[200,70831,252],{"class":214},[200,70833,70834,70836,70838,70840,70842,70844,70846,70848,70850],{"class":202,"line":861},[200,70835,4929],{"class":235},[200,70837,239],{"class":214},[200,70839,553],{"class":210},[200,70841,215],{"class":214},[200,70843,558],{"class":223},[200,70845,227],{"class":235},[200,70847,3531],{"class":210},[200,70849,2089],{"class":235},[200,70851,252],{"class":214},[200,70853,70854],{"class":202,"line":890},[200,70855,4950],{"class":214},[200,70857,70858,70860],{"class":202,"line":900},[200,70859,4955],{"class":235},[200,70861,252],{"class":214},[200,70863,70864],{"class":202,"line":905},[200,70865,4962],{"class":214},[200,70867,70868],{"class":202,"line":931},[200,70869,368],{"class":214},[200,70871,70872],{"class":202,"line":3640},[200,70873,416],{"class":210},[35,70875,4973],{},[192,70877,70878],{"className":194,"code":4976,"language":196,"meta":13,"style":13},[156,70879,70880,70902,70926,70938,70950,70964,70976,70980,70986,71000,71010,71014],{"__ignoreMap":13},[200,70881,70882,70884,70886,70888,70890,70892,70894,70896,70898,70900],{"class":202,"line":203},[200,70883,1380],{"class":206},[200,70885,380],{"class":379},[200,70887,4987],{"class":379},[200,70889,4990],{"class":223},[200,70891,4993],{"class":214},[200,70893,4996],{"class":1978},[200,70895,600],{"class":214},[200,70897,5001],{"class":1978},[200,70899,675],{"class":214},[200,70901,280],{"class":214},[200,70903,70904,70906,70908,70910,70912,70914,70916,70918,70920,70922,70924],{"class":202,"line":14},[200,70905,3723],{"class":379},[200,70907,3237],{"class":210},[200,70909,510],{"class":214},[200,70911,452],{"class":206},[200,70913,5018],{"class":223},[200,70915,227],{"class":235},[200,70917,249],{"class":214},[200,70919,5025],{"class":245},[200,70921,249],{"class":214},[200,70923,550],{"class":214},[200,70925,280],{"class":214},[200,70927,70928,70930,70932,70934,70936],{"class":202,"line":255},[200,70929,5036],{"class":223},[200,70931,239],{"class":214},[200,70933,5041],{"class":386},[200,70935,393],{"class":379},[200,70937,5046],{"class":210},[200,70939,70940,70942,70944,70946,70948],{"class":202,"line":272},[200,70941,5051],{"class":214},[200,70943,3758],{"class":223},[200,70945,227],{"class":235},[200,70947,5058],{"class":210},[200,70949,416],{"class":235},[200,70951,70952,70954,70956,70958,70960,70962],{"class":202,"line":283},[200,70953,5051],{"class":214},[200,70955,3526],{"class":223},[200,70957,227],{"class":235},[200,70959,5071],{"class":210},[200,70961,2089],{"class":235},[200,70963,252],{"class":214},[200,70965,70966,70968,70970,70972,70974],{"class":202,"line":300},[200,70967,5080],{"class":214},[200,70969,2089],{"class":235},[200,70971,215],{"class":214},[200,70973,5087],{"class":223},[200,70975,466],{"class":235},[200,70977,70978],{"class":202,"line":310},[200,70979,529],{"emptyLinePlaceholder":20},[200,70981,70982,70984],{"class":202,"line":336},[200,70983,3743],{"class":206},[200,70985,280],{"class":214},[200,70987,70988,70990,70992,70994,70996,70998],{"class":202,"line":342},[200,70989,5104],{"class":235},[200,70991,239],{"class":214},[200,70993,242],{"class":214},[200,70995,4711],{"class":245},[200,70997,249],{"class":214},[200,70999,252],{"class":214},[200,71001,71002,71004,71006,71008],{"class":202,"line":365},[200,71003,5119],{"class":235},[200,71005,239],{"class":214},[200,71007,3237],{"class":210},[200,71009,252],{"class":214},[200,71011,71012],{"class":202,"line":371},[200,71013,4490],{"class":214},[200,71015,71016],{"class":202,"line":398},[200,71017,1130],{"class":214},[35,71019,5136,71020,5140],{},[156,71021,5139],{},[35,71023,5143,71024,5146],{},[156,71025,3075],{},[192,71027,71028],{"className":194,"code":5149,"language":196,"meta":13,"style":13},[156,71029,71030,71042,71052,71062,71066,71106,71110,71124,71144,71156,71160,71164,71184,71196,71200,71204,71236,71250,71256,71262,71270,71274,71288,71308,71312,71318,71322,71326],{"__ignoreMap":13},[200,71031,71032,71034,71036,71038,71040],{"class":202,"line":203},[200,71033,4726],{"class":214},[200,71035,3108],{"class":210},[200,71037,215],{"class":214},[200,71039,505],{"class":223},[200,71041,3115],{"class":210},[200,71043,71044,71046,71048,71050],{"class":202,"line":14},[200,71045,3120],{"class":214},[200,71047,3075],{"class":245},[200,71049,249],{"class":214},[200,71051,252],{"class":214},[200,71053,71054,71056,71058,71060],{"class":202,"line":255},[200,71055,3120],{"class":214},[200,71057,574],{"class":245},[200,71059,249],{"class":214},[200,71061,252],{"class":214},[200,71063,71064],{"class":202,"line":272},[200,71065,3131],{"class":214},[200,71067,71068,71070,71072,71074,71076,71078,71080,71082,71084,71086,71088,71090,71092,71094,71096,71098,71100,71102,71104],{"class":202,"line":283},[200,71069,5192],{"class":235},[200,71071,239],{"class":214},[200,71073,3165],{"class":210},[200,71075,215],{"class":214},[200,71077,328],{"class":223},[200,71079,1532],{"class":210},[200,71081,215],{"class":214},[200,71083,3183],{"class":223},[200,71085,227],{"class":210},[200,71087,3188],{"class":2045},[200,71089,2089],{"class":210},[200,71091,215],{"class":214},[200,71093,3195],{"class":223},[200,71095,227],{"class":210},[200,71097,249],{"class":214},[200,71099,5223],{"class":245},[200,71101,249],{"class":214},[200,71103,2089],{"class":210},[200,71105,252],{"class":214},[200,71107,71108],{"class":202,"line":300},[200,71109,368],{"class":214},[200,71111,71112,71114,71116,71118,71120,71122],{"class":202,"line":310},[200,71113,3219],{"class":379},[200,71115,383],{"class":214},[200,71117,3973],{"class":386},[200,71119,390],{"class":214},[200,71121,393],{"class":379},[200,71123,280],{"class":214},[200,71125,71126,71128,71130,71132,71134,71136,71138,71140,71142],{"class":202,"line":336},[200,71127,5252],{"class":206},[200,71129,1095],{"class":235},[200,71131,2020],{"class":210},[200,71133,3283],{"class":214},[200,71135,242],{"class":214},[200,71137,2882],{"class":245},[200,71139,249],{"class":214},[200,71141,5267],{"class":235},[200,71143,230],{"class":214},[200,71145,71146,71148,71150,71152,71154],{"class":202,"line":342},[200,71147,3605],{"class":210},[200,71149,510],{"class":214},[200,71151,242],{"class":214},[200,71153,5280],{"class":245},[200,71155,992],{"class":214},[200,71157,71158],{"class":202,"line":365},[200,71159,4962],{"class":214},[200,71161,71162],{"class":202,"line":371},[200,71163,529],{"emptyLinePlaceholder":20},[200,71165,71166,71168,71170,71172,71174,71176,71178,71180,71182],{"class":202,"line":398},[200,71167,5252],{"class":206},[200,71169,1095],{"class":235},[200,71171,2020],{"class":210},[200,71173,3283],{"class":214},[200,71175,242],{"class":214},[200,71177,5305],{"class":245},[200,71179,249],{"class":214},[200,71181,5267],{"class":235},[200,71183,230],{"class":214},[200,71185,71186,71188,71190,71192,71194],{"class":202,"line":405},[200,71187,3605],{"class":210},[200,71189,510],{"class":214},[200,71191,242],{"class":214},[200,71193,5322],{"class":245},[200,71195,992],{"class":214},[200,71197,71198],{"class":202,"line":410},[200,71199,4962],{"class":214},[200,71201,71202],{"class":202,"line":861},[200,71203,529],{"emptyLinePlaceholder":20},[200,71205,71206,71208,71210,71212,71214,71216,71218,71220,71222,71224,71226,71228,71230,71232,71234],{"class":202,"line":890},[200,71207,1999],{"class":379},[200,71209,3546],{"class":210},[200,71211,510],{"class":214},[200,71213,452],{"class":206},[200,71215,4843],{"class":223},[200,71217,227],{"class":235},[200,71219,3318],{"class":214},[200,71221,5351],{"class":245},[200,71223,4853],{"class":214},[200,71225,2020],{"class":210},[200,71227,413],{"class":214},[200,71229,3315],{"class":245},[200,71231,3318],{"class":214},[200,71233,550],{"class":214},[200,71235,280],{"class":214},[200,71237,71238,71240,71242,71244,71246,71248],{"class":202,"line":900},[200,71239,4872],{"class":235},[200,71241,239],{"class":214},[200,71243,4877],{"class":210},[200,71245,215],{"class":214},[200,71247,4882],{"class":210},[200,71249,252],{"class":214},[200,71251,71252,71254],{"class":202,"line":905},[200,71253,2096],{"class":214},[200,71255,416],{"class":235},[200,71257,71258,71260],{"class":202,"line":931},[200,71259,3296],{"class":206},[200,71261,280],{"class":214},[200,71263,71264,71266,71268],{"class":202,"line":3640},[200,71265,3589],{"class":235},[200,71267,239],{"class":214},[200,71269,1400],{"class":235},[200,71271,71272],{"class":202,"line":3645},[200,71273,4909],{"class":214},[200,71275,71276,71278,71280,71282,71284,71286],{"class":202,"line":4020},[200,71277,4914],{"class":235},[200,71279,239],{"class":214},[200,71281,242],{"class":214},[200,71283,698],{"class":245},[200,71285,249],{"class":214},[200,71287,252],{"class":214},[200,71289,71290,71292,71294,71296,71298,71300,71302,71304,71306],{"class":202,"line":4475},[200,71291,4929],{"class":235},[200,71293,239],{"class":214},[200,71295,553],{"class":210},[200,71297,215],{"class":214},[200,71299,558],{"class":223},[200,71301,227],{"class":235},[200,71303,3531],{"class":210},[200,71305,2089],{"class":235},[200,71307,252],{"class":214},[200,71309,71310],{"class":202,"line":4481},[200,71311,4950],{"class":214},[200,71313,71314,71316],{"class":202,"line":4487},[200,71315,4955],{"class":235},[200,71317,252],{"class":214},[200,71319,71320],{"class":202,"line":4493},[200,71321,4962],{"class":214},[200,71323,71324],{"class":202,"line":5454},[200,71325,368],{"class":214},[200,71327,71328],{"class":202,"line":5459},[200,71329,416],{"class":210},[35,71331,5464],{},[68,71333,71334,71336],{},[71,71335,5469],{},[71,71337,5472],{},[35,71339,5475],{},[35,71341,5478],{},[68,71343,71344,71346],{},[71,71345,5483],{},[71,71347,5486],{},[35,71349,5489],{},[68,71351,71352,71356],{},[71,71353,71354,5496],{},[156,71355,3075],{},[71,71357,71358,215],{},[156,71359,5501],{},[35,71361,419,71362,5506,71364,5509,71366,5512,71368,5516,71371,5519],{},[156,71363,3075],{},[156,71365,3075],{},[156,71367,5501],{},[39,71369,3370],{"href":3368,"rel":71370},[77],[156,71372,3075],{},[35,71374,5522,71375,5525],{},[156,71376,4274],{},[35,71378,5528],{},[35,71380,5531],{},[35,71382,5534],{},[35,71384,5537],{},[35,71386,5540,71387,5546,71390,5550],{},[39,71388,5545],{"href":5543,"rel":71389},[77],[156,71391,5549],{},[35,71393,5553,71394,5557,71396,1638,71398,5557,71400,5564],{},[156,71395,5556],{},[156,71397,3075],{},[156,71399,5549],{},[156,71401,3083],{},[51,71403,5568],{"id":5567},[35,71405,5571],{},[1576,71407,71408,71412,71416],{},[71,71409,71410,5578],{},[156,71411,3075],{},[71,71413,71414,5583],{},[156,71415,3079],{},[71,71417,71418,5588],{},[156,71419,3083],{},[35,71421,5591],{},[35,71423,5594],{},[35,71425,5597],{},[35,71427,5600],{},[1159,71429],{},[35,71431,5605,71432,5609],{},[156,71433,5608],{},[1171,71435,5612],{},{"title":13,"searchDepth":14,"depth":14,"links":71437},[71438,71439,71440,71441],{"id":3038,"depth":14,"text":3039},{"id":3068,"depth":14,"text":3069},{"id":4256,"depth":14,"text":4257},{"id":5567,"depth":14,"text":5568},{},{"title":3005,"description":5620},{"loc":5622},{"id":5629,"title":5630,"body":71446,"contentId":5731,"date":5732,"description":5733,"extension":18,"meta":71509,"navigation":20,"path":5735,"rawbody":5736,"robots":23,"schemaOrg":23,"seo":71510,"sitemap":71511,"stem":5739,"__hash__":5740},{"type":7,"value":71447,"toc":71505},[71448,71450,71452,71454,71459,71463,71465,71467,71469,71471,71473,71475,71477,71482,71487,71495,71497,71499,71501,71503],[35,71449,5635],{},[35,71451,5638],{},[51,71453,5642],{"id":5641},[35,71455,5645,71456,5651],{},[39,71457,5650],{"href":5648,"rel":71458},[77],[35,71460,5654,71461],{},[5656,71462,5659],{"id":5658},[35,71464,5662],{},[35,71466,5665],{},[35,71468,5668],{},[35,71470,5671],{},[35,71472,5674],{},[51,71474,5678],{"id":5677},[35,71476,5681],{},[35,71478,5684,71479,5690],{},[39,71480,5689],{"href":5687,"rel":71481},[77],[35,71483,5693,71484,5699],{},[39,71485,5698],{"href":5696,"rel":71486},[77],[35,71488,5702,71489,5708,71492,5713],{},[39,71490,5707],{"href":5705,"rel":71491},[77],[39,71493,5712],{"href":1328,"rel":71494},[77],[1159,71496],{},[35,71498,5718],{},[1159,71500],{},[35,71502,5723],{},[35,71504,5726],{},{"title":13,"searchDepth":14,"depth":14,"links":71506},[71507,71508],{"id":5641,"depth":14,"text":5642},{"id":5677,"depth":14,"text":5678},{},{"title":5630,"description":5733},{"loc":5735},{"id":5742,"title":4033,"body":71513,"contentId":6781,"date":6782,"description":6783,"extension":18,"meta":72347,"navigation":20,"path":6785,"rawbody":6786,"robots":23,"schemaOrg":23,"seo":72348,"sitemap":72349,"stem":6789,"__hash__":6790},{"type":7,"value":71514,"toc":72344},[71515,71517,71519,71521,71523,71525,71527,71532,71544,71546,72342],[35,71516,5747],{},[35,71518,5750],{},[35,71520,5753],{},[35,71522,5756],{},[51,71524,5760],{"id":5759},[35,71526,5763],{},[35,71528,419,71529,5771],{},[39,71530,5770],{"href":5768,"rel":71531},[77],[1576,71533,71534,71536,71538,71540,71542],{},[71,71535,5776],{},[71,71537,5779],{},[71,71539,5782],{},[71,71541,5785],{},[71,71543,5788],{},[35,71545,5791],{},[5793,71547,71548,71550],{},[35,71549,5797],{},[5799,71551,71552,71554],{},[35,71553,5803],{},[5805,71555,71556,71558,71560,71562,71564,71566,71568,71570,71572,71577,71583,71585,71587,71589,71591,71597,71599],{},[35,71557,5809],{},[35,71559,5812],{},[35,71561,5815],{},[35,71563,5818],{},[35,71565,5821],{},[51,71567,5825],{"id":5824},[35,71569,5828],{},[35,71571,5831],{},[35,71573,5834,71574,5839],{},[39,71575,5838],{"href":5543,"rel":71576},[77],[939,71578,71579],{},[35,71580,5844,71581,5847],{},[200,71582,574],{},[35,71584,5850],{},[35,71586,5853],{},[35,71588,5856],{},[35,71590,5859],{},[68,71592,71593,71595],{},[71,71594,5864],{},[71,71596,5867],{},[35,71598,5870],{},[5872,71600,71601,71603,71605,71607,71609,71611,71715,71717,71887,71891],{},[35,71602,5876],{},[35,71604,5879],{},[35,71606,5882],{},[35,71608,5885],{},[35,71610,5888],{},[192,71612,71613],{"className":5891,"code":5892,"language":18,"meta":13,"style":13},[156,71614,71615,71619,71623,71627,71631,71635,71647,71659,71663,71667,71671,71675,71679,71683,71687,71691,71695,71699,71703,71707,71711],{"__ignoreMap":13},[200,71616,71617],{"class":202,"line":203},[200,71618,5899],{"class":210},[200,71620,71621],{"class":202,"line":14},[200,71622,529],{"emptyLinePlaceholder":20},[200,71624,71625],{"class":202,"line":255},[200,71626,5908],{"class":210},[200,71628,71629],{"class":202,"line":272},[200,71630,5913],{"class":210},[200,71632,71633],{"class":202,"line":283},[200,71634,5918],{"class":210},[200,71636,71637,71639,71641,71643,71645],{"class":202,"line":300},[200,71638,5923],{"class":210},[200,71640,600],{"class":214},[200,71642,328],{"class":235},[200,71644,675],{"class":214},[200,71646,5932],{"class":210},[200,71648,71649,71651,71653,71655,71657],{"class":202,"line":310},[200,71650,5937],{"class":210},[200,71652,600],{"class":214},[200,71654,5942],{"class":235},[200,71656,675],{"class":214},[200,71658,5932],{"class":210},[200,71660,71661],{"class":202,"line":336},[200,71662,1130],{"class":210},[200,71664,71665],{"class":202,"line":342},[200,71666,529],{"emptyLinePlaceholder":20},[200,71668,71669],{"class":202,"line":365},[200,71670,5959],{"class":210},[200,71672,71673],{"class":202,"line":371},[200,71674,529],{"emptyLinePlaceholder":20},[200,71676,71677],{"class":202,"line":398},[200,71678,5968],{"class":210},[200,71680,71681],{"class":202,"line":405},[200,71682,529],{"emptyLinePlaceholder":20},[200,71684,71685],{"class":202,"line":410},[200,71686,5977],{"class":210},[200,71688,71689],{"class":202,"line":861},[200,71690,529],{"emptyLinePlaceholder":20},[200,71692,71693],{"class":202,"line":890},[200,71694,5986],{"class":210},[200,71696,71697],{"class":202,"line":900},[200,71698,5991],{"class":210},[200,71700,71701],{"class":202,"line":905},[200,71702,5996],{"class":210},[200,71704,71705],{"class":202,"line":931},[200,71706,6001],{"class":210},[200,71708,71709],{"class":202,"line":3640},[200,71710,6006],{"class":210},[200,71712,71713],{"class":202,"line":3645},[200,71714,6011],{"class":210},[35,71716,6014],{},[192,71718,71719],{"className":1370,"code":6017,"language":1373,"meta":13,"style":13},[156,71720,71721,71725,71735,71761,71847,71883],{"__ignoreMap":13},[200,71722,71723],{"class":202,"line":203},[200,71724,6024],{"class":401},[200,71726,71727,71729,71731,71733],{"class":202,"line":14},[200,71728,3711],{"class":379},[200,71730,3714],{"class":214},[200,71732,393],{"class":379},[200,71734,280],{"class":214},[200,71736,71737,71739,71741,71743,71745,71747,71749,71751,71753,71755,71757,71759],{"class":202,"line":255},[200,71738,3723],{"class":379},[200,71740,6041],{"class":210},[200,71742,510],{"class":214},[200,71744,452],{"class":206},[200,71746,6048],{"class":210},[200,71748,215],{"class":214},[200,71750,6053],{"class":223},[200,71752,227],{"class":235},[200,71754,249],{"class":214},[200,71756,2856],{"class":245},[200,71758,249],{"class":214},[200,71760,416],{"class":235},[200,71762,71763,71765,71767,71769,71771,71773,71775,71777,71779,71781,71783,71785,71787,71789,71791,71793,71795,71797,71799,71801,71803,71805,71807,71809,71811,71813,71815,71817,71819,71821,71823,71825,71827,71829,71831,71833,71835,71837,71839,71841,71843,71845],{"class":202,"line":272},[200,71764,3723],{"class":379},[200,71766,6070],{"class":210},[200,71768,510],{"class":214},[200,71770,6041],{"class":210},[200,71772,215],{"class":214},[200,71774,6079],{"class":223},[200,71776,227],{"class":235},[200,71778,249],{"class":214},[200,71780,6086],{"class":210},[200,71782,249],{"class":214},[200,71784,2089],{"class":235},[200,71786,215],{"class":214},[200,71788,3758],{"class":223},[200,71790,227],{"class":235},[200,71792,202],{"class":386},[200,71794,393],{"class":379},[200,71796,6103],{"class":210},[200,71798,215],{"class":214},[200,71800,6108],{"class":223},[200,71802,227],{"class":235},[200,71804,249],{"class":214},[200,71806,2886],{"class":245},[200,71808,249],{"class":214},[200,71810,6119],{"class":235},[200,71812,215],{"class":214},[200,71814,3526],{"class":223},[200,71816,227],{"class":235},[200,71818,202],{"class":386},[200,71820,393],{"class":379},[200,71822,6103],{"class":210},[200,71824,215],{"class":214},[200,71826,6136],{"class":223},[200,71828,227],{"class":235},[200,71830,2882],{"class":214},[200,71832,6143],{"class":206},[200,71834,2886],{"class":245},[200,71836,6148],{"class":214},[200,71838,6151],{"class":245},[200,71840,6154],{"class":214},[200,71842,550],{"class":214},[200,71844,3894],{"class":214},[200,71846,585],{"class":235},[200,71848,71849,71851,71853,71855,71857,71859,71861,71863,71865,71867,71869,71871,71873,71875,71877,71879,71881],{"class":202,"line":283},[200,71850,6165],{"class":206},[200,71852,6048],{"class":210},[200,71854,215],{"class":214},[200,71856,6172],{"class":223},[200,71858,227],{"class":235},[200,71860,249],{"class":214},[200,71862,6179],{"class":245},[200,71864,249],{"class":214},[200,71866,550],{"class":214},[200,71868,6070],{"class":210},[200,71870,215],{"class":214},[200,71872,6190],{"class":223},[200,71874,227],{"class":235},[200,71876,249],{"class":214},[200,71878,6086],{"class":210},[200,71880,249],{"class":214},[200,71882,585],{"class":235},[200,71884,71885],{"class":202,"line":300},[200,71886,1130],{"class":214},[35,71888,6207,71889,6211],{},[156,71890,6210],{},[6213,71892,71893,71895,71897,71899,71904,71906,71910,71912,71914,71916,71918,71920,71922,71978,71980,71982,71984,71986,71988,71990,71992,71994,71999,72003,72005,72007,72009,72015,72019,72021,72025,72231,72233,72237,72311,72313,72320,72322,72324,72326,72328,72336,72338,72340],{},[35,71894,6217],{},[51,71896,6221],{"id":6220},[35,71898,6224],{},[35,71900,6227,71901,6233],{},[39,71902,6232],{"href":6230,"rel":71903},[77],[35,71905,6236],{},[35,71907,6239,71908],{},[1217,71909,6242],{},[1913,71911,6246],{"id":6245},[35,71913,6249],{},[35,71915,6252],{},[1913,71917,6256],{"id":6255},[35,71919,6259],{},[35,71921,6262],{},[192,71923,71924],{"className":1370,"code":6265,"language":1373,"meta":13,"style":13},[156,71925,71926],{"__ignoreMap":13},[200,71927,71928,71930,71932,71934,71936,71938,71940,71942,71944,71946,71948,71950,71952,71954,71956,71958,71960,71962,71964,71966,71968,71970,71972,71974,71976],{"class":202,"line":203},[200,71929,1532],{"class":214},[200,71931,393],{"class":379},[200,71933,6041],{"class":210},[200,71935,215],{"class":214},[200,71937,6079],{"class":223},[200,71939,227],{"class":210},[200,71941,249],{"class":214},[200,71943,2886],{"class":245},[200,71945,249],{"class":214},[200,71947,6290],{"class":210},[200,71949,3188],{"class":2045},[200,71951,360],{"class":210},[200,71953,215],{"class":214},[200,71955,6079],{"class":223},[200,71957,227],{"class":210},[200,71959,249],{"class":214},[200,71961,6305],{"class":245},[200,71963,249],{"class":214},[200,71965,6290],{"class":210},[200,71967,3933],{"class":2045},[200,71969,360],{"class":210},[200,71971,215],{"class":214},[200,71973,3176],{"class":223},[200,71975,6320],{"class":210},[200,71977,6323],{"class":401},[35,71979,6326],{},[35,71981,6329],{},[35,71983,6332],{},[35,71985,6335],{},[51,71987,6339],{"id":6338},[35,71989,6342],{},[35,71991,6345],{},[35,71993,6348],{},[35,71995,6351,71996,6357],{},[39,71997,6356],{"href":6354,"rel":71998},[77],[939,72000,72001],{},[35,72002,6362],{},[35,72004,6365],{},[1913,72006,6369],{"id":6368},[35,72008,6372],{},[68,72010,72011,72013],{},[71,72012,6377],{},[71,72014,6380],{},[35,72016,6383,72017,6387],{},[156,72018,6386],{},[35,72020,6390],{},[68,72022,72023],{},[71,72024,6395],{},[192,72026,72027],{"className":1370,"code":6398,"language":1373,"meta":13,"style":13},[156,72028,72029,72039,72049,72087,72121,72165,72209,72213,72217,72221,72227],{"__ignoreMap":13},[200,72030,72031,72033,72035,72037],{"class":202,"line":203},[200,72032,3711],{"class":379},[200,72034,3714],{"class":214},[200,72036,393],{"class":379},[200,72038,280],{"class":214},[200,72040,72041,72043,72045,72047],{"class":202,"line":14},[200,72042,3723],{"class":379},[200,72044,3497],{"class":210},[200,72046,510],{"class":214},[200,72048,6421],{"class":235},[200,72050,72051,72053,72055,72057,72059,72061,72063,72065,72067,72069,72071,72073,72075,72077,72079,72081,72083,72085],{"class":202,"line":255},[200,72052,6426],{"class":206},[200,72054,1095],{"class":235},[200,72056,443],{"class":379},[200,72058,350],{"class":214},[200,72060,6435],{"class":210},[200,72062,550],{"class":214},[200,72064,6440],{"class":210},[200,72066,360],{"class":214},[200,72068,6445],{"class":214},[200,72070,6448],{"class":210},[200,72072,215],{"class":214},[200,72074,6453],{"class":223},[200,72076,227],{"class":235},[200,72078,6458],{"class":210},[200,72080,215],{"class":214},[200,72082,6463],{"class":210},[200,72084,6466],{"class":235},[200,72086,230],{"class":214},[200,72088,72089,72091,72093,72095,72097,72099,72101,72103,72105,72107,72109,72111,72113,72115,72117,72119],{"class":202,"line":272},[200,72090,6473],{"class":206},[200,72092,1095],{"class":235},[200,72094,443],{"class":379},[200,72096,350],{"class":214},[200,72098,6482],{"class":210},[200,72100,550],{"class":214},[200,72102,6487],{"class":210},[200,72104,360],{"class":214},[200,72106,6445],{"class":214},[200,72108,6448],{"class":210},[200,72110,215],{"class":214},[200,72112,6453],{"class":223},[200,72114,227],{"class":235},[200,72116,6502],{"class":210},[200,72118,6466],{"class":235},[200,72120,230],{"class":214},[200,72122,72123,72125,72127,72129,72131,72133,72135,72137,72139,72141,72143,72145,72147,72149,72151,72153,72155,72157,72159,72161,72163],{"class":202,"line":283},[200,72124,6511],{"class":206},[200,72126,1095],{"class":235},[200,72128,6516],{"class":210},[200,72130,215],{"class":214},[200,72132,6521],{"class":210},[200,72134,6524],{"class":214},[200,72136,6527],{"class":223},[200,72138,227],{"class":235},[200,72140,6532],{"class":386},[200,72142,393],{"class":379},[200,72144,6537],{"class":210},[200,72146,215],{"class":214},[200,72148,3832],{"class":223},[200,72150,6320],{"class":235},[200,72152,6546],{"class":214},[200,72154,242],{"class":214},[200,72156,6551],{"class":245},[200,72158,249],{"class":214},[200,72160,6466],{"class":235},[200,72162,2752],{"class":214},[200,72164,6560],{"class":401},[200,72166,72167,72169,72171,72173,72175,72177,72179,72181,72183,72185,72187,72189,72191,72193,72195,72197,72199,72201,72203,72205,72207],{"class":202,"line":300},[200,72168,6565],{"class":210},[200,72170,215],{"class":214},[200,72172,6570],{"class":223},[200,72174,227],{"class":235},[200,72176,2752],{"class":214},[200,72178,6577],{"class":235},[200,72180,239],{"class":214},[200,72182,6577],{"class":210},[200,72184,215],{"class":214},[200,72186,6586],{"class":223},[200,72188,1532],{"class":235},[200,72190,550],{"class":214},[200,72192,6593],{"class":210},[200,72194,550],{"class":214},[200,72196,6598],{"class":235},[200,72198,239],{"class":214},[200,72200,6487],{"class":210},[200,72202,215],{"class":214},[200,72204,6607],{"class":210},[200,72206,981],{"class":214},[200,72208,416],{"class":235},[200,72210,72211],{"class":202,"line":310},[200,72212,4478],{"class":214},[200,72214,72215],{"class":202,"line":336},[200,72216,4962],{"class":214},[200,72218,72219],{"class":202,"line":342},[200,72220,4490],{"class":214},[200,72222,72223,72225],{"class":202,"line":365},[200,72224,3743],{"class":206},[200,72226,6630],{"class":210},[200,72228,72229],{"class":202,"line":371},[200,72230,1130],{"class":214},[35,72232,6637],{},[68,72234,72235],{"start":14},[71,72236,6642],{},[192,72238,72239],{"className":1370,"code":6645,"language":1373,"meta":13,"style":13},[156,72240,72241,72259,72273,72289,72305],{"__ignoreMap":13},[200,72242,72243,72245,72247,72249,72251,72253,72255,72257],{"class":202,"line":203},[200,72244,3711],{"class":379},[200,72246,3714],{"class":214},[200,72248,393],{"class":379},[200,72250,6658],{"class":210},[200,72252,215],{"class":214},[200,72254,6663],{"class":223},[200,72256,227],{"class":210},[200,72258,230],{"class":214},[200,72260,72261,72263,72265,72267,72269,72271],{"class":202,"line":14},[200,72262,6672],{"class":235},[200,72264,239],{"class":214},[200,72266,242],{"class":214},[200,72268,6679],{"class":245},[200,72270,249],{"class":214},[200,72272,252],{"class":214},[200,72274,72275,72277,72279,72281,72283,72285,72287],{"class":202,"line":255},[200,72276,6688],{"class":235},[200,72278,239],{"class":214},[200,72280,242],{"class":214},[200,72282,6695],{"class":245},[200,72284,249],{"class":214},[200,72286,550],{"class":214},[200,72288,6702],{"class":401},[200,72290,72291,72293,72295,72297,72299,72301,72303],{"class":202,"line":272},[200,72292,6707],{"class":235},[200,72294,239],{"class":214},[200,72296,318],{"class":214},[200,72298,6714],{"class":235},[200,72300,239],{"class":214},[200,72302,6719],{"class":2045},[200,72304,333],{"class":214},[200,72306,72307,72309],{"class":202,"line":283},[200,72308,413],{"class":214},[200,72310,416],{"class":210},[35,72312,6730],{},[35,72314,6733,72315,6737,72317,6743],{},[156,72316,6736],{},[39,72318,6742],{"href":6740,"rel":72319},[77],[35,72321,6746],{},[35,72323,6749],{},[51,72325,6753],{"id":6752},[35,72327,6756],{},[130,72329,132,72330,132,72334],{},[134,72331,136,72332,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,72333],{"src":6763,"type":141},[143,72335,6766],{},[1159,72337],{},[35,72339,6771],{},[35,72341,6774],{},[1171,72343,6777],{},{"title":13,"searchDepth":14,"depth":14,"links":72345},[72346],{"id":5759,"depth":14,"text":5760},{},{"title":4033,"description":6783},{"loc":6785},{"id":6792,"title":6793,"body":72351,"contentId":7451,"date":7452,"description":7453,"extension":18,"meta":72802,"navigation":20,"path":7455,"rawbody":7456,"robots":23,"schemaOrg":23,"seo":72803,"sitemap":72804,"stem":7470,"__hash__":7471},{"type":7,"value":72352,"toc":72795},[72353,72355,72357,72361,72363,72365,72369,72371,72373,72378,72380,72382,72580,72584,72588,72590],[35,72354,6798],{},[35,72356,6801],{},[35,72358,72359],{},[1690,72360],{"alt":6806,"src":6807},[35,72362,6810],{},[35,72364,6813],{},[35,72366,72367],{},[1217,72368,6818],{},[51,72370,6822],{"id":6821},[35,72372,6825],{},[35,72374,6828,72375,215],{},[39,72376,6833],{"href":6831,"rel":72377},[77],[35,72379,6836],{},[35,72381,6839],{},[68,72383,72384,72454,72524],{},[71,72385,6844,72386],{},[1576,72387,72388,72395,72402,72409,72416,72423,72430,72440,72447],{},[71,72389,72390,6853,72392],{},[6850,72391,6852],{},[39,72393,6858],{"href":6856,"rel":72394},[77],[71,72396,72397,6853,72399],{},[6850,72398,6863],{},[39,72400,6868],{"href":6866,"rel":72401},[77],[71,72403,72404,6853,72406],{},[6850,72405,6873],{},[39,72407,6878],{"href":6876,"rel":72408},[77],[71,72410,72411,6853,72413],{},[6850,72412,6883],{},[39,72414,6888],{"href":6886,"rel":72415},[77],[71,72417,72418,6853,72420],{},[6850,72419,6893],{},[39,72421,6898],{"href":6896,"rel":72422},[77],[71,72424,72425,6853,72427],{},[6850,72426,6903],{},[39,72428,6908],{"href":6906,"rel":72429},[77],[71,72431,72432,6853,72434,1638,72437],{},[6850,72433,6913],{},[39,72435,6918],{"href":6916,"rel":72436},[77],[39,72438,6923],{"href":6921,"rel":72439},[77],[71,72441,72442,6853,72444],{},[6850,72443,6928],{},[39,72445,6933],{"href":6931,"rel":72446},[77],[71,72448,72449,6853,72451],{},[6850,72450,6938],{},[39,72452,6943],{"href":6941,"rel":72453},[77],[71,72455,6946,72456],{},[1576,72457,72458,72465,72472,72479,72486,72493,72500,72510,72517],{},[71,72459,72460,6853,72462],{},[6850,72461,6953],{},[39,72463,6958],{"href":6956,"rel":72464},[77],[71,72466,72467,6853,72469],{},[6850,72468,6963],{},[39,72470,6968],{"href":6966,"rel":72471},[77],[71,72473,72474,6853,72476],{},[6850,72475,6973],{},[39,72477,6978],{"href":6976,"rel":72478},[77],[71,72480,72481,6853,72483],{},[6850,72482,6983],{},[39,72484,6988],{"href":6986,"rel":72485},[77],[71,72487,72488,6853,72490],{},[6850,72489,6993],{},[39,72491,6998],{"href":6996,"rel":72492},[77],[71,72494,72495,6853,72497],{},[6850,72496,7003],{},[39,72498,7008],{"href":7006,"rel":72499},[77],[71,72501,72502,6853,72504,1638,72507],{},[6850,72503,7013],{},[39,72505,7018],{"href":7016,"rel":72506},[77],[39,72508,7023],{"href":7021,"rel":72509},[77],[71,72511,72512,6853,72514],{},[6850,72513,7028],{},[39,72515,7033],{"href":7031,"rel":72516},[77],[71,72518,72519,6853,72521],{},[6850,72520,7038],{},[39,72522,7043],{"href":7041,"rel":72523},[77],[71,72525,7046,72526],{},[1576,72527,72528,72535,72542,72549,72556,72566,72573],{},[71,72529,72530,6853,72532],{},[6850,72531,7053],{},[39,72533,7058],{"href":7056,"rel":72534},[77],[71,72536,72537,6853,72539],{},[6850,72538,7063],{},[39,72540,7068],{"href":7066,"rel":72541},[77],[71,72543,72544,6853,72546],{},[6850,72545,7073],{},[39,72547,7078],{"href":7076,"rel":72548},[77],[71,72550,72551,6853,72553],{},[6850,72552,7083],{},[39,72554,7088],{"href":7086,"rel":72555},[77],[71,72557,72558,6853,72560,1638,72563],{},[6850,72559,7093],{},[39,72561,7098],{"href":7096,"rel":72562},[77],[39,72564,7103],{"href":7101,"rel":72565},[77],[71,72567,72568,6853,72570],{},[6850,72569,7108],{},[39,72571,7113],{"href":7111,"rel":72572},[77],[71,72574,72575,6853,72577],{},[6850,72576,7118],{},[39,72578,7123],{"href":7121,"rel":72579},[77],[35,72581,72582],{},[1217,72583,7128],{},[35,72585,7131,72586],{},[1217,72587,7134],{},[35,72589,7137],{},[7139,72591,72592,72594,72596,72637,72639,72641,72645,72647,72651,72655,72662,72667,72669,72671,72675,72677,72681,72683,72715,72717,72719,72723,72727,72731,72733,72737,72751,72755,72768,72781,72783,72787,72789,72791,72793],{"w-full":13,"icon":7141,"text":7142,"to":7143,"target":7144},[51,72593,7148],{"id":7147},[35,72595,7151],{},[68,72597,72598,72611,72624],{},[71,72599,72600,6853,72602,72605,7164,72607,72609],{},[6850,72601,7108],{},[39,72603,7113],{"href":7111,"rel":72604},[77],[7162,72606],{},[7162,72608],{},[1690,72610],{"alt":7169,"src":7170},[71,72612,72613,6853,72615,72618,7180,72620,72622],{},[6850,72614,6903],{},[39,72616,6908],{"href":6906,"rel":72617},[77],[7162,72619],{},[7162,72621],{},[1690,72623],{"alt":7185,"src":7186},[71,72625,72626,6853,72628,72631,7196,72633,72635],{},[6850,72627,6973],{},[39,72629,6978],{"href":6976,"rel":72630},[77],[7162,72632],{},[7162,72634],{},[1690,72636],{"alt":7201,"src":7202},[51,72638,7206],{"id":7205},[35,72640,7209],{},[35,72642,72643],{},[1690,72644],{"alt":7214,"src":7215},[35,72646,7218],{},[35,72648,72649],{},[1690,72650],{"alt":7223,"src":7224},[35,72652,72653],{},[1690,72654],{"alt":7229,"src":7230},[35,72656,7233,72657,7238,72659,215],{},[39,72658,7237],{"href":7236},[39,72660,7243],{"href":7241,"rel":72661},[77],[35,72663,7246,72664,215],{},[39,72665,7251],{"href":7249,"rel":72666},[77],[51,72668,7255],{"id":7254},[35,72670,7258],{},[35,72672,7261,72673],{},[1217,72674,7264],{},[35,72676,7267],{},[35,72678,7270,72679],{},[1217,72680,7273],{},[35,72682,7276],{},[1576,72684,72685,72687,72689,72691,72693,72695,72697,72699,72701,72703,72705,72707,72709,72711],{},[71,72686,7281],{},[71,72688,7284],{},[71,72690,7287],{},[71,72692,7290],{},[71,72694,7293],{},[71,72696,7296],{},[71,72698,7299],{},[71,72700,7302],{},[71,72702,7305],{},[71,72704,7308],{},[71,72706,7311],{},[71,72708,7314],{},[71,72710,7317],{},[71,72712,7320,72713],{},[1217,72714,7323],{},[35,72716,7326],{},[35,72718,7329],{},[35,72720,72721],{},[1690,72722],{"alt":7334,"src":7335},[35,72724,72725],{},[1690,72726],{"alt":7340,"src":7341},[35,72728,72729],{},[1690,72730],{"alt":7346,"src":7347},[51,72732,7350],{"id":5567},[35,72734,7353,72735],{},[1217,72736,7356],{},[35,72738,7359,72739,3076,72742,3080,72745,106,72748,7378],{},[39,72740,1775],{"href":1773,"rel":72741},[77],[39,72743,7367],{"href":7365,"rel":72744},[77],[39,72746,7372],{"href":7370,"rel":72747},[77],[39,72749,7377],{"href":7375,"rel":72750},[77],[35,72752,7381,72753],{},[1217,72754,7384],{},[35,72756,7387,72757,3076,72760,3080,72763,7397,72766],{},[39,72758,1775],{"href":1773,"rel":72759},[77],[39,72761,7367],{"href":7365,"rel":72762},[77],[39,72764,7372],{"href":7370,"rel":72765},[77],[1217,72767,7400],{},[35,72769,7403,72770,7409,72773,7415,72776,7421,72779],{},[39,72771,7408],{"href":7406,"rel":72772},[77],[39,72774,7414],{"href":7412,"rel":72775},[77],[39,72777,7420],{"href":7418,"rel":72778},[77],[1217,72780,7424],{},[35,72782,7427],{},[35,72784,7430,72785],{},[1217,72786,7433],{},[1159,72788],{},[35,72790,7438],{},[1159,72792],{},[35,72794,7443],{},{"title":13,"searchDepth":14,"depth":14,"links":72796},[72797,72798,72799,72800,72801],{"id":6821,"depth":14,"text":6822},{"id":7147,"depth":14,"text":7148},{"id":7205,"depth":14,"text":7206},{"id":7254,"depth":14,"text":7255},{"id":5567,"depth":14,"text":7350},{},{"title":6793,"description":7453},{"loc":7455,"images":72805},[72806,72807,72808,72809,72810,72811,72812,72813,72814,72815],{"loc":6807},{"loc":7170},{"loc":7186},{"loc":7202},{"loc":7215},{"loc":7224},{"loc":7230},{"loc":7335},{"loc":7341},{"loc":7347},{"id":7473,"title":7474,"body":72817,"contentId":8151,"date":8152,"description":8153,"extension":18,"meta":73290,"navigation":20,"path":8155,"rawbody":8156,"robots":23,"schemaOrg":23,"seo":73291,"sitemap":73292,"stem":8174,"__hash__":8175},{"type":7,"value":72818,"toc":73280},[72819,72826,72828,72832,72834,72845,72849,72862,72864,72866,72871,72878,72885,72887,72889,72893,72895,72897,72945,72949,72951,72955,72957,73000,73004,73009,73019,73030,73034,73038,73042,73044,73046,73086,73093,73095,73099,73101,73105,73107,73112,73114,73118,73122,73124,73126],[35,72820,7479,72821,106,72824,7489],{},[39,72822,7484],{"href":7482,"rel":72823},[77],[39,72825,7488],{"href":7487},[35,72827,7492],{},[35,72829,72830],{},[1690,72831],{"alt":7497,"src":7498},[35,72833,7501],{},[35,72835,7504,72836,3076,72839,3080,72842,215],{},[39,72837,1775],{"href":1773,"rel":72838},[77],[39,72840,7367],{"href":7365,"rel":72841},[77],[39,72843,7372],{"href":7370,"rel":72844},[77],[35,72846,72847],{},[1690,72848],{"alt":7518,"src":7519},[35,72850,7522,72851,1638,72854,7529,72857,7533,72860],{},[39,72852,1775],{"href":1773,"rel":72853},[77],[39,72855,7367],{"href":7365,"rel":72856},[77],[39,72858,7372],{"href":7370,"rel":72859},[77],[1217,72861,7536],{},[35,72863,7539],{},[35,72865,7542],{},[35,72867,7545,72868,7550],{},[39,72869,6833],{"href":7548,"rel":72870},[77],[35,72872,72873,7556,72876],{},[39,72874,6833],{"href":7548,"rel":72875},[77],[1217,72877,7559],{},[35,72879,7562,72880],{},[1217,72881,7565,72882,7569],{},[39,72883,6833],{"href":7548,"rel":72884},[77],[51,72886,7237],{"id":7572},[35,72888,7575],{},[35,72890,7578,72891],{},[1217,72892,7581],{},[35,72894,7584],{},[35,72896,7587],{},[1576,72898,72899,72906,72913,72920,72927,72934,72941],{},[71,72900,72901,6853,72903],{},[6850,72902,7594],{},[39,72904,7599],{"href":7597,"rel":72905},[77],[71,72907,72908,6853,72910],{},[6850,72909,7604],{},[39,72911,7033],{"href":7031,"rel":72912},[77],[71,72914,72915,6853,72917],{},[6850,72916,7612],{},[39,72918,7617],{"href":7615,"rel":72919},[77],[71,72921,72922,6853,72924],{},[6850,72923,7622],{},[39,72925,7627],{"href":7625,"rel":72926},[77],[71,72928,72929,6853,72931],{},[6850,72930,7632],{},[39,72932,7637],{"href":7635,"rel":72933},[77],[71,72935,72936,6853,72938],{},[6850,72937,7642],{},[39,72939,7647],{"href":7645,"rel":72940},[77],[71,72942,72943,7653],{},[6850,72944,7652],{},[35,72946,72947],{},[1690,72948],{"alt":7658,"src":7659},[35,72950,7662],{},[35,72952,7665,72953],{},[1217,72954,7668],{},[35,72956,7671],{},[1576,72958,72959,72966,72973,72983,72993],{},[71,72960,72961,6853,72963],{},[6850,72962,7678],{},[39,72964,7683],{"href":7681,"rel":72965},[77],[71,72967,72968,6853,72970],{},[6850,72969,7688],{},[39,72971,7693],{"href":7691,"rel":72972},[77],[71,72974,72975,6853,72977,1638,72980],{},[6850,72976,7698],{},[39,72978,7702],{"href":7548,"rel":72979},[77],[39,72981,7033],{"href":7031,"rel":72982},[77],[71,72984,72985,6853,72987,1638,72990],{},[6850,72986,7710],{},[39,72988,7714],{"href":1773,"rel":72989},[77],[39,72991,7718],{"href":7365,"rel":72992},[77],[71,72994,72995,6853,72997],{},[6850,72996,7723],{},[39,72998,7728],{"href":7726,"rel":72999},[77],[35,73001,7731,73002],{},[1217,73003,7734],{},[35,73005,7737,73006,7741],{},[39,73007,7372],{"href":7370,"rel":73008},[77],[35,73010,7744,73011,1638,73014,7751,73017],{},[39,73012,1775],{"href":1773,"rel":73013},[77],[39,73015,7367],{"href":7365,"rel":73016},[77],[1217,73018,7754],{},[35,73020,7757,73021,3076,73024,3080,73027,215],{},[39,73022,1775],{"href":1773,"rel":73023},[77],[39,73025,7367],{"href":7365,"rel":73026},[77],[39,73028,7372],{"href":7370,"rel":73029},[77],[35,73031,73032],{},[1690,73033],{"alt":7771,"src":7772},[35,73035,73036],{},[1690,73037],{"alt":7777,"src":7778},[35,73039,7781,73040],{},[1217,73041,7784],{},[51,73043,7788],{"id":7787},[35,73045,7791],{},[68,73047,73048,73070],{},[71,73049,73050,6853,73052,1638,73055,73058,7806,73060,1638,73063,7813,73066,73068],{},[6850,73051,7710],{},[39,73053,7714],{"href":1773,"rel":73054},[77],[39,73056,7718],{"href":7365,"rel":73057},[77],[7162,73059],{},[39,73061,1775],{"href":1773,"rel":73062},[77],[39,73064,7367],{"href":7365,"rel":73065},[77],[7162,73067],{},[1690,73069],{"alt":7818,"src":7819},[71,73071,73072,6853,73074,73077,7829,73079,7834,73082,73084],{},[6850,73073,7632],{},[39,73075,7637],{"href":7635,"rel":73076},[77],[7162,73078],{},[39,73080,7833],{"href":7635,"rel":73081},[77],[7162,73083],{},[1690,73085],{"alt":7839,"src":7840},[35,73087,7843,73088,7847,73091],{},[39,73089,7372],{"href":7370,"rel":73090},[77],[1217,73092,7850],{},[51,73094,7854],{"id":7853},[35,73096,7857,73097],{},[1217,73098,7860],{},[1913,73100,7864],{"id":7863},[35,73102,73103],{},[1690,73104],{"alt":7869,"src":7870},[35,73106,7873],{},[35,73108,7876,73109,7882],{},[39,73110,7881],{"href":7879,"rel":73111},[77],[35,73113,7885],{},[35,73115,7888,73116],{},[1217,73117,7891],{},[35,73119,73120],{},[1690,73121],{"alt":7896,"src":7897},[35,73123,7900],{},[35,73125,7903],{},[7905,73127,73128,73130,73137,73139,73145,73147,73149,73151,73155,73159,73163,73168,73170,73172,73176,73181,73189,73193,73195,73200,73202,73204,73212,73214,73221,73225,73227,73231,73235,73237,73251,73259,73270,73272,73276,73278],{"href":7907,"text":7908},[1913,73129,7912],{"id":7911},[35,73131,7915,73132,7919,73135],{},[39,73133,6833],{"href":7548,"rel":73134},[77],[1217,73136,7922],{},[35,73138,7925],{},[35,73140,7928,73141],{},[1217,73142,7931,73143,7936],{},[39,73144,7935],{"href":7934},[35,73146,7939],{},[35,73148,7942],{},[35,73150,7945],{},[35,73152,7948,73153],{},[1217,73154,7951],{},[35,73156,7954,73157],{},[1217,73158,7957],{},[35,73160,7960,73161],{},[1217,73162,7963],{},[35,73164,7966,73165,7970],{},[39,73166,6833],{"href":7548,"rel":73167},[77],[35,73169,7973],{},[35,73171,7976],{},[35,73173,73174],{},[1690,73175],{"alt":7981,"src":7982},[35,73177,7985,73178,7989],{},[39,73179,6833],{"href":7548,"rel":73180},[77],[35,73182,7992,73183,7998,73186,215],{},[39,73184,7997],{"href":7995,"rel":73185},[77],[39,73187,8003],{"href":8001,"rel":73188},[77],[35,73190,73191],{},[1690,73192],{"alt":8008,"src":8009},[35,73194,8012],{},[35,73196,8015,73197,215],{},[39,73198,8020],{"href":8018,"rel":73199},[77],[35,73201,8023],{},[1913,73203,8027],{"id":8026},[35,73205,8030,73206,8035,73209,215],{},[39,73207,8027],{"href":8033,"rel":73208},[77],[39,73210,8040],{"href":8038,"rel":73211},[77],[35,73213,8043],{},[35,73215,8046,73216,8052,73219],{},[39,73217,8051],{"href":8049,"rel":73218},[77],[1217,73220,8055],{},[35,73222,73223],{},[1690,73224],{"alt":8060,"src":8061},[51,73226,7350],{"id":5567},[35,73228,8066,73229],{},[1217,73230,8069],{},[35,73232,73233],{},[1690,73234],{"alt":8074,"src":8075},[35,73236,8078],{},[35,73238,8081,73239,3076,73242,3080,73245,106,73248,8094],{},[39,73240,7372],{"href":7370,"rel":73241},[77],[39,73243,7367],{"href":7365,"rel":73244},[77],[39,73246,1775],{"href":1773,"rel":73247},[77],[39,73249,7377],{"href":7375,"rel":73250},[77],[35,73252,8097,73253,8103,73256,8108],{},[39,73254,8102],{"href":8100,"rel":73255},[77],[39,73257,8107],{"href":7406,"rel":73258},[77],[35,73260,8111,73261,8117,73264,1638,73267,8126],{},[39,73262,8116],{"href":8114,"rel":73263},[77],[39,73265,8122],{"href":8120,"rel":73266},[77],[39,73268,6908],{"href":6906,"rel":73269},[77],[35,73271,8129],{},[35,73273,73274],{},[1690,73275],{"alt":8134,"src":8135},[1159,73277],{},[35,73279,8140],{},{"title":13,"searchDepth":14,"depth":14,"links":73281},[73282,73283,73284,73289],{"id":7572,"depth":14,"text":7237},{"id":7787,"depth":14,"text":7788},{"id":7853,"depth":14,"text":7854,"children":73285},[73286,73287,73288],{"id":7863,"depth":255,"text":7864},{"id":7911,"depth":255,"text":7912},{"id":8026,"depth":255,"text":8027},{"id":5567,"depth":14,"text":7350},{},{"title":7474,"description":8153},{"loc":8155,"images":73293},[73294,73295,73296,73297,73298,73299,73300,73301,73302,73303,73304,73305,73306,73307],{"loc":7498},{"loc":7519},{"loc":7659},{"loc":7772},{"loc":7778},{"loc":7819},{"loc":7840},{"loc":7870},{"loc":7897},{"loc":7982},{"loc":8009},{"loc":8061},{"loc":8075},{"loc":8135},{"id":8177,"title":1740,"body":73309,"contentId":8746,"date":8747,"description":8748,"extension":18,"meta":73672,"navigation":20,"path":8750,"rawbody":8751,"robots":23,"schemaOrg":23,"seo":73673,"sitemap":73674,"stem":8764,"__hash__":8765},{"type":7,"value":73310,"toc":73664},[73311,73315,73320,73322,73327,73329,73331,73333,73337,73341,73343,73345,73347,73349,73351,73353,73355,73357,73359,73363,73367,73378,73380,73384,73388,73392,73396,73398,73400,73404,73406,73408,73412,73416,73420,73424,73429,73433,73440,73442,73446,73448,73454,73458,73460,73462,73466,73468,73470,73472,73474,73476,73478,73480,73482,73484,73486,73488,73490,73492,73498,73500],[35,73312,8182,73313],{},[1217,73314,8185],{},[35,73316,8188,73317,8194],{},[39,73318,8193],{"href":8191,"rel":73319},[77],[51,73321,8198],{"id":8197},[35,73323,8201,73324,8207],{},[39,73325,8206],{"href":8204,"rel":73326},[77],[35,73328,8210],{},[35,73330,8213],{},[35,73332,8216],{},[35,73334,8219,73335,215],{},[6850,73336,8222],{},[35,73338,8225,73339,215],{},[6850,73340,8228],{},[35,73342,8231],{},[51,73344,8235],{"id":8234},[35,73346,8238],{},[35,73348,8241],{},[35,73350,8244],{},[35,73352,8247],{},[35,73354,8250],{},[35,73356,8253],{},[51,73358,8257],{"id":8256},[35,73360,73361],{},[1217,73362,8262],{},[35,73364,8265,73365,8269],{},[6850,73366,8268],{},[35,73368,8272,73369,8278,73372,1638,73375,8289],{},[39,73370,8277],{"href":8275,"rel":73371},[77],[39,73373,8283],{"href":8281,"rel":73374},[77],[39,73376,8288],{"href":8286,"rel":73377},[77],[35,73379,8292],{},[35,73381,73382],{},[1690,73383],{"alt":8297,"src":8298},[35,73385,73386],{},[1690,73387],{"alt":8303,"src":8304},[35,73389,8307,73390],{},[1217,73391,8310],{},[35,73393,73394],{},[1690,73395],{"alt":8315,"src":8316},[35,73397,8319],{},[35,73399,8322],{},[35,73401,73402],{},[1690,73403],{"alt":8327,"src":8328},[35,73405,8331],{},[35,73407,8334],{},[35,73409,8337,73410],{},[1217,73411,8340],{},[35,73413,8343,73414],{},[1217,73415,8346],{},[35,73417,8349,73418],{},[1217,73419,8352],{},[35,73421,73422],{},[1690,73423],{"alt":8357,"src":8358},[35,73425,8361,73426,8367],{},[39,73427,8366],{"href":8364,"rel":73428},[77],[35,73430,73431],{},[1690,73432],{"alt":8372,"src":8373},[35,73434,73435],{},[1217,73436,8378,73437,8384],{},[39,73438,8383],{"href":8381,"rel":73439},[77],[35,73441,8387],{},[35,73443,8390,73444],{},[1217,73445,8393],{},[35,73447,8396],{},[35,73449,73450,8402,73452],{},[6850,73451,8401],{},[1217,73453,8405],{},[35,73455,73456],{},[1690,73457],{"alt":8410,"src":8411},[35,73459,8414],{},[35,73461,8417],{},[35,73463,73464],{},[1690,73465],{"alt":8422,"src":8423},[35,73467,8426],{},[51,73469,8430],{"id":8429},[35,73471,8433],{},[51,73473,8437],{"id":8436},[35,73475,8440],{},[35,73477,8443],{},[35,73479,8446],{},[35,73481,8449],{},[35,73483,8452],{},[35,73485,8455],{},[51,73487,8459],{"id":8458},[35,73489,8462],{},[35,73491,8465],{},[68,73493,73494,73496],{},[71,73495,8470],{},[71,73497,8473],{},[35,73499,8476],{},[8478,73501,73502,73504],{},[35,73503,8482],{},[8484,73505,73506,73508,73514,73516,73518,73520,73522,73526,73530,73532,73536,73541,73545,73547,73551,73553,73571,73573,73577],{"title":8486,":steps":8487,":relations":8488,":ui":8489},[35,73507,8492],{},[35,73509,8495,73510,106,73512],{},[6850,73511,8498],{},[1217,73513,8501],{},[1913,73515,8505],{"id":8504},[35,73517,8508],{},[35,73519,8511],{},[35,73521,8514],{},[35,73523,8517,73524,8521],{},[156,73525,8520],{},[35,73527,73528],{},[1690,73529],{"alt":8526,"src":8527},[35,73531,8530],{},[35,73533,73534],{},[1217,73535,8535],{},[35,73537,8538,73538,8544],{},[39,73539,8543],{"href":8541,"rel":73540},[77],[35,73542,8547,73543],{},[1217,73544,8550],{},[35,73546,8553],{},[35,73548,8556,73549,8560],{},[6850,73550,8559],{},[35,73552,8563],{},[68,73554,73555,73559,73563,73567],{},[71,73556,73557,8571],{},[6850,73558,8570],{},[71,73560,73561,8577],{},[6850,73562,8576],{},[71,73564,73565,8583],{},[6850,73566,8582],{},[71,73568,73569,8589],{},[6850,73570,8588],{},[35,73572,8592],{},[35,73574,8595,73575,8599],{},[6850,73576,8598],{},[8484,73578,73579,73581,73583,73585,73589,73591,73593,73595,73597,73599],{"title":8602,":steps":8603,":relations":8604},[35,73580,8607],{},[35,73582,8610],{},[35,73584,8613],{},[35,73586,8616,73587,8620],{},[1217,73588,8619],{},[35,73590,8623],{},[35,73592,8626],{},[35,73594,8629],{},[35,73596,8632],{},[35,73598,8635],{},[8484,73600,73601,73605,73607,73609,73611,73620,73624,73626,73628,73630,73632,73634,73636,73640,73642,73644,73646,73648,73650,73652,73654,73656,73658,73660,73662],{"title":8638,":steps":8639,":relations":8640,":ui":8641},[35,73602,8644,73603],{},[1582,73604],{"repo":8647},[1913,73606,8651],{"id":8650},[35,73608,8654],{},[35,73610,8657],{},[68,73612,73613,73618],{},[71,73614,8662,73615,8668],{},[39,73616,8667],{"href":8665,"rel":73617},[77],[71,73619,8671],{},[35,73621,8674,73622],{},[1217,73623,8677],{},[35,73625,8680],{},[35,73627,8683],{},[1913,73629,8687],{"id":8686},[35,73631,8690],{},[35,73633,8693],{},[35,73635,8696],{},[35,73637,73638],{},[1217,73639,8701],{},[51,73641,8705],{"id":8704},[35,73643,8708],{},[35,73645,8711],{},[35,73647,8714],{},[35,73649,8717],{},[35,73651,8720],{},[35,73653,8723],{},[35,73655,8726],{},[1159,73657],{},[35,73659,8731],{},[35,73661,8734],{},[35,73663,8737],{},{"title":13,"searchDepth":14,"depth":14,"links":73665},[73666,73667,73668,73669,73670,73671],{"id":8197,"depth":14,"text":8198},{"id":8234,"depth":14,"text":8235},{"id":8256,"depth":14,"text":8257},{"id":8429,"depth":14,"text":8430},{"id":8436,"depth":14,"text":8437},{"id":8458,"depth":14,"text":8459},{},{"title":1740,"description":8748},{"loc":8750,"images":73675},[73676,73677,73678,73679,73680,73681,73682,73683,73684],{"loc":8298},{"loc":8304},{"loc":8316},{"loc":8328},{"loc":8358},{"loc":8373},{"loc":8411},{"loc":8423},{"loc":8527},{"id":8767,"title":8768,"body":73686,"contentId":9399,"date":9400,"description":9401,"extension":18,"meta":74106,"navigation":20,"path":9403,"rawbody":9404,"robots":23,"schemaOrg":23,"seo":74107,"sitemap":74108,"stem":9421,"__hash__":9422},{"type":7,"value":73687,"toc":74098},[73688,73690,73692,73699,73705,73709,73711,73737,73739,73741,73745,73756,73758,73764,73768,73772,73774,73778,73780,73784,73788,73792,73794,73796,73800,73802,73806,73810,73812,73816,73820,73824,73826,73832,73836,73842,73846,73848,73862,73866,73870,73874,73878,73880,73884,73926,73928,73932,73936,73938,73944,73951,73953,73960,73966,73971,73979,73981,73983,73985,73987,74017,74019,74041,74043,74057,74059,74061,74063,74065,74069,74074,74078,74080,74082,74084,74086,74088,74090,74092,74094],[35,73689,8773],{},[35,73691,8776],{},[35,73693,8779,73694],{},[3029,73695,1638,73696,8788],{"id":8782},[39,73697,8787],{"href":8785,"rel":73698},[77],[68,73700,73701,73703],{},[71,73702,8793],{},[71,73704,8796],{},[35,73706,73707],{},[1690,73708],{"alt":8801,"src":8802},[35,73710,8805],{},[1576,73712,73713,73715,73717,73719,73721,73723,73725,73727,73729,73731,73733,73735],{},[71,73714,8810],{},[71,73716,8813],{},[71,73718,8816],{},[71,73720,8819],{},[71,73722,8822],{},[71,73724,8825],{},[71,73726,8828],{},[71,73728,8831],{},[71,73730,8834],{},[71,73732,8837],{},[71,73734,8840],{},[71,73736,8843],{},[35,73738,8846],{},[51,73740,8850],{"id":8849},[35,73742,8853,73743],{},[1217,73744,8856],{},[35,73746,8859,73747,8865,73750,8869,73752,8873,73754],{},[39,73748,8864],{"href":8862,"rel":73749},[77],[1217,73751,8868],{},[1217,73753,8872],{},[1217,73755,8876],{},[35,73757,8879],{},[1576,73759,73760],{},[71,73761,8884,73762],{},[1217,73763,8887],{},[35,73765,73766],{},[1690,73767],{"alt":8892,"src":8893},[1576,73769,73770],{},[71,73771,8898],{},[35,73773,8901],{},[35,73775,73776],{},[1690,73777],{"alt":8906,"src":8907},[51,73779,8911],{"id":8910},[35,73781,8914,73782,215],{},[39,73783,8918],{"href":8917},[35,73785,8921,73786],{},[1217,73787,8924],{},[35,73789,8927,73790],{},[1217,73791,8930],{},[35,73793,8933],{},[35,73795,8936],{},[35,73797,73798],{},[1217,73799,8941],{},[51,73801,8945],{"id":8944},[35,73803,8948,73804,8953],{},[39,73805,8952],{"href":8951},[939,73807,73808],{},[35,73809,8958],{},[35,73811,8961],{},[35,73813,8964,73814],{},[1217,73815,8967],{},[35,73817,8970,73818,8975],{},[39,73819,8974],{"href":8973},[35,73821,73822],{},[1690,73823],{"alt":8980,"src":8981},[35,73825,8984],{},[35,73827,8987,73828,8990,73830,8994],{},[39,73829,7484],{"href":7487},[1217,73831,8993],{},[35,73833,73834],{},[1690,73835],{"alt":8999,"src":9000},[35,73837,9003,73838,9008,73840],{},[39,73839,9007],{"href":9006},[1217,73841,9011],{},[35,73843,73844],{},[1690,73845],{"alt":9016,"src":9017},[35,73847,9020],{},[1576,73849,73850,73854,73858,73860],{},[71,73851,73852,9029],{},[39,73853,9028],{"href":9027},[71,73855,73856,9036],{},[39,73857,9035],{"href":9034},[71,73859,9039],{},[71,73861,9042],{},[35,73863,73864],{},[1690,73865],{"alt":9047,"src":9048},[35,73867,73868],{},[1690,73869],{"alt":9053,"src":9054},[35,73871,73872],{},[1690,73873],{"alt":9059,"src":9060},[35,73875,73876],{},[1690,73877],{"alt":9065,"src":9066},[35,73879,9069],{},[35,73881,9072,73882],{},[1217,73883,9075],{},[1576,73885,73886,73891,73896,73901,73906,73911,73916,73921],{},[71,73887,9080,73888],{},[39,73889,9085],{"href":9083,"rel":73890},[77],[71,73892,9088,73893],{},[39,73894,9093],{"href":9091,"rel":73895},[77],[71,73897,9096,73898],{},[39,73899,9101],{"href":9099,"rel":73900},[77],[71,73902,9104,73903],{},[39,73904,9109],{"href":9107,"rel":73905},[77],[71,73907,9112,73908],{},[39,73909,9109],{"href":9115,"rel":73910},[77],[71,73912,9119,73913],{},[39,73914,9124],{"href":9122,"rel":73915},[77],[71,73917,9127,73918],{},[39,73919,9124],{"href":9130,"rel":73920},[77],[71,73922,9134,73923],{},[39,73924,9139],{"href":9137,"rel":73925},[77],[35,73927,9142],{},[35,73929,73930],{},[1690,73931],{"alt":9147,"src":9148},[35,73933,73934],{},[1690,73935],{"alt":9153,"src":9154},[35,73937,9157],{},[35,73939,9160,73940,9165,73942],{},[39,73941,9164],{"href":9163},[1582,73943,215],{"repo":9168},[35,73945,73946],{},[1217,73947,9173,73948,215],{},[39,73949,9178],{"href":9176,"rel":73950},[77],[51,73952,9182],{"id":9181},[35,73954,9185,73955,9191,73958,9196],{},[39,73956,9190],{"href":9188,"rel":73957},[77],[39,73959,9195],{"href":9194},[35,73961,9199,73962],{},[1582,73963,9203,73964],{"repo":9202},[1582,73965,215],{"repo":9206},[35,73967,9209,73968,9213],{},[39,73969,7881],{"href":7879,"rel":73970},[77],[35,73972,9216,73973,9222,73976,215],{},[39,73974,9221],{"href":9219,"rel":73975},[77],[39,73977,9227],{"href":9225,"rel":73978},[77],[35,73980,9230],{},[51,73982,9234],{"id":9233},[35,73984,9237],{},[35,73986,9240],{},[1576,73988,73989,73993,73997,74001,74005,74009,74013],{},[71,73990,73991],{},[3029,73992],{"id":9247},[71,73994,73995],{},[3029,73996],{"id":9252},[71,73998,73999],{},[3029,74000],{"id":9257},[71,74002,74003],{},[3029,74004],{"id":4263},[71,74006,74007],{},[3029,74008],{"id":9266},[71,74010,74011],{},[3029,74012],{"id":9271},[71,74014,74015],{},[3029,74016],{"id":9276},[35,74018,9279],{},[1576,74020,74021,74025,74029,74033,74037],{},[71,74022,74023],{},[3029,74024],{"id":9286},[71,74026,74027],{},[3029,74028],{"id":9291},[71,74030,74031],{},[3029,74032],{"id":9296},[71,74034,74035],{},[3029,74036],{"id":9301},[71,74038,74039],{},[3029,74040],{"id":9306},[35,74042,9309],{},[1576,74044,74045,74049,74053],{},[71,74046,74047],{},[3029,74048],{"id":9316},[71,74050,74051],{},[3029,74052],{"id":9321},[71,74054,74055],{},[3029,74056],{"id":9326},[35,74058,9329],{},[35,74060,9332],{},[51,74062,9336],{"id":9335},[35,74064,9339],{},[35,74066,9342,74067],{},[1217,74068,9345],{},[35,74070,9348,74071,9354],{},[39,74072,9353],{"href":9351,"rel":74073},[77],[35,74075,9357,74076],{},[1217,74077,9360],{},[35,74079,9363],{},[35,74081,9366],{},[35,74083,9369],{},[35,74085,9372],{},[35,74087,9375],{},[35,74089,9378],{},[35,74091,9381],{},[35,74093,9384],{},[35,74095,74096],{},[1690,74097],{"alt":9389,"src":9390},{"title":13,"searchDepth":14,"depth":14,"links":74099},[74100,74101,74102,74103,74104,74105],{"id":8849,"depth":14,"text":8850},{"id":8910,"depth":14,"text":8911},{"id":8944,"depth":14,"text":8945},{"id":9181,"depth":14,"text":9182},{"id":9233,"depth":14,"text":9234},{"id":9335,"depth":14,"text":9336},{},{"title":8768,"description":9401},{"loc":9403,"images":74109},[74110,74111,74112,74113,74114,74115,74116,74117,74118,74119,74120,74121,74122],{"loc":8802},{"loc":8893},{"loc":8907},{"loc":8981},{"loc":9000},{"loc":9017},{"loc":9048},{"loc":9054},{"loc":9060},{"loc":9066},{"loc":9148},{"loc":9154},{"loc":9390},{"id":9424,"title":9425,"body":74124,"contentId":9642,"date":9643,"description":9644,"extension":18,"meta":74267,"navigation":20,"path":8917,"rawbody":9646,"robots":23,"schemaOrg":23,"seo":74268,"sitemap":74269,"stem":9649,"__hash__":9650},{"type":7,"value":74125,"toc":74261},[74126,74130,74132,74134,74138,74142,74144,74148,74150,74152,74154,74156,74160,74168,74170,74172,74174,74176,74180,74184,74188,74190,74192,74194,74198,74200,74202,74204,74206,74208,74210,74212,74214,74216,74221,74226,74231,74233,74235,74237,74241,74243,74247,74249,74251,74253,74255,74257,74259],[35,74127,9430,74128],{},[1217,74129,9433],{},[51,74131,9437],{"id":9436},[35,74133,9440],{},[35,74135,9443,74136],{},[1217,74137,9446],{},[35,74139,9449,74140],{},[1217,74141,9452],{},[35,74143,9455],{},[35,74145,9458,74146],{},[1217,74147,9461],{},[35,74149,9464],{},[35,74151,9467],{},[35,74153,9470],{},[51,74155,9474],{"id":9473},[35,74157,9477,74158,9481],{},[1217,74159,9480],{},[35,74161,9484,74162],{},[3029,74163,3076,74164],{"id":9247},[3029,74165,3080,74166],{"id":9266},[3029,74167,215],{"id":9491},[35,74169,9494],{},[35,74171,9497],{},[35,74173,9500],{},[35,74175,9503],{},[35,74177,9506,74178,9511],{},[39,74179,9510],{"href":9509},[35,74181,74182],{},[1217,74183,9516],{},[35,74185,9519,74186],{},[1217,74187,9522],{},[35,74189,9525],{},[35,74191,9528],{},[51,74193,9532],{"id":9531},[35,74195,9535,74196,9539],{},[39,74197,9538],{"href":9509},[35,74199,9542],{},[35,74201,9545],{},[35,74203,9548],{},[35,74205,9551],{},[35,74207,9554],{},[35,74209,9557],{},[35,74211,9560],{},[51,74213,9564],{"id":9563},[35,74215,9567],{},[35,74217,9570,74218,9576],{},[39,74219,9575],{"href":9573,"rel":74220},[77],[35,74222,9579,74223,9585],{},[39,74224,9584],{"href":9582,"rel":74225},[77],[35,74227,9588,74228,9593],{},[39,74229,8051],{"href":9591,"rel":74230},[77],[35,74232,9596],{},[35,74234,9599],{},[35,74236,9602],{},[35,74238,9605,74239],{},[1217,74240,9608],{},[1159,74242],{},[35,74244,9613,74245],{},[1217,74246,9616],{},[1159,74248],{},[35,74250,9621],{},[35,74252,9624],{},[35,74254,9627],{},[35,74256,9630],{},[1159,74258],{},[35,74260,9635],{},{"title":13,"searchDepth":14,"depth":14,"links":74262},[74263,74264,74265,74266],{"id":9436,"depth":14,"text":9437},{"id":9473,"depth":14,"text":9474},{"id":9531,"depth":14,"text":9532},{"id":9563,"depth":14,"text":9564},{},{"title":9425,"description":9644},{"loc":8917},{"id":9652,"title":9653,"body":74271,"contentId":9276,"date":11286,"description":11287,"extension":18,"meta":75679,"navigation":20,"path":11289,"rawbody":11290,"robots":23,"schemaOrg":23,"seo":75680,"sitemap":75681,"stem":11293,"__hash__":11294},{"type":7,"value":74272,"toc":75672},[74273,74277,74279,74281,74286,74291,74293,74295,74297,74311,75670],[35,74274,9658,74275],{},[3029,74276,9661],{"id":9271},[35,74278,9664],{},[1159,74280],{},[35,74282,9669,74283,9675],{},[39,74284,9674],{"href":9672,"rel":74285},[77],[35,74287,9678,74288,9684],{},[39,74289,9683],{"href":9681,"rel":74290},[77],[35,74292,9687],{},[51,74294,9691],{"id":9690},[35,74296,9694],{},[35,74298,9697,74299,9701,74301,9705,74303,9709,74305,9713,74307,9716,74309,9720],{},[156,74300,9700],{},[156,74302,9704],{},[156,74304,9708],{},[156,74306,9712],{},[156,74308,9712],{},[156,74310,9719],{},[7905,74312,74313,74315,74321,74502,74508,74510,74512,74521,74523,74528,74530,74534,74536,74540,74544,74546,74556,74558,74584,74586,74694,74696,74701,74703,74823,74825,74881,74883,74887,74893,75121,75125,75127,75133,75147,75153,75285,75287,75353,75355,75359,75461,75463,75585,75589,75623,75628,75632,75634,75636,75638,75662,75666,75668],{"text":9723,"href":9724},[35,74314,9727],{},[35,74316,9730,74317,9734,74319,9738],{},[156,74318,9733],{},[156,74320,9737],{},[192,74322,74323],{"className":9741,"code":9742,"language":1373,"meta":13,"style":13},[156,74324,74325,74345,74369,74413,74429,74433,74460,74464,74492,74496],{"__ignoreMap":13},[200,74326,74327,74329,74331,74333,74335,74337,74339,74341,74343],{"class":202,"line":203},[200,74328,18],{"class":210},[200,74330,215],{"class":214},[200,74332,9753],{"class":223},[200,74334,227],{"class":210},[200,74336,227],{"class":214},[200,74338,18],{"class":386},[200,74340,2089],{"class":214},[200,74342,393],{"class":379},[200,74344,280],{"class":214},[200,74346,74347,74349,74351,74353,74355,74357,74359,74361,74363,74365,74367],{"class":202,"line":14},[200,74348,3723],{"class":379},[200,74350,9772],{"class":210},[200,74352,510],{"class":214},[200,74354,9777],{"class":210},[200,74356,215],{"class":214},[200,74358,9782],{"class":210},[200,74360,215],{"class":214},[200,74362,9787],{"class":210},[200,74364,215],{"class":214},[200,74366,9737],{"class":210},[200,74368,9794],{"class":214},[200,74370,74371,74373,74375,74377,74379,74381,74383,74385,74387,74389,74391,74393,74395,74397,74399,74401,74403,74405,74407,74409,74411],{"class":202,"line":255},[200,74372,9799],{"class":210},[200,74374,215],{"class":214},[200,74376,9782],{"class":210},[200,74378,215],{"class":214},[200,74380,9787],{"class":210},[200,74382,215],{"class":214},[200,74384,9737],{"class":223},[200,74386,510],{"class":214},[200,74388,1095],{"class":214},[200,74390,9818],{"class":386},[200,74392,550],{"class":214},[200,74394,9823],{"class":386},[200,74396,550],{"class":214},[200,74398,9828],{"class":386},[200,74400,550],{"class":214},[200,74402,4877],{"class":386},[200,74404,550],{"class":214},[200,74406,9837],{"class":386},[200,74408,2089],{"class":214},[200,74410,393],{"class":379},[200,74412,280],{"class":214},[200,74414,74415,74417,74419,74421,74423,74425,74427],{"class":202,"line":272},[200,74416,1999],{"class":379},[200,74418,9850],{"class":210},[200,74420,510],{"class":214},[200,74422,9855],{"class":210},[200,74424,9858],{"class":235},[200,74426,9861],{"class":210},[200,74428,4496],{"class":235},[200,74430,74431],{"class":202,"line":283},[200,74432,529],{"emptyLinePlaceholder":20},[200,74434,74436,74438,74440,74442,74444,74446,74448,74450,74452,74454,74456,74458],{"class":74435,"line":300},[202,9871],[200,74437,9874],{"class":210},[200,74439,215],{"class":214},[200,74441,9879],{"class":223},[200,74443,227],{"class":235},[200,74445,249],{"class":214},[200,74447,9886],{"class":245},[200,74449,249],{"class":214},[200,74451,550],{"class":214},[200,74453,242],{"class":214},[200,74455,9895],{"class":245},[200,74457,249],{"class":214},[200,74459,5267],{"class":235},[200,74461,74462],{"class":202,"line":310},[200,74463,529],{"emptyLinePlaceholder":20},[200,74465,74466,74468,74470,74472,74474,74476,74478,74480,74482,74484,74486,74488,74490],{"class":202,"line":336},[200,74467,3296],{"class":206},[200,74469,9772],{"class":223},[200,74471,227],{"class":235},[200,74473,9818],{"class":210},[200,74475,550],{"class":214},[200,74477,9823],{"class":210},[200,74479,550],{"class":214},[200,74481,9828],{"class":210},[200,74483,550],{"class":214},[200,74485,4877],{"class":210},[200,74487,550],{"class":214},[200,74489,9837],{"class":210},[200,74491,416],{"class":235},[200,74493,74494],{"class":202,"line":342},[200,74495,4490],{"class":214},[200,74497,74498,74500],{"class":202,"line":365},[200,74499,413],{"class":214},[200,74501,416],{"class":210},[35,74503,9944,74504,9948,74506,9952],{},[156,74505,9947],{},[156,74507,9951],{},[51,74509,9956],{"id":9955},[35,74511,9959],{},[939,74513,74514],{},[35,74515,74516,9966,74518,9972],{},[200,74517,945],{},[39,74519,9971],{"href":9969,"rel":74520},[77],[35,74522,9975],{},[35,74524,9978,74525,9984],{},[39,74526,9983],{"href":9981,"rel":74527},[77],[51,74529,9988],{"id":9987},[35,74531,9991,74532,9996],{},[39,74533,9995],{"href":9994},[35,74535,9999],{},[35,74537,10002,74538],{},[1582,74539,10006],{"repo":10005},[35,74541,10009,74542,10013],{},[156,74543,10012],{},[35,74545,10016],{},[192,74547,74548],{"className":1355,"code":10019,"language":1358,"meta":13,"style":13},[156,74549,74550],{"__ignoreMap":13},[200,74551,74552,74554],{"class":202,"line":203},[200,74553,10026],{"class":1978},[200,74555,10029],{"class":245},[35,74557,10032],{},[192,74559,74560],{"className":5891,"code":10035,"language":18,"meta":13,"style":13},[156,74561,74562,74568,74572],{"__ignoreMap":13},[200,74563,74564,74566],{"class":202,"line":203},[200,74565,10042],{"class":214},[200,74567,10045],{"class":1978},[200,74569,74570],{"class":202,"line":14},[200,74571,529],{"emptyLinePlaceholder":20},[200,74573,74574,74576,74578,74580,74582],{"class":202,"line":255},[200,74575,10054],{"class":214},[200,74577,10057],{"class":245},[200,74579,10060],{"class":214},[200,74581,10064],{"class":10063},[200,74583,416],{"class":214},[35,74585,10069],{},[192,74587,74588],{"className":591,"code":10072,"language":593,"meta":13,"style":13},[156,74589,74590,74606],{"__ignoreMap":13},[200,74591,74592,74594,74596,74598,74600,74602,74604],{"class":202,"line":203},[200,74593,600],{"class":214},[200,74595,10081],{"class":235},[200,74597,675],{"class":214},[200,74599,10086],{"class":210},[200,74601,681],{"class":214},[200,74603,10081],{"class":235},[200,74605,648],{"class":214},[200,74607,74608,74610,74612,74614,74616,74618,74620,74622,74624,74626,74628,74630,74632,74634,74636,74638,74640,74642,74644,74646,74648,74650,74652,74654,74656,74658,74660,74662,74664,74666,74668,74670,74672,74674,74676,74678,74680,74682,74684,74686,74688,74690,74692],{"class":202,"line":14},[200,74609,600],{"class":214},[200,74611,35],{"class":235},[200,74613,10101],{"class":214},[200,74615,1690],{"class":235},[200,74617,10106],{"class":379},[200,74619,449],{"class":214},[200,74621,611],{"class":214},[200,74623,10113],{"class":245},[200,74625,10116],{"class":210},[200,74627,10119],{"class":245},[200,74629,10116],{"class":210},[200,74631,10124],{"class":245},[200,74633,10116],{"class":210},[200,74635,10129],{"class":245},[200,74637,611],{"class":214},[200,74639,10134],{"class":379},[200,74641,449],{"class":214},[200,74643,611],{"class":214},[200,74645,10057],{"class":245},[200,74647,611],{"class":214},[200,74649,10145],{"class":379},[200,74651,449],{"class":214},[200,74653,611],{"class":214},[200,74655,9895],{"class":245},[200,74657,611],{"class":214},[200,74659,10156],{"class":379},[200,74661,449],{"class":214},[200,74663,611],{"class":214},[200,74665,10163],{"class":245},[200,74667,611],{"class":214},[200,74669,10168],{"class":379},[200,74671,449],{"class":214},[200,74673,611],{"class":214},[200,74675,10175],{"class":245},[200,74677,611],{"class":214},[200,74679,10180],{"class":379},[200,74681,449],{"class":214},[200,74683,611],{"class":214},[200,74685,10187],{"class":245},[200,74687,611],{"class":214},[200,74689,10192],{"class":214},[200,74691,35],{"class":235},[200,74693,648],{"class":214},[51,74695,10200],{"id":10199},[35,74697,10203,74698,215],{},[39,74699,10208],{"href":10206,"rel":74700},[77],[35,74702,10211],{},[192,74704,74705],{"className":1370,"code":10214,"language":1373,"meta":13,"style":13},[156,74706,74707,74725,74729,74741,74745,74757,74779,74783,74791,74795,74809,74813,74817],{"__ignoreMap":13},[200,74708,74709,74711,74713,74715,74717,74719,74721,74723],{"class":202,"line":203},[200,74710,973],{"class":206},[200,74712,318],{"class":214},[200,74714,10225],{"class":210},[200,74716,981],{"class":214},[200,74718,984],{"class":206},[200,74720,242],{"class":214},[200,74722,9983],{"class":245},[200,74724,992],{"class":214},[200,74726,74727],{"class":202,"line":14},[200,74728,529],{"emptyLinePlaceholder":20},[200,74730,74731,74733,74735,74737,74739],{"class":202,"line":255},[200,74732,443],{"class":379},[200,74734,10246],{"class":210},[200,74736,449],{"class":214},[200,74738,10225],{"class":223},[200,74740,466],{"class":210},[200,74742,74743],{"class":202,"line":272},[200,74744,529],{"emptyLinePlaceholder":20},[200,74746,74747,74749,74751,74753,74755],{"class":202,"line":283},[200,74748,3711],{"class":379},[200,74750,4987],{"class":379},[200,74752,10265],{"class":223},[200,74754,1532],{"class":214},[200,74756,280],{"class":214},[200,74758,74759,74761,74763,74765,74767,74769,74771,74773,74775,74777],{"class":202,"line":300},[200,74760,3723],{"class":379},[200,74762,10276],{"class":210},[200,74764,510],{"class":214},[200,74766,452],{"class":206},[200,74768,9777],{"class":210},[200,74770,215],{"class":214},[200,74772,10287],{"class":223},[200,74774,227],{"class":235},[200,74776,3318],{"class":214},[200,74778,10294],{"class":245},[200,74780,74781],{"class":202,"line":310},[200,74782,529],{"emptyLinePlaceholder":20},[200,74784,74785,74787,74789],{"class":202,"line":336},[200,74786,10303],{"class":245},[200,74788,3318],{"class":214},[200,74790,416],{"class":235},[200,74792,74793],{"class":202,"line":342},[200,74794,529],{"emptyLinePlaceholder":20},[200,74796,74797,74799,74801,74803,74805,74807],{"class":202,"line":365},[200,74798,1113],{"class":210},[200,74800,215],{"class":214},[200,74802,1118],{"class":223},[200,74804,227],{"class":235},[200,74806,593],{"class":210},[200,74808,416],{"class":235},[200,74810,74811],{"class":202,"line":371},[200,74812,1130],{"class":214},[200,74814,74815],{"class":202,"line":398},[200,74816,529],{"emptyLinePlaceholder":20},[200,74818,74819,74821],{"class":202,"line":405},[200,74820,10338],{"class":223},[200,74822,466],{"class":210},[35,74824,10343],{},[192,74826,74827],{"className":591,"code":10346,"language":593,"meta":13,"style":13},[156,74828,74829,74845],{"__ignoreMap":13},[200,74830,74831,74833,74835,74837,74839,74841,74843],{"class":202,"line":203},[200,74832,600],{"class":214},[200,74834,10081],{"class":235},[200,74836,675],{"class":214},[200,74838,10086],{"class":210},[200,74840,681],{"class":214},[200,74842,10081],{"class":235},[200,74844,648],{"class":214},[200,74846,74847,74849,74851,74853,74855,74857,74859,74861,74863,74865,74867,74869,74871,74873,74875,74877,74879],{"class":202,"line":14},[200,74848,600],{"class":214},[200,74850,35],{"class":235},[200,74852,10101],{"class":214},[200,74854,1690],{"class":235},[200,74856,10106],{"class":379},[200,74858,449],{"class":214},[200,74860,611],{"class":214},[200,74862,10064],{"class":245},[200,74864,611],{"class":214},[200,74866,10134],{"class":379},[200,74868,449],{"class":214},[200,74870,611],{"class":214},[200,74872,10057],{"class":245},[200,74874,611],{"class":214},[200,74876,10192],{"class":214},[200,74878,35],{"class":235},[200,74880,648],{"class":214},[35,74882,10403],{},[35,74884,10406,74885,10409],{},[156,74886,9737],{},[35,74888,10412,74889,10415,74891,9738],{},[156,74890,9753],{},[156,74892,9737],{},[192,74894,74895],{"className":1370,"code":10420,"language":1373,"meta":13,"style":13},[156,74896,74897,74917,74935,74939,74951,74955,74979,75003,75049,75077,75081,75087,75091,75103,75107,75111,75115],{"__ignoreMap":13},[200,74898,74899,74901,74903,74905,74907,74909,74911,74913,74915],{"class":202,"line":203},[200,74900,973],{"class":206},[200,74902,321],{"class":206},[200,74904,318],{"class":214},[200,74906,10433],{"class":210},[200,74908,981],{"class":214},[200,74910,984],{"class":206},[200,74912,242],{"class":214},[200,74914,9983],{"class":245},[200,74916,992],{"class":214},[200,74918,74919,74921,74923,74925,74927,74929,74931,74933],{"class":202,"line":14},[200,74920,973],{"class":206},[200,74922,318],{"class":214},[200,74924,10225],{"class":210},[200,74926,981],{"class":214},[200,74928,984],{"class":206},[200,74930,242],{"class":214},[200,74932,9983],{"class":245},[200,74934,992],{"class":214},[200,74936,74937],{"class":202,"line":255},[200,74938,529],{"emptyLinePlaceholder":20},[200,74940,74941,74943,74945,74947,74949],{"class":202,"line":272},[200,74942,443],{"class":379},[200,74944,10246],{"class":210},[200,74946,449],{"class":214},[200,74948,10225],{"class":223},[200,74950,466],{"class":210},[200,74952,74953],{"class":202,"line":283},[200,74954,529],{"emptyLinePlaceholder":20},[200,74956,74957,74959,74961,74963,74965,74967,74969,74971,74973,74975,74977],{"class":202,"line":300},[200,74958,18],{"class":210},[200,74960,215],{"class":214},[200,74962,9753],{"class":223},[200,74964,227],{"class":210},[200,74966,227],{"class":214},[200,74968,18],{"class":386},[200,74970,239],{"class":214},[200,74972,10433],{"class":1978},[200,74974,2089],{"class":214},[200,74976,393],{"class":379},[200,74978,280],{"class":214},[200,74980,74981,74983,74985,74987,74989,74991,74993,74995,74997,74999,75001],{"class":202,"line":310},[200,74982,3723],{"class":379},[200,74984,9772],{"class":210},[200,74986,510],{"class":214},[200,74988,9777],{"class":210},[200,74990,215],{"class":214},[200,74992,9782],{"class":210},[200,74994,215],{"class":214},[200,74996,9787],{"class":210},[200,74998,215],{"class":214},[200,75000,9737],{"class":210},[200,75002,9794],{"class":214},[200,75004,75005,75007,75009,75011,75013,75015,75017,75019,75021,75023,75025,75027,75029,75031,75033,75035,75037,75039,75041,75043,75045,75047],{"class":202,"line":336},[200,75006,9799],{"class":210},[200,75008,215],{"class":214},[200,75010,9782],{"class":210},[200,75012,215],{"class":214},[200,75014,9787],{"class":210},[200,75016,215],{"class":214},[200,75018,9737],{"class":223},[200,75020,510],{"class":214},[200,75022,380],{"class":379},[200,75024,1095],{"class":214},[200,75026,9818],{"class":386},[200,75028,550],{"class":214},[200,75030,9823],{"class":386},[200,75032,550],{"class":214},[200,75034,9828],{"class":386},[200,75036,550],{"class":214},[200,75038,4877],{"class":386},[200,75040,550],{"class":214},[200,75042,9837],{"class":386},[200,75044,2089],{"class":214},[200,75046,393],{"class":379},[200,75048,280],{"class":214},[200,75050,75051,75053,75055,75057,75059,75061,75063,75065,75067,75069,75071,75073,75075],{"class":202,"line":342},[200,75052,3296],{"class":206},[200,75054,9772],{"class":223},[200,75056,227],{"class":235},[200,75058,9818],{"class":210},[200,75060,550],{"class":214},[200,75062,9823],{"class":210},[200,75064,550],{"class":214},[200,75066,9828],{"class":210},[200,75068,550],{"class":214},[200,75070,4877],{"class":210},[200,75072,550],{"class":214},[200,75074,9837],{"class":210},[200,75076,416],{"class":235},[200,75078,75079],{"class":202,"line":365},[200,75080,4490],{"class":214},[200,75082,75083,75085],{"class":202,"line":371},[200,75084,413],{"class":214},[200,75086,416],{"class":210},[200,75088,75089],{"class":202,"line":398},[200,75090,529],{"emptyLinePlaceholder":20},[200,75092,75093,75095,75097,75099,75101],{"class":202,"line":405},[200,75094,3711],{"class":379},[200,75096,4987],{"class":379},[200,75098,10265],{"class":223},[200,75100,1532],{"class":214},[200,75102,280],{"class":214},[200,75104,75105],{"class":202,"line":410},[200,75106,10634],{"class":401},[200,75108,75109],{"class":202,"line":861},[200,75110,1130],{"class":214},[200,75112,75113],{"class":202,"line":890},[200,75114,529],{"emptyLinePlaceholder":20},[200,75116,75117,75119],{"class":202,"line":900},[200,75118,10338],{"class":223},[200,75120,466],{"class":210},[35,75122,10651,75123,10654],{},[156,75124,9737],{},[35,75126,10657],{},[35,75128,419,75129,10662,75131,10665],{},[156,75130,9818],{},[156,75132,9861],{},[192,75134,75135],{"className":1370,"code":10668,"language":1373,"meta":13,"style":13},[156,75136,75137],{"__ignoreMap":13},[200,75138,75139,75141,75143,75145],{"class":202,"line":203},[200,75140,443],{"class":379},[200,75142,10677],{"class":210},[200,75144,449],{"class":214},[200,75146,10682],{"class":210},[35,75148,10685,75149,10688,75151,215],{},[156,75150,9886],{},[156,75152,9895],{},[192,75154,75155],{"className":1370,"code":10693,"language":1373,"meta":13,"style":13},[156,75156,75157,75203,75219,75223,75249,75253,75281],{"__ignoreMap":13},[200,75158,75159,75161,75163,75165,75167,75169,75171,75173,75175,75177,75179,75181,75183,75185,75187,75189,75191,75193,75195,75197,75199,75201],{"class":202,"line":203},[200,75160,18],{"class":210},[200,75162,215],{"class":214},[200,75164,9782],{"class":210},[200,75166,215],{"class":214},[200,75168,9787],{"class":210},[200,75170,215],{"class":214},[200,75172,9737],{"class":223},[200,75174,510],{"class":214},[200,75176,380],{"class":379},[200,75178,1095],{"class":214},[200,75180,9818],{"class":386},[200,75182,550],{"class":214},[200,75184,9823],{"class":386},[200,75186,550],{"class":214},[200,75188,9828],{"class":386},[200,75190,550],{"class":214},[200,75192,4877],{"class":386},[200,75194,550],{"class":214},[200,75196,9837],{"class":386},[200,75198,2089],{"class":214},[200,75200,393],{"class":379},[200,75202,280],{"class":214},[200,75204,75205,75207,75209,75211,75213,75215,75217],{"class":202,"line":14},[200,75206,3723],{"class":379},[200,75208,9850],{"class":210},[200,75210,510],{"class":214},[200,75212,9855],{"class":210},[200,75214,9858],{"class":235},[200,75216,9861],{"class":210},[200,75218,4496],{"class":235},[200,75220,75221],{"class":202,"line":255},[200,75222,529],{"emptyLinePlaceholder":20},[200,75224,75225,75227,75229,75231,75233,75235,75237,75239,75241,75243,75245,75247],{"class":202,"line":272},[200,75226,10766],{"class":210},[200,75228,215],{"class":214},[200,75230,9879],{"class":223},[200,75232,227],{"class":235},[200,75234,249],{"class":214},[200,75236,9886],{"class":245},[200,75238,249],{"class":214},[200,75240,550],{"class":214},[200,75242,242],{"class":214},[200,75244,9895],{"class":245},[200,75246,249],{"class":214},[200,75248,416],{"class":235},[200,75250,75251],{"class":202,"line":283},[200,75252,529],{"emptyLinePlaceholder":20},[200,75254,75255,75257,75259,75261,75263,75265,75267,75269,75271,75273,75275,75277,75279],{"class":202,"line":300},[200,75256,3743],{"class":206},[200,75258,9772],{"class":223},[200,75260,227],{"class":235},[200,75262,9818],{"class":210},[200,75264,550],{"class":214},[200,75266,9823],{"class":210},[200,75268,550],{"class":214},[200,75270,9828],{"class":210},[200,75272,550],{"class":214},[200,75274,4877],{"class":210},[200,75276,550],{"class":214},[200,75278,9837],{"class":210},[200,75280,416],{"class":235},[200,75282,75283],{"class":202,"line":310},[200,75284,1130],{"class":214},[35,75286,10827],{},[192,75288,75289],{"className":591,"code":10830,"language":593,"meta":13,"style":13},[156,75290,75291,75307],{"__ignoreMap":13},[200,75292,75293,75295,75297,75299,75301,75303,75305],{"class":202,"line":203},[200,75294,600],{"class":214},[200,75296,10081],{"class":235},[200,75298,675],{"class":214},[200,75300,10086],{"class":210},[200,75302,681],{"class":214},[200,75304,10081],{"class":235},[200,75306,648],{"class":214},[200,75308,75309,75311,75313,75315,75317,75319,75321,75323,75325,75327,75329,75331,75333,75335,75337,75339,75341,75343,75345,75347,75349,75351],{"class":202,"line":14},[200,75310,600],{"class":214},[200,75312,35],{"class":235},[200,75314,10101],{"class":214},[200,75316,1690],{"class":235},[200,75318,10106],{"class":379},[200,75320,449],{"class":214},[200,75322,611],{"class":214},[200,75324,10064],{"class":245},[200,75326,611],{"class":214},[200,75328,10134],{"class":379},[200,75330,449],{"class":214},[200,75332,611],{"class":214},[200,75334,10057],{"class":245},[200,75336,611],{"class":214},[200,75338,10145],{"class":379},[200,75340,449],{"class":214},[200,75342,611],{"class":214},[200,75344,9895],{"class":245},[200,75346,611],{"class":214},[200,75348,10192],{"class":214},[200,75350,35],{"class":235},[200,75352,648],{"class":214},[35,75354,10897],{},[35,75356,10900,75357],{},[3029,75358,10903],{"id":9271},[192,75360,75361],{"className":1370,"code":10906,"language":1373,"meta":13,"style":13},[156,75362,75363,75395,75409,75413],{"__ignoreMap":13},[200,75364,75365,75367,75369,75371,75373,75375,75377,75379,75381,75383,75385,75387,75389,75391,75393],{"class":202,"line":203},[200,75366,443],{"class":379},[200,75368,10915],{"class":210},[200,75370,449],{"class":214},[200,75372,452],{"class":206},[200,75374,3299],{"class":223},[200,75376,10924],{"class":210},[200,75378,215],{"class":214},[200,75380,10929],{"class":223},[200,75382,227],{"class":210},[200,75384,10934],{"class":386},[200,75386,393],{"class":379},[200,75388,10939],{"class":210},[200,75390,215],{"class":214},[200,75392,10944],{"class":223},[200,75394,10947],{"class":210},[200,75396,75397,75399,75401,75403,75405,75407],{"class":202,"line":14},[200,75398,443],{"class":379},[200,75400,10954],{"class":210},[200,75402,449],{"class":214},[200,75404,452],{"class":206},[200,75406,10961],{"class":223},[200,75408,10964],{"class":210},[200,75410,75411],{"class":202,"line":255},[200,75412,529],{"emptyLinePlaceholder":20},[200,75414,75415,75417,75419,75421,75423,75425,75427,75429,75431,75433,75435,75437,75439,75441,75443,75445,75447,75449,75451,75453,75455,75457,75459],{"class":202,"line":272},[200,75416,443],{"class":379},[200,75418,10975],{"class":210},[200,75420,449],{"class":214},[200,75422,10980],{"class":223},[200,75424,10983],{"class":210},[200,75426,215],{"class":214},[200,75428,1472],{"class":223},[200,75430,10990],{"class":210},[200,75432,215],{"class":214},[200,75434,10995],{"class":210},[200,75436,550],{"class":214},[200,75438,5041],{"class":210},[200,75440,215],{"class":214},[200,75442,11004],{"class":210},[200,75444,550],{"class":214},[200,75446,5041],{"class":210},[200,75448,215],{"class":214},[200,75450,11013],{"class":210},[200,75452,550],{"class":214},[200,75454,11018],{"class":2045},[200,75456,550],{"class":214},[200,75458,11018],{"class":2045},[200,75460,416],{"class":210},[35,75462,11027],{},[192,75464,75465],{"className":1370,"code":11030,"language":1373,"meta":13,"style":13},[156,75466,75467,75485,75489,75519,75549],{"__ignoreMap":13},[200,75468,75469,75471,75473,75475,75477,75479,75481,75483],{"class":202,"line":203},[200,75470,973],{"class":206},[200,75472,318],{"class":214},[200,75474,11041],{"class":210},[200,75476,981],{"class":214},[200,75478,984],{"class":206},[200,75480,242],{"class":214},[200,75482,11050],{"class":245},[200,75484,992],{"class":214},[200,75486,75487],{"class":202,"line":14},[200,75488,529],{"emptyLinePlaceholder":20},[200,75490,75491,75493,75495,75497,75499,75501,75503,75505,75507,75509,75511,75513,75515,75517],{"class":202,"line":255},[200,75492,11061],{"class":210},[200,75494,215],{"class":214},[200,75496,9879],{"class":223},[200,75498,227],{"class":210},[200,75500,249],{"class":214},[200,75502,11004],{"class":245},[200,75504,249],{"class":214},[200,75506,550],{"class":214},[200,75508,5041],{"class":210},[200,75510,215],{"class":214},[200,75512,11004],{"class":210},[200,75514,215],{"class":214},[200,75516,11086],{"class":223},[200,75518,10947],{"class":210},[200,75520,75521,75523,75525,75527,75529,75531,75533,75535,75537,75539,75541,75543,75545,75547],{"class":202,"line":272},[200,75522,11061],{"class":210},[200,75524,215],{"class":214},[200,75526,9879],{"class":223},[200,75528,227],{"class":210},[200,75530,249],{"class":214},[200,75532,11013],{"class":245},[200,75534,249],{"class":214},[200,75536,550],{"class":214},[200,75538,5041],{"class":210},[200,75540,215],{"class":214},[200,75542,11013],{"class":210},[200,75544,215],{"class":214},[200,75546,11086],{"class":223},[200,75548,10947],{"class":210},[200,75550,75551,75553,75555,75557,75559,75561,75563,75565,75567,75569,75571,75573,75575,75577,75579,75581,75583],{"class":202,"line":283},[200,75552,11061],{"class":210},[200,75554,215],{"class":214},[200,75556,9879],{"class":223},[200,75558,227],{"class":210},[200,75560,249],{"class":214},[200,75562,1171],{"class":245},[200,75564,249],{"class":214},[200,75566,550],{"class":214},[200,75568,11139],{"class":214},[200,75570,11142],{"class":245},[200,75572,4853],{"class":214},[200,75574,11147],{"class":223},[200,75576,11150],{"class":210},[200,75578,413],{"class":214},[200,75580,11155],{"class":245},[200,75582,3318],{"class":214},[200,75584,416],{"class":210},[35,75586,11162,75587,11166],{},[156,75588,11165],{},[192,75590,75591],{"className":1370,"code":11169,"language":1373,"meta":13,"style":13},[156,75592,75593],{"__ignoreMap":13},[200,75594,75595,75597,75599,75601,75603,75605,75607,75609,75611,75613,75615,75617,75619,75621],{"class":202,"line":203},[200,75596,11061],{"class":210},[200,75598,215],{"class":214},[200,75600,9879],{"class":223},[200,75602,227],{"class":210},[200,75604,249],{"class":214},[200,75606,11165],{"class":245},[200,75608,249],{"class":214},[200,75610,550],{"class":214},[200,75612,11192],{"class":214},[200,75614,11165],{"class":210},[200,75616,413],{"class":214},[200,75618,11199],{"class":245},[200,75620,3318],{"class":214},[200,75622,416],{"class":210},[35,75624,11206,75625,11210],{},[39,75626,10208],{"href":10206,"rel":75627},[77],[35,75629,11213,75630,11217],{},[156,75631,11216],{},[51,75633,11221],{"id":11220},[35,75635,11224],{},[35,75637,11227],{},[68,75639,75640,75644,75650],{},[71,75641,75642,11235],{},[6850,75643,11234],{},[71,75645,75646,11241,75648],{},[6850,75647,11240],{},[1217,75649,11244],{},[71,75651,75652,11250,75654],{},[6850,75653,11249],{},[1217,75655,11253,75656,1638,75659,11264],{},[39,75657,11258],{"href":11256,"rel":75658},[77],[39,75660,11263],{"href":11261,"rel":75661},[77],[35,75663,75664],{},[1217,75665,11269],{},[35,75667,11272],{},[35,75669,11275],{},[1171,75671,11278],{},{"title":13,"searchDepth":14,"depth":14,"links":75673},[75674,75675,75676,75677,75678],{"id":9690,"depth":14,"text":9691},{"id":9955,"depth":14,"text":9956},{"id":9987,"depth":14,"text":9988},{"id":10199,"depth":14,"text":10200},{"id":11220,"depth":14,"text":11221},{},{"title":9653,"description":11287},{"loc":11289},{"id":11296,"title":11297,"body":75683,"contentId":9271,"date":13770,"description":13771,"extension":18,"meta":77841,"navigation":20,"path":9994,"rawbody":13773,"robots":23,"schemaOrg":23,"seo":77842,"sitemap":77843,"stem":13778,"__hash__":13779},{"type":7,"value":75684,"toc":77834},[75685,75687,75689,75693,75695,75697,75699,75701,75709,75713,75724,75726,75728,75732,75734,75736,75784,75786,75888,75890,75894,75900,75902,75904,75912,75918,75920,75928,75930,75932,75941,75945,76065,76067,76074,76205,76207,76382,76388,77832],[35,75686,11302],{},[35,75688,11305],{},[35,75690,11308,75691],{},[1217,75692,11311],{},[1159,75694],{},[35,75696,11316],{},[35,75698,11319],{},[35,75700,11322],{},[130,75702,132,75703,132,75707],{},[134,75704,136,75705,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,75706],{"src":11329,"type":141},[143,75708,11332],{},[35,75710,11335,75711],{},[1217,75712,11338],{},[939,75714,75715],{},[35,75716,75717,11345,75719,11350,75721,215],{},[200,75718,945],{},[39,75720,11349],{"href":11348},[39,75722,11355],{"href":11353,"rel":75723},[77],[51,75725,11359],{"id":11358},[35,75727,11362],{},[35,75729,11365,75730],{},[1217,75731,11368],{},[35,75733,11371],{},[35,75735,11374],{},[192,75737,75738],{"className":591,"code":11377,"language":593,"meta":13,"style":13},[156,75739,75740,75748,75776],{"__ignoreMap":13},[200,75741,75742,75744,75746],{"class":202,"line":203},[200,75743,600],{"class":214},[200,75745,11386],{"class":235},[200,75747,648],{"class":214},[200,75749,75750,75752,75754,75756,75758,75760,75762,75764,75766,75768,75770,75772,75774],{"class":202,"line":14},[200,75751,657],{"class":214},[200,75753,1690],{"class":235},[200,75755,10106],{"class":379},[200,75757,449],{"class":214},[200,75759,611],{"class":214},[200,75761,11403],{"class":245},[200,75763,611],{"class":214},[200,75765,10134],{"class":379},[200,75767,449],{"class":214},[200,75769,611],{"class":214},[200,75771,11414],{"class":245},[200,75773,611],{"class":214},[200,75775,648],{"class":214},[200,75777,75778,75780,75782],{"class":202,"line":255},[200,75779,681],{"class":214},[200,75781,11386],{"class":235},[200,75783,648],{"class":214},[35,75785,11429],{},[192,75787,75788],{"className":591,"code":11432,"language":593,"meta":13,"style":13},[156,75789,75790,75798,75804,75816,75828,75840,75852,75864,75876,75880],{"__ignoreMap":13},[200,75791,75792,75794,75796],{"class":202,"line":203},[200,75793,600],{"class":214},[200,75795,11386],{"class":235},[200,75797,648],{"class":214},[200,75799,75800,75802],{"class":202,"line":14},[200,75801,657],{"class":214},[200,75803,11449],{"class":235},[200,75805,75806,75808,75810,75812,75814],{"class":202,"line":255},[200,75807,11454],{"class":379},[200,75809,449],{"class":214},[200,75811,611],{"class":214},[200,75813,11461],{"class":245},[200,75815,617],{"class":214},[200,75817,75818,75820,75822,75824,75826],{"class":202,"line":272},[200,75819,11468],{"class":379},[200,75821,449],{"class":214},[200,75823,611],{"class":214},[200,75825,11414],{"class":245},[200,75827,617],{"class":214},[200,75829,75830,75832,75834,75836,75838],{"class":202,"line":283},[200,75831,11481],{"class":379},[200,75833,449],{"class":214},[200,75835,611],{"class":214},[200,75837,11488],{"class":245},[200,75839,617],{"class":214},[200,75841,75842,75844,75846,75848,75850],{"class":202,"line":300},[200,75843,11495],{"class":379},[200,75845,449],{"class":214},[200,75847,611],{"class":214},[200,75849,11502],{"class":245},[200,75851,617],{"class":214},[200,75853,75854,75856,75858,75860,75862],{"class":202,"line":310},[200,75855,11509],{"class":379},[200,75857,449],{"class":214},[200,75859,611],{"class":214},[200,75861,9895],{"class":245},[200,75863,617],{"class":214},[200,75865,75866,75868,75870,75872,75874],{"class":202,"line":336},[200,75867,11522],{"class":379},[200,75869,449],{"class":214},[200,75871,611],{"class":214},[200,75873,11529],{"class":245},[200,75875,617],{"class":214},[200,75877,75878],{"class":202,"line":342},[200,75879,11536],{"class":214},[200,75881,75882,75884,75886],{"class":202,"line":365},[200,75883,681],{"class":214},[200,75885,11386],{"class":235},[200,75887,648],{"class":214},[35,75889,11547],{},[35,75891,11550,75892],{},[1217,75893,11553],{},[939,75895,75896],{},[35,75897,75898,11560],{},[200,75899,945],{},[51,75901,11564],{"id":11563},[35,75903,11567],{},[35,75905,11570,75906,11574,75908,11577,75910,11580],{},[156,75907,11573],{},[156,75909,11165],{},[156,75911,11573],{},[939,75913,75914],{},[35,75915,75916,11587],{},[200,75917,945],{},[35,75919,11590],{},[35,75921,11593,75922,11599,75925,215],{},[39,75923,11598],{"href":11596,"rel":75924},[77],[39,75926,11604],{"href":11602,"rel":75927},[77],[35,75929,11607],{},[35,75931,11610],{},[939,75933,75934],{},[35,75935,75936,11617,75938,215],{},[200,75937,945],{},[39,75939,11622],{"href":11620,"rel":75940},[77],[35,75942,2908,75943,11627],{},[156,75944,1460],{},[192,75946,75947],{"className":1370,"code":11630,"filename":1460,"language":1373,"meta":13,"style":13},[156,75948,75949,75963,75981,75985,75997,76005,76013,76023,76029,76043,76047,76053,76059],{"__ignoreMap":13},[200,75950,75951,75953,75955,75957,75959,75961],{"class":202,"line":203},[200,75952,973],{"class":206},[200,75954,11639],{"class":210},[200,75956,1472],{"class":206},[200,75958,242],{"class":214},[200,75960,11646],{"class":245},[200,75962,992],{"class":214},[200,75964,75965,75967,75969,75971,75973,75975,75977,75979],{"class":202,"line":14},[200,75966,973],{"class":206},[200,75968,318],{"class":214},[200,75970,1488],{"class":210},[200,75972,981],{"class":214},[200,75974,984],{"class":206},[200,75976,242],{"class":214},[200,75978,1497],{"class":245},[200,75980,992],{"class":214},[200,75982,75983],{"class":202,"line":255},[200,75984,529],{"emptyLinePlaceholder":20},[200,75986,75987,75989,75991,75993,75995],{"class":202,"line":272},[200,75988,1380],{"class":206},[200,75990,1383],{"class":206},[200,75992,1488],{"class":223},[200,75994,227],{"class":210},[200,75996,230],{"class":214},[200,75998,75999,76001,76003],{"class":202,"line":283},[200,76000,1520],{"class":235},[200,76002,239],{"class":214},[200,76004,1400],{"class":210},[200,76006,76007,76009,76011],{"class":202,"line":300},[200,76008,11695],{"class":223},[200,76010,1532],{"class":210},[200,76012,252],{"class":214},[200,76014,76015,76017,76019,76021],{"class":202,"line":310},[200,76016,11704],{"class":210},[200,76018,1532],{"class":214},[200,76020,393],{"class":379},[200,76022,280],{"class":214},[200,76024,76025,76027],{"class":202,"line":336},[200,76026,11715],{"class":206},[200,76028,280],{"class":214},[200,76030,76031,76033,76035,76037,76039,76041],{"class":202,"line":342},[200,76032,11722],{"class":235},[200,76034,239],{"class":214},[200,76036,242],{"class":214},[200,76038,11598],{"class":245},[200,76040,249],{"class":214},[200,76042,252],{"class":214},[200,76044,76045],{"class":202,"line":365},[200,76046,4478],{"class":214},[200,76048,76049,76051],{"class":202,"line":371},[200,76050,2096],{"class":214},[200,76052,11743],{"class":210},[200,76054,76055,76057],{"class":202,"line":398},[200,76056,1417],{"class":210},[200,76058,252],{"class":214},[200,76060,76061,76063],{"class":202,"line":405},[200,76062,413],{"class":214},[200,76064,416],{"class":210},[35,76066,11758],{},[35,76068,11761,76069,11767,76072,11771],{},[39,76070,11766],{"href":11764,"rel":76071},[77],[156,76073,11770],{},[192,76075,76076],{"className":9741,"code":11774,"filename":1460,"language":1373,"meta":13,"style":13},[156,76077,76078,76088,76094,76108,76126,76151,76155,76159,76163,76167,76173,76179,76187,76191,76195,76199],{"__ignoreMap":13},[200,76079,76080,76082,76084,76086],{"class":202,"line":203},[200,76081,227],{"class":210},[200,76083,1532],{"class":214},[200,76085,393],{"class":379},[200,76087,280],{"class":214},[200,76089,76090,76092],{"class":202,"line":14},[200,76091,3743],{"class":206},[200,76093,280],{"class":214},[200,76095,76096,76098,76100,76102,76104,76106],{"class":202,"line":255},[200,76097,11797],{"class":235},[200,76099,239],{"class":214},[200,76101,242],{"class":214},[200,76103,11598],{"class":245},[200,76105,249],{"class":214},[200,76107,252],{"class":214},[200,76109,76110,76112,76114,76116,76118,76120,76122,76124],{"class":202,"line":272},[200,76111,11812],{"class":379},[200,76113,11815],{"class":235},[200,76115,227],{"class":214},[200,76117,156],{"class":386},[200,76119,550],{"class":214},[200,76121,3224],{"class":386},[200,76123,2089],{"class":214},[200,76125,280],{"class":214},[200,76127,76129,76131,76133,76135,76137,76139,76141,76143,76145,76147,76149],{"class":76128,"line":283},[202,9871],[200,76130,6511],{"class":206},[200,76132,1095],{"class":235},[200,76134,11837],{"class":214},[200,76136,3280],{"class":210},[200,76138,215],{"class":214},[200,76140,11844],{"class":223},[200,76142,227],{"class":235},[200,76144,249],{"class":214},[200,76146,11851],{"class":245},[200,76148,249],{"class":214},[200,76150,6466],{"class":235},[200,76152,76153],{"class":202,"line":300},[200,76154,11860],{"class":206},[200,76156,76157],{"class":202,"line":310},[200,76158,529],{"emptyLinePlaceholder":20},[200,76160,76161],{"class":202,"line":336},[200,76162,11869],{"class":401},[200,76164,76165],{"class":202,"line":342},[200,76166,529],{"emptyLinePlaceholder":20},[200,76168,76169,76171],{"class":202,"line":365},[200,76170,11715],{"class":206},[200,76172,280],{"class":214},[200,76174,76175,76177],{"class":202,"line":371},[200,76176,11884],{"class":210},[200,76178,252],{"class":214},[200,76180,76181,76183,76185],{"class":202,"line":398},[200,76182,11891],{"class":235},[200,76184,239],{"class":214},[200,76186,11896],{"class":214},[200,76188,76189],{"class":202,"line":405},[200,76190,4478],{"class":214},[200,76192,76193],{"class":202,"line":410},[200,76194,4962],{"class":214},[200,76196,76197],{"class":202,"line":861},[200,76198,4490],{"class":214},[200,76200,76201,76203],{"class":202,"line":890},[200,76202,413],{"class":214},[200,76204,11743],{"class":210},[35,76206,11917],{},[192,76208,76209],{"className":9741,"code":11920,"filename":1460,"language":1373,"meta":13,"style":13},[156,76210,76211,76221,76266,76270,76276,76290,76304,76312,76320,76339,76350,76354,76368,76372,76376],{"__ignoreMap":13},[200,76212,76213,76215,76217,76219],{"class":202,"line":203},[200,76214,227],{"class":210},[200,76216,1532],{"class":214},[200,76218,393],{"class":379},[200,76220,280],{"class":214},[200,76222,76224,76226,76228,76230,76232,76234,76236,76238,76240,76242,76244,76246,76248,76250,76252,76254,76256,76258,76260,76262,76264],{"class":76223,"line":14},[202,9871],[200,76225,3723],{"class":379},[200,76227,11940],{"class":210},[200,76229,510],{"class":214},[200,76231,11945],{"class":214},[200,76233,11948],{"class":245},[200,76235,11951],{"class":214},[200,76237,675],{"class":245},[200,76239,11956],{"class":214},[200,76241,11959],{"class":245},[200,76243,9858],{"class":214},[200,76245,11964],{"class":245},[200,76247,11967],{"class":214},[200,76249,11964],{"class":245},[200,76251,11972],{"class":214},[200,76253,11964],{"class":245},[200,76255,11977],{"class":214},[200,76257,675],{"class":245},[200,76259,11956],{"class":214},[200,76261,675],{"class":245},[200,76263,2882],{"class":214},[200,76265,11988],{"class":2045},[200,76267,76268],{"class":202,"line":255},[200,76269,529],{"emptyLinePlaceholder":20},[200,76271,76272,76274],{"class":202,"line":272},[200,76273,3743],{"class":206},[200,76275,280],{"class":214},[200,76277,76278,76280,76282,76284,76286,76288],{"class":202,"line":283},[200,76279,11797],{"class":235},[200,76281,239],{"class":214},[200,76283,242],{"class":214},[200,76285,11598],{"class":245},[200,76287,249],{"class":214},[200,76289,252],{"class":214},[200,76291,76292,76294,76296,76298,76300,76302],{"class":202,"line":300},[200,76293,12017],{"class":235},[200,76295,239],{"class":214},[200,76297,242],{"class":214},[200,76299,192],{"class":245},[200,76301,249],{"class":214},[200,76303,252],{"class":214},[200,76305,76306,76308,76310],{"class":202,"line":310},[200,76307,5036],{"class":235},[200,76309,239],{"class":214},[200,76311,280],{"class":214},[200,76313,76314,76316,76318],{"class":202,"line":336},[200,76315,12040],{"class":235},[200,76317,239],{"class":214},[200,76319,280],{"class":214},[200,76321,76323,76325,76327,76329,76331,76333,76335,76337],{"class":76322,"line":342},[202,9871],[200,76324,12050],{"class":235},[200,76326,239],{"class":214},[200,76328,11945],{"class":214},[200,76330,12057],{"class":210},[200,76332,12060],{"class":245},[200,76334,12063],{"class":206},[200,76336,2882],{"class":214},[200,76338,550],{"class":214},[200,76340,76342,76344,76346,76348],{"class":76341,"line":365},[202,9871],[200,76343,11884],{"class":235},[200,76345,239],{"class":214},[200,76347,11940],{"class":210},[200,76349,550],{"class":214},[200,76351,76352],{"class":202,"line":371},[200,76353,12083],{"class":214},[200,76355,76356,76358,76360,76362,76364,76366],{"class":202,"line":398},[200,76357,12088],{"class":379},[200,76359,12091],{"class":235},[200,76361,227],{"class":214},[200,76363,156],{"class":386},[200,76365,2089],{"class":214},[200,76367,12100],{"class":214},[200,76369,76370],{"class":202,"line":405},[200,76371,4962],{"class":214},[200,76373,76374],{"class":202,"line":410},[200,76375,4490],{"class":214},[200,76377,76378,76380],{"class":202,"line":861},[200,76379,413],{"class":214},[200,76381,11743],{"class":210},[35,76383,12117,76384,12120,76386,12123],{},[156,76385,3758],{},[156,76387,11573],{},[7905,76389,76390,76392,76402,76477,76481,76483,76487,76511,76513,76533,76535,76541,76567,76575,76637,76641,76711,76717,76841,76849,76955,76957,77637,77647,77651,77653,77661,77669,77671,77676,77690,77694,77778,77780,77790,77794,77798,77802,77804,77806,77808,77822,77824,77828,77830],{"text":12126,"href":12127},[35,76391,12130],{},[68,76393,76394,76398],{},[71,76395,12135,76396,12139],{},[156,76397,12138],{},[71,76399,12142,76400,12146],{},[156,76401,12145],{},[192,76403,76404],{"className":9741,"code":12149,"filename":1460,"language":1373,"meta":13,"style":13},[156,76405,76406,76416,76422,76436,76451,76459,76463,76467,76471],{"__ignoreMap":13},[200,76407,76408,76410,76412,76414],{"class":202,"line":203},[200,76409,227],{"class":210},[200,76411,1532],{"class":214},[200,76413,393],{"class":379},[200,76415,280],{"class":214},[200,76417,76418,76420],{"class":202,"line":14},[200,76419,3743],{"class":206},[200,76421,280],{"class":214},[200,76423,76424,76426,76428,76430,76432,76434],{"class":202,"line":255},[200,76425,11797],{"class":235},[200,76427,239],{"class":214},[200,76429,242],{"class":214},[200,76431,11598],{"class":245},[200,76433,249],{"class":214},[200,76435,252],{"class":214},[200,76437,76439,76441,76443,76445,76447,76449],{"class":76438,"line":272},[202,9871],[200,76440,12017],{"class":235},[200,76442,239],{"class":214},[200,76444,242],{"class":214},[200,76446,192],{"class":245},[200,76448,249],{"class":214},[200,76450,550],{"class":214},[200,76452,76453,76455,76457],{"class":202,"line":283},[200,76454,5036],{"class":235},[200,76456,239],{"class":214},[200,76458,280],{"class":214},[200,76460,76461],{"class":202,"line":300},[200,76462,12209],{"class":401},[200,76464,76465],{"class":202,"line":310},[200,76466,4962],{"class":214},[200,76468,76469],{"class":202,"line":336},[200,76470,4490],{"class":214},[200,76472,76473,76475],{"class":202,"line":342},[200,76474,413],{"class":214},[200,76476,11743],{"class":210},[35,76478,12226,76479,12229],{},[156,76480,11573],{},[51,76482,12233],{"id":12232},[35,76484,12236,76485,12239],{},[156,76486,11770],{},[68,76488,76489,76493,76499,76501,76505],{},[71,76490,12244,76491,12229],{},[156,76492,11573],{},[71,76494,12249,76495,12252,76497,12255],{},[156,76496,11573],{},[156,76498,11165],{},[71,76500,12258],{},[71,76502,12261,76503,12264],{},[156,76504,11598],{},[71,76506,12267,76507,12270,76509,12273],{},[156,76508,11573],{},[156,76510,11573],{},[35,76512,12276],{},[192,76514,76515],{"className":2151,"code":12279,"language":2153,"meta":13,"style":13},[156,76516,76517],{"__ignoreMap":13},[200,76518,76519,76521,76523,76525,76527,76529,76531],{"class":202,"line":203},[200,76520,2160],{"class":1978},[200,76522,12288],{"class":245},[200,76524,12291],{"class":245},[200,76526,12294],{"class":245},[200,76528,12297],{"class":245},[200,76530,12300],{"class":245},[200,76532,12303],{"class":245},[35,76534,12306],{},[68,76536,76537],{},[71,76538,12244,76539,12229],{},[156,76540,11573],{},[192,76542,76543],{"className":1370,"code":12315,"filename":1460,"language":1373,"meta":13,"style":13},[156,76544,76545,76549],{"__ignoreMap":13},[200,76546,76547],{"class":202,"line":203},[200,76548,12322],{"class":401},[200,76550,76551,76553,76555,76557,76559,76561,76563,76565],{"class":202,"line":14},[200,76552,12327],{"class":379},[200,76554,12330],{"class":210},[200,76556,449],{"class":214},[200,76558,11940],{"class":210},[200,76560,215],{"class":214},[200,76562,12339],{"class":223},[200,76564,12342],{"class":210},[200,76566,12345],{"class":401},[68,76568,76569],{"start":14},[71,76570,12249,76571,12252,76573,12255],{},[156,76572,11573],{},[156,76574,11165],{},[192,76576,76577],{"className":1370,"code":12356,"filename":1460,"language":1373,"meta":13,"style":13},[156,76578,76579,76585,76601,76605,76623],{"__ignoreMap":13},[200,76580,76581,76583],{"class":202,"line":203},[200,76582,12363],{"class":206},[200,76584,280],{"class":214},[200,76586,76587,76589,76591,76593,76595,76597,76599],{"class":202,"line":14},[200,76588,3723],{"class":379},[200,76590,12372],{"class":210},[200,76592,510],{"class":214},[200,76594,12377],{"class":210},[200,76596,9858],{"class":235},[200,76598,3188],{"class":2045},[200,76600,4496],{"class":235},[200,76602,76603],{"class":202,"line":255},[200,76604,529],{"emptyLinePlaceholder":20},[200,76606,76607,76609,76611,76613,76615,76617,76619,76621],{"class":202,"line":272},[200,76608,12392],{"class":210},[200,76610,510],{"class":214},[200,76612,11940],{"class":210},[200,76614,215],{"class":214},[200,76616,12339],{"class":223},[200,76618,227],{"class":235},[200,76620,156],{"class":210},[200,76622,416],{"class":235},[200,76624,76625,76627,76629,76631,76633,76635],{"class":202,"line":283},[200,76626,413],{"class":214},[200,76628,12413],{"class":206},[200,76630,12416],{"class":210},[200,76632,12419],{"class":214},[200,76634,12422],{"class":214},[200,76636,416],{"class":210},[68,76638,76639],{"start":255},[71,76640,12258],{},[192,76642,76643],{"className":1370,"code":12431,"filename":1460,"language":1373,"meta":13,"style":13},[156,76644,76645,76663,76681,76685],{"__ignoreMap":13},[200,76646,76647,76649,76651,76653,76655,76657,76659,76661],{"class":202,"line":203},[200,76648,973],{"class":206},[200,76650,318],{"class":214},[200,76652,12442],{"class":210},[200,76654,981],{"class":214},[200,76656,984],{"class":206},[200,76658,242],{"class":214},[200,76660,12451],{"class":245},[200,76662,992],{"class":214},[200,76664,76665,76667,76669,76671,76673,76675,76677,76679],{"class":202,"line":14},[200,76666,973],{"class":206},[200,76668,318],{"class":214},[200,76670,12462],{"class":210},[200,76672,981],{"class":214},[200,76674,984],{"class":206},[200,76676,242],{"class":214},[200,76678,12471],{"class":245},[200,76680,992],{"class":214},[200,76682,76683],{"class":202,"line":255},[200,76684,529],{"emptyLinePlaceholder":20},[200,76686,76687,76689,76691,76693,76695,76697,76699,76701,76703,76705,76707,76709],{"class":202,"line":272},[200,76688,443],{"class":379},[200,76690,10915],{"class":210},[200,76692,449],{"class":214},[200,76694,452],{"class":206},[200,76696,12442],{"class":223},[200,76698,227],{"class":210},[200,76700,6190],{"class":223},[200,76702,12496],{"class":210},[200,76704,215],{"class":214},[200,76706,12501],{"class":210},[200,76708,550],{"class":214},[200,76710,12506],{"class":210},[68,76712,76713],{"start":272},[71,76714,12261,76715,12264],{},[156,76716,11598],{},[192,76718,76719],{"className":1370,"code":12515,"filename":1460,"language":1373,"meta":13,"style":13},[156,76720,76721,76739,76757,76775,76779,76793],{"__ignoreMap":13},[200,76722,76723,76725,76727,76729,76731,76733,76735,76737],{"class":202,"line":203},[200,76724,973],{"class":206},[200,76726,318],{"class":214},[200,76728,10961],{"class":210},[200,76730,981],{"class":214},[200,76732,984],{"class":206},[200,76734,242],{"class":214},[200,76736,12534],{"class":245},[200,76738,992],{"class":214},[200,76740,76741,76743,76745,76747,76749,76751,76753,76755],{"class":202,"line":14},[200,76742,973],{"class":206},[200,76744,318],{"class":214},[200,76746,11041],{"class":210},[200,76748,981],{"class":214},[200,76750,984],{"class":206},[200,76752,242],{"class":214},[200,76754,11050],{"class":245},[200,76756,992],{"class":214},[200,76758,76759,76761,76763,76765,76767,76769,76771,76773],{"class":202,"line":255},[200,76760,973],{"class":206},[200,76762,318],{"class":214},[200,76764,10980],{"class":210},[200,76766,981],{"class":214},[200,76768,984],{"class":206},[200,76770,242],{"class":214},[200,76772,12571],{"class":245},[200,76774,992],{"class":214},[200,76776,76777],{"class":202,"line":272},[200,76778,529],{"emptyLinePlaceholder":20},[200,76780,76781,76783,76785,76787,76789,76791],{"class":202,"line":283},[200,76782,443],{"class":379},[200,76784,10954],{"class":210},[200,76786,449],{"class":214},[200,76788,452],{"class":206},[200,76790,10961],{"class":223},[200,76792,10964],{"class":210},[200,76794,76795,76797,76799,76801,76803,76805,76807,76809,76811,76813,76815,76817,76819,76821,76823,76825,76827,76829,76831,76833,76835,76837,76839],{"class":202,"line":300},[200,76796,443],{"class":379},[200,76798,10975],{"class":210},[200,76800,449],{"class":214},[200,76802,10980],{"class":223},[200,76804,10983],{"class":210},[200,76806,215],{"class":214},[200,76808,1472],{"class":223},[200,76810,10990],{"class":210},[200,76812,215],{"class":214},[200,76814,10995],{"class":210},[200,76816,550],{"class":214},[200,76818,5041],{"class":210},[200,76820,215],{"class":214},[200,76822,11004],{"class":210},[200,76824,550],{"class":214},[200,76826,5041],{"class":210},[200,76828,215],{"class":214},[200,76830,11013],{"class":210},[200,76832,550],{"class":214},[200,76834,11018],{"class":2045},[200,76836,550],{"class":214},[200,76838,11018],{"class":2045},[200,76840,416],{"class":210},[68,76842,76843],{"start":283},[71,76844,12267,76845,12270,76847,12273],{},[156,76846,11573],{},[156,76848,11573],{},[192,76850,76851],{"className":1370,"code":12650,"filename":1460,"language":1373,"meta":13,"style":13},[156,76852,76853,76871,76875,76895,76909,76951],{"__ignoreMap":13},[200,76854,76855,76857,76859,76861,76863,76865,76867,76869],{"class":202,"line":203},[200,76856,973],{"class":206},[200,76858,318],{"class":214},[200,76860,11041],{"class":210},[200,76862,981],{"class":214},[200,76864,984],{"class":206},[200,76866,242],{"class":214},[200,76868,11050],{"class":245},[200,76870,992],{"class":214},[200,76872,76873],{"class":202,"line":14},[200,76874,529],{"emptyLinePlaceholder":20},[200,76876,76877,76879,76881,76883,76885,76887,76889,76891,76893],{"class":202,"line":255},[200,76878,443],{"class":379},[200,76880,12681],{"class":210},[200,76882,449],{"class":214},[200,76884,12686],{"class":210},[200,76886,3933],{"class":2045},[200,76888,360],{"class":210},[200,76890,215],{"class":214},[200,76892,6136],{"class":223},[200,76894,3115],{"class":210},[200,76896,76897,76899,76901,76903,76905,76907],{"class":202,"line":272},[200,76898,12701],{"class":214},[200,76900,12704],{"class":245},[200,76902,227],{"class":214},[200,76904,6151],{"class":245},[200,76906,12711],{"class":214},[200,76908,252],{"class":214},[200,76910,76911,76913,76915,76917,76919,76921,76923,76925,76927,76929,76931,76933,76935,76937,76939,76941,76943,76945,76947,76949],{"class":202,"line":283},[200,76912,12718],{"class":214},[200,76914,12721],{"class":245},[200,76916,4853],{"class":214},[200,76918,12726],{"class":210},[200,76920,215],{"class":214},[200,76922,11004],{"class":210},[200,76924,413],{"class":214},[200,76926,12735],{"class":245},[200,76928,4853],{"class":214},[200,76930,12726],{"class":210},[200,76932,215],{"class":214},[200,76934,11013],{"class":210},[200,76936,413],{"class":214},[200,76938,12748],{"class":245},[200,76940,4853],{"class":214},[200,76942,11147],{"class":223},[200,76944,11150],{"class":210},[200,76946,413],{"class":214},[200,76948,12759],{"class":245},[200,76950,12762],{"class":214},[200,76952,76953],{"class":202,"line":300},[200,76954,416],{"class":210},[35,76956,12769],{},[192,76958,76959],{"className":1370,"code":12772,"filename":1460,"language":1373,"meta":13,"style":13},[156,76960,76961,76971,76981,76985,77029,77033,77039,77053,77067,77075,77083,77101,77145,77149,77167,77185,77189,77209,77213,77219,77237,77241,77271,77289,77343,77347,77361,77387,77391,77415,77429,77471,77475,77479,77501,77505,77523,77539,77543,77549,77565,77591,77595,77599,77603,77615,77623,77627,77631],{"__ignoreMap":13},[200,76962,76963,76965,76967,76969],{"class":202,"line":203},[200,76964,227],{"class":210},[200,76966,1532],{"class":214},[200,76968,393],{"class":379},[200,76970,280],{"class":214},[200,76972,76973,76975,76977,76979],{"class":202,"line":14},[200,76974,12789],{"class":379},[200,76976,12792],{"class":210},[200,76978,239],{"class":214},[200,76980,12797],{"class":1978},[200,76982,76983],{"class":202,"line":255},[200,76984,529],{"emptyLinePlaceholder":20},[200,76986,76987,76989,76991,76993,76995,76997,76999,77001,77003,77005,77007,77009,77011,77013,77015,77017,77019,77021,77023,77025,77027],{"class":202,"line":272},[200,76988,3723],{"class":379},[200,76990,11940],{"class":210},[200,76992,510],{"class":214},[200,76994,11945],{"class":214},[200,76996,11948],{"class":245},[200,76998,11951],{"class":214},[200,77000,675],{"class":245},[200,77002,11956],{"class":214},[200,77004,11959],{"class":245},[200,77006,9858],{"class":214},[200,77008,11964],{"class":245},[200,77010,11967],{"class":214},[200,77012,11964],{"class":245},[200,77014,11972],{"class":214},[200,77016,11964],{"class":245},[200,77018,11977],{"class":214},[200,77020,675],{"class":245},[200,77022,11956],{"class":214},[200,77024,675],{"class":245},[200,77026,2882],{"class":214},[200,77028,12846],{"class":2045},[200,77030,77031],{"class":202,"line":283},[200,77032,529],{"emptyLinePlaceholder":20},[200,77034,77035,77037],{"class":202,"line":300},[200,77036,3743],{"class":206},[200,77038,280],{"class":214},[200,77040,77041,77043,77045,77047,77049,77051],{"class":202,"line":310},[200,77042,11797],{"class":235},[200,77044,239],{"class":214},[200,77046,242],{"class":214},[200,77048,11598],{"class":245},[200,77050,249],{"class":214},[200,77052,252],{"class":214},[200,77054,77055,77057,77059,77061,77063,77065],{"class":202,"line":336},[200,77056,12017],{"class":235},[200,77058,239],{"class":214},[200,77060,242],{"class":214},[200,77062,192],{"class":245},[200,77064,249],{"class":214},[200,77066,252],{"class":214},[200,77068,77069,77071,77073],{"class":202,"line":342},[200,77070,5036],{"class":235},[200,77072,239],{"class":214},[200,77074,280],{"class":214},[200,77076,77077,77079,77081],{"class":202,"line":365},[200,77078,12040],{"class":235},[200,77080,239],{"class":214},[200,77082,280],{"class":214},[200,77084,77085,77087,77089,77091,77093,77095,77097,77099],{"class":202,"line":371},[200,77086,12050],{"class":235},[200,77088,239],{"class":214},[200,77090,11945],{"class":214},[200,77092,12057],{"class":210},[200,77094,12060],{"class":245},[200,77096,12063],{"class":206},[200,77098,2882],{"class":214},[200,77100,252],{"class":214},[200,77102,77103,77105,77107,77109,77111,77113,77115,77117,77119,77121,77123,77125,77127,77129,77131,77133,77135,77137,77139,77141,77143],{"class":202,"line":398},[200,77104,11884],{"class":235},[200,77106,239],{"class":214},[200,77108,11945],{"class":214},[200,77110,11948],{"class":245},[200,77112,11951],{"class":214},[200,77114,675],{"class":245},[200,77116,11956],{"class":214},[200,77118,11959],{"class":245},[200,77120,9858],{"class":214},[200,77122,11964],{"class":245},[200,77124,11967],{"class":214},[200,77126,11964],{"class":245},[200,77128,11972],{"class":214},[200,77130,11964],{"class":245},[200,77132,11977],{"class":214},[200,77134,675],{"class":245},[200,77136,11956],{"class":214},[200,77138,675],{"class":245},[200,77140,2882],{"class":214},[200,77142,11988],{"class":2045},[200,77144,252],{"class":214},[200,77146,77147],{"class":202,"line":405},[200,77148,12083],{"class":214},[200,77150,77151,77153,77155,77157,77159,77161,77163,77165],{"class":202,"line":410},[200,77152,12088],{"class":379},[200,77154,12091],{"class":235},[200,77156,227],{"class":214},[200,77158,156],{"class":386},[200,77160,550],{"class":214},[200,77162,3224],{"class":386},[200,77164,2089],{"class":214},[200,77166,280],{"class":214},[200,77168,77169,77171,77173,77175,77177,77179,77181,77183],{"class":202,"line":861},[200,77170,12989],{"class":379},[200,77172,12992],{"class":210},[200,77174,510],{"class":214},[200,77176,12997],{"class":214},[200,77178,13000],{"class":223},[200,77180,227],{"class":235},[200,77182,156],{"class":210},[200,77184,416],{"class":235},[200,77186,77187],{"class":202,"line":890},[200,77188,529],{"emptyLinePlaceholder":20},[200,77190,77191,77193,77195,77197,77199,77201,77203,77205,77207],{"class":202,"line":900},[200,77192,13015],{"class":379},[200,77194,12377],{"class":210},[200,77196,510],{"class":214},[200,77198,11940],{"class":210},[200,77200,215],{"class":214},[200,77202,12339],{"class":223},[200,77204,227],{"class":235},[200,77206,156],{"class":210},[200,77208,416],{"class":235},[200,77210,77211],{"class":202,"line":905},[200,77212,529],{"emptyLinePlaceholder":20},[200,77214,77215,77217],{"class":202,"line":931},[200,77216,13040],{"class":206},[200,77218,280],{"class":214},[200,77220,77221,77223,77225,77227,77229,77231,77233,77235],{"class":202,"line":3640},[200,77222,13047],{"class":379},[200,77224,12372],{"class":210},[200,77226,510],{"class":214},[200,77228,12377],{"class":210},[200,77230,11837],{"class":214},[200,77232,9858],{"class":235},[200,77234,3188],{"class":2045},[200,77236,4496],{"class":235},[200,77238,77239],{"class":202,"line":3645},[200,77240,529],{"emptyLinePlaceholder":20},[200,77242,77243,77245,77247,77249,77251,77253,77255,77257,77259,77261,77263,77265,77267,77269],{"class":202,"line":4020},[200,77244,13047],{"class":379},[200,77246,13072],{"class":210},[200,77248,510],{"class":214},[200,77250,452],{"class":206},[200,77252,12442],{"class":223},[200,77254,227],{"class":235},[200,77256,6190],{"class":223},[200,77258,227],{"class":235},[200,77260,13087],{"class":210},[200,77262,215],{"class":214},[200,77264,12501],{"class":210},[200,77266,550],{"class":214},[200,77268,12372],{"class":210},[200,77270,585],{"class":235},[200,77272,77273,77275,77277,77279,77281,77283,77285,77287],{"class":202,"line":4475},[200,77274,13047],{"class":379},[200,77276,5041],{"class":210},[200,77278,510],{"class":214},[200,77280,452],{"class":206},[200,77282,10961],{"class":223},[200,77284,227],{"class":235},[200,77286,1690],{"class":210},[200,77288,416],{"class":235},[200,77290,77291,77293,77295,77297,77299,77301,77303,77305,77307,77309,77311,77313,77315,77317,77319,77321,77323,77325,77327,77329,77331,77333,77335,77337,77339,77341],{"class":202,"line":4481},[200,77292,13047],{"class":379},[200,77294,12300],{"class":210},[200,77296,510],{"class":214},[200,77298,10980],{"class":223},[200,77300,227],{"class":235},[200,77302,13130],{"class":210},[200,77304,215],{"class":214},[200,77306,1472],{"class":223},[200,77308,227],{"class":235},[200,77310,12726],{"class":210},[200,77312,215],{"class":214},[200,77314,12726],{"class":210},[200,77316,2089],{"class":235},[200,77318,550],{"class":214},[200,77320,5041],{"class":210},[200,77322,215],{"class":214},[200,77324,11004],{"class":210},[200,77326,550],{"class":214},[200,77328,5041],{"class":210},[200,77330,215],{"class":214},[200,77332,11013],{"class":210},[200,77334,550],{"class":214},[200,77336,11018],{"class":2045},[200,77338,550],{"class":214},[200,77340,11018],{"class":2045},[200,77342,416],{"class":235},[200,77344,77345],{"class":202,"line":4487},[200,77346,529],{"emptyLinePlaceholder":20},[200,77348,77349,77351,77353,77355,77357,77359],{"class":202,"line":4493},[200,77350,13047],{"class":379},[200,77352,13181],{"class":210},[200,77354,510],{"class":214},[200,77356,12377],{"class":210},[200,77358,13188],{"class":214},[200,77360,13191],{"class":210},[200,77362,77363,77365,77367,77369,77371,77373,77375,77377,77379,77381,77383,77385],{"class":202,"line":5454},[200,77364,13047],{"class":379},[200,77366,13198],{"class":210},[200,77368,510],{"class":214},[200,77370,13181],{"class":210},[200,77372,13205],{"class":214},[200,77374,12377],{"class":210},[200,77376,11837],{"class":214},[200,77378,9858],{"class":235},[200,77380,3933],{"class":2045},[200,77382,360],{"class":235},[200,77384,215],{"class":214},[200,77386,13220],{"class":210},[200,77388,77389],{"class":202,"line":5459},[200,77390,529],{"emptyLinePlaceholder":20},[200,77392,77393,77395,77397,77399,77401,77403,77405,77407,77409,77411,77413],{"class":202,"line":13227},[200,77394,13047],{"class":379},[200,77396,13232],{"class":210},[200,77398,510],{"class":214},[200,77400,12377],{"class":210},[200,77402,11837],{"class":214},[200,77404,9858],{"class":235},[200,77406,3933],{"class":2045},[200,77408,360],{"class":235},[200,77410,215],{"class":214},[200,77412,6136],{"class":223},[200,77414,3115],{"class":235},[200,77416,77417,77419,77421,77423,77425,77427],{"class":202,"line":13253},[200,77418,13256],{"class":214},[200,77420,12704],{"class":245},[200,77422,227],{"class":214},[200,77424,6151],{"class":245},[200,77426,12711],{"class":214},[200,77428,252],{"class":214},[200,77430,77431,77433,77435,77437,77439,77441,77443,77445,77447,77449,77451,77453,77455,77457,77459,77461,77463,77465,77467,77469],{"class":202,"line":13269},[200,77432,13272],{"class":214},[200,77434,12721],{"class":245},[200,77436,4853],{"class":214},[200,77438,12726],{"class":210},[200,77440,215],{"class":214},[200,77442,11004],{"class":210},[200,77444,413],{"class":214},[200,77446,12735],{"class":245},[200,77448,4853],{"class":214},[200,77450,12726],{"class":210},[200,77452,215],{"class":214},[200,77454,11013],{"class":210},[200,77456,413],{"class":214},[200,77458,12748],{"class":245},[200,77460,4853],{"class":214},[200,77462,11147],{"class":223},[200,77464,11150],{"class":210},[200,77466,413],{"class":214},[200,77468,12759],{"class":245},[200,77470,12762],{"class":214},[200,77472,77473],{"class":202,"line":13313},[200,77474,13316],{"class":235},[200,77476,77477],{"class":202,"line":13319},[200,77478,529],{"emptyLinePlaceholder":20},[200,77480,77481,77483,77485,77487,77489,77491,77493,77495,77497,77499],{"class":202,"line":13324},[200,77482,13327],{"class":210},[200,77484,215],{"class":214},[200,77486,13332],{"class":223},[200,77488,227],{"class":235},[200,77490,13337],{"class":210},[200,77492,550],{"class":214},[200,77494,13198],{"class":210},[200,77496,550],{"class":214},[200,77498,13232],{"class":210},[200,77500,416],{"class":235},[200,77502,77503],{"class":202,"line":13350},[200,77504,529],{"emptyLinePlaceholder":20},[200,77506,77507,77509,77511,77513,77515,77517,77519,77521],{"class":202,"line":13355},[200,77508,13358],{"class":210},[200,77510,510],{"class":214},[200,77512,11940],{"class":210},[200,77514,215],{"class":214},[200,77516,12339],{"class":223},[200,77518,227],{"class":235},[200,77520,156],{"class":210},[200,77522,416],{"class":235},[200,77524,77525,77527,77529,77531,77533,77535,77537],{"class":202,"line":13375},[200,77526,13378],{"class":214},[200,77528,12413],{"class":206},[200,77530,1095],{"class":235},[200,77532,13385],{"class":210},[200,77534,13388],{"class":214},[200,77536,12422],{"class":214},[200,77538,416],{"class":235},[200,77540,77541],{"class":202,"line":13395},[200,77542,529],{"emptyLinePlaceholder":20},[200,77544,77545,77547],{"class":202,"line":13400},[200,77546,13403],{"class":206},[200,77548,280],{"class":214},[200,77550,77551,77553,77555,77557,77559,77561,77563],{"class":202,"line":13408},[200,77552,13411],{"class":235},[200,77554,239],{"class":214},[200,77556,12992],{"class":210},[200,77558,215],{"class":214},[200,77560,11086],{"class":223},[200,77562,1532],{"class":235},[200,77564,252],{"class":214},[200,77566,77567,77569,77571,77573,77575,77577,77579,77581,77583,77585,77587,77589],{"class":202,"line":13426},[200,77568,13429],{"class":235},[200,77570,239],{"class":214},[200,77572,12992],{"class":210},[200,77574,215],{"class":214},[200,77576,13438],{"class":223},[200,77578,227],{"class":235},[200,77580,2752],{"class":214},[200,77582,13445],{"class":235},[200,77584,239],{"class":214},[200,77586,2424],{"class":2423},[200,77588,981],{"class":214},[200,77590,416],{"class":235},[200,77592,77593],{"class":202,"line":13456},[200,77594,13459],{"class":214},[200,77596,77597],{"class":202,"line":13462},[200,77598,4478],{"class":214},[200,77600,77601],{"class":202,"line":13467},[200,77602,339],{"class":214},[200,77604,77605,77607,77609,77611,77613],{"class":202,"line":13472},[200,77606,13475],{"class":235},[200,77608,227],{"class":214},[200,77610,13480],{"class":386},[200,77612,2089],{"class":214},[200,77614,280],{"class":214},[200,77616,77617,77619,77621],{"class":202,"line":13487},[200,77618,13490],{"class":210},[200,77620,510],{"class":214},[200,77622,13495],{"class":210},[200,77624,77625],{"class":202,"line":13498},[200,77626,4962],{"class":214},[200,77628,77629],{"class":202,"line":13503},[200,77630,4490],{"class":214},[200,77632,77633,77635],{"class":202,"line":13508},[200,77634,413],{"class":214},[200,77636,11743],{"class":210},[939,77638,77639],{},[35,77640,77641,13519,77643,13523,77645,13526],{},[200,77642,945],{},[156,77644,13522],{},[156,77646,12501],{},[35,77648,13529,77649],{},[1582,77650,215],{"repo":13532},[35,77652,13535],{},[130,77654,132,77655,132,77659],{},[134,77656,136,77657,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,77658],{"src":13542,"type":141},[143,77660,13545],{},[35,77662,13548,77663,1638,77665,13553,77667,13557],{},[156,77664,11004],{},[156,77666,11013],{},[156,77668,13556],{},[51,77670,13561],{"id":13560},[35,77672,13564,77673,215],{},[39,77674,13569],{"href":13567,"rel":77675},[77],[192,77677,77678],{"className":1355,"code":13572,"language":1358,"meta":13,"style":13},[156,77679,77680],{"__ignoreMap":13},[200,77681,77682,77684,77686,77688],{"class":202,"line":203},[200,77683,2160],{"class":1978},[200,77685,2163],{"class":245},[200,77687,12291],{"class":245},[200,77689,13585],{"class":245},[35,77691,13588,77692,13591],{},[156,77693,1460],{},[192,77695,77696],{"className":1370,"code":13594,"filename":1460,"language":1373,"meta":13,"style":13},[156,77697,77698,77716,77730,77734,77746,77754,77760,77764,77768,77772],{"__ignoreMap":13},[200,77699,77700,77702,77704,77706,77708,77710,77712,77714],{"class":202,"line":203},[200,77701,973],{"class":206},[200,77703,318],{"class":214},[200,77705,1488],{"class":210},[200,77707,981],{"class":214},[200,77709,984],{"class":206},[200,77711,242],{"class":214},[200,77713,1497],{"class":245},[200,77715,992],{"class":214},[200,77717,77718,77720,77722,77724,77726,77728],{"class":202,"line":14},[200,77719,973],{"class":206},[200,77721,13621],{"class":210},[200,77723,1472],{"class":206},[200,77725,242],{"class":214},[200,77727,13569],{"class":245},[200,77729,992],{"class":214},[200,77731,77732],{"class":202,"line":255},[200,77733,529],{"emptyLinePlaceholder":20},[200,77735,77736,77738,77740,77742,77744],{"class":202,"line":272},[200,77737,1380],{"class":206},[200,77739,1383],{"class":206},[200,77741,1488],{"class":223},[200,77743,227],{"class":210},[200,77745,230],{"class":214},[200,77747,77748,77750,77752],{"class":202,"line":283},[200,77749,1520],{"class":235},[200,77751,239],{"class":214},[200,77753,1400],{"class":210},[200,77755,77756,77758],{"class":202,"line":300},[200,77757,13658],{"class":223},[200,77759,466],{"class":210},[200,77761,77762],{"class":202,"line":310},[200,77763,529],{"emptyLinePlaceholder":20},[200,77765,77766],{"class":202,"line":336},[200,77767,13669],{"class":401},[200,77769,77770],{"class":202,"line":342},[200,77771,13674],{"class":210},[200,77773,77774,77776],{"class":202,"line":365},[200,77775,413],{"class":214},[200,77777,416],{"class":210},[35,77779,13683],{},[192,77781,77782],{"className":1355,"code":13686,"language":1358,"meta":13,"style":13},[156,77783,77784],{"__ignoreMap":13},[200,77785,77786,77788],{"class":202,"line":203},[200,77787,2160],{"class":1978},[200,77789,13695],{"class":245},[35,77791,13698,77792,215],{},[156,77793,13701],{},[35,77795,13704,77796,13708],{},[156,77797,13707],{},[35,77799,77800],{},[1690,77801],{"alt":13713,"src":13714},[51,77803,11221],{"id":11220},[35,77805,13719],{},[35,77807,13722],{},[35,77809,13725,77810,13731,77813,1638,77816,106,77819,13745],{},[39,77811,13730],{"href":13728,"rel":77812},[77],[39,77814,13736],{"href":13734,"rel":77815},[77],[39,77817,13741],{"href":13739,"rel":77818},[77],[39,77820,13569],{"href":13567,"rel":77821},[77],[35,77823,13748],{},[35,77825,13751,77826,13754],{},[156,77827,11165],{},[1159,77829],{},[35,77831,13759],{},[1171,77833,13762],{},{"title":13,"searchDepth":14,"depth":14,"links":77835},[77836,77837,77838,77839,77840],{"id":11358,"depth":14,"text":11359},{"id":11563,"depth":14,"text":11564},{"id":12232,"depth":14,"text":12233},{"id":13560,"depth":14,"text":13561},{"id":11220,"depth":14,"text":11221},{},{"title":11297,"description":13771},{"loc":9994,"images":77844},[77845],{"loc":13714},{"id":13781,"title":13782,"body":77847,"contentId":9306,"date":14109,"description":14110,"extension":18,"meta":78064,"navigation":20,"path":14112,"rawbody":14113,"robots":23,"schemaOrg":23,"seo":78065,"sitemap":78066,"stem":14123,"__hash__":14124},{"type":7,"value":77848,"toc":78059},[77849,77854,77856,77860,77862,77864,77866,77871,77876,77878,77882,77887,77894,77902,77906,77908,77910,77914,77919,77923,77925,77927,77929,77983,77985,77989,77991,77993,77997,77999,78017,78021,78023,78027,78031,78033,78035,78037,78039,78044,78046,78051,78053,78055],[35,77850,13787,77851,13793],{},[39,77852,13792],{"href":13790,"rel":77853},[77],[35,77855,13796],{},[35,77857,77858,13802],{},[1217,77859,13801],{},[35,77861,13805],{},[51,77863,13809],{"id":13808},[35,77865,13812],{},[35,77867,13815,77868,13820],{},[39,77869,2918],{"href":13818,"rel":77870},[77],[35,77872,13823,77873,13829],{},[39,77874,13828],{"href":13826,"rel":77875},[77],[35,77877,13832],{},[35,77879,77880],{},[1690,77881],{"alt":13837,"src":13838},[35,77883,13841,77884,13847],{},[39,77885,13846],{"href":13844,"rel":77886},[77],[35,77888,13850,77889,13856,77892],{},[39,77890,13855],{"href":13853,"rel":77891},[77],[1217,77893,13859],{},[35,77895,13862,77896,1638,77899,13873],{},[39,77897,13867],{"href":13865,"rel":77898},[77],[39,77900,13872],{"href":13870,"rel":77901},[77],[35,77903,77904],{},[1690,77905],{"alt":13878,"src":13879},[51,77907,13883],{"id":13882},[35,77909,13886],{},[35,77911,77912],{},[1690,77913],{"alt":13891,"src":13892},[35,77915,13895,77916,13900],{},[39,77917,13899],{"href":6941,"rel":77918},[77],[35,77920,77921],{},[1690,77922],{"alt":13905,"src":13906},[35,77924,13909],{},[35,77926,13912],{},[35,77928,13915],{},[1576,77930,77931],{},[71,77932,77933,77935],{},[6850,77934,13922],{},[1576,77936,77937,77944,77951,77958,77962,77972,77976],{},[71,77938,77939,6853,77941],{},[6850,77940,13929],{},[39,77942,13934],{"href":13932,"rel":77943},[77],[71,77945,77946,6853,77948],{},[6850,77947,13939],{},[39,77949,13944],{"href":13942,"rel":77950},[77],[71,77952,77953,6853,77955],{},[6850,77954,13949],{},[39,77956,13954],{"href":13952,"rel":77957},[77],[71,77959,77960,13960],{},[6850,77961,13959],{},[71,77963,77964,6853,77966,1638,77969],{},[6850,77965,13965],{},[39,77967,13970],{"href":13968,"rel":77968},[77],[39,77970,13975],{"href":13973,"rel":77971},[77],[71,77973,77974,13980],{},[6850,77975,9124],{},[71,77977,77978,6853,77980],{},[6850,77979,13985],{},[39,77981,13990],{"href":13988,"rel":77982},[77],[35,77984,13993],{},[35,77986,77987],{},[1690,77988],{"alt":13998,"src":13999},[51,77990,14003],{"id":14002},[35,77992,14006],{},[35,77994,14009,77995],{},[1217,77996,14012],{},[35,77998,14015],{},[1576,78000,78001,78010],{},[71,78002,78003,14022,78005,215],{},[6850,78004,13959],{},[39,78006,78008],{"href":14025,"rel":78007},[77],[156,78009,14029],{},[71,78011,78012,14034,78014,14040],{},[6850,78013,9124],{},[39,78015,14039],{"href":14037,"rel":78016},[77],[35,78018,78019],{},[1690,78020],{"alt":14045,"src":14046},[35,78022,14049],{},[35,78024,14052,78025],{},[1217,78026,14055],{},[35,78028,14058,78029,14061],{},[156,78030,14029],{},[35,78032,14064],{},[35,78034,14067],{},[1159,78036],{},[35,78038,14072],{},[35,78040,14075,78041,14079],{},[39,78042,7881],{"href":7879,"rel":78043},[77],[35,78045,14082],{},[35,78047,14085,78048,14091],{},[39,78049,14090],{"href":14088,"rel":78050},[77],[35,78052,14094],{},[35,78054,14097],{},[35,78056,14100,78057],{},[1217,78058,14103],{},{"title":13,"searchDepth":14,"depth":14,"links":78060},[78061,78062,78063],{"id":13808,"depth":14,"text":13809},{"id":13882,"depth":14,"text":13883},{"id":14002,"depth":14,"text":14003},{},{"title":13782,"description":14110},{"loc":14112,"images":78067},[78068,78069,78070,78071,78072,78073],{"loc":13838},{"loc":13879},{"loc":13892},{"loc":13906},{"loc":13999},{"loc":14046},{"id":14126,"title":14127,"body":78075,"contentId":9326,"date":16364,"description":16365,"extension":18,"meta":79951,"navigation":20,"path":2971,"rawbody":16367,"robots":23,"schemaOrg":23,"seo":79952,"sitemap":79953,"stem":16372,"__hash__":16373},{"type":7,"value":78076,"toc":79945},[78077,78079,78081,78083,78085,78087,78089,78096,78100,78102,78117,78119,78121,78128,78130,78132,78137,78139,78141,78143,78256,78262,78266,78408,78410,78412,78420,78422,78424,78428,78437,78441,79099,79101,79106,79110,79118,79120,79122,79124,79126,79336,79340,79344,79346,79348,79452,79456,79458,79463,79465,79467,79471,79473,79475,79477,79479,79481,79485,79487,79753,79755,79757,79761,79765,79772,79846,79850,79854,79943],[35,78078,14132],{},[35,78080,14135],{},[35,78082,14138],{},[35,78084,14141],{},[35,78086,14144],{},[1159,78088],{},[35,78090,14149,78091,14155,78094],{},[39,78092,14154],{"href":14152,"rel":78093},[77],[1217,78095,14158],{},[35,78097,14161,78098,14165],{},[39,78099,14164],{"href":4700},[35,78101,14168],{},[68,78103,78104,78112],{},[71,78105,14173,78106,14177,78109],{},[39,78107,2918],{"href":13818,"rel":78108},[77],[39,78110,14182],{"href":14180,"rel":78111},[77],[71,78113,14185,78114,14191],{},[39,78115,14190],{"href":14188,"rel":78116},[77],[35,78118,14194],{},[35,78120,14197],{},[35,78122,14200,78123,14205,78126],{},[39,78124,9190],{"href":14203,"rel":78125},[77],[1217,78127,14208],{},[51,78129,14212],{"id":14211},[35,78131,14215],{},[35,78133,14218,78134,215],{},[39,78135,14223],{"href":14221,"rel":78136},[77],[35,78138,14226],{},[35,78140,14229],{},[35,78142,14232],{},[192,78144,78145],{"className":9741,"code":14235,"filename":14236,"language":1373,"meta":13,"style":13},[156,78146,78147,78161,78179,78193,78197,78211,78217,78221,78233,78248,78252],{"__ignoreMap":13},[200,78148,78149,78151,78153,78155,78157,78159],{"class":202,"line":203},[200,78150,973],{"class":206},[200,78152,14245],{"class":210},[200,78154,1472],{"class":206},[200,78156,242],{"class":214},[200,78158,14252],{"class":245},[200,78160,992],{"class":214},[200,78162,78163,78165,78167,78169,78171,78173,78175,78177],{"class":202,"line":14},[200,78164,973],{"class":206},[200,78166,318],{"class":214},[200,78168,14263],{"class":210},[200,78170,981],{"class":214},[200,78172,984],{"class":206},[200,78174,242],{"class":214},[200,78176,14272],{"class":245},[200,78178,992],{"class":214},[200,78180,78181,78183,78185,78187,78189,78191],{"class":202,"line":255},[200,78182,973],{"class":206},[200,78184,14281],{"class":210},[200,78186,1472],{"class":206},[200,78188,242],{"class":214},[200,78190,14288],{"class":245},[200,78192,992],{"class":214},[200,78194,78195],{"class":202,"line":272},[200,78196,529],{"emptyLinePlaceholder":20},[200,78198,78199,78201,78203,78205,78207,78209],{"class":202,"line":283},[200,78200,1380],{"class":206},[200,78202,14301],{"class":379},[200,78204,14304],{"class":210},[200,78206,449],{"class":214},[200,78208,14263],{"class":223},[200,78210,3115],{"class":210},[200,78212,78213,78215],{"class":202,"line":300},[200,78214,14315],{"class":210},[200,78216,252],{"class":214},[200,78218,78219],{"class":202,"line":310},[200,78220,14322],{"class":214},[200,78222,78223,78225,78227,78229,78231],{"class":202,"line":336},[200,78224,14327],{"class":214},[200,78226,14330],{"class":386},[200,78228,390],{"class":214},[200,78230,393],{"class":379},[200,78232,280],{"class":214},[200,78234,78236,78238,78240,78242,78244,78246],{"class":78235,"line":342},[202,9871],[200,78237,14342],{"class":210},[200,78239,215],{"class":214},[200,78241,9753],{"class":223},[200,78243,227],{"class":235},[200,78245,14351],{"class":210},[200,78247,5267],{"class":235},[200,78249,78250],{"class":202,"line":365},[200,78251,368],{"class":214},[200,78253,78254],{"class":202,"line":371},[200,78255,416],{"class":210},[35,78257,14364,78258,14367,78260,14371],{},[156,78259,422],{},[156,78261,14370],{},[35,78263,14374,78264,14378],{},[156,78265,14377],{},[192,78267,78268],{"className":9741,"code":14381,"filename":14236,"language":1373,"meta":13,"style":13},[156,78269,78270,78284,78302,78316,78320,78334,78340,78344,78356,78381,78395,78400,78404],{"__ignoreMap":13},[200,78271,78272,78274,78276,78278,78280,78282],{"class":202,"line":203},[200,78273,973],{"class":206},[200,78275,14245],{"class":210},[200,78277,1472],{"class":206},[200,78279,242],{"class":214},[200,78281,14252],{"class":245},[200,78283,992],{"class":214},[200,78285,78286,78288,78290,78292,78294,78296,78298,78300],{"class":202,"line":14},[200,78287,973],{"class":206},[200,78289,318],{"class":214},[200,78291,14263],{"class":210},[200,78293,981],{"class":214},[200,78295,984],{"class":206},[200,78297,242],{"class":214},[200,78299,14272],{"class":245},[200,78301,992],{"class":214},[200,78303,78304,78306,78308,78310,78312,78314],{"class":202,"line":255},[200,78305,973],{"class":206},[200,78307,14281],{"class":210},[200,78309,1472],{"class":206},[200,78311,242],{"class":214},[200,78313,14288],{"class":245},[200,78315,992],{"class":214},[200,78317,78318],{"class":202,"line":272},[200,78319,529],{"emptyLinePlaceholder":20},[200,78321,78322,78324,78326,78328,78330,78332],{"class":202,"line":283},[200,78323,1380],{"class":206},[200,78325,14301],{"class":379},[200,78327,14304],{"class":210},[200,78329,449],{"class":214},[200,78331,14263],{"class":223},[200,78333,3115],{"class":210},[200,78335,78336,78338],{"class":202,"line":300},[200,78337,14315],{"class":210},[200,78339,252],{"class":214},[200,78341,78342],{"class":202,"line":310},[200,78343,14322],{"class":214},[200,78345,78346,78348,78350,78352,78354],{"class":202,"line":336},[200,78347,14327],{"class":214},[200,78349,14330],{"class":386},[200,78351,390],{"class":214},[200,78353,393],{"class":379},[200,78355,280],{"class":214},[200,78357,78359,78361,78363,78365,78367,78369,78371,78373,78375,78377,78379],{"class":78358,"line":342},[202,9871],[200,78360,5252],{"class":206},[200,78362,1095],{"class":235},[200,78364,973],{"class":206},[200,78366,215],{"class":214},[200,78368,2015],{"class":210},[200,78370,215],{"class":214},[200,78372,14487],{"class":210},[200,78374,215],{"class":214},[200,78376,14492],{"class":210},[200,78378,5267],{"class":235},[200,78380,2752],{"class":214},[200,78382,78383,78385,78387,78389,78391,78393],{"class":202,"line":365},[200,78384,14501],{"class":210},[200,78386,215],{"class":214},[200,78388,9753],{"class":223},[200,78390,227],{"class":235},[200,78392,14351],{"class":210},[200,78394,416],{"class":235},[200,78396,78398],{"class":78397,"line":371},[202,9871],[200,78399,2096],{"class":214},[200,78401,78402],{"class":202,"line":398},[200,78403,368],{"class":214},[200,78405,78406],{"class":202,"line":405},[200,78407,416],{"class":210},[35,78409,14527],{},[35,78411,14530],{},[130,78413,132,78414,132,78418],{},[134,78415,136,78416,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,78417],{"src":14537,"type":141},[143,78419,14540],{},[35,78421,14543],{},[35,78423,14546],{},[35,78425,78426,14552],{},[6850,78427,14551],{},[35,78429,14555,78430,14561,78433,14565,78435,14568],{},[39,78431,14560],{"href":14558,"rel":78432},[77],[156,78434,14564],{},[156,78436,14564],{},[35,78438,14571,78439,14575],{},[156,78440,14574],{},[192,78442,78443],{"className":591,"code":14578,"filename":14579,"language":593,"meta":13,"style":13},[156,78444,78445,78463,78471,78509,78573,78591,78619,78647,78663,78691,78719,78727,78739,78775,78807,78843,78873,78879,78887,78925,78959,78993,79027,79055,79083,79091],{"__ignoreMap":13},[200,78446,78447,78449,78451,78453,78455,78457,78459,78461],{"class":202,"line":203},[200,78448,600],{"class":214},[200,78450,593],{"class":235},[200,78452,14590],{"class":379},[200,78454,449],{"class":214},[200,78456,611],{"class":214},[200,78458,14597],{"class":245},[200,78460,611],{"class":214},[200,78462,648],{"class":214},[200,78464,78465,78467,78469],{"class":202,"line":14},[200,78466,657],{"class":214},[200,78468,14574],{"class":235},[200,78470,648],{"class":214},[200,78472,78473,78475,78477,78479,78481,78483,78485,78487,78489,78491,78493,78495,78497,78499,78501,78503,78505,78507],{"class":202,"line":255},[200,78474,804],{"class":214},[200,78476,14616],{"class":235},[200,78478,14619],{"class":379},[200,78480,449],{"class":214},[200,78482,611],{"class":214},[200,78484,14626],{"class":245},[200,78486,611],{"class":214},[200,78488,14631],{"class":379},[200,78490,449],{"class":214},[200,78492,611],{"class":214},[200,78494,14638],{"class":245},[200,78496,611],{"class":214},[200,78498,14643],{"class":379},[200,78500,449],{"class":214},[200,78502,611],{"class":214},[200,78504,14650],{"class":245},[200,78506,611],{"class":214},[200,78508,648],{"class":214},[200,78510,78511,78513,78515,78517,78519,78521,78523,78525,78527,78529,78531,78533,78535,78537,78539,78541,78543,78545,78547,78549,78551,78553,78555,78557,78559,78561,78563,78565,78567,78569,78571],{"class":202,"line":272},[200,78512,804],{"class":214},[200,78514,14616],{"class":235},[200,78516,14619],{"class":379},[200,78518,449],{"class":214},[200,78520,611],{"class":214},[200,78522,14669],{"class":245},[200,78524,611],{"class":214},[200,78526,14674],{"class":379},[200,78528,449],{"class":214},[200,78530,611],{"class":214},[200,78532,1171],{"class":245},[200,78534,611],{"class":214},[200,78536,14685],{"class":379},[200,78538,449],{"class":214},[200,78540,14690],{"class":214},[200,78542,14693],{"class":210},[200,78544,14696],{"class":214},[200,78546,14699],{"class":245},[200,78548,14702],{"class":214},[200,78550,14631],{"class":379},[200,78552,449],{"class":214},[200,78554,611],{"class":214},[200,78556,14711],{"class":245},[200,78558,10116],{"class":210},[200,78560,14716],{"class":245},[200,78562,10116],{"class":210},[200,78564,14721],{"class":245},[200,78566,10116],{"class":210},[200,78568,14726],{"class":245},[200,78570,611],{"class":214},[200,78572,648],{"class":214},[200,78574,78575,78577,78579,78581,78583,78585,78587,78589],{"class":202,"line":283},[200,78576,804],{"class":214},[200,78578,2015],{"class":235},[200,78580,14739],{"class":379},[200,78582,449],{"class":214},[200,78584,611],{"class":214},[200,78586,14746],{"class":245},[200,78588,611],{"class":214},[200,78590,648],{"class":214},[200,78592,78593,78595,78597,78599,78601,78603,78605,78607,78609,78611,78613,78615,78617],{"class":202,"line":300},[200,78594,804],{"class":214},[200,78596,2015],{"class":235},[200,78598,701],{"class":379},[200,78600,449],{"class":214},[200,78602,611],{"class":214},[200,78604,14765],{"class":245},[200,78606,611],{"class":214},[200,78608,6041],{"class":379},[200,78610,449],{"class":214},[200,78612,611],{"class":214},[200,78614,7033],{"class":245},[200,78616,611],{"class":214},[200,78618,648],{"class":214},[200,78620,78621,78623,78625,78627,78629,78631,78633,78635,78637,78639,78641,78643,78645],{"class":202,"line":310},[200,78622,804],{"class":214},[200,78624,2015],{"class":235},[200,78626,701],{"class":379},[200,78628,449],{"class":214},[200,78630,611],{"class":214},[200,78632,14794],{"class":245},[200,78634,611],{"class":214},[200,78636,6041],{"class":379},[200,78638,449],{"class":214},[200,78640,611],{"class":214},[200,78642,14805],{"class":245},[200,78644,611],{"class":214},[200,78646,648],{"class":214},[200,78648,78649,78651,78653,78655,78657,78659,78661],{"class":202,"line":336},[200,78650,804],{"class":214},[200,78652,3566],{"class":235},[200,78654,675],{"class":214},[200,78656,14820],{"class":210},[200,78658,681],{"class":214},[200,78660,3566],{"class":235},[200,78662,648],{"class":214},[200,78664,78665,78667,78669,78671,78673,78675,78677,78679,78681,78683,78685,78687,78689],{"class":202,"line":342},[200,78666,804],{"class":214},[200,78668,2015],{"class":235},[200,78670,701],{"class":379},[200,78672,449],{"class":214},[200,78674,611],{"class":214},[200,78676,14841],{"class":245},[200,78678,611],{"class":214},[200,78680,6041],{"class":379},[200,78682,449],{"class":214},[200,78684,611],{"class":214},[200,78686,14852],{"class":245},[200,78688,611],{"class":214},[200,78690,648],{"class":214},[200,78692,78693,78695,78697,78699,78701,78703,78705,78707,78709,78711,78713,78715,78717],{"class":202,"line":365},[200,78694,804],{"class":214},[200,78696,2015],{"class":235},[200,78698,701],{"class":379},[200,78700,449],{"class":214},[200,78702,611],{"class":214},[200,78704,14871],{"class":245},[200,78706,611],{"class":214},[200,78708,6041],{"class":379},[200,78710,449],{"class":214},[200,78712,611],{"class":214},[200,78714,14852],{"class":245},[200,78716,611],{"class":214},[200,78718,648],{"class":214},[200,78720,78721,78723,78725],{"class":202,"line":371},[200,78722,804],{"class":214},[200,78724,14892],{"class":235},[200,78726,648],{"class":214},[200,78728,78729,78731,78733,78735,78737],{"class":202,"line":398},[200,78730,14899],{"class":214},[200,78732,227],{"class":210},[200,78734,14904],{"class":379},[200,78736,3714],{"class":214},[200,78738,280],{"class":214},[200,78740,78741,78743,78745,78747,78749,78751,78753,78755,78757,78759,78761,78763,78765,78767,78769,78771,78773],{"class":202,"line":405},[200,78742,12989],{"class":379},[200,78744,14915],{"class":210},[200,78746,510],{"class":214},[200,78748,14920],{"class":210},[200,78750,215],{"class":214},[200,78752,14925],{"class":210},[200,78754,14928],{"class":214},[200,78756,14920],{"class":210},[200,78758,215],{"class":214},[200,78760,14925],{"class":223},[200,78762,227],{"class":235},[200,78764,249],{"class":214},[200,78766,14941],{"class":245},[200,78768,249],{"class":214},[200,78770,2089],{"class":235},[200,78772,215],{"class":214},[200,78774,14950],{"class":210},[200,78776,78777,78779,78781,78783,78785,78787,78789,78791,78793,78795,78797,78799,78801,78803,78805],{"class":202,"line":410},[200,78778,12989],{"class":379},[200,78780,14957],{"class":210},[200,78782,510],{"class":214},[200,78784,14962],{"class":210},[200,78786,215],{"class":214},[200,78788,14967],{"class":223},[200,78790,227],{"class":235},[200,78792,249],{"class":214},[200,78794,14974],{"class":245},[200,78796,249],{"class":214},[200,78798,5267],{"class":235},[200,78800,14981],{"class":214},[200,78802,242],{"class":214},[200,78804,14986],{"class":245},[200,78806,992],{"class":214},[200,78808,78809,78811,78813,78815,78817,78819,78821,78823,78825,78827,78829,78831,78833,78835,78837,78839,78841],{"class":202,"line":861},[200,78810,14993],{"class":206},[200,78812,1095],{"class":235},[200,78814,14998],{"class":210},[200,78816,3283],{"class":214},[200,78818,242],{"class":214},[200,78820,14564],{"class":245},[200,78822,249],{"class":214},[200,78824,15009],{"class":214},[200,78826,1095],{"class":235},[200,78828,15014],{"class":210},[200,78830,14928],{"class":214},[200,78832,14957],{"class":210},[200,78834,13388],{"class":214},[200,78836,242],{"class":214},[200,78838,15025],{"class":245},[200,78840,249],{"class":214},[200,78842,585],{"class":235},[200,78844,78845,78847,78849,78851,78853,78855,78857,78859,78861,78863,78865,78867,78869,78871],{"class":202,"line":890},[200,78846,15034],{"class":210},[200,78848,215],{"class":214},[200,78850,15039],{"class":210},[200,78852,215],{"class":214},[200,78854,15044],{"class":210},[200,78856,215],{"class":214},[200,78858,15049],{"class":223},[200,78860,227],{"class":235},[200,78862,249],{"class":214},[200,78864,14564],{"class":245},[200,78866,249],{"class":214},[200,78868,550],{"class":214},[200,78870,2424],{"class":2423},[200,78872,416],{"class":235},[200,78874,78875,78877],{"class":202,"line":900},[200,78876,2632],{"class":214},[200,78878,11743],{"class":210},[200,78880,78881,78883,78885],{"class":202,"line":905},[200,78882,15074],{"class":214},[200,78884,14892],{"class":235},[200,78886,648],{"class":214},[200,78888,78889,78891,78893,78895,78897,78899,78901,78903,78905,78907,78909,78911,78913,78915,78917,78919,78921,78923],{"class":202,"line":931},[200,78890,804],{"class":214},[200,78892,14892],{"class":235},[200,78894,321],{"class":379},[200,78896,449],{"class":214},[200,78898,611],{"class":214},[200,78900,15093],{"class":245},[200,78902,611],{"class":214},[200,78904,14643],{"class":379},[200,78906,449],{"class":214},[200,78908,15102],{"class":214},[200,78910,10106],{"class":379},[200,78912,449],{"class":214},[200,78914,611],{"class":214},[200,78916,15111],{"class":245},[200,78918,611],{"class":214},[200,78920,10192],{"class":214},[200,78922,14892],{"class":235},[200,78924,648],{"class":214},[200,78926,78927,78929,78931,78933,78935,78937,78939,78941,78943,78945,78947,78949,78951,78953,78955,78957],{"class":202,"line":3640},[200,78928,804],{"class":214},[200,78930,14616],{"class":235},[200,78932,14619],{"class":379},[200,78934,449],{"class":214},[200,78936,611],{"class":214},[200,78938,14699],{"class":245},[200,78940,611],{"class":214},[200,78942,14643],{"class":379},[200,78944,449],{"class":214},[200,78946,15102],{"class":214},[200,78948,14631],{"class":379},[200,78950,449],{"class":214},[200,78952,611],{"class":214},[200,78954,15150],{"class":245},[200,78956,611],{"class":214},[200,78958,648],{"class":214},[200,78960,78961,78963,78965,78967,78969,78971,78973,78975,78977,78979,78981,78983,78985,78987,78989,78991],{"class":202,"line":3645},[200,78962,804],{"class":214},[200,78964,14616],{"class":235},[200,78966,14619],{"class":379},[200,78968,449],{"class":214},[200,78970,611],{"class":214},[200,78972,15169],{"class":245},[200,78974,611],{"class":214},[200,78976,14643],{"class":379},[200,78978,449],{"class":214},[200,78980,15102],{"class":214},[200,78982,14631],{"class":379},[200,78984,449],{"class":214},[200,78986,611],{"class":214},[200,78988,15186],{"class":245},[200,78990,611],{"class":214},[200,78992,648],{"class":214},[200,78994,78995,78997,78999,79001,79003,79005,79007,79009,79011,79013,79015,79017,79019,79021,79023,79025],{"class":202,"line":4020},[200,78996,804],{"class":214},[200,78998,14616],{"class":235},[200,79000,14619],{"class":379},[200,79002,449],{"class":214},[200,79004,611],{"class":214},[200,79006,15169],{"class":245},[200,79008,611],{"class":214},[200,79010,14643],{"class":379},[200,79012,449],{"class":214},[200,79014,15102],{"class":214},[200,79016,14631],{"class":379},[200,79018,449],{"class":214},[200,79020,611],{"class":214},[200,79022,15221],{"class":245},[200,79024,611],{"class":214},[200,79026,648],{"class":214},[200,79028,79029,79031,79033,79035,79037,79039,79041,79043,79045,79047,79049,79051,79053],{"class":202,"line":4475},[200,79030,804],{"class":214},[200,79032,2015],{"class":235},[200,79034,15234],{"class":379},[200,79036,449],{"class":214},[200,79038,611],{"class":214},[200,79040,15241],{"class":245},[200,79042,611],{"class":214},[200,79044,6041],{"class":379},[200,79046,449],{"class":214},[200,79048,611],{"class":214},[200,79050,15252],{"class":245},[200,79052,611],{"class":214},[200,79054,648],{"class":214},[200,79056,79057,79059,79061,79063,79065,79067,79069,79071,79073,79075,79077,79079,79081],{"class":202,"line":4481},[200,79058,804],{"class":214},[200,79060,2015],{"class":235},[200,79062,701],{"class":379},[200,79064,449],{"class":214},[200,79066,611],{"class":214},[200,79068,15271],{"class":245},[200,79070,611],{"class":214},[200,79072,6041],{"class":379},[200,79074,449],{"class":214},[200,79076,611],{"class":214},[200,79078,15252],{"class":245},[200,79080,611],{"class":214},[200,79082,648],{"class":214},[200,79084,79085,79087,79089],{"class":202,"line":4487},[200,79086,893],{"class":214},[200,79088,14574],{"class":235},[200,79090,648],{"class":214},[200,79092,79093,79095,79097],{"class":202,"line":4493},[200,79094,681],{"class":214},[200,79096,593],{"class":235},[200,79098,648],{"class":214},[35,79100,15304],{},[35,79102,15307,79103,15313],{},[39,79104,15312],{"href":15310,"rel":79105},[77],[35,79107,79108],{},[1690,79109],{"alt":15318,"src":15319},[35,79111,15322,79112,15328,79115,15334],{},[39,79113,15327],{"href":15325,"rel":79114},[77],[39,79116,15333],{"href":15331,"rel":79117},[77],[51,79119,15338],{"id":15337},[35,79121,15341],{},[35,79123,15344],{},[35,79125,15347],{},[192,79127,79128],{"className":9741,"code":15350,"filename":15351,"language":1373,"meta":13,"style":13},[156,79129,79130,79134,79148,79152,79156,79196,79200,79204,79211,79242,79246,79262,79292,79298,79314,79318],{"__ignoreMap":13},[200,79131,79132],{"class":202,"line":203},[200,79133,15358],{"class":401},[200,79135,79136,79138,79140,79142,79144,79146],{"class":202,"line":14},[200,79137,443],{"class":379},[200,79139,15365],{"class":210},[200,79141,449],{"class":214},[200,79143,12997],{"class":214},[200,79145,15372],{"class":223},[200,79147,15375],{"class":210},[200,79149,79150],{"class":202,"line":255},[200,79151,529],{"emptyLinePlaceholder":20},[200,79153,79154],{"class":202,"line":272},[200,79155,15384],{"class":401},[200,79157,79158,79160,79162,79164,79166,79168,79170,79172,79174,79176,79178,79180,79182,79184,79186,79188,79190,79192,79194],{"class":202,"line":283},[200,79159,15389],{"class":223},[200,79161,15392],{"class":210},[200,79163,215],{"class":214},[200,79165,15397],{"class":210},[200,79167,215],{"class":214},[200,79169,422],{"class":210},[200,79171,550],{"class":214},[200,79173,15406],{"class":210},[200,79175,215],{"class":214},[200,79177,15411],{"class":210},[200,79179,14981],{"class":214},[200,79181,12997],{"class":214},[200,79183,15418],{"class":223},[200,79185,600],{"class":214},[200,79187,328],{"class":1978},[200,79189,675],{"class":214},[200,79191,1532],{"class":210},[200,79193,550],{"class":214},[200,79195,15431],{"class":210},[200,79197,79198],{"class":202,"line":300},[200,79199,529],{"emptyLinePlaceholder":20},[200,79201,79202],{"class":202,"line":310},[200,79203,15440],{"class":401},[200,79205,79207,79209],{"class":79206,"line":336},[202,9871],[200,79208,15446],{"class":206},[200,79210,15449],{"class":210},[200,79212,79214,79216,79218,79220,79222,79224,79226,79228,79230,79232,79234,79236,79238,79240],{"class":79213,"line":342},[202,9871],[200,79215,6165],{"class":206},[200,79217,15457],{"class":223},[200,79219,15460],{"class":210},[200,79221,550],{"class":214},[200,79223,318],{"class":214},[200,79225,211],{"class":235},[200,79227,239],{"class":214},[200,79229,15471],{"class":210},[200,79231,215],{"class":214},[200,79233,15397],{"class":210},[200,79235,215],{"class":214},[200,79237,15480],{"class":210},[200,79239,413],{"class":214},[200,79241,5267],{"class":210},[200,79243,79244],{"class":202,"line":365},[200,79245,529],{"emptyLinePlaceholder":20},[200,79247,79248,79250,79252,79254,79256,79258,79260],{"class":202,"line":371},[200,79249,443],{"class":379},[200,79251,15495],{"class":210},[200,79253,449],{"class":214},[200,79255,15471],{"class":210},[200,79257,215],{"class":214},[200,79259,15504],{"class":223},[200,79261,466],{"class":210},[200,79263,79264,79266,79268,79270,79272,79274,79276,79278,79280,79282,79284,79286,79288,79290],{"class":202,"line":398},[200,79265,12327],{"class":379},[200,79267,15513],{"class":210},[200,79269,449],{"class":214},[200,79271,1095],{"class":210},[200,79273,207],{"class":206},[200,79275,15522],{"class":223},[200,79277,6524],{"class":214},[200,79279,15527],{"class":210},[200,79281,550],{"class":214},[200,79283,10276],{"class":210},[200,79285,550],{"class":214},[200,79287,15536],{"class":210},[200,79289,14981],{"class":214},[200,79291,15541],{"class":210},[200,79293,79294,79296],{"class":202,"line":405},[200,79295,15446],{"class":206},[200,79297,15548],{"class":210},[200,79299,79300,79302,79304,79306,79308,79310,79312],{"class":202,"line":410},[200,79301,15553],{"class":210},[200,79303,449],{"class":214},[200,79305,452],{"class":206},[200,79307,15560],{"class":210},[200,79309,215],{"class":214},[200,79311,15565],{"class":223},[200,79313,15568],{"class":210},[200,79315,79316],{"class":202,"line":861},[200,79317,529],{"emptyLinePlaceholder":20},[200,79319,79320,79322,79324,79326,79328,79330,79332,79334],{"class":202,"line":890},[200,79321,443],{"class":379},[200,79323,15579],{"class":210},[200,79325,449],{"class":214},[200,79327,452],{"class":206},[200,79329,15586],{"class":223},[200,79331,15589],{"class":210},[200,79333,550],{"class":214},[200,79335,15594],{"class":210},[35,79337,15597,79338,15601],{},[156,79339,15600],{},[35,79341,15604,79342,15608],{},[156,79343,15607],{},[35,79345,15611],{},[35,79347,15614],{},[192,79349,79350],{"className":9741,"code":15617,"filename":15618,"language":1373,"meta":13,"style":13},[156,79351,79352,79371,79375,79389,79393,79408,79418,79448],{"__ignoreMap":13},[200,79353,79355,79357,79359,79361,79363,79365,79367,79369],{"class":79354,"line":203},[202,9871],[200,79356,973],{"class":206},[200,79358,318],{"class":214},[200,79360,15630],{"class":210},[200,79362,981],{"class":214},[200,79364,984],{"class":206},[200,79366,242],{"class":214},[200,79368,15639],{"class":245},[200,79370,249],{"class":214},[200,79372,79373],{"class":202,"line":14},[200,79374,529],{"emptyLinePlaceholder":20},[200,79376,79377,79379,79381,79383,79385,79387],{"class":202,"line":255},[200,79378,443],{"class":379},[200,79380,15652],{"class":210},[200,79382,449],{"class":214},[200,79384,452],{"class":206},[200,79386,10265],{"class":223},[200,79388,15661],{"class":210},[200,79390,79391],{"class":202,"line":272},[200,79392,529],{"emptyLinePlaceholder":20},[200,79394,79396,79398,79400,79402,79404,79406],{"class":79395,"line":283},[202,9871],[200,79397,443],{"class":379},[200,79399,15495],{"class":210},[200,79401,449],{"class":214},[200,79403,452],{"class":206},[200,79405,15630],{"class":223},[200,79407,15681],{"class":210},[200,79409,79410,79412,79414,79416],{"class":202,"line":300},[200,79411,15686],{"class":210},[200,79413,215],{"class":214},[200,79415,14574],{"class":210},[200,79417,252],{"class":214},[200,79419,79420,79422,79424,79426,79428,79430,79432,79434,79436,79438,79440,79442,79444,79446],{"class":202,"line":310},[200,79421,15697],{"class":210},[200,79423,215],{"class":214},[200,79425,6136],{"class":223},[200,79427,227],{"class":210},[200,79429,3318],{"class":214},[200,79431,15708],{"class":245},[200,79433,3318],{"class":214},[200,79435,550],{"class":214},[200,79437,15715],{"class":210},[200,79439,215],{"class":214},[200,79441,15720],{"class":210},[200,79443,3821],{"class":214},[200,79445,3894],{"class":214},[200,79447,416],{"class":210},[200,79449,79450],{"class":202,"line":336},[200,79451,416],{"class":210},[35,79453,15733,79454,15737],{},[156,79455,15736],{},[35,79457,15740],{},[35,79459,79460],{},[39,79461,15747],{"href":15745,"rel":79462},[77],[35,79464,15750],{},[35,79466,15753],{},[35,79468,15756,79469],{},[1217,79470,15759],{},[35,79472,15762],{},[51,79474,15766],{"id":15765},[35,79476,15769],{},[35,79478,15772],{},[35,79480,15775],{},[35,79482,15778,79483,15782],{},[156,79484,15781],{},[35,79486,15785],{},[192,79488,79489],{"className":1370,"code":15788,"filename":15789,"language":1373,"meta":13,"style":13},[156,79490,79491,79505,79515,79533,79545,79557,79563,79567,79571,79611,79635,79639,79655,79677,79695,79699,79713,79733,79739,79743,79747],{"__ignoreMap":13},[200,79492,79493,79495,79497,79499,79501,79503],{"class":202,"line":203},[200,79494,443],{"class":379},[200,79496,15798],{"class":210},[200,79498,239],{"class":214},[200,79500,15803],{"class":1978},[200,79502,510],{"class":214},[200,79504,280],{"class":214},[200,79506,79507,79509,79511,79513],{"class":202,"line":14},[200,79508,15812],{"class":235},[200,79510,239],{"class":214},[200,79512,350],{"class":210},[200,79514,230],{"class":214},[200,79516,79517,79519,79521,79523,79525,79527,79529,79531],{"class":202,"line":255},[200,79518,15823],{"class":223},[200,79520,239],{"class":214},[200,79522,3714],{"class":214},[200,79524,393],{"class":379},[200,79526,15832],{"class":210},[200,79528,215],{"class":214},[200,79530,15837],{"class":210},[200,79532,252],{"class":214},[200,79534,79535,79537,79539,79541,79543],{"class":202,"line":272},[200,79536,15844],{"class":235},[200,79538,239],{"class":214},[200,79540,15849],{"class":214},[200,79542,4782],{"class":2045},[200,79544,252],{"class":214},[200,79546,79547,79549,79551,79553,79555],{"class":202,"line":283},[200,79548,15858],{"class":235},[200,79550,239],{"class":214},[200,79552,242],{"class":214},[200,79554,15865],{"class":245},[200,79556,992],{"class":214},[200,79558,79559,79561],{"class":202,"line":300},[200,79560,5080],{"class":214},[200,79562,4496],{"class":210},[200,79564,79565],{"class":202,"line":310},[200,79566,1130],{"class":214},[200,79568,79569],{"class":202,"line":336},[200,79570,529],{"emptyLinePlaceholder":20},[200,79572,79573,79575,79577,79579,79581,79583,79585,79587,79589,79591,79593,79595,79597,79599,79601,79603,79605,79607,79609],{"class":202,"line":342},[200,79574,15446],{"class":206},[200,79576,1095],{"class":210},[200,79578,973],{"class":206},[200,79580,215],{"class":214},[200,79582,2015],{"class":210},[200,79584,215],{"class":214},[200,79586,15898],{"class":210},[200,79588,15901],{"class":214},[200,79590,15904],{"class":210},[200,79592,215],{"class":214},[200,79594,15909],{"class":210},[200,79596,15901],{"class":214},[200,79598,15914],{"class":214},[200,79600,15917],{"class":210},[200,79602,215],{"class":214},[200,79604,15922],{"class":210},[200,79606,215],{"class":214},[200,79608,15927],{"class":210},[200,79610,230],{"class":214},[200,79612,79613,79615,79617,79619,79621,79623,79625,79627,79629,79631,79633],{"class":202,"line":365},[200,79614,3723],{"class":379},[200,79616,10180],{"class":210},[200,79618,510],{"class":214},[200,79620,211],{"class":210},[200,79622,215],{"class":214},[200,79624,15944],{"class":223},[200,79626,227],{"class":235},[200,79628,249],{"class":214},[200,79630,1171],{"class":245},[200,79632,249],{"class":214},[200,79634,416],{"class":235},[200,79636,79637],{"class":202,"line":371},[200,79638,529],{"emptyLinePlaceholder":20},[200,79640,79641,79643,79645,79647,79649,79651,79653],{"class":202,"line":398},[200,79642,15812],{"class":210},[200,79644,215],{"class":214},[200,79646,15967],{"class":210},[200,79648,510],{"class":214},[200,79650,15832],{"class":210},[200,79652,215],{"class":214},[200,79654,15976],{"class":210},[200,79656,79657,79659,79661,79663,79665,79667,79669,79671,79673,79675],{"class":202,"line":405},[200,79658,15812],{"class":210},[200,79660,215],{"class":214},[200,79662,15985],{"class":223},[200,79664,227],{"class":235},[200,79666,249],{"class":214},[200,79668,15992],{"class":245},[200,79670,249],{"class":214},[200,79672,550],{"class":214},[200,79674,3894],{"class":214},[200,79676,416],{"class":235},[200,79678,79679,79681,79683,79685,79687,79689,79691,79693],{"class":202,"line":410},[200,79680,16005],{"class":210},[200,79682,215],{"class":214},[200,79684,14574],{"class":210},[200,79686,215],{"class":214},[200,79688,16014],{"class":223},[200,79690,227],{"class":235},[200,79692,1171],{"class":210},[200,79694,416],{"class":235},[200,79696,79697],{"class":202,"line":861},[200,79698,529],{"emptyLinePlaceholder":20},[200,79700,79701,79703,79705,79707,79709,79711],{"class":202,"line":890},[200,79702,16029],{"class":210},[200,79704,215],{"class":214},[200,79706,14892],{"class":210},[200,79708,510],{"class":214},[200,79710,350],{"class":235},[200,79712,230],{"class":214},[200,79714,79715,79717,79719,79721,79723,79725,79727,79729,79731],{"class":202,"line":900},[200,79716,15823],{"class":235},[200,79718,239],{"class":214},[200,79720,242],{"class":214},[200,79722,16050],{"class":245},[200,79724,16053],{"class":210},[200,79726,16056],{"class":245},[200,79728,16053],{"class":210},[200,79730,6119],{"class":245},[200,79732,992],{"class":214},[200,79734,79735,79737],{"class":202,"line":905},[200,79736,5080],{"class":214},[200,79738,4496],{"class":235},[200,79740,79741],{"class":202,"line":931},[200,79742,1130],{"class":214},[200,79744,79745],{"class":202,"line":3640},[200,79746,529],{"emptyLinePlaceholder":20},[200,79748,79749,79751],{"class":202,"line":3645},[200,79750,16081],{"class":223},[200,79752,16084],{"class":210},[35,79754,16087],{},[35,79756,16090],{},[35,79758,419,79759,16095],{},[156,79760,15446],{},[35,79762,16098,79763,16101],{},[156,79764,15917],{},[35,79766,16104,79767,16109,79770,16113],{},[39,79768,16108],{"href":1877,"rel":79769},[77],[156,79771,16112],{},[192,79773,79774],{"className":1370,"code":16116,"filename":16117,"language":1373,"meta":13,"style":13},[156,79775,79776,79788,79794,79804,79820,79826,79838,79842],{"__ignoreMap":13},[200,79777,79778,79780,79782,79784,79786],{"class":202,"line":203},[200,79779,1380],{"class":206},[200,79781,4987],{"class":379},[200,79783,16128],{"class":223},[200,79785,1532],{"class":214},[200,79787,280],{"class":214},[200,79789,79790,79792],{"class":202,"line":14},[200,79791,3743],{"class":206},[200,79793,280],{"class":214},[200,79795,79796,79798,79800,79802],{"class":202,"line":255},[200,79797,16143],{"class":235},[200,79799,239],{"class":214},[200,79801,2424],{"class":2423},[200,79803,252],{"class":214},[200,79805,79806,79808,79810,79812,79814,79816,79818],{"class":202,"line":272},[200,79807,16154],{"class":235},[200,79809,239],{"class":214},[200,79811,318],{"class":214},[200,79813,16161],{"class":235},[200,79815,239],{"class":214},[200,79817,16166],{"class":2423},[200,79819,333],{"class":214},[200,79821,79822,79824],{"class":202,"line":283},[200,79823,16173],{"class":210},[200,79825,252],{"class":214},[200,79827,79828,79830,79832,79834,79836],{"class":202,"line":300},[200,79829,16180],{"class":235},[200,79831,239],{"class":214},[200,79833,16185],{"class":210},[200,79835,215],{"class":214},[200,79837,16190],{"class":210},[200,79839,79840],{"class":202,"line":310},[200,79841,4490],{"class":214},[200,79843,79844],{"class":202,"line":336},[200,79845,1130],{"class":214},[35,79847,16201,79848,16205],{},[156,79849,16204],{},[35,79851,16208,79852,16212],{},[156,79853,16211],{},[2895,79855,16216,79856,16219,79858,79860,79864,79866,79868,79870,79872,79874,79888,79893,79895],{"href":16215},[156,79857,14377],{},[156,79859,14272],{},[35,79861,16224,79862],{},[1217,79863,16227],{},[51,79865,16231],{"id":16230},[35,79867,16234],{},[35,79869,16237],{},[35,79871,16240],{},[35,79873,16243],{},[68,79875,79876,79882],{},[71,79877,16248,79878,16252,79880,16256],{},[156,79879,16251],{},[156,79881,16255],{},[71,79883,16259,79884,1638,79886,16265],{},[156,79885,16262],{},[156,79887,15093],{},[35,79889,16268,79890,16274],{},[39,79891,16273],{"href":16271,"rel":79892},[77],[35,79894,16277],{},[2895,79896,79897],{"title":16280,"href":16281},[16283,79898,79899,79901],{"title":16285,"href":16286},[35,79900,16289],{},[2895,79902,79903],{"title":16292,"href":16293},[16283,79904,79905],{"title":16296,"href":16297},[16283,79906,79907,79909,79911,79915,79917,79919,79923,79925,79927,79929,79931,79935,79937,79941],{"title":16300,"href":16301},[35,79908,16304],{},[35,79910,16307],{},[35,79912,79913],{},[1217,79914,16312],{},[51,79916,16316],{"id":16315},[35,79918,16319],{},[35,79920,16322,79921],{},[1217,79922,16325],{},[35,79924,16328],{},[35,79926,16331],{},[35,79928,16334],{},[1159,79930],{},[35,79932,16339,79933,215],{},[156,79934,16342],{},[35,79936,16345],{},[35,79938,79939,16351],{},[6850,79940,16350],{},[35,79942,16354],{},[1171,79944,16357],{},{"title":13,"searchDepth":14,"depth":14,"links":79946},[79947,79948,79949,79950],{"id":14211,"depth":14,"text":14212},{"id":15337,"depth":14,"text":15338},{"id":15765,"depth":14,"text":15766},{"id":16230,"depth":14,"text":16231},{},{"title":14127,"description":16365},{"loc":2971,"images":79954},[79955],{"loc":15319},{"id":16375,"title":16376,"body":79957,"contentId":9301,"date":16846,"description":16847,"extension":18,"meta":80279,"navigation":20,"path":16849,"rawbody":16850,"robots":23,"schemaOrg":23,"seo":80280,"sitemap":80281,"stem":16866,"__hash__":16867},{"type":7,"value":79958,"toc":80274},[79959,79961,79963,79971,79973,79977,79979,79984,79988,79992,79996,79998,80002,80010,80015,80019,80023,80028,80032,80034,80036,80038,80155,80157,80159,80161,80202,80204,80206,80210,80214,80218,80220,80225,80229,80234,80238,80240,80242,80246,80250,80252,80256,80261,80263,80265,80269],[35,79960,16381],{},[35,79962,16384],{},[35,79964,16387,79965,16392,79967,1638,79969,16398],{},[39,79966,16391],{"href":16390},[6850,79968,16395],{},[6850,79970,13959],{},[51,79972,16402],{"id":16401},[35,79974,79975],{},[1217,79976,16407],{},[35,79978,16410],{},[35,79980,16413,79981,16419],{},[39,79982,16418],{"href":16416,"rel":79983},[77],[35,79985,79986],{},[1690,79987],{"alt":16418,"src":16424},[35,79989,79990],{},[1690,79991],{"alt":16429,"src":16430},[35,79993,16433,79994],{},[1217,79995,16436],{},[35,79997,16439],{},[35,79999,80000],{},[1690,80001],{"alt":16444,"src":16445},[35,80003,16448,80004,16454,80007,16460],{},[39,80005,16453],{"href":16451,"rel":80006},[77],[39,80008,16459],{"href":16457,"rel":80009},[77],[35,80011,16463,80012,16469],{},[39,80013,16468],{"href":16466,"rel":80014},[77],[35,80016,80017],{},[1690,80018],{"alt":16474,"src":16475},[35,80020,16478,80021],{},[1217,80022,16481],{},[35,80024,16484,80025,16490],{},[39,80026,16489],{"href":16487,"rel":80027},[77],[35,80029,80030],{},[1690,80031],{"alt":16495,"src":16496},[35,80033,16499],{},[35,80035,16502],{},[35,80037,16505],{},[1576,80039,80040,80091],{},[71,80041,80042,80044],{},[6850,80043,16512],{},[1576,80045,80046,80053,80060,80070,80077,80084],{},[71,80047,80048,6853,80050],{},[6850,80049,16519],{},[39,80051,16524],{"href":16522,"rel":80052},[77],[71,80054,80055,6853,80057],{},[6850,80056,16529],{},[39,80058,16534],{"href":16532,"rel":80059},[77],[71,80061,80062,6853,80064,1638,80067],{},[6850,80063,16539],{},[39,80065,16544],{"href":16542,"rel":80066},[77],[39,80068,16549],{"href":16547,"rel":80069},[77],[71,80071,80072,6853,80074],{},[6850,80073,16554],{},[39,80075,16559],{"href":16557,"rel":80076},[77],[71,80078,80079,6853,80081],{},[6850,80080,16564],{},[39,80082,16569],{"href":16567,"rel":80083},[77],[71,80085,80086,6853,80088],{},[6850,80087,16574],{},[39,80089,16579],{"href":16577,"rel":80090},[77],[71,80092,80093,80095],{},[6850,80094,16584],{},[1576,80096,80097,80104,80114,80124,80131,80141,80148],{},[71,80098,80099,6853,80101,16597],{},[6850,80100,16591],{},[39,80102,16596],{"href":16594,"rel":80103},[77],[71,80105,80106,6853,80108,1638,80111],{},[6850,80107,16602],{},[39,80109,16606],{"href":9582,"rel":80110},[77],[39,80112,16611],{"href":16609,"rel":80113},[77],[71,80115,80116,6853,80118,1638,80121],{},[6850,80117,16616],{},[39,80119,16621],{"href":16619,"rel":80120},[77],[39,80122,16626],{"href":16624,"rel":80123},[77],[71,80125,80126,6853,80128],{},[6850,80127,16631],{},[39,80129,16636],{"href":16634,"rel":80130},[77],[71,80132,80133,6853,80135,1638,80138],{},[6850,80134,16641],{},[39,80136,16646],{"href":16644,"rel":80137},[77],[39,80139,16651],{"href":16649,"rel":80140},[77],[71,80142,80143,6853,80145],{},[6850,80144,16656],{},[39,80146,16661],{"href":16659,"rel":80147},[77],[71,80149,80150,6853,80152,16669],{},[6850,80151,13959],{},[39,80153,7033],{"href":7241,"rel":80154},[77],[35,80156,16672],{},[51,80158,16676],{"id":16675},[35,80160,16679],{},[1576,80162,80163,80184],{},[71,80164,80165,6853,80167,80170,16691,80172,1638,80175,16702,80178,16705,80180,80182],{},[6850,80166,16554],{},[39,80168,16559],{"href":16557,"rel":80169},[77],[7162,80171],{},[39,80173,16696],{"href":16694,"rel":80174},[77],[39,80176,16701],{"href":16699,"rel":80177},[77],[7162,80179],{},[7162,80181],{},[1690,80183],{"alt":16710,"src":16711},[71,80185,80186,6853,80188,1638,80191,80194,16724,80196,16727,80198,80200],{},[6850,80187,16602],{},[39,80189,16606],{"href":9582,"rel":80190},[77],[39,80192,16611],{"href":16609,"rel":80193},[77],[7162,80195],{},[39,80197,16391],{"href":8973},[7162,80199],{},[1690,80201],{"alt":16732,"src":16733},[35,80203,16736],{},[51,80205,7206],{"id":7205},[35,80207,16741,80208,16744],{},[6850,80209,9139],{},[35,80211,80212],{},[1690,80213],{"alt":16749,"src":16750},[35,80215,16753,80216],{},[1217,80217,16756],{},[35,80219,16759],{},[35,80221,16762,80222,16768],{},[39,80223,16767],{"href":16765,"rel":80224},[77],[35,80226,80227],{},[1690,80228],{"alt":16773,"src":16774},[35,80230,16777,80231,16782],{},[39,80232,16781],{"href":14025,"rel":80233},[77],[35,80235,80236],{},[1690,80237],{"alt":16787,"src":16788},[35,80239,16791],{},[1159,80241],{},[35,80243,16796,80244,11837],{},[6850,80245,16799],{},[35,80247,80248],{},[1690,80249],{"alt":16804,"src":16805},[1159,80251],{},[35,80253,80254],{},[1217,80255,16812],{},[35,80257,16815,80258,16819],{},[39,80259,7881],{"href":7879,"rel":80260},[77],[35,80262,16822],{},[35,80264,16825],{},[35,80266,80267],{},[1690,80268],{"alt":16830,"src":16831},[35,80270,16834,80271,16840],{},[39,80272,16839],{"href":16837,"rel":80273},[77],{"title":13,"searchDepth":14,"depth":14,"links":80275},[80276,80277,80278],{"id":16401,"depth":14,"text":16402},{"id":16675,"depth":14,"text":16676},{"id":7205,"depth":14,"text":7206},{},{"title":16376,"description":16847},{"loc":16849,"images":80282},[80283,80284,80285,80286,80287,80288,80289,80290,80291,80292,80293,80294],{"loc":16424},{"loc":16430},{"loc":16445},{"loc":16475},{"loc":16496},{"loc":16711},{"loc":16733},{"loc":16750},{"loc":16774},{"loc":16788},{"loc":16805},{"loc":16831},{"id":16869,"title":42,"body":80296,"contentId":3031,"date":17215,"description":17216,"extension":18,"meta":80512,"navigation":20,"path":41,"rawbody":17218,"robots":23,"schemaOrg":23,"seo":80513,"sitemap":80514,"stem":17221,"__hash__":17222},{"type":7,"value":80297,"toc":80504},[80298,80300,80314,80318,80320,80324,80326,80328,80332,80334,80341,80346,80351,80353,80355,80357,80359,80364,80366,80368,80370,80375,80380,80387,80392,80394,80396,80400,80407,80409,80413,80441,80443,80445,80450,80454,80462,80464,80466,80468,80476,80480,80482,80486,80488,80490,80492,80496,80498],[35,80299,16874],{},[35,80301,16877,80302,3076,80305,16888,80308,16894,80311,16900],{},[39,80303,16882],{"href":16880,"rel":80304},[77],[39,80306,16887],{"href":16885,"rel":80307},[77],[39,80309,16893],{"href":16891,"rel":80310},[77],[39,80312,16899],{"href":16897,"rel":80313},[77],[35,80315,16903,80316],{},[1217,80317,16906],{},[35,80319,16909],{},[35,80321,80322],{},[6850,80323,16914],{},[35,80325,16917],{},[35,80327,16920],{},[35,80329,16923,80330],{},[1217,80331,16926],{},[51,80333,16930],{"id":16929},[35,80335,16933,80336,16939,80339],{},[39,80337,16938],{"href":16936,"rel":80338},[77],[1217,80340,16942],{},[35,80342,16945,80343,16951],{},[39,80344,16950],{"href":16948,"rel":80345},[77],[35,80347,80348,16959],{},[39,80349,16958],{"href":16956,"rel":80350},[77],[35,80352,16962],{},[51,80354,16966],{"id":16965},[35,80356,16969],{},[35,80358,16972],{},[35,80360,16975,80361,215],{},[39,80362,16980],{"href":16978,"rel":80363},[77],[35,80365,16983],{},[51,80367,16987],{"id":16986},[35,80369,16990],{},[35,80371,16993,80372,16999],{},[39,80373,16998],{"href":16996,"rel":80374},[77],[35,80376,17002,80377,17008],{},[39,80378,17007],{"href":17005,"rel":80379},[77],[35,80381,17011,80382,17017,80385],{},[39,80383,17016],{"href":17014,"rel":80384},[77],[1217,80386,17020],{},[35,80388,7737,80389,17028],{},[39,80390,17027],{"href":17025,"rel":80391},[77],[35,80393,17031],{},[35,80395,17034],{},[35,80397,17037,80398],{},[6850,80399,17040],{},[35,80401,17043,80402,17049,80405],{},[39,80403,17048],{"href":17046,"rel":80404},[77],[1217,80406,17052],{},[51,80408,17056],{"id":17055},[35,80410,17059,80411],{},[1217,80412,17062],{},[68,80414,80415,80420,80422,80436],{},[71,80416,17067,80417,17073],{},[39,80418,17072],{"href":17070,"rel":80419},[77],[71,80421,17076],{},[71,80423,17079,80424,17085,80427,1638,80430,17096,80433,17102],{},[39,80425,17084],{"href":17082,"rel":80426},[77],[39,80428,17090],{"href":17088,"rel":80429},[77],[39,80431,17095],{"href":17093,"rel":80432},[77],[39,80434,17101],{"href":17099,"rel":80435},[77],[71,80437,17105,80438,17111],{},[39,80439,17110],{"href":17108,"rel":80440},[77],[35,80442,17114],{},[51,80444,17118],{"id":17117},[35,80446,17121,80447,17127],{},[39,80448,17126],{"href":17124,"rel":80449},[77],[35,80451,17130,80452],{},[1217,80453,17133],{},[35,80455,17136,80456,17141,80459,17147],{},[39,80457,16958],{"href":17139,"rel":80458},[77],[39,80460,17146],{"href":17144,"rel":80461},[77],[35,80463,17150],{},[35,80465,17153],{},[35,80467,17156],{},[130,80469,132,80470,132,80474],{},[134,80471,136,80472,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,80473],{"src":17163,"type":141},[143,80475,17166],{},[35,80477,80478],{},[1217,80479,17171],{},[51,80481,17175],{"id":17174},[35,80483,17178,80484],{},[1217,80485,17181],{},[35,80487,17184],{},[35,80489,17187],{},[35,80491,17190],{},[35,80493,80494],{},[6850,80495,54],{},[1159,80497],{},[35,80499,17199,80500],{},[1217,80501,17202,80502,17206],{},[39,80503,17205],{"href":9163},{"title":13,"searchDepth":14,"depth":14,"links":80505},[80506,80507,80508,80509,80510,80511],{"id":16929,"depth":14,"text":16930},{"id":16965,"depth":14,"text":16966},{"id":16986,"depth":14,"text":16987},{"id":17055,"depth":14,"text":17056},{"id":17117,"depth":14,"text":17118},{"id":17174,"depth":14,"text":17175},{},{"title":42,"description":17216},{"loc":41},{"id":17224,"title":17225,"body":80516,"contentId":9321,"date":17537,"description":17538,"extension":18,"meta":80718,"navigation":20,"path":17540,"rawbody":17541,"robots":23,"schemaOrg":23,"seo":80719,"sitemap":80720,"stem":17544,"__hash__":17545},{"type":7,"value":80517,"toc":80712},[80518,80523,80527,80529,80531,80533,80537,80539,80541,80546,80550,80552,80554,80556,80558,80560,80565,80567,80572,80577,80579,80586,80588,80590,80598,80600,80602,80604,80606,80608,80610,80612,80614,80616,80635,80637,80642,80644,80646,80648,80650,80652,80654,80659,80661,80666,80668,80670,80672,80674,80676,80688,80690,80701,80703,80708,80710],[35,80519,17230,80520,215],{},[39,80521,17235],{"href":17233,"rel":80522},[77],[35,80524,80525],{},[1217,80526,17240],{},[35,80528,17243],{},[35,80530,17246],{},[1159,80532],{},[35,80534,17251,80535,17255],{},[39,80536,17254],{"href":7934},[35,80538,17258],{},[1159,80540],{},[35,80542,17263,80543,239],{},[39,80544,17268],{"href":17266,"rel":80545},[77],[939,80547,80548],{},[35,80549,17273],{},[35,80551,17276],{},[35,80553,17279],{},[35,80555,17282],{},[51,80557,17286],{"id":17285},[35,80559,17289],{},[35,80561,17292,80562,17298],{},[39,80563,17297],{"href":17295,"rel":80564},[77],[35,80566,17301],{},[35,80568,17304,80569,17310],{},[39,80570,17309],{"href":17307,"rel":80571},[77],[35,80573,17313,80574,17319],{},[39,80575,17318],{"href":17316,"rel":80576},[77],[35,80578,17322],{},[35,80580,17325,80581,17330,80584],{},[39,80582,13867],{"href":17328,"rel":80583},[77],[1217,80585,17333],{},[35,80587,17336],{},[51,80589,17340],{"id":17339},[35,80591,17343,80592,17349,80595,17354],{},[39,80593,17348],{"href":17346,"rel":80594},[77],[39,80596,17353],{"href":9115,"rel":80597},[77],[35,80599,17357],{},[35,80601,17360],{},[35,80603,17363],{},[35,80605,17366],{},[35,80607,17369],{},[35,80609,17372],{},[35,80611,17375],{},[35,80613,17378],{},[35,80615,17381],{},[35,80617,17384,80618,3076,80621,3076,80624,3076,80627,3080,80630,17410,80633],{},[39,80619,17389],{"href":17387,"rel":80620},[77],[39,80622,17394],{"href":17392,"rel":80623},[77],[39,80625,17399],{"href":17397,"rel":80626},[77],[39,80628,17404],{"href":17402,"rel":80629},[77],[39,80631,17409],{"href":17407,"rel":80632},[77],[1217,80634,17413],{},[35,80636,17416],{},[35,80638,17419,80639,17424],{},[39,80640,17423],{"href":1877,"rel":80641},[77],[51,80643,17428],{"id":17427},[35,80645,17431],{},[35,80647,17434],{},[35,80649,17437],{},[35,80651,17440],{},[35,80653,17443],{},[35,80655,17446,80656,17452],{},[39,80657,17451],{"href":17449,"rel":80658},[77],[51,80660,17456],{"id":17455},[35,80662,17459,80663,17465],{},[39,80664,17464],{"href":17462,"rel":80665},[77],[35,80667,17468],{},[35,80669,17471],{},[35,80671,17474],{},[35,80673,17477],{},[35,80675,17480],{},[1576,80677,80678,80683],{},[71,80679,17485,80680],{},[39,80681,17490],{"href":17488,"rel":80682},[77],[71,80684,17493,80685],{},[39,80686,17498],{"href":17496,"rel":80687},[77],[1159,80689],{},[35,80691,17503,80692,3076,80695,3080,80698,17515],{},[39,80693,13867],{"href":17328,"rel":80694},[77],[39,80696,13872],{"href":17509,"rel":80697},[77],[39,80699,9353],{"href":17513,"rel":80700},[77],[1159,80702],{},[35,80704,17520,80705,215],{},[39,80706,17525],{"href":17523,"rel":80707},[77],[1159,80709],{},[35,80711,17530],{},{"title":13,"searchDepth":14,"depth":14,"links":80713},[80714,80715,80716,80717],{"id":17285,"depth":14,"text":17286},{"id":17339,"depth":14,"text":17340},{"id":17427,"depth":14,"text":17428},{"id":17455,"depth":14,"text":17456},{},{"title":17225,"description":17538},{"loc":17540},{"id":17547,"title":17548,"body":80722,"contentId":9491,"date":17735,"description":17736,"extension":18,"meta":80850,"navigation":20,"path":17738,"rawbody":17739,"robots":23,"schemaOrg":23,"seo":80851,"sitemap":80852,"stem":17742,"__hash__":17743},{"type":7,"value":80723,"toc":80848},[80724,80728,80730,80732,80734,80736,80738,80740,80742,80744,80746,80748,80750,80752,80754,80758,80762,80766,80768,80770,80772,80774,80776,80778,80782,80784,80786,80788,80793,80795,80797,80799,80801,80803,80805,80807,80809,80811,80813,80815,80817,80819,80823,80825,80830,80832,80837,80839,80841],[35,80725,80726],{},[1217,80727,17555],{},[35,80729,17558],{},[1159,80731],{},[35,80733,17563],{},[35,80735,17566],{},[1159,80737],{},[35,80739,17571],{},[35,80741,17574],{},[35,80743,17577],{},[35,80745,17580],{},[35,80747,17583],{},[35,80749,17586],{},[35,80751,17589],{},[35,80753,17592],{},[35,80755,17595,80756,17599],{},[1217,80757,17598],{},[35,80759,80760],{},[1217,80761,17604],{},[35,80763,17607,80764,17611],{},[6850,80765,17610],{},[35,80767,17614],{},[35,80769,17617],{},[35,80771,17620],{},[35,80773,17623],{},[1159,80775],{},[35,80777,17628],{},[35,80779,17631,80780,215],{},[1217,80781,17634],{},[35,80783,17637],{},[35,80785,17640],{},[35,80787,17643],{},[35,80789,17646,80790,17652],{},[39,80791,17651],{"href":17649,"rel":80792},[77],[35,80794,17655],{},[35,80796,17658],{},[35,80798,17661],{},[1159,80800],{},[35,80802,17666],{},[35,80804,17669],{},[35,80806,17672],{},[35,80808,17675],{},[35,80810,17678],{},[35,80812,17681],{},[35,80814,17684],{},[35,80816,17687],{},[1159,80818],{},[939,80820,80821],{},[35,80822,17694],{},[35,80824,17697],{},[35,80826,80827],{},[39,80828,17704],{"href":17702,"rel":80829},[77],[35,80831,17707],{},[35,80833,80834,17715],{},[39,80835,17714],{"href":17712,"rel":80836},[77],[35,80838,17718],{},[35,80840,17721],{},[939,80842,80843],{},[35,80844,17726,80845,17732],{},[39,80846,17731],{"href":17729,"rel":80847},[77],{"title":13,"searchDepth":14,"depth":14,"links":80849},[],{},{"title":17548,"description":17736},{"loc":17738},{"id":17745,"title":17746,"body":80854,"contentId":9316,"date":17839,"description":17840,"extension":18,"meta":80920,"navigation":20,"path":17842,"rawbody":17843,"robots":23,"schemaOrg":23,"seo":80921,"sitemap":80922,"stem":17846,"__hash__":17847},{"type":7,"value":80855,"toc":80918},[80856,80860,80862,80864,80868,80870,80872,80874,80876,80878,80880,80882,80884,80886,80888,80890,80892,80894,80898,80900,80902,80904,80906,80908,80910,80912,80914,80916],[35,80857,80858],{},[1217,80859,17753],{},[35,80861,17756],{},[35,80863,17759],{},[35,80865,17762,80866],{},[1217,80867,17765],{},[35,80869,17768],{},[35,80871,17771],{},[35,80873,17774],{},[35,80875,17777],{},[35,80877,17780],{},[35,80879,17783],{},[35,80881,17786],{},[35,80883,17789],{},[35,80885,17792],{},[35,80887,17795],{},[35,80889,17798],{},[35,80891,17801],{},[35,80893,17804],{},[35,80895,80896],{},[1217,80897,17809],{},[1159,80899],{},[35,80901,17814],{},[35,80903,17817],{},[1159,80905],{},[35,80907,17822],{},[35,80909,17825],{},[35,80911,17828],{},[1159,80913],{},[35,80915,17833],{},[35,80917,17836],{},{"title":13,"searchDepth":14,"depth":14,"links":80919},[],{},{"title":17746,"description":17840},{"loc":17842},{"id":17849,"title":17525,"body":80924,"contentId":5658,"date":18173,"description":18174,"extension":18,"meta":81134,"navigation":20,"path":18176,"rawbody":18177,"robots":23,"schemaOrg":23,"seo":81135,"sitemap":81136,"stem":18180,"__hash__":18181},{"type":7,"value":80925,"toc":81129},[80926,80930,80938,80952,80954,80956,80962,80967,80969,80973,80975,80977,80979,80987,80989,80991,80998,81000,81002,81004,81006,81008,81019,81023,81031,81035,81039,81050,81052,81054,81056,81060,81062,81066,81068,81070,81072,81074,81079,81081,81083,81087,81091,81093,81095,81105,81107,81109,81111,81113,81117,81119,81123,81125],[939,80927,80928],{},[35,80929,17856],{},[35,80931,17859,80932,17865,80935,215],{},[39,80933,17864],{"href":17862,"rel":80934},[77],[39,80936,17870],{"href":17868,"rel":80937},[77],[35,80939,17873,80940,17879,80943,17885,80946,17879,80949,17894],{},[39,80941,17878],{"href":17876,"rel":80942},[77],[39,80944,17884],{"href":17882,"rel":80945},[77],[39,80947,17890],{"href":17888,"rel":80948},[77],[39,80950,17864],{"href":17862,"rel":80951},[77],[35,80953,17897],{},[1159,80955],{},[939,80957,80958],{},[35,80959,80960,17906],{},[200,80961,945],{},[35,80963,17909,80964,17915],{},[39,80965,17914],{"href":17912,"rel":80966},[77],[51,80968,17919],{"id":17918},[35,80970,17922,80971],{},[1217,80972,17925],{},[35,80974,17928],{},[35,80976,17931],{},[35,80978,17934],{},[1576,80980,80981,80983,80985],{},[71,80982,17939],{},[71,80984,17942],{},[71,80986,17945],{},[35,80988,17948],{},[35,80990,17951],{},[35,80992,17954,80993],{},[1217,80994,80995,17962],{},[39,80996,17961],{"href":17959,"rel":80997},[77],[35,80999,17965],{},[51,81001,17969],{"id":17968},[35,81003,17972],{},[35,81005,17975],{},[35,81007,17978],{},[35,81009,17981,81010,1638,81013,17992,81016,17998],{},[39,81011,17986],{"href":17984,"rel":81012},[77],[39,81014,17991],{"href":17989,"rel":81015},[77],[39,81017,17997],{"href":17995,"rel":81018},[77],[35,81020,18001,81021],{},[1217,81022,18004],{},[35,81024,18007,81025,18013,81028,18019],{},[39,81026,18012],{"href":18010,"rel":81027},[77],[39,81029,18018],{"href":18016,"rel":81030},[77],[35,81032,18022,81033],{},[1217,81034,18025],{},[35,81036,18028,81037],{},[1217,81038,18031],{},[35,81040,18034,81041,18040,81044,18046,81047,18052],{},[39,81042,18039],{"href":18037,"rel":81043},[77],[39,81045,18045],{"href":18043,"rel":81046},[77],[39,81048,18051],{"href":18049,"rel":81049},[77],[35,81051,18055],{},[35,81053,18058],{},[51,81055,18062],{"id":18061},[35,81057,81058,18068],{},[6850,81059,18067],{},[35,81061,18071],{},[35,81063,18074,81064,18078],{},[6850,81065,18077],{},[35,81067,18081],{},[35,81069,18084],{},[35,81071,18087],{},[35,81073,18090],{},[35,81075,18093,81076,18098],{},[39,81077,18097],{"href":17082,"rel":81078},[77],[35,81080,18101],{},[35,81082,18104],{},[35,81084,18107,81085],{},[1217,81086,18110],{},[35,81088,18113,81089],{},[1217,81090,18116],{},[35,81092,18119],{},[35,81094,18122],{},[1576,81096,81097,81099,81101,81103],{},[71,81098,18127],{},[71,81100,18130],{},[71,81102,18133],{},[71,81104,18136],{},[35,81106,18139],{},[35,81108,18142],{},[35,81110,18145],{},[35,81112,18148],{},[35,81114,81115],{},[6850,81116,18153],{},[35,81118,18156],{},[35,81120,81121],{},[1217,81122,18004],{},[1159,81124],{},[35,81126,81127],{},[1217,81128,18167],{},{"title":13,"searchDepth":14,"depth":14,"links":81130},[81131,81132,81133],{"id":17918,"depth":14,"text":17919},{"id":17968,"depth":14,"text":17969},{"id":18061,"depth":14,"text":18062},{},{"title":17525,"description":18174},{"loc":18176},{"id":18183,"title":18184,"body":81138,"contentId":9296,"date":18581,"description":18582,"extension":18,"meta":81424,"navigation":20,"path":18584,"rawbody":18585,"robots":23,"schemaOrg":23,"seo":81425,"sitemap":81426,"stem":18593,"__hash__":18594},{"type":7,"value":81139,"toc":81418},[81140,81145,81151,81153,81157,81161,81163,81165,81167,81318],[35,81141,18189,81142,18195],{},[39,81143,18194],{"href":18192,"rel":81144},[77],[35,81146,18198,81147,13731,81149,18203],{},[39,81148,7142],{"href":8973},[39,81150,7484],{"href":7487},[35,81152,18206],{},[35,81154,81155],{},[1217,81156,18211],{},[35,81158,81159],{},[1690,81160],{"alt":18216,"src":18217},[51,81162,18194],{"id":18220},[35,81164,18223],{},[35,81166,18226],{},[1576,81168,81169,81212,81281],{},[71,81170,81171,239,81173],{},[6850,81172,18233],{},[1576,81174,81175,81185,81189,81199],{},[71,81176,81177,6853,81179,1638,81182],{},[6850,81178,18240],{},[39,81180,18245],{"href":18243,"rel":81181},[77],[39,81183,18250],{"href":18248,"rel":81184},[77],[71,81186,81187,18255],{},[6850,81188,9124],{},[71,81190,81191,6853,81193,1638,81196],{},[6850,81192,18260],{},[39,81194,18265],{"href":18263,"rel":81195},[77],[39,81197,18270],{"href":18268,"rel":81198},[77],[71,81200,81201,6853,81203,3076,81206,3080,81209],{},[6850,81202,18275],{},[39,81204,18280],{"href":18278,"rel":81205},[77],[39,81207,18285],{"href":18283,"rel":81208},[77],[39,81210,18290],{"href":18288,"rel":81211},[77],[71,81213,81214,239,81216],{},[6850,81215,18295],{},[1576,81217,81218,81225,81232,81242,81246,81253,81260,81267,81274],{},[71,81219,81220,6853,81222],{},[6850,81221,18302],{},[39,81223,6943],{"href":6941,"rel":81224},[77],[71,81226,81227,6853,81229],{},[6850,81228,18310],{},[39,81230,18315],{"href":18313,"rel":81231},[77],[71,81233,81234,6853,81236,1638,81239],{},[6850,81235,18320],{},[39,81237,18265],{"href":18263,"rel":81238},[77],[39,81240,18328],{"href":18326,"rel":81241},[77],[71,81243,81244,18333],{},[6850,81245,13959],{},[71,81247,81248,6853,81250],{},[6850,81249,18338],{},[39,81251,18343],{"href":18341,"rel":81252},[77],[71,81254,81255,6853,81257],{},[6850,81256,18348],{},[39,81258,18353],{"href":18351,"rel":81259},[77],[71,81261,81262,6853,81264],{},[6850,81263,18358],{},[39,81265,18363],{"href":18361,"rel":81266},[77],[71,81268,81269,6853,81271],{},[6850,81270,18368],{},[39,81272,18373],{"href":18371,"rel":81273},[77],[71,81275,81276,6853,81278],{},[6850,81277,18378],{},[39,81279,18383],{"href":18381,"rel":81280},[77],[71,81282,81283,239,81285],{},[6850,81284,18388],{},[1576,81286,81287,81294,81301,81308],{},[71,81288,81289,6853,81291],{},[6850,81290,18395],{},[39,81292,18400],{"href":18398,"rel":81293},[77],[71,81295,81296,6853,81298],{},[6850,81297,18405],{},[39,81299,18410],{"href":18408,"rel":81300},[77],[71,81302,81303,6853,81305],{},[6850,81304,18415],{},[39,81306,18420],{"href":18418,"rel":81307},[77],[71,81309,81310,6853,81312,1638,81315],{},[6850,81311,18425],{},[39,81313,18430],{"href":18428,"rel":81314},[77],[39,81316,18435],{"href":18433,"rel":81317},[77],[7139,81319,81320,81322,81324,81326,81328,81365,81367,81369,81371,81381,81387,81391,81395,81397,81399,81403,81405,81407,81409,81414],{"icon":7141,"text":9007,"to":18438,"target":7144},[35,81321,18441],{},[51,81323,16676],{"id":16675},[35,81325,18446],{},[35,81327,18449],{},[1576,81329,81330,81349],{},[71,81331,81332,6853,81334,81337,18461,81339,18467,81342,18473,81345,81347],{},[6850,81333,18302],{},[39,81335,6943],{"href":6941,"rel":81336},[77],[7162,81338],{},[39,81340,18466],{"href":18464,"rel":81341},[77],[39,81343,18472],{"href":18470,"rel":81344},[77],[7162,81346],{},[1690,81348],{"alt":18478,"src":18479},[71,81350,81351,6853,81353,1638,81356,81359,18492,81361,81363],{},[6850,81352,18320],{},[39,81354,18265],{"href":18263,"rel":81355},[77],[39,81357,18328],{"href":18326,"rel":81358},[77],[7162,81360],{},[7162,81362],{},[1690,81364],{"alt":18497,"src":18498},[35,81366,18501],{},[51,81368,18505],{"id":18504},[35,81370,18508],{},[68,81372,81373,81377],{},[71,81374,81375],{},[6850,81376,9124],{},[71,81378,81379],{},[6850,81380,13959],{},[35,81382,18521,81383,1638,81385,18528],{},[39,81384,18524],{"href":8973},[39,81386,18527],{"href":7487},[35,81388,18531,81389],{},[1217,81390,18534],{},[35,81392,81393],{},[1690,81394],{"alt":18539,"src":18540},[51,81396,18544],{"id":18543},[35,81398,18547],{},[939,81400,81401],{},[35,81402,18552],{},[35,81404,18555],{},[35,81406,18558],{},[35,81408,18561],{},[35,81410,18564,81411,215],{},[39,81412,18569],{"href":18567,"rel":81413},[77],[35,81415,81416],{},[1217,81417,18574],{},{"title":13,"searchDepth":14,"depth":14,"links":81419},[81420,81421,81422,81423],{"id":18220,"depth":14,"text":18194},{"id":16675,"depth":14,"text":16676},{"id":18504,"depth":14,"text":18505},{"id":18543,"depth":14,"text":18544},{},{"title":18184,"description":18582},{"loc":18584,"images":81427},[81428,81429,81430,81431],{"loc":18217},{"loc":18479},{"loc":18498},{"loc":18540},{"id":18596,"title":18597,"body":81433,"contentId":9266,"date":19366,"description":19367,"extension":18,"meta":82088,"navigation":20,"path":19369,"rawbody":19370,"robots":23,"schemaOrg":23,"seo":82089,"sitemap":82090,"stem":19373,"__hash__":19374},{"type":7,"value":81434,"toc":82082},[81435,81442,81453,81457,81459,81461,81469,81471,81473,81477,81479,81483,81687,81691,81703,81707,81775,81781,81787,81789,81791,81801,81805,81807,81816,81820,81822,81824,82046,82054,82062,82064,82066,82072,82076,82080],[35,81436,18602,81437,18610],{},[39,81438,81440],{"href":18605,"rel":81439},[77],[6850,81441,18609],{},[35,81443,81444,18615,81446,18621,81451,18625],{},[6850,81445,18609],{},[39,81447,81449],{"href":16699,"rel":81448},[77],[6850,81450,16701],{},[6850,81452,18624],{},[35,81454,18628,81455,18631],{},[6850,81456,18609],{},[51,81458,18635],{"id":18634},[35,81460,18638],{},[130,81462,132,81463,132,81467],{},[134,81464,136,81465,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,81466],{"src":18645,"type":141},[143,81468,18648],{},[35,81470,18651],{},[35,81472,18654],{},[35,81474,18657,81475],{},[1582,81476,215],{"repo":18660},[35,81478,18663],{},[35,81480,419,81481,18668],{},[6850,81482,18609],{},[192,81484,81485],{"className":1370,"code":18671,"language":1373,"meta":13,"style":13},[156,81486,81487,81503,81533,81557,81571,81583,81591,81603,81633,81637,81649,81665,81677,81681],{"__ignoreMap":13},[200,81488,81489,81491,81493,81495,81497,81499,81501],{"class":202,"line":203},[200,81490,443],{"class":379},[200,81492,18680],{"class":210},[200,81494,449],{"class":214},[200,81496,18685],{"class":223},[200,81498,227],{"class":210},[200,81500,18690],{"class":2423},[200,81502,416],{"class":210},[200,81504,81505,81507,81509,81511,81513,81515,81517,81519,81521,81523,81525,81527,81529,81531],{"class":202,"line":14},[200,81506,443],{"class":379},[200,81508,318],{"class":214},[200,81510,18701],{"class":235},[200,81512,239],{"class":214},[200,81514,18706],{"class":210},[200,81516,550],{"class":214},[200,81518,18711],{"class":235},[200,81520,239],{"class":214},[200,81522,18716],{"class":210},[200,81524,413],{"class":214},[200,81526,510],{"class":214},[200,81528,18723],{"class":223},[200,81530,227],{"class":210},[200,81532,230],{"class":214},[200,81534,81535,81537,81539,81541,81543,81545,81547,81549,81551,81553,81555],{"class":202,"line":255},[200,81536,18732],{"class":223},[200,81538,239],{"class":214},[200,81540,3714],{"class":214},[200,81542,393],{"class":379},[200,81544,18741],{"class":223},[200,81546,227],{"class":210},[200,81548,249],{"class":214},[200,81550,18748],{"class":245},[200,81552,249],{"class":214},[200,81554,550],{"class":214},[200,81556,280],{"class":214},[200,81558,81559,81561,81563,81565,81567,81569],{"class":202,"line":272},[200,81560,18759],{"class":235},[200,81562,239],{"class":214},[200,81564,242],{"class":214},[200,81566,18766],{"class":245},[200,81568,249],{"class":214},[200,81570,252],{"class":214},[200,81572,81573,81575,81577,81579,81581],{"class":202,"line":283},[200,81574,18775],{"class":235},[200,81576,239],{"class":214},[200,81578,18780],{"class":210},[200,81580,215],{"class":214},[200,81582,15976],{"class":210},[200,81584,81585,81587,81589],{"class":202,"line":300},[200,81586,5080],{"class":214},[200,81588,2089],{"class":210},[200,81590,252],{"class":214},[200,81592,81593,81595,81597,81599,81601],{"class":202,"line":310},[200,81594,18797],{"class":223},[200,81596,239],{"class":214},[200,81598,3714],{"class":214},[200,81600,393],{"class":379},[200,81602,280],{"class":214},[200,81604,81605,81607,81609,81611,81613,81615,81617,81619,81621,81623,81625,81627,81629,81631],{"class":202,"line":336},[200,81606,18810],{"class":210},[200,81608,215],{"class":214},[200,81610,18815],{"class":223},[200,81612,227],{"class":235},[200,81614,2752],{"class":214},[200,81616,18822],{"class":235},[200,81618,239],{"class":214},[200,81620,350],{"class":235},[200,81622,249],{"class":214},[200,81624,18831],{"class":245},[200,81626,249],{"class":214},[200,81628,18836],{"class":235},[200,81630,413],{"class":214},[200,81632,416],{"class":235},[200,81634,81635],{"class":202,"line":342},[200,81636,368],{"class":214},[200,81638,81639,81641,81643,81645,81647],{"class":202,"line":365},[200,81640,18849],{"class":223},[200,81642,239],{"class":214},[200,81644,3714],{"class":214},[200,81646,393],{"class":379},[200,81648,280],{"class":214},[200,81650,81651,81653,81655,81657,81659,81661,81663],{"class":202,"line":371},[200,81652,18862],{"class":210},[200,81654,215],{"class":214},[200,81656,15837],{"class":210},[200,81658,215],{"class":214},[200,81660,698],{"class":210},[200,81662,510],{"class":214},[200,81664,18875],{"class":214},[200,81666,81667,81669,81671,81673,81675],{"class":202,"line":398},[200,81668,18880],{"class":210},[200,81670,215],{"class":214},[200,81672,15837],{"class":210},[200,81674,510],{"class":214},[200,81676,18889],{"class":2423},[200,81678,81679],{"class":202,"line":405},[200,81680,4490],{"class":214},[200,81682,81683,81685],{"class":202,"line":410},[200,81684,413],{"class":214},[200,81686,416],{"class":210},[35,81688,419,81689,18905],{},[156,81690,18904],{},[35,81692,18908,81693,18912,81695,18916,81697,18920,81699,18924,81701,18927],{},[156,81694,18911],{},[156,81696,18915],{},[156,81698,18919],{},[156,81700,18923],{},[156,81702,18831],{},[35,81704,419,81705,18932],{},[6850,81706,18609],{},[192,81708,81709],{"className":1370,"code":18935,"language":1373,"meta":13,"style":13},[156,81710,81711,81729,81747,81769],{"__ignoreMap":13},[200,81712,81713,81715,81717,81719,81721,81723,81725,81727],{"class":202,"line":203},[200,81714,443],{"class":379},[200,81716,318],{"class":214},[200,81718,18946],{"class":210},[200,81720,413],{"class":214},[200,81722,510],{"class":214},[200,81724,18953],{"class":223},[200,81726,227],{"class":210},[200,81728,230],{"class":214},[200,81730,81731,81733,81735,81737,81739,81741,81743,81745],{"class":202,"line":14},[200,81732,18962],{"class":235},[200,81734,239],{"class":214},[200,81736,350],{"class":210},[200,81738,249],{"class":214},[200,81740,18831],{"class":245},[200,81742,249],{"class":214},[200,81744,360],{"class":210},[200,81746,252],{"class":214},[200,81748,81749,81751,81753,81755,81757,81759,81761,81763,81765,81767],{"class":202,"line":255},[200,81750,6707],{"class":223},[200,81752,239],{"class":214},[200,81754,3714],{"class":214},[200,81756,393],{"class":379},[200,81758,18741],{"class":223},[200,81760,227],{"class":210},[200,81762,249],{"class":214},[200,81764,18748],{"class":245},[200,81766,249],{"class":214},[200,81768,416],{"class":210},[200,81770,81771,81773],{"class":202,"line":272},[200,81772,413],{"class":214},[200,81774,416],{"class":210},[35,81776,19007,81777,19010,81779,19013],{},[156,81778,18923],{},[156,81780,18919],{},[35,81782,19016,81783,5557,81785,19022],{},[156,81784,19019],{},[156,81786,18923],{},[51,81788,19026],{"id":19025},[35,81790,19029],{},[1576,81792,81793,81797],{},[71,81794,81795,19037],{},[6850,81796,19036],{},[71,81798,81799,19043],{},[6850,81800,19042],{},[35,81802,19046,81803,19049],{},[6850,81804,18609],{},[51,81806,19053],{"id":19052},[35,81808,19056,81809,19067],{},[39,81810,19061,81812,19064,81814,611],{"href":19059,"rel":81811},[77],[156,81813,207],{},[156,81815,207],{},[939,81817,81818],{},[35,81819,19072],{},[35,81821,19075],{},[35,81823,19078],{},[192,81825,81826],{"className":1370,"code":19081,"language":1373,"meta":13,"style":13},[156,81827,81828,81844,81874,81898,81912,81924,81932,81954,81986,81990,82004,82020,82032,82036,82040],{"__ignoreMap":13},[200,81829,81830,81832,81834,81836,81838,81840,81842],{"class":202,"line":203},[200,81831,443],{"class":379},[200,81833,18680],{"class":210},[200,81835,449],{"class":214},[200,81837,18685],{"class":223},[200,81839,227],{"class":210},[200,81841,18690],{"class":2423},[200,81843,416],{"class":210},[200,81845,81846,81848,81850,81852,81854,81856,81858,81860,81862,81864,81866,81868,81870,81872],{"class":202,"line":14},[200,81847,443],{"class":379},[200,81849,318],{"class":214},[200,81851,18701],{"class":235},[200,81853,239],{"class":214},[200,81855,18706],{"class":210},[200,81857,550],{"class":214},[200,81859,18711],{"class":235},[200,81861,239],{"class":214},[200,81863,18716],{"class":210},[200,81865,413],{"class":214},[200,81867,510],{"class":214},[200,81869,18723],{"class":223},[200,81871,227],{"class":210},[200,81873,230],{"class":214},[200,81875,81876,81878,81880,81882,81884,81886,81888,81890,81892,81894,81896],{"class":202,"line":255},[200,81877,18732],{"class":223},[200,81879,239],{"class":214},[200,81881,3714],{"class":214},[200,81883,393],{"class":379},[200,81885,18741],{"class":223},[200,81887,227],{"class":210},[200,81889,249],{"class":214},[200,81891,18748],{"class":245},[200,81893,249],{"class":214},[200,81895,550],{"class":214},[200,81897,280],{"class":214},[200,81899,81900,81902,81904,81906,81908,81910],{"class":202,"line":272},[200,81901,18759],{"class":235},[200,81903,239],{"class":214},[200,81905,242],{"class":214},[200,81907,18766],{"class":245},[200,81909,249],{"class":214},[200,81911,252],{"class":214},[200,81913,81914,81916,81918,81920,81922],{"class":202,"line":283},[200,81915,18775],{"class":235},[200,81917,239],{"class":214},[200,81919,18780],{"class":210},[200,81921,215],{"class":214},[200,81923,15976],{"class":210},[200,81925,81926,81928,81930],{"class":202,"line":300},[200,81927,5080],{"class":214},[200,81929,2089],{"class":210},[200,81931,252],{"class":214},[200,81933,81934,81936,81938,81940,81942,81944,81946,81948,81950,81952],{"class":202,"line":310},[200,81935,18797],{"class":223},[200,81937,239],{"class":214},[200,81939,380],{"class":379},[200,81941,1095],{"class":214},[200,81943,19200],{"class":386},[200,81945,550],{"class":214},[200,81947,19205],{"class":386},[200,81949,2089],{"class":214},[200,81951,393],{"class":379},[200,81953,280],{"class":214},[200,81955,81956,81958,81960,81962,81964,81966,81968,81970,81972,81974,81976,81978,81980,81982,81984],{"class":202,"line":336},[200,81957,19216],{"class":206},[200,81959,19219],{"class":210},[200,81961,215],{"class":214},[200,81963,18815],{"class":223},[200,81965,227],{"class":235},[200,81967,2752],{"class":214},[200,81969,18822],{"class":235},[200,81971,239],{"class":214},[200,81973,350],{"class":235},[200,81975,249],{"class":214},[200,81977,18831],{"class":245},[200,81979,249],{"class":214},[200,81981,18836],{"class":235},[200,81983,413],{"class":214},[200,81985,416],{"class":235},[200,81987,81988],{"class":202,"line":342},[200,81989,529],{"emptyLinePlaceholder":20},[200,81991,81992,81994,81996,81998,82000,82002],{"class":202,"line":365},[200,81993,5252],{"class":206},[200,81995,1095],{"class":235},[200,81997,11837],{"class":214},[200,81999,19260],{"class":210},[200,82001,5267],{"class":235},[200,82003,230],{"class":214},[200,82005,82006,82008,82010,82012,82014,82016,82018],{"class":202,"line":371},[200,82007,19269],{"class":210},[200,82009,215],{"class":214},[200,82011,15837],{"class":210},[200,82013,215],{"class":214},[200,82015,698],{"class":210},[200,82017,510],{"class":214},[200,82019,18875],{"class":214},[200,82021,82022,82024,82026,82028,82030],{"class":202,"line":398},[200,82023,19286],{"class":210},[200,82025,215],{"class":214},[200,82027,15837],{"class":210},[200,82029,510],{"class":214},[200,82031,18889],{"class":2423},[200,82033,82034],{"class":202,"line":405},[200,82035,4962],{"class":214},[200,82037,82038],{"class":202,"line":410},[200,82039,368],{"class":214},[200,82041,82042,82044],{"class":202,"line":861},[200,82043,413],{"class":214},[200,82045,416],{"class":210},[35,82047,19311,82048,19314,82050,19317,82052,19320],{},[156,82049,18919],{},[156,82051,18923],{},[156,82053,207],{},[130,82055,132,82056,132,82060],{},[134,82057,136,82058,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,82059],{"src":19327,"type":141},[143,82061,19330],{},[51,82063,19334],{"id":19333},[35,82065,19337],{},[1576,82067,82068,82070],{},[71,82069,19342],{},[71,82071,19345],{},[35,82073,19348,82074,19351],{},[6850,82075,18609],{},[35,82077,19354,82078],{},[1582,82079,215],{"repo":18660},[1171,82081,19359],{},{"title":13,"searchDepth":14,"depth":14,"links":82083},[82084,82085,82086,82087],{"id":18634,"depth":14,"text":18635},{"id":19025,"depth":14,"text":19026},{"id":19052,"depth":14,"text":19053},{"id":19333,"depth":14,"text":19334},{},{"title":18597,"description":19367},{"loc":19369},{"id":19376,"title":19377,"body":82092,"contentId":4263,"date":20403,"description":20404,"extension":18,"meta":82956,"navigation":20,"path":20406,"rawbody":20407,"robots":23,"schemaOrg":23,"seo":82957,"sitemap":82958,"stem":20410,"__hash__":20411},{"type":7,"value":82093,"toc":82950},[82094,82098,82107,82116,82118,82120,82140,82142,82166,82170,82174,82180,82188,82192,82194,82196,82200,82224,82304,82306,82308,82315,82899,82901,82913,82917,82919,82928,82930,82932,82940,82944,82948],[35,82095,82096,19385],{},[6850,82097,19384],{},[35,82099,19388,82100,19392,82102,19400],{},[6850,82101,19391],{},[39,82103,82105],{"href":19395,"rel":82104},[77],[6850,82106,19399],{},[939,82108,82109],{},[35,82110,82111,19407,82113,215],{},[200,82112,945],{},[39,82114,19412],{"href":19410,"rel":82115},[77],[51,82117,19416],{"id":19415},[35,82119,19419],{},[1576,82121,82122,82131],{},[71,82123,419,82124,19427,82126,19434],{},[6850,82125,19426],{},[39,82127,82129],{"href":19430,"rel":82128},[77],[6850,82130,2913],{},[71,82132,419,82133,19440,82135,19448],{},[6850,82134,19439],{},[39,82136,82138],{"href":19443,"rel":82137},[77],[6850,82139,19447],{},[35,82141,19451],{},[192,82143,82144],{"className":5891,"code":19454,"filename":19455,"language":18,"meta":13,"style":13},[156,82145,82146,82150,82154,82158,82162],{"__ignoreMap":13},[200,82147,82148],{"class":202,"line":203},[200,82149,19462],{"class":210},[200,82151,82152],{"class":202,"line":14},[200,82153,19467],{"class":210},[200,82155,82156],{"class":202,"line":255},[200,82157,19472],{"class":210},[200,82159,82160],{"class":202,"line":272},[200,82161,19477],{"class":210},[200,82163,82164],{"class":202,"line":283},[200,82165,19462],{"class":214},[35,82167,19484,82168,19487],{},[156,82169,26],{},[35,82171,19490,82172,19493],{},[6850,82173,2913],{},[35,82175,19496,82176,19499,82178,19502],{},[156,82177,26],{},[156,82179,3280],{},[35,82181,19505,82182,19509,82184,19513,82186],{},[6850,82183,19508],{},[6850,82185,19512],{},[6850,82187,19516],{},[35,82189,82190],{},[1217,82191,19521],{},[51,82193,19525],{"id":19524},[35,82195,19528],{},[35,82197,19531,82198,19534],{},[6850,82199,19447],{},[192,82201,82202],{"className":19537,"code":19538,"filename":19539,"language":19540,"meta":13,"style":13},[156,82203,82204,82208,82212,82216,82220],{"__ignoreMap":13},[200,82205,82206],{"class":202,"line":203},[200,82207,19547],{},[200,82209,82210],{"class":202,"line":14},[200,82211,19552],{},[200,82213,82214],{"class":202,"line":255},[200,82215,529],{"emptyLinePlaceholder":20},[200,82217,82218],{"class":202,"line":272},[200,82219,19561],{},[200,82221,82222],{"class":202,"line":283},[200,82223,19566],{},[192,82225,82226],{"className":19537,"code":19569,"filename":19570,"language":19540,"meta":13,"style":13},[156,82227,82228,82232,82236,82240,82244,82248,82252,82256,82260,82264,82268,82272,82276,82280,82284,82288,82292,82296,82300],{"__ignoreMap":13},[200,82229,82230],{"class":202,"line":203},[200,82231,19577],{},[200,82233,82234],{"class":202,"line":14},[200,82235,529],{"emptyLinePlaceholder":20},[200,82237,82238],{"class":202,"line":255},[200,82239,19586],{},[200,82241,82242],{"class":202,"line":272},[200,82243,529],{"emptyLinePlaceholder":20},[200,82245,82246],{"class":202,"line":283},[200,82247,19595],{},[200,82249,82250],{"class":202,"line":300},[200,82251,19600],{},[200,82253,82254],{"class":202,"line":310},[200,82255,529],{"emptyLinePlaceholder":20},[200,82257,82258],{"class":202,"line":336},[200,82259,19609],{},[200,82261,82262],{"class":202,"line":342},[200,82263,230],{},[200,82265,82266],{"class":202,"line":365},[200,82267,19618],{},[200,82269,82270],{"class":202,"line":371},[200,82271,19623],{},[200,82273,82274],{"class":202,"line":398},[200,82275,19628],{},[200,82277,82278],{"class":202,"line":405},[200,82279,19633],{},[200,82281,82282],{"class":202,"line":410},[200,82283,19638],{},[200,82285,82286],{"class":202,"line":861},[200,82287,19643],{},[200,82289,82290],{"class":202,"line":890},[200,82291,529],{"emptyLinePlaceholder":20},[200,82293,82294],{"class":202,"line":900},[200,82295,19652],{},[200,82297,82298],{"class":202,"line":905},[200,82299,4962],{},[200,82301,82302],{"class":202,"line":931},[200,82303,1130],{},[35,82305,19663],{},[51,82307,19667],{"id":19666},[35,82309,19670,82310,19678],{},[39,82311,82313],{"href":19673,"rel":82312},[77],[6850,82314,19677],{},[192,82316,82317],{"className":1370,"code":19681,"filename":10012,"language":1373,"meta":13,"style":13},[156,82318,82319,82337,82355,82361,82367,82371,82383,82401,82419,82437,82441,82455,82459,82473,82485,82489,82493,82501,82509,82513,82517,82521,82525,82545,82549,82569,82575,82583,82587,82601,82615,82631,82635,82639,82643,82649,82653,82677,82705,82711,82743,82747,82753,82763,82777,82789,82795,82799,82803,82815,82833,82837,82841,82845,82863,82869,82873,82887],{"__ignoreMap":13},[200,82320,82321,82323,82325,82327,82329,82331,82333,82335],{"class":202,"line":203},[200,82322,973],{"class":206},[200,82324,318],{"class":214},[200,82326,19692],{"class":210},[200,82328,981],{"class":214},[200,82330,984],{"class":206},[200,82332,242],{"class":214},[200,82334,19701],{"class":245},[200,82336,992],{"class":214},[200,82338,82339,82341,82343,82345,82347,82349,82351,82353],{"class":202,"line":14},[200,82340,973],{"class":206},[200,82342,318],{"class":214},[200,82344,19712],{"class":210},[200,82346,981],{"class":214},[200,82348,984],{"class":206},[200,82350,242],{"class":214},[200,82352,19721],{"class":245},[200,82354,992],{"class":214},[200,82356,82357,82359],{"class":202,"line":255},[200,82358,973],{"class":206},[200,82360,280],{"class":214},[200,82362,82363,82365],{"class":202,"line":272},[200,82364,19734],{"class":210},[200,82366,252],{"class":214},[200,82368,82369],{"class":202,"line":283},[200,82370,19741],{"class":210},[200,82372,82373,82375,82377,82379,82381],{"class":202,"line":300},[200,82374,413],{"class":214},[200,82376,984],{"class":206},[200,82378,242],{"class":214},[200,82380,19752],{"class":245},[200,82382,992],{"class":214},[200,82384,82385,82387,82389,82391,82393,82395,82397,82399],{"class":202,"line":310},[200,82386,973],{"class":206},[200,82388,318],{"class":214},[200,82390,4843],{"class":210},[200,82392,981],{"class":214},[200,82394,984],{"class":206},[200,82396,242],{"class":214},[200,82398,19771],{"class":245},[200,82400,992],{"class":214},[200,82402,82403,82405,82407,82409,82411,82413,82415,82417],{"class":202,"line":336},[200,82404,973],{"class":206},[200,82406,318],{"class":214},[200,82408,3165],{"class":210},[200,82410,981],{"class":214},[200,82412,984],{"class":206},[200,82414,242],{"class":214},[200,82416,19790],{"class":245},[200,82418,992],{"class":214},[200,82420,82421,82423,82425,82427,82429,82431,82433,82435],{"class":202,"line":342},[200,82422,973],{"class":206},[200,82424,318],{"class":214},[200,82426,19801],{"class":210},[200,82428,981],{"class":214},[200,82430,984],{"class":206},[200,82432,242],{"class":214},[200,82434,19810],{"class":245},[200,82436,992],{"class":214},[200,82438,82439],{"class":202,"line":365},[200,82440,529],{"emptyLinePlaceholder":20},[200,82442,82443,82445,82447,82449,82451,82453],{"class":202,"line":371},[200,82444,443],{"class":379},[200,82446,19823],{"class":210},[200,82448,449],{"class":214},[200,82450,12997],{"class":214},[200,82452,19692],{"class":223},[200,82454,3115],{"class":210},[200,82456,82457],{"class":202,"line":398},[200,82458,3131],{"class":214},[200,82460,82461,82463,82465,82467,82469,82471],{"class":202,"line":405},[200,82462,11797],{"class":235},[200,82464,239],{"class":214},[200,82466,242],{"class":214},[200,82468,19846],{"class":245},[200,82470,249],{"class":214},[200,82472,252],{"class":214},[200,82474,82475,82477,82479,82481,82483],{"class":202,"line":410},[200,82476,19855],{"class":235},[200,82478,239],{"class":214},[200,82480,242],{"class":214},[200,82482,19862],{"class":245},[200,82484,992],{"class":214},[200,82486,82487],{"class":202,"line":861},[200,82488,368],{"class":214},[200,82490,82491],{"class":202,"line":890},[200,82492,3131],{"class":214},[200,82494,82495,82497,82499],{"class":202,"line":900},[200,82496,19877],{"class":235},[200,82498,239],{"class":214},[200,82500,280],{"class":214},[200,82502,82503,82505,82507],{"class":202,"line":905},[200,82504,19886],{"class":235},[200,82506,239],{"class":214},[200,82508,19891],{"class":214},[200,82510,82511],{"class":202,"line":931},[200,82512,4962],{"class":214},[200,82514,82515],{"class":202,"line":3640},[200,82516,4490],{"class":214},[200,82518,82519],{"class":202,"line":3645},[200,82520,416],{"class":210},[200,82522,82523],{"class":202,"line":4020},[200,82524,529],{"emptyLinePlaceholder":20},[200,82526,82527,82529,82531,82533,82535,82537,82539,82541,82543],{"class":202,"line":4475},[200,82528,443],{"class":379},[200,82530,19914],{"class":210},[200,82532,449],{"class":214},[200,82534,3165],{"class":210},[200,82536,215],{"class":214},[200,82538,293],{"class":223},[200,82540,227],{"class":210},[200,82542,19927],{"class":214},[200,82544,416],{"class":210},[200,82546,82547],{"class":202,"line":4481},[200,82548,529],{"emptyLinePlaceholder":20},[200,82550,82551,82553,82555,82557,82559,82561,82563,82565,82567],{"class":202,"line":4487},[200,82552,3108],{"class":210},[200,82554,215],{"class":214},[200,82556,19942],{"class":223},[200,82558,19945],{"class":210},[200,82560,550],{"class":214},[200,82562,380],{"class":379},[200,82564,3714],{"class":214},[200,82566,393],{"class":379},[200,82568,280],{"class":214},[200,82570,82571,82573],{"class":202,"line":4493},[200,82572,3743],{"class":206},[200,82574,280],{"class":214},[200,82576,82577,82579,82581],{"class":202,"line":5454},[200,82578,19966],{"class":235},[200,82580,239],{"class":214},[200,82582,1400],{"class":235},[200,82584,82585],{"class":202,"line":5459},[200,82586,4442],{"class":214},[200,82588,82589,82591,82593,82595,82597,82599],{"class":202,"line":13227},[200,82590,11722],{"class":235},[200,82592,239],{"class":214},[200,82594,242],{"class":214},[200,82596,19985],{"class":245},[200,82598,249],{"class":214},[200,82600,252],{"class":214},[200,82602,82603,82605,82607,82609,82611,82613],{"class":202,"line":13253},[200,82604,19994],{"class":235},[200,82606,239],{"class":214},[200,82608,242],{"class":214},[200,82610,20001],{"class":245},[200,82612,249],{"class":214},[200,82614,252],{"class":214},[200,82616,82617,82619,82621,82623,82625,82627,82629],{"class":202,"line":13269},[200,82618,20010],{"class":235},[200,82620,239],{"class":214},[200,82622,19801],{"class":223},[200,82624,227],{"class":235},[200,82626,20019],{"class":210},[200,82628,2089],{"class":235},[200,82630,252],{"class":214},[200,82632,82633],{"class":202,"line":13313},[200,82634,12083],{"class":214},[200,82636,82637],{"class":202,"line":13319},[200,82638,4484],{"class":235},[200,82640,82641],{"class":202,"line":13324},[200,82642,4490],{"class":214},[200,82644,82645,82647],{"class":202,"line":13350},[200,82646,413],{"class":214},[200,82648,416],{"class":210},[200,82650,82651],{"class":202,"line":13355},[200,82652,529],{"emptyLinePlaceholder":20},[200,82654,82655,82657,82659,82661,82663,82665,82667,82669,82671,82673,82675],{"class":202,"line":13375},[200,82656,3108],{"class":210},[200,82658,215],{"class":214},[200,82660,19942],{"class":223},[200,82662,20056],{"class":210},[200,82664,550],{"class":214},[200,82666,380],{"class":379},[200,82668,1095],{"class":214},[200,82670,6663],{"class":386},[200,82672,2089],{"class":214},[200,82674,393],{"class":379},[200,82676,280],{"class":214},[200,82678,82679,82681,82683,82685,82687,82689,82691,82693,82695,82697,82699,82701,82703],{"class":202,"line":13395},[200,82680,20075],{"class":206},[200,82682,1095],{"class":235},[200,82684,6663],{"class":210},[200,82686,215],{"class":214},[200,82688,20084],{"class":210},[200,82690,215],{"class":214},[200,82692,20089],{"class":210},[200,82694,3283],{"class":214},[200,82696,242],{"class":214},[200,82698,19985],{"class":245},[200,82700,249],{"class":214},[200,82702,5267],{"class":235},[200,82704,230],{"class":214},[200,82706,82707,82709],{"class":202,"line":13400},[200,82708,20106],{"class":206},[200,82710,280],{"class":214},[200,82712,82713,82715,82717,82719,82721,82723,82725,82727,82729,82731,82733,82735,82737,82739,82741],{"class":202,"line":13408},[200,82714,20113],{"class":379},[200,82716,5041],{"class":210},[200,82718,510],{"class":214},[200,82720,452],{"class":206},[200,82722,4843],{"class":223},[200,82724,20124],{"class":214},[200,82726,20127],{"class":235},[200,82728,239],{"class":214},[200,82730,20132],{"class":1978},[200,82732,20135],{"class":214},[200,82734,227],{"class":235},[200,82736,249],{"class":214},[200,82738,20142],{"class":245},[200,82740,249],{"class":214},[200,82742,416],{"class":235},[200,82744,82745],{"class":202,"line":13426},[200,82746,529],{"emptyLinePlaceholder":20},[200,82748,82749,82751],{"class":202,"line":13456},[200,82750,11715],{"class":206},[200,82752,280],{"class":214},[200,82754,82755,82757,82759,82761],{"class":202,"line":13462},[200,82756,20161],{"class":235},[200,82758,239],{"class":214},[200,82760,350],{"class":235},[200,82762,230],{"class":214},[200,82764,82765,82767,82769,82771,82773,82775],{"class":202,"line":13467},[200,82766,4914],{"class":235},[200,82768,239],{"class":214},[200,82770,242],{"class":214},[200,82772,698],{"class":245},[200,82774,249],{"class":214},[200,82776,252],{"class":214},[200,82778,82779,82781,82783,82785,82787],{"class":202,"line":13472},[200,82780,4929],{"class":235},[200,82782,239],{"class":214},[200,82784,5041],{"class":210},[200,82786,215],{"class":214},[200,82788,20194],{"class":210},[200,82790,82791,82793],{"class":202,"line":13487},[200,82792,13378],{"class":214},[200,82794,4496],{"class":235},[200,82796,82797],{"class":202,"line":13498},[200,82798,4478],{"class":214},[200,82800,82801],{"class":202,"line":13503},[200,82802,4962],{"class":214},[200,82804,82805,82807,82809,82811,82813],{"class":202,"line":13508},[200,82806,20213],{"class":206},[200,82808,1095],{"class":235},[200,82810,19260],{"class":210},[200,82812,5267],{"class":235},[200,82814,230],{"class":214},[200,82816,82817,82819,82821,82823,82825,82827,82829,82831],{"class":202,"line":20224},[200,82818,20227],{"class":206},[200,82820,12997],{"class":214},[200,82822,20232],{"class":223},[200,82824,227],{"class":235},[200,82826,249],{"class":214},[200,82828,20239],{"class":245},[200,82830,249],{"class":214},[200,82832,416],{"class":235},[200,82834,82835],{"class":202,"line":20246},[200,82836,4962],{"class":214},[200,82838,82839],{"class":202,"line":20251},[200,82840,4490],{"class":214},[200,82842,82843],{"class":202,"line":20256},[200,82844,529],{"emptyLinePlaceholder":20},[200,82846,82847,82849,82851,82853,82855,82857,82859,82861],{"class":202,"line":20261},[200,82848,20264],{"class":206},[200,82850,12997],{"class":214},[200,82852,20232],{"class":223},[200,82854,227],{"class":235},[200,82856,249],{"class":214},[200,82858,20275],{"class":245},[200,82860,249],{"class":214},[200,82862,416],{"class":235},[200,82864,82865,82867],{"class":202,"line":20282},[200,82866,413],{"class":214},[200,82868,416],{"class":210},[200,82870,82871],{"class":202,"line":20289},[200,82872,529],{"emptyLinePlaceholder":20},[200,82874,82875,82877,82879,82881,82883,82885],{"class":202,"line":20294},[200,82876,443],{"class":379},[200,82878,20299],{"class":210},[200,82880,449],{"class":214},[200,82882,12997],{"class":214},[200,82884,19712],{"class":223},[200,82886,466],{"class":210},[200,82888,82889,82891,82893,82895,82897],{"class":202,"line":20310},[200,82890,207],{"class":206},[200,82892,20315],{"class":210},[200,82894,215],{"class":214},[200,82896,20320],{"class":223},[200,82898,20323],{"class":210},[35,82900,20326],{},[68,82902,82903,82909],{},[71,82904,419,82905,20334,82907,215],{},[156,82906,20333],{},[156,82908,19985],{},[71,82910,419,82911,20342],{},[156,82912,20341],{},[35,82914,20345,82915,20348],{},[156,82916,19771],{},[35,82918,20351],{},[939,82920,82921],{},[35,82922,82923,20358,82925,215],{},[200,82924,945],{},[39,82926,20362],{"href":19410,"rel":82927},[77],[51,82929,20366],{"id":20365},[35,82931,20369],{},[130,82933,132,82934,132,82938],{},[134,82935,136,82936,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,82937],{"src":20376,"type":141},[143,82939,20379],{},[35,82941,20382,82942,20386],{},[6850,82943,20385],{},[35,82945,20389,82946,20393],{},[6850,82947,20392],{},[1171,82949,20396],{},{"title":13,"searchDepth":14,"depth":14,"links":82951},[82952,82953,82954,82955],{"id":19415,"depth":14,"text":19416},{"id":19524,"depth":14,"text":19525},{"id":19666,"depth":14,"text":19667},{"id":20365,"depth":14,"text":20366},{},{"title":19377,"description":20404},{"loc":20406},{"id":20413,"title":20414,"body":82960,"contentId":9291,"date":20793,"description":20794,"extension":18,"meta":83239,"navigation":20,"path":20796,"rawbody":20797,"robots":23,"schemaOrg":23,"seo":83240,"sitemap":83241,"stem":20811,"__hash__":20812},{"type":7,"value":82961,"toc":83233},[82962,82964,82968,82972,82976,82980,82982,82984,82986,83094],[35,82963,20419],{},[35,82965,20422,82966,20425],{},[6850,82967,7484],{},[35,82969,82970],{},[1690,82971],{"alt":20430,"src":20431},[35,82973,82974],{},[1690,82975],{"alt":20436,"src":20437},[35,82977,82978],{},[1217,82979,20442],{},[51,82981,18527],{"id":20445},[35,82983,20448],{},[35,82985,20451],{},[1576,82987,82988,83053],{},[71,82989,82990,82992],{},[6850,82991,20458],{},[1576,82993,82994,83001,83008,83015,83022,83029,83039,83046],{},[71,82995,82996,6853,82998],{},[6850,82997,20465],{},[39,82999,20470],{"href":20468,"rel":83000},[77],[71,83002,83003,6853,83005],{},[6850,83004,20475],{},[39,83006,16636],{"href":16634,"rel":83007},[77],[71,83009,83010,6853,83012],{},[6850,83011,20483],{},[39,83013,7018],{"href":7016,"rel":83014},[77],[71,83016,83017,6853,83019],{},[6850,83018,20491],{},[39,83020,20496],{"href":20494,"rel":83021},[77],[71,83023,83024,6853,83026],{},[6850,83025,20501],{},[39,83027,20506],{"href":20504,"rel":83028},[77],[71,83030,83031,6853,83033,1638,83036],{},[6850,83032,20511],{},[39,83034,20516],{"href":20514,"rel":83035},[77],[39,83037,20521],{"href":20519,"rel":83038},[77],[71,83040,83041,6853,83043],{},[6850,83042,13959],{},[39,83044,7033],{"href":7031,"rel":83045},[77],[71,83047,83048,6853,83050],{},[6850,83049,20533],{},[39,83051,20538],{"href":20536,"rel":83052},[77],[71,83054,83055,83057],{},[6850,83056,20543],{},[1576,83058,83059,83066,83073,83077,83084],{},[71,83060,83061,6853,83063],{},[6850,83062,20550],{},[39,83064,20555],{"href":20553,"rel":83065},[77],[71,83067,83068,6853,83070],{},[6850,83069,9124],{},[39,83071,7033],{"href":7031,"rel":83072},[77],[71,83074,83075,20568],{},[6850,83076,20567],{},[71,83078,83079,6853,83081],{},[6850,83080,20573],{},[39,83082,20578],{"href":20576,"rel":83083},[77],[71,83085,83086,6853,83088,1638,83091],{},[6850,83087,20583],{},[39,83089,20588],{"href":20586,"rel":83090},[77],[39,83092,20593],{"href":20591,"rel":83093},[77],[7139,83095,83096,83098,83100,83104,83108,83112,83114,83116,83163,83165,83167,83172,83177,83184,83188,83190],{"icon":7141,"text":7484,"to":20596,"target":7144},[35,83097,20599],{},[35,83099,20602],{},[35,83101,83102],{},[1690,83103],{"alt":20607,"src":20608},[35,83105,83106],{},[1690,83107],{"alt":20613,"src":20614},[35,83109,83110],{},[1690,83111],{"alt":20619,"src":20620},[51,83113,16676],{"id":16675},[35,83115,20625],{},[68,83117,83118,83131,83150],{},[71,83119,83120,6853,83122,83125,20637,83127,83129],{},[6850,83121,20483],{},[39,83123,7018],{"href":7016,"rel":83124},[77],[7162,83126],{},[7162,83128],{},[1690,83130],{"alt":20642,"src":20643},[71,83132,83133,6853,83135,1638,83138,83141,20656,83143,20662,83146,83148],{},[6850,83134,20511],{},[39,83136,20516],{"href":20514,"rel":83137},[77],[39,83139,20521],{"href":20519,"rel":83140},[77],[7162,83142],{},[39,83144,20661],{"href":20659,"rel":83145},[77],[7162,83147],{},[1690,83149],{"alt":20667,"src":20668},[71,83151,83152,6853,83154,83157,20678,83159,83161],{},[6850,83153,20573],{},[39,83155,20578],{"href":20576,"rel":83156},[77],[7162,83158],{},[7162,83160],{},[1690,83162],{"alt":20683,"src":20684},[35,83164,18501],{},[51,83166,20690],{"id":20689},[35,83168,20693,83169,20698],{},[39,83170,8027],{"href":20696,"rel":83171},[77],[35,83173,20701,83174,20705],{},[39,83175,14039],{"href":14037,"rel":83176},[77],[35,83178,83179],{},[1217,83180,20710,83181,20715],{},[39,83182,20714],{"href":14025,"rel":83183},[77],[35,83185,83186],{},[1690,83187],{"alt":20720,"src":20721},[51,83189,18505],{"id":18504},[7139,83191,83192,83194,83215,83219],{"icon":20726,"text":20727,"href":20728},[35,83193,20731],{},[68,83195,83196,83204],{},[71,83197,83198,20738,83200,20741,83202,20744],{},[6850,83199,9124],{},[7162,83201],{},[6850,83203,14039],{},[71,83205,83206,20749,83208,20752,83210,20756,83213,20759],{},[6850,83207,13959],{},[7162,83209],{},[39,83211,20714],{"href":14025,"rel":83212},[77],[156,83214,14029],{},[35,83216,83217],{},[1690,83218],{"alt":20764,"src":20765},[7139,83220,83221,83223,83225,83227,83231],{"icon":20726,"text":20768,"href":1816,"target":7144},[51,83222,20772],{"id":20771},[35,83224,20775],{},[35,83226,20778],{},[35,83228,83229],{},[1217,83230,20783],{},[35,83232,20786],{},{"title":13,"searchDepth":14,"depth":14,"links":83234},[83235,83236,83237,83238],{"id":20445,"depth":14,"text":18527},{"id":16675,"depth":14,"text":16676},{"id":20689,"depth":14,"text":20690},{"id":18504,"depth":14,"text":18505},{},{"title":20414,"description":20794},{"loc":20796,"images":83242},[83243,83244,83245,83246,83247,83248,83249,83250,83251,83252],{"loc":20431},{"loc":20437},{"loc":20608},{"loc":20614},{"loc":20620},{"loc":20643},{"loc":20668},{"loc":20684},{"loc":20721},{"loc":20765},{"id":20814,"title":20815,"body":83254,"contentId":9286,"date":21315,"description":21316,"extension":18,"meta":83599,"navigation":20,"path":21318,"rawbody":21319,"robots":23,"schemaOrg":23,"seo":83600,"sitemap":83601,"stem":21336,"__hash__":21337},{"type":7,"value":83255,"toc":83596},[83256,83260],[35,83257,20820,83258,20823],{},[6850,83259,7142],{},[7905,83261,83262,83264,83268,83272,83274,83276,83278,83286,83288,83290,83380,83384],{"id":20826},[35,83263,20829],{},[35,83265,83266],{},[1217,83267,20834],{},[35,83269,83270],{},[1690,83271],{"alt":20839,"src":20840},[51,83273,6822],{"id":6821},[35,83275,20845],{},[35,83277,20848],{},[35,83279,20851,83280,3076,83282,3080,83284,20860],{},[6850,83281,20854],{},[6850,83283,19508],{},[6850,83285,20859],{},[35,83287,20863],{},[35,83289,20866],{},[68,83291,83292,83316,83350],{},[71,83293,20871,83294],{},[1576,83295,83296,83300,83304,83308,83312],{},[71,83297,83298,20879],{},[6850,83299,20878],{},[71,83301,83302,20879],{},[6850,83303,20884],{},[71,83305,83306,20890],{},[6850,83307,20889],{},[71,83309,83310,20896],{},[6850,83311,20895],{},[71,83313,83314,20902],{},[6850,83315,20901],{},[71,83317,20905,83318],{},[1576,83319,83320,83324,83328,83332,83336,83340,83344],{},[71,83321,83322,20913],{},[6850,83323,20912],{},[71,83325,83326,20919],{},[6850,83327,20918],{},[71,83329,83330,20925],{},[6850,83331,20924],{},[71,83333,83334,20931],{},[6850,83335,20930],{},[71,83337,83338,20937],{},[6850,83339,20936],{},[71,83341,83342,20943],{},[6850,83343,20942],{},[71,83345,83346,20948,83348,2089],{},[6850,83347,9124],{},[1217,83349,20951],{},[71,83351,20954,83352],{},[1576,83353,83354,83358,83362,83366,83372,83376],{},[71,83355,83356,20962],{},[6850,83357,20961],{},[71,83359,83360,20968],{},[6850,83361,20967],{},[71,83363,83364,20973],{},[6850,83365,16602],{},[71,83367,83368,20948,83370,2089],{},[6850,83369,13959],{},[1217,83371,20980],{},[71,83373,83374,20986],{},[6850,83375,20985],{},[71,83377,83378,20992],{},[6850,83379,20991],{},[35,83381,20995,83382,215],{},[6850,83383,20998],{},[7139,83385,83386,83400,83404,83406,83408,83493,83495,83497,83499,83545],{"w-full":13,"icon":7141,"text":7142,"to":7143,"target":7144},[35,83387,21003,83388,21009,83391,21015,83394,21021,83397,21027],{},[39,83389,21008],{"href":21006,"rel":83390},[77],[39,83392,21014],{"href":21012,"rel":83393},[77],[39,83395,21020],{"href":21018,"rel":83396},[77],[39,83398,21026],{"href":21024,"rel":83399},[77],[35,83401,83402],{},[1217,83403,21032],{},[51,83405,7148],{"id":7147},[35,83407,21037],{},[68,83409,83410,83429,83448,83470],{},[71,83411,83412,6853,83414,83417,83419,21056,83422,21062,83425,83427],{},[6850,83413,20889],{},[39,83415,21048],{"href":21046,"rel":83416},[77],[7162,83418],{},[39,83420,21055],{"href":21053,"rel":83421},[77],[39,83423,21061],{"href":21059,"rel":83424},[77],[7162,83426],{},[1690,83428],{"alt":21067,"src":21068},[71,83430,83431,6853,83433,1638,83436,83439,419,83441,21088,83444,83446],{},[6850,83432,20895],{},[39,83434,6943],{"href":6941,"rel":83435},[77],[39,83437,21080],{"href":21078,"rel":83438},[77],[7162,83440],{},[39,83442,21087],{"href":21085,"rel":83443},[77],[7162,83445],{},[1690,83447],{"alt":21093,"src":21094},[71,83449,83450,6853,83452,83455,21106,83457,21112,83460,21118,83463,215,83466,83468],{},[6850,83451,20930],{},[39,83453,21103],{"href":21101,"rel":83454},[77],[7162,83456],{},[39,83458,21111],{"href":21109,"rel":83459},[77],[39,83461,21117],{"href":21115,"rel":83462},[77],[39,83464,21123],{"href":21121,"rel":83465},[77],[7162,83467],{},[1690,83469],{"alt":21128,"src":21129},[71,83471,83472,6853,83474,1638,83477,83480,21142,83482,21148,83485,83487,83489,83491],{},[6850,83473,16602],{},[39,83475,16606],{"href":9582,"rel":83476},[77],[39,83478,16611],{"href":16609,"rel":83479},[77],[7162,83481],{},[39,83483,21147],{"href":21145,"rel":83484},[77],[7162,83486],{},[1690,83488],{"alt":21153,"src":21154},[7162,83490],{},[1690,83492],{"alt":21159,"src":21160},[35,83494,18501],{},[51,83496,18505],{"id":18504},[35,83498,21167],{},[68,83500,83501,83530],{},[71,83502,83503,21174,83505,21177,83507,21180,83509,83511,83513,21189,83515,21194,83518,21197,83520,83522,83524,21206,83526,21209,83528,21213],{},[6850,83504,9124],{},[7162,83506],{},[6850,83508,14039],{},[7162,83510],{},[1690,83512],{"alt":21185,"src":21186},[7162,83514],{},[39,83516,7881],{"href":21192,"rel":83517},[77],[7162,83519],{},[7162,83521],{},[1690,83523],{"alt":21202,"src":21203},[7162,83525],{},[7162,83527],{},[6850,83529,21212],{},[71,83531,83532,21218,83534,21221,83536,21225,83539,21228,83541,83543],{},[6850,83533,13959],{},[7162,83535],{},[39,83537,20714],{"href":14025,"rel":83538},[77],[156,83540,14029],{},[7162,83542],{},[1690,83544],{"alt":21233,"src":21234},[7139,83546,83547,83549,83551,83553,83558,83564,83568,83572,83576,83580,83584,83586,83594],{"icon":20726,"text":20768,"href":1816,"target":7144},[51,83548,21240],{"id":21239},[35,83550,21243],{},[35,83552,21246],{},[35,83554,21249,83555,21253],{},[39,83556,9575],{"href":9573,"rel":83557},[77],[35,83559,21256,83560,21259,83562,21263],{},[6850,83561,9575],{},[1217,83563,21262],{},[35,83565,83566],{},[1690,83567],{"alt":21268,"src":21269},[35,83569,83570],{},[1690,83571],{"alt":21274,"src":21275},[35,83573,83574],{},[1690,83575],{"alt":21280,"src":21281},[35,83577,83578],{},[1690,83579],{"alt":21286,"src":21287},[35,83581,83582],{},[1217,83583,21292],{},[51,83585,21296],{"id":21295},[35,83587,21299,83588,21304,83591,21308],{},[39,83589,7484],{"href":21302,"rel":83590},[77],[39,83592,9007],{"href":18192,"rel":83593},[77],[35,83595,21311],{},{"title":13,"searchDepth":14,"depth":14,"links":83597},[83598],{"id":6821,"depth":14,"text":6822},{},{"title":20815,"description":21316},{"loc":21318,"images":83602},[83603,83604,83605,83606,83607,83608,83609,83610,83611,83612,83613,83614,83615],{"loc":20840},{"loc":21068},{"loc":21094},{"loc":21129},{"loc":21154},{"loc":21160},{"loc":21186},{"loc":21203},{"loc":21234},{"loc":21269},{"loc":21275},{"loc":21281},{"loc":21287},{"id":21339,"title":21340,"body":83617,"contentId":9257,"date":24160,"description":24161,"extension":18,"meta":86160,"navigation":20,"path":24163,"rawbody":24164,"robots":23,"schemaOrg":23,"seo":86161,"sitemap":86162,"stem":24167,"__hash__":24168},{"type":7,"value":83618,"toc":86152},[83619,83623,86150],[35,83620,21345,83621,21349],{},[6850,83622,21348],{},[7905,83624,83625,83629,83631,83639,83641,83648,83650,83657,83659,83661,83741,83821,83823,83825,83827,83857,83859,83861,83869,83871,83875,83955,83957,83963,84171,84173,84175,84177,84181,84280,84282,84435,84437,84558,84741,84743,84983,84999,85001,85005,85009,85149,85151,85161,85249,85251,85263,85405,85411,85413,85415,85417,85419,85651,85973,86105,86107,86114,86121,86123,86125,86135,86148],{"id":21352},[35,83626,21355,83627,21359],{},[1217,83628,21358],{},[51,83630,21363],{"id":21362},[35,83632,21366,83633,3076,83635,3080,83637,21376],{},[6850,83634,21369],{},[6850,83636,21372],{},[6850,83638,21375],{},[35,83640,21379],{},[11386,83642,132,83644],{"className":83643},[21383,21384],[21386,83645,136,83646],{"title":21388,"description":21389,":ui":21390},[910,83647,21394],{"label":21393},[35,83649,21397],{},[11386,83651,132,83653],{"className":83652},[21383,21384],[21402,83654,136,83655],{"title":21404,"description":21405,":ui":21390},[910,83656,21394],{"label":21408},[35,83658,21411],{},[35,83660,21414],{},[192,83662,83663],{"className":21417,"code":21418,"filename":21419,"language":12060,"meta":13,"style":13},[156,83664,83665,83673,83679,83691,83703,83707,83725,83733],{"__ignoreMap":13},[200,83666,83667,83669,83671],{"class":202,"line":203},[200,83668,600],{"class":214},[200,83670,21428],{"class":235},[200,83672,648],{"class":214},[200,83674,83675,83677],{"class":202,"line":14},[200,83676,657],{"class":214},[200,83678,21437],{"class":235},[200,83680,83681,83683,83685,83687,83689],{"class":202,"line":255},[200,83682,21442],{"class":379},[200,83684,449],{"class":214},[200,83686,611],{"class":214},[200,83688,21388],{"class":245},[200,83690,617],{"class":214},[200,83692,83693,83695,83697,83699,83701],{"class":202,"line":272},[200,83694,3136],{"class":379},[200,83696,449],{"class":214},[200,83698,611],{"class":214},[200,83700,21389],{"class":245},[200,83702,617],{"class":214},[200,83704,83705],{"class":202,"line":283},[200,83706,11536],{"class":214},[200,83708,83709,83711,83713,83715,83717,83719,83721,83723],{"class":202,"line":300},[200,83710,804],{"class":214},[200,83712,21473],{"class":235},[200,83714,21476],{"class":379},[200,83716,449],{"class":214},[200,83718,611],{"class":214},[200,83720,21393],{"class":245},[200,83722,611],{"class":214},[200,83724,21487],{"class":214},[200,83726,83727,83729,83731],{"class":202,"line":310},[200,83728,893],{"class":214},[200,83730,21494],{"class":235},[200,83732,648],{"class":214},[200,83734,83735,83737,83739],{"class":202,"line":336},[200,83736,681],{"class":214},[200,83738,21428],{"class":235},[200,83740,648],{"class":214},[192,83742,83743],{"className":21417,"code":21507,"filename":21508,"language":12060,"meta":13,"style":13},[156,83744,83745,83753,83759,83771,83783,83787,83805,83813],{"__ignoreMap":13},[200,83746,83747,83749,83751],{"class":202,"line":203},[200,83748,600],{"class":214},[200,83750,21428],{"class":235},[200,83752,648],{"class":214},[200,83754,83755,83757],{"class":202,"line":14},[200,83756,657],{"class":214},[200,83758,21525],{"class":235},[200,83760,83761,83763,83765,83767,83769],{"class":202,"line":255},[200,83762,21442],{"class":379},[200,83764,449],{"class":214},[200,83766,611],{"class":214},[200,83768,21404],{"class":245},[200,83770,617],{"class":214},[200,83772,83773,83775,83777,83779,83781],{"class":202,"line":272},[200,83774,3136],{"class":379},[200,83776,449],{"class":214},[200,83778,611],{"class":214},[200,83780,21405],{"class":245},[200,83782,617],{"class":214},[200,83784,83785],{"class":202,"line":283},[200,83786,11536],{"class":214},[200,83788,83789,83791,83793,83795,83797,83799,83801,83803],{"class":202,"line":300},[200,83790,804],{"class":214},[200,83792,21473],{"class":235},[200,83794,21476],{"class":379},[200,83796,449],{"class":214},[200,83798,611],{"class":214},[200,83800,21408],{"class":245},[200,83802,611],{"class":214},[200,83804,21487],{"class":214},[200,83806,83807,83809,83811],{"class":202,"line":310},[200,83808,893],{"class":214},[200,83810,21404],{"class":235},[200,83812,648],{"class":214},[200,83814,83815,83817,83819],{"class":202,"line":336},[200,83816,681],{"class":214},[200,83818,21428],{"class":235},[200,83820,648],{"class":214},[35,83822,21590],{},[35,83824,21593],{},[35,83826,21596],{},[68,83828,83829,83831,83841],{},[71,83830,21601],{},[71,83832,21604,83833],{},[1576,83834,83835,83837,83839],{},[71,83836,21609],{},[71,83838,21612],{},[71,83840,21615],{},[71,83842,21618,83843,83855,21638],{},[1576,83844,83845,83847,83849,83851,83853],{},[71,83846,21623],{},[71,83848,21626],{},[71,83850,21629],{},[71,83852,21632],{},[71,83854,21635],{},[7162,83856],{},[35,83858,21641],{},[35,83860,21644],{},[130,83862,132,83863,132,83867],{},[134,83864,136,83865,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,83866],{"src":21651,"type":141},[143,83868,21654],{},[51,83870,21658],{"id":21657},[35,83872,21661,83873,21665],{},[6850,83874,21664],{},[192,83876,83877],{"className":21417,"code":21418,"filename":21419,"language":12060,"meta":13,"style":13},[156,83878,83879,83887,83893,83905,83917,83921,83939,83947],{"__ignoreMap":13},[200,83880,83881,83883,83885],{"class":202,"line":203},[200,83882,600],{"class":214},[200,83884,21428],{"class":235},[200,83886,648],{"class":214},[200,83888,83889,83891],{"class":202,"line":14},[200,83890,657],{"class":214},[200,83892,21437],{"class":235},[200,83894,83895,83897,83899,83901,83903],{"class":202,"line":255},[200,83896,21442],{"class":379},[200,83898,449],{"class":214},[200,83900,611],{"class":214},[200,83902,21388],{"class":245},[200,83904,617],{"class":214},[200,83906,83907,83909,83911,83913,83915],{"class":202,"line":272},[200,83908,3136],{"class":379},[200,83910,449],{"class":214},[200,83912,611],{"class":214},[200,83914,21389],{"class":245},[200,83916,617],{"class":214},[200,83918,83919],{"class":202,"line":283},[200,83920,11536],{"class":214},[200,83922,83923,83925,83927,83929,83931,83933,83935,83937],{"class":202,"line":300},[200,83924,804],{"class":214},[200,83926,21473],{"class":235},[200,83928,21476],{"class":379},[200,83930,449],{"class":214},[200,83932,611],{"class":214},[200,83934,21393],{"class":245},[200,83936,611],{"class":214},[200,83938,21487],{"class":214},[200,83940,83941,83943,83945],{"class":202,"line":310},[200,83942,893],{"class":214},[200,83944,21494],{"class":235},[200,83946,648],{"class":214},[200,83948,83949,83951,83953],{"class":202,"line":336},[200,83950,681],{"class":214},[200,83952,21428],{"class":235},[200,83954,648],{"class":214},[35,83956,21748],{},[68,83958,83959,83961],{},[71,83960,21753],{},[71,83962,21756],{},[192,83964,83965],{"className":21417,"code":21759,"filename":21760,"language":12060,"meta":13,"style":13},[156,83966,83967,83987,84005,84009,84025,84029,84037,84049,84067,84071,84077,84085,84089,84097,84105,84111,84127,84139,84151,84155,84163],{"__ignoreMap":13},[200,83968,83969,83971,83973,83975,83977,83979,83981,83983,83985],{"class":202,"line":203},[200,83970,600],{"class":214},[200,83972,14892],{"class":235},[200,83974,14590],{"class":379},[200,83976,449],{"class":214},[200,83978,611],{"class":214},[200,83980,1373],{"class":245},[200,83982,611],{"class":214},[200,83984,21781],{"class":379},[200,83986,648],{"class":214},[200,83988,83989,83991,83993,83995,83997,83999,84001,84003],{"class":202,"line":14},[200,83990,973],{"class":206},[200,83992,318],{"class":214},[200,83994,18685],{"class":210},[200,83996,981],{"class":214},[200,83998,984],{"class":206},[200,84000,242],{"class":214},[200,84002,12060],{"class":245},[200,84004,992],{"class":214},[200,84006,84007],{"class":202,"line":255},[200,84008,529],{"emptyLinePlaceholder":20},[200,84010,84011,84013,84015,84017,84019,84021,84023],{"class":202,"line":272},[200,84012,443],{"class":379},[200,84014,21812],{"class":210},[200,84016,449],{"class":214},[200,84018,18685],{"class":223},[200,84020,227],{"class":210},[200,84022,18690],{"class":2423},[200,84024,416],{"class":210},[200,84026,84027],{"class":202,"line":283},[200,84028,529],{"emptyLinePlaceholder":20},[200,84030,84031,84033,84035],{"class":202,"line":300},[200,84032,21831],{"class":223},[200,84034,227],{"class":210},[200,84036,230],{"class":214},[200,84038,84039,84041,84043,84045,84047],{"class":202,"line":310},[200,84040,21840],{"class":223},[200,84042,239],{"class":214},[200,84044,3714],{"class":214},[200,84046,393],{"class":379},[200,84048,280],{"class":214},[200,84050,84051,84053,84055,84057,84059,84061,84063,84065],{"class":202,"line":336},[200,84052,21853],{"class":210},[200,84054,215],{"class":214},[200,84056,15837],{"class":210},[200,84058,510],{"class":214},[200,84060,15914],{"class":214},[200,84062,21864],{"class":210},[200,84064,215],{"class":214},[200,84066,15976],{"class":210},[200,84068,84069],{"class":202,"line":342},[200,84070,4490],{"class":214},[200,84072,84073,84075],{"class":202,"line":365},[200,84074,413],{"class":214},[200,84076,416],{"class":210},[200,84078,84079,84081,84083],{"class":202,"line":371},[200,84080,681],{"class":214},[200,84082,14892],{"class":235},[200,84084,648],{"class":214},[200,84086,84087],{"class":202,"line":398},[200,84088,529],{"emptyLinePlaceholder":20},[200,84090,84091,84093,84095],{"class":202,"line":405},[200,84092,600],{"class":214},[200,84094,21428],{"class":235},[200,84096,648],{"class":214},[200,84098,84099,84101,84103],{"class":202,"line":410},[200,84100,657],{"class":214},[200,84102,11386],{"class":235},[200,84104,648],{"class":214},[200,84106,84107,84109],{"class":202,"line":861},[200,84108,804],{"class":214},[200,84110,21437],{"class":235},[200,84112,84113,84115,84117,84119,84121,84123,84125],{"class":202,"line":890},[200,84114,21917],{"class":379},[200,84116,239],{"class":214},[200,84118,18904],{"class":379},[200,84120,449],{"class":214},[200,84122,611],{"class":214},[200,84124,21864],{"class":210},[200,84126,617],{"class":214},[200,84128,84129,84131,84133,84135,84137],{"class":202,"line":900},[200,84130,3557],{"class":379},[200,84132,449],{"class":214},[200,84134,611],{"class":214},[200,84136,21388],{"class":245},[200,84138,617],{"class":214},[200,84140,84141,84143,84145,84147,84149],{"class":202,"line":905},[200,84142,3573],{"class":379},[200,84144,449],{"class":214},[200,84146,611],{"class":214},[200,84148,21389],{"class":245},[200,84150,617],{"class":214},[200,84152,84153],{"class":202,"line":931},[200,84154,21958],{"class":214},[200,84156,84157,84159,84161],{"class":202,"line":3640},[200,84158,893],{"class":214},[200,84160,11386],{"class":235},[200,84162,648],{"class":214},[200,84164,84165,84167,84169],{"class":202,"line":3645},[200,84166,681],{"class":214},[200,84168,21428],{"class":235},[200,84170,648],{"class":214},[35,84172,21977],{},[51,84174,21981],{"id":21980},[35,84176,21984],{},[35,84178,21987,84179,21990],{},[156,84180,13707],{},[192,84182,84183],{"className":21993,"code":21994,"filename":21995,"language":12060,"meta":13,"style":13},[156,84184,84185,84193,84201,84209,84217,84225,84229,84238,84247,84256,84260,84264,84272],{"__ignoreMap":13},[200,84186,84187,84189,84191],{"class":202,"line":203},[200,84188,600],{"class":214},[200,84190,21428],{"class":235},[200,84192,648],{"class":214},[200,84194,84195,84197,84199],{"class":202,"line":14},[200,84196,657],{"class":214},[200,84198,11386],{"class":235},[200,84200,648],{"class":214},[200,84202,84203,84205,84207],{"class":202,"line":255},[200,84204,804],{"class":214},[200,84206,22020],{"class":235},[200,84208,648],{"class":214},[200,84210,84211,84213,84215],{"class":202,"line":272},[200,84212,22027],{"class":214},[200,84214,22030],{"class":235},[200,84216,21487],{"class":214},[200,84218,84219,84221,84223],{"class":202,"line":283},[200,84220,15074],{"class":214},[200,84222,22020],{"class":235},[200,84224,648],{"class":214},[200,84226,84227],{"class":202,"line":300},[200,84228,529],{"emptyLinePlaceholder":20},[200,84230,84232,84234,84236],{"class":84231,"line":310},[202,9871],[200,84233,804],{"class":214},[200,84235,22052],{"class":235},[200,84237,22055],{"class":214},[200,84239,84241,84243,84245],{"class":84240,"line":336},[202,9871],[200,84242,804],{"class":214},[200,84244,22063],{"class":235},[200,84246,22055],{"class":214},[200,84248,84250,84252,84254],{"class":84249,"line":342},[202,9871],[200,84251,804],{"class":214},[200,84253,22073],{"class":235},[200,84255,22055],{"class":214},[200,84257,84258],{"class":202,"line":365},[200,84259,529],{"emptyLinePlaceholder":20},[200,84261,84262],{"class":202,"line":371},[200,84263,22084],{"class":401},[200,84265,84266,84268,84270],{"class":202,"line":398},[200,84267,893],{"class":214},[200,84269,11386],{"class":235},[200,84271,648],{"class":214},[200,84273,84274,84276,84278],{"class":202,"line":405},[200,84275,681],{"class":214},[200,84277,21428],{"class":235},[200,84279,648],{"class":214},[35,84281,22103],{},[192,84283,84284],{"className":9741,"code":22106,"filename":22107,"language":1373,"meta":13,"style":13},[156,84285,84286,84304,84308,84325,84329,84341,84351,84363,84367,84371,84381,84393,84397,84401,84407,84413,84417,84423,84427,84431],{"__ignoreMap":13},[200,84287,84288,84290,84292,84294,84296,84298,84300,84302],{"class":202,"line":203},[200,84289,973],{"class":206},[200,84291,318],{"class":214},[200,84293,18685],{"class":210},[200,84295,981],{"class":214},[200,84297,984],{"class":206},[200,84299,242],{"class":214},[200,84301,12060],{"class":245},[200,84303,992],{"class":214},[200,84305,84306],{"class":202,"line":14},[200,84307,529],{"emptyLinePlaceholder":20},[200,84309,84311,84313,84315,84317,84319,84321,84323],{"class":84310,"line":255},[202,9871],[200,84312,443],{"class":379},[200,84314,22139],{"class":210},[200,84316,449],{"class":214},[200,84318,18685],{"class":223},[200,84320,227],{"class":210},[200,84322,18690],{"class":2423},[200,84324,5267],{"class":210},[200,84326,84327],{"class":202,"line":272},[200,84328,529],{"emptyLinePlaceholder":20},[200,84330,84331,84333,84335,84337,84339],{"class":202,"line":283},[200,84332,1380],{"class":206},[200,84334,4987],{"class":379},[200,84336,22162],{"class":223},[200,84338,1532],{"class":214},[200,84340,280],{"class":214},[200,84342,84343,84345,84347,84349],{"class":202,"line":300},[200,84344,22171],{"class":379},[200,84346,22174],{"class":223},[200,84348,1532],{"class":214},[200,84350,280],{"class":214},[200,84352,84353,84355,84357,84359,84361],{"class":202,"line":310},[200,84354,22183],{"class":210},[200,84356,215],{"class":214},[200,84358,15837],{"class":210},[200,84360,510],{"class":214},[200,84362,22192],{"class":2423},[200,84364,84365],{"class":202,"line":336},[200,84366,4490],{"class":214},[200,84368,84369],{"class":202,"line":342},[200,84370,529],{"emptyLinePlaceholder":20},[200,84372,84373,84375,84377,84379],{"class":202,"line":365},[200,84374,22171],{"class":379},[200,84376,22207],{"class":223},[200,84378,1532],{"class":214},[200,84380,280],{"class":214},[200,84382,84383,84385,84387,84389,84391],{"class":202,"line":371},[200,84384,22183],{"class":210},[200,84386,215],{"class":214},[200,84388,15837],{"class":210},[200,84390,510],{"class":214},[200,84392,18889],{"class":2423},[200,84394,84395],{"class":202,"line":398},[200,84396,4490],{"class":214},[200,84398,84399],{"class":202,"line":405},[200,84400,529],{"emptyLinePlaceholder":20},[200,84402,84403,84405],{"class":202,"line":410},[200,84404,3743],{"class":206},[200,84406,280],{"class":214},[200,84408,84409,84411],{"class":202,"line":861},[200,84410,22183],{"class":210},[200,84412,252],{"class":214},[200,84414,84415],{"class":202,"line":890},[200,84416,529],{"emptyLinePlaceholder":20},[200,84418,84419,84421],{"class":202,"line":900},[200,84420,18880],{"class":210},[200,84422,252],{"class":214},[200,84424,84425],{"class":202,"line":905},[200,84426,22258],{"class":210},[200,84428,84429],{"class":202,"line":931},[200,84430,4490],{"class":214},[200,84432,84433],{"class":202,"line":3640},[200,84434,1130],{"class":214},[35,84436,22269],{},[192,84438,84439],{"className":21993,"code":22272,"filename":21995,"language":12060,"meta":13,"style":13},[156,84440,84441,84461,84479,84483,84500,84508,84512,84520,84550],{"__ignoreMap":13},[200,84442,84443,84445,84447,84449,84451,84453,84455,84457,84459],{"class":202,"line":203},[200,84444,600],{"class":214},[200,84446,14892],{"class":235},[200,84448,14590],{"class":379},[200,84450,449],{"class":214},[200,84452,611],{"class":214},[200,84454,1373],{"class":245},[200,84456,611],{"class":214},[200,84458,21781],{"class":379},[200,84460,648],{"class":214},[200,84462,84463,84465,84467,84469,84471,84473,84475,84477],{"class":202,"line":14},[200,84464,973],{"class":206},[200,84466,318],{"class":214},[200,84468,22162],{"class":210},[200,84470,981],{"class":214},[200,84472,984],{"class":206},[200,84474,242],{"class":214},[200,84476,22311],{"class":245},[200,84478,992],{"class":214},[200,84480,84481],{"class":202,"line":255},[200,84482,529],{"emptyLinePlaceholder":20},[200,84484,84486,84488,84490,84492,84494,84496,84498],{"class":84485,"line":272},[202,9871],[200,84487,443],{"class":379},[200,84489,318],{"class":214},[200,84491,18680],{"class":210},[200,84493,413],{"class":214},[200,84495,510],{"class":214},[200,84497,22162],{"class":223},[200,84499,6320],{"class":210},[200,84501,84502,84504,84506],{"class":202,"line":283},[200,84503,681],{"class":214},[200,84505,14892],{"class":235},[200,84507,648],{"class":214},[200,84509,84510],{"class":202,"line":300},[200,84511,529],{"emptyLinePlaceholder":20},[200,84513,84514,84516,84518],{"class":202,"line":310},[200,84515,600],{"class":214},[200,84517,21428],{"class":235},[200,84519,648],{"class":214},[200,84521,84522,84524,84526,84528,84530,84532,84534,84536,84538,84540,84542,84544,84546,84548],{"class":202,"line":336},[200,84523,657],{"class":214},[200,84525,21473],{"class":235},[200,84527,21476],{"class":379},[200,84529,449],{"class":214},[200,84531,611],{"class":214},[200,84533,21393],{"class":245},[200,84535,611],{"class":214},[200,84537,22373],{"class":214},[200,84539,22376],{"class":379},[200,84541,449],{"class":214},[200,84543,611],{"class":214},[200,84545,18904],{"class":210},[200,84547,611],{"class":214},[200,84549,21487],{"class":214},[200,84551,84552,84554,84556],{"class":202,"line":342},[200,84553,681],{"class":214},[200,84555,21428],{"class":235},[200,84557,648],{"class":214},[192,84559,84560],{"className":21993,"code":22397,"filename":22398,"language":12060,"meta":13,"style":13},[156,84561,84562,84582,84600,84604,84625,84633,84637,84645,84651,84667,84679,84691,84695,84725,84733],{"__ignoreMap":13},[200,84563,84564,84566,84568,84570,84572,84574,84576,84578,84580],{"class":202,"line":203},[200,84565,600],{"class":214},[200,84567,14892],{"class":235},[200,84569,14590],{"class":379},[200,84571,449],{"class":214},[200,84573,611],{"class":214},[200,84575,1373],{"class":245},[200,84577,611],{"class":214},[200,84579,21781],{"class":379},[200,84581,648],{"class":214},[200,84583,84584,84586,84588,84590,84592,84594,84596,84598],{"class":202,"line":14},[200,84585,973],{"class":206},[200,84587,318],{"class":214},[200,84589,22162],{"class":210},[200,84591,981],{"class":214},[200,84593,984],{"class":206},[200,84595,242],{"class":214},[200,84597,22311],{"class":245},[200,84599,992],{"class":214},[200,84601,84602],{"class":202,"line":255},[200,84603,529],{"emptyLinePlaceholder":20},[200,84605,84607,84609,84611,84613,84615,84617,84619,84621,84623],{"class":84606,"line":272},[202,9871],[200,84608,443],{"class":379},[200,84610,318],{"class":214},[200,84612,22452],{"class":210},[200,84614,550],{"class":214},[200,84616,22457],{"class":210},[200,84618,413],{"class":214},[200,84620,510],{"class":214},[200,84622,22162],{"class":223},[200,84624,6320],{"class":210},[200,84626,84627,84629,84631],{"class":202,"line":283},[200,84628,681],{"class":214},[200,84630,14892],{"class":235},[200,84632,648],{"class":214},[200,84634,84635],{"class":202,"line":300},[200,84636,529],{"emptyLinePlaceholder":20},[200,84638,84639,84641,84643],{"class":202,"line":310},[200,84640,600],{"class":214},[200,84642,21428],{"class":235},[200,84644,648],{"class":214},[200,84646,84647,84649],{"class":202,"line":336},[200,84648,657],{"class":214},[200,84650,21437],{"class":235},[200,84652,84653,84655,84657,84659,84661,84663,84665],{"class":202,"line":342},[200,84654,22496],{"class":379},[200,84656,239],{"class":214},[200,84658,18904],{"class":379},[200,84660,449],{"class":214},[200,84662,611],{"class":214},[200,84664,22507],{"class":210},[200,84666,617],{"class":214},[200,84668,84669,84671,84673,84675,84677],{"class":202,"line":365},[200,84670,21442],{"class":379},[200,84672,449],{"class":214},[200,84674,611],{"class":214},[200,84676,21388],{"class":245},[200,84678,617],{"class":214},[200,84680,84681,84683,84685,84687,84689],{"class":202,"line":371},[200,84682,3136],{"class":379},[200,84684,449],{"class":214},[200,84686,611],{"class":214},[200,84688,21389],{"class":245},[200,84690,617],{"class":214},[200,84692,84693],{"class":202,"line":398},[200,84694,11536],{"class":214},[200,84696,84697,84699,84701,84703,84705,84707,84709,84711,84713,84715,84717,84719,84721,84723],{"class":202,"line":405},[200,84698,804],{"class":214},[200,84700,21473],{"class":235},[200,84702,21476],{"class":379},[200,84704,449],{"class":214},[200,84706,611],{"class":214},[200,84708,22552],{"class":245},[200,84710,611],{"class":214},[200,84712,22373],{"class":214},[200,84714,22376],{"class":379},[200,84716,449],{"class":214},[200,84718,611],{"class":214},[200,84720,22565],{"class":210},[200,84722,611],{"class":214},[200,84724,21487],{"class":214},[200,84726,84727,84729,84731],{"class":202,"line":410},[200,84728,893],{"class":214},[200,84730,21494],{"class":235},[200,84732,648],{"class":214},[200,84734,84735,84737,84739],{"class":202,"line":861},[200,84736,681],{"class":214},[200,84738,21428],{"class":235},[200,84740,648],{"class":214},[35,84742,22588],{},[68,84744,84745,84747],{},[71,84746,22593],{},[71,84748,22596,84749],{},[192,84750,84751],{"className":9741,"code":22599,"language":1373,"meta":13,"style":13},[156,84752,84753,84771,84775,84791,84808,84825,84829,84841,84867,84879,84891,84903,84907,84911,84921,84933,84937,84941,84947,84953,84959,84965,84971,84975,84979],{"__ignoreMap":13},[200,84754,84755,84757,84759,84761,84763,84765,84767,84769],{"class":202,"line":203},[200,84756,973],{"class":206},[200,84758,318],{"class":214},[200,84760,18685],{"class":210},[200,84762,981],{"class":214},[200,84764,984],{"class":206},[200,84766,242],{"class":214},[200,84768,12060],{"class":245},[200,84770,992],{"class":214},[200,84772,84773],{"class":202,"line":14},[200,84774,529],{"emptyLinePlaceholder":20},[200,84776,84777,84779,84781,84783,84785,84787,84789],{"class":202,"line":255},[200,84778,443],{"class":379},[200,84780,22139],{"class":210},[200,84782,449],{"class":214},[200,84784,18685],{"class":223},[200,84786,227],{"class":210},[200,84788,18690],{"class":2423},[200,84790,416],{"class":210},[200,84792,84794,84796,84798,84800,84802,84804,84806],{"class":84793,"line":272},[202,9871],[200,84795,443],{"class":379},[200,84797,22647],{"class":210},[200,84799,449],{"class":214},[200,84801,18685],{"class":223},[200,84803,227],{"class":210},[200,84805,22656],{"class":214},[200,84807,5267],{"class":210},[200,84809,84811,84813,84815,84817,84819,84821,84823],{"class":84810,"line":283},[202,9871],[200,84812,443],{"class":379},[200,84814,22666],{"class":210},[200,84816,449],{"class":214},[200,84818,18685],{"class":223},[200,84820,227],{"class":210},[200,84822,22656],{"class":214},[200,84824,5267],{"class":210},[200,84826,84827],{"class":202,"line":300},[200,84828,529],{"emptyLinePlaceholder":20},[200,84830,84831,84833,84835,84837,84839],{"class":202,"line":310},[200,84832,1380],{"class":206},[200,84834,4987],{"class":379},[200,84836,22162],{"class":223},[200,84838,1532],{"class":214},[200,84840,280],{"class":214},[200,84842,84843,84845,84847,84849,84851,84853,84855,84857,84859,84861,84863,84865],{"class":202,"line":336},[200,84844,22171],{"class":379},[200,84846,22174],{"class":223},[200,84848,227],{"class":214},[200,84850,22703],{"class":386},[200,84852,239],{"class":214},[200,84854,22708],{"class":1978},[200,84856,550],{"class":214},[200,84858,22713],{"class":386},[200,84860,239],{"class":214},[200,84862,22708],{"class":1978},[200,84864,2089],{"class":214},[200,84866,280],{"class":214},[200,84868,84869,84871,84873,84875,84877],{"class":202,"line":342},[200,84870,21442],{"class":210},[200,84872,215],{"class":214},[200,84874,15837],{"class":210},[200,84876,510],{"class":214},[200,84878,22734],{"class":210},[200,84880,84881,84883,84885,84887,84889],{"class":202,"line":365},[200,84882,3136],{"class":210},[200,84884,215],{"class":214},[200,84886,15837],{"class":210},[200,84888,510],{"class":214},[200,84890,22747],{"class":210},[200,84892,84893,84895,84897,84899,84901],{"class":202,"line":371},[200,84894,22183],{"class":210},[200,84896,215],{"class":214},[200,84898,15837],{"class":210},[200,84900,510],{"class":214},[200,84902,22192],{"class":2423},[200,84904,84905],{"class":202,"line":398},[200,84906,4490],{"class":214},[200,84908,84909],{"class":202,"line":405},[200,84910,529],{"emptyLinePlaceholder":20},[200,84912,84913,84915,84917,84919],{"class":202,"line":410},[200,84914,22171],{"class":379},[200,84916,22207],{"class":223},[200,84918,1532],{"class":214},[200,84920,280],{"class":214},[200,84922,84923,84925,84927,84929,84931],{"class":202,"line":861},[200,84924,22183],{"class":210},[200,84926,215],{"class":214},[200,84928,15837],{"class":210},[200,84930,510],{"class":214},[200,84932,18889],{"class":2423},[200,84934,84935],{"class":202,"line":890},[200,84936,4490],{"class":214},[200,84938,84939],{"class":202,"line":900},[200,84940,529],{"emptyLinePlaceholder":20},[200,84942,84943,84945],{"class":202,"line":905},[200,84944,3743],{"class":206},[200,84946,280],{"class":214},[200,84948,84949,84951],{"class":202,"line":931},[200,84950,22183],{"class":210},[200,84952,252],{"class":214},[200,84954,84955,84957],{"class":202,"line":3640},[200,84956,21442],{"class":210},[200,84958,252],{"class":214},[200,84960,84961,84963],{"class":202,"line":3645},[200,84962,3136],{"class":210},[200,84964,252],{"class":214},[200,84966,84967,84969],{"class":202,"line":4020},[200,84968,18880],{"class":210},[200,84970,252],{"class":214},[200,84972,84973],{"class":202,"line":4475},[200,84974,22258],{"class":210},[200,84976,84977],{"class":202,"line":4481},[200,84978,4490],{"class":214},[200,84980,84981],{"class":202,"line":4487},[200,84982,1130],{"class":214},[939,84984,84985],{},[35,84986,84987,22846,84989,17879,84994,22861],{},[200,84988,945],{},[39,84990,84992],{"href":22849,"rel":84991},[77],[156,84993,22853],{},[39,84995,84997],{"href":22856,"rel":84996},[77],[156,84998,22860],{},[51,85000,22865],{"id":22864},[35,85002,22868,85003,22872],{},[6850,85004,22871],{},[35,85006,22875,85007,22879],{},[6850,85008,22878],{},[192,85010,85011],{"className":1370,"code":22882,"filename":22883,"language":1373,"meta":13,"style":13},[156,85012,85013,85033,85047,85061,85067,85071,85097,85121,85127,85131],{"__ignoreMap":13},[200,85014,85015,85017,85019,85021,85023,85025,85027,85029,85031],{"class":202,"line":203},[200,85016,443],{"class":379},[200,85018,22892],{"class":210},[200,85020,449],{"class":214},[200,85022,22897],{"class":210},[200,85024,215],{"class":214},[200,85026,18904],{"class":223},[200,85028,22904],{"class":210},[200,85030,550],{"class":214},[200,85032,280],{"class":214},[200,85034,85035,85037,85039,85041,85043,85045],{"class":202,"line":14},[200,85036,22913],{"class":235},[200,85038,239],{"class":214},[200,85040,242],{"class":214},[200,85042,22920],{"class":245},[200,85044,249],{"class":214},[200,85046,252],{"class":214},[200,85048,85049,85051,85053,85055,85057,85059],{"class":202,"line":255},[200,85050,22929],{"class":235},[200,85052,239],{"class":214},[200,85054,242],{"class":214},[200,85056,22936],{"class":245},[200,85058,249],{"class":214},[200,85060,252],{"class":214},[200,85062,85063,85065],{"class":202,"line":272},[200,85064,413],{"class":214},[200,85066,416],{"class":210},[200,85068,85069],{"class":202,"line":283},[200,85070,529],{"emptyLinePlaceholder":20},[200,85072,85073,85075,85077,85079,85081,85083,85085,85087,85089,85091,85093,85095],{"class":202,"line":300},[200,85074,22955],{"class":210},[200,85076,215],{"class":214},[200,85078,22960],{"class":223},[200,85080,1532],{"class":210},[200,85082,215],{"class":214},[200,85084,22967],{"class":223},[200,85086,227],{"class":210},[200,85088,227],{"class":214},[200,85090,3531],{"class":386},[200,85092,2089],{"class":214},[200,85094,393],{"class":379},[200,85096,280],{"class":214},[200,85098,85099,85101,85103,85105,85107,85109,85111,85113,85115,85117,85119],{"class":202,"line":310},[200,85100,1113],{"class":210},[200,85102,215],{"class":214},[200,85104,1118],{"class":223},[200,85106,227],{"class":235},[200,85108,3318],{"class":214},[200,85110,22994],{"class":245},[200,85112,4853],{"class":214},[200,85114,3531],{"class":210},[200,85116,23001],{"class":214},[200,85118,5267],{"class":235},[200,85120,23006],{"class":401},[200,85122,85123,85125],{"class":202,"line":336},[200,85124,413],{"class":214},[200,85126,416],{"class":210},[200,85128,85129],{"class":202,"line":342},[200,85130,529],{"emptyLinePlaceholder":20},[200,85132,85133,85135,85137,85139,85141,85143,85145,85147],{"class":202,"line":365},[200,85134,22955],{"class":210},[200,85136,215],{"class":214},[200,85138,22565],{"class":223},[200,85140,227],{"class":210},[200,85142,249],{"class":214},[200,85144,23031],{"class":245},[200,85146,249],{"class":214},[200,85148,416],{"class":210},[35,85150,23038],{},[35,85152,23041,85153,23046,85156,23051,85159,23055],{},[39,85154,2918],{"href":23044,"rel":85155},[77],[39,85157,9190],{"href":23049,"rel":85158},[77],[156,85160,23054],{},[192,85162,85163],{"className":21417,"code":23058,"filename":21995,"language":12060,"meta":13,"style":13},[156,85164,85165,85185,85197,85201,85213,85217,85229,85237,85241],{"__ignoreMap":13},[200,85166,85167,85169,85171,85173,85175,85177,85179,85181,85183],{"class":202,"line":203},[200,85168,600],{"class":214},[200,85170,14892],{"class":235},[200,85172,21781],{"class":379},[200,85174,14590],{"class":379},[200,85176,449],{"class":214},[200,85178,611],{"class":214},[200,85180,1373],{"class":245},[200,85182,611],{"class":214},[200,85184,648],{"class":214},[200,85186,85187,85189,85191,85193,85195],{"class":202,"line":14},[200,85188,443],{"class":379},[200,85190,23087],{"class":210},[200,85192,449],{"class":214},[200,85194,23092],{"class":223},[200,85196,466],{"class":210},[200,85198,85199],{"class":202,"line":255},[200,85200,529],{"emptyLinePlaceholder":20},[200,85202,85203,85205,85207,85209,85211],{"class":202,"line":272},[200,85204,3711],{"class":379},[200,85206,4987],{"class":379},[200,85208,23107],{"class":223},[200,85210,1532],{"class":214},[200,85212,280],{"class":214},[200,85214,85215],{"class":202,"line":283},[200,85216,23116],{"class":210},[200,85218,85219,85221,85223,85225,85227],{"class":202,"line":300},[200,85220,3755],{"class":214},[200,85222,23123],{"class":223},[200,85224,227],{"class":235},[200,85226,23128],{"class":210},[200,85228,416],{"class":235},[200,85230,85231,85233,85235],{"class":202,"line":310},[200,85232,3755],{"class":214},[200,85234,18904],{"class":223},[200,85236,466],{"class":235},[200,85238,85239],{"class":202,"line":336},[200,85240,1130],{"class":214},[200,85242,85243,85245,85247],{"class":202,"line":342},[200,85244,681],{"class":214},[200,85246,14892],{"class":235},[200,85248,648],{"class":214},[35,85250,23153],{},[35,85252,23156,85253,23164,85258,23172],{},[39,85254,85256],{"href":23159,"rel":85255},[77],[156,85257,23163],{},[39,85259,85261],{"href":23167,"rel":85260},[77],[156,85262,23171],{},[192,85264,85265],{"className":21417,"code":23175,"filename":23176,"language":12060,"meta":13,"style":13},[156,85266,85267,85287,85307,85315,85319,85327,85333,85345,85357,85369,85381,85393,85397],{"__ignoreMap":13},[200,85268,85269,85271,85273,85275,85277,85279,85281,85283,85285],{"class":202,"line":203},[200,85270,600],{"class":214},[200,85272,14892],{"class":235},[200,85274,14590],{"class":379},[200,85276,449],{"class":214},[200,85278,611],{"class":214},[200,85280,1373],{"class":245},[200,85282,611],{"class":214},[200,85284,21781],{"class":379},[200,85286,648],{"class":214},[200,85288,85289,85291,85293,85295,85297,85299,85301,85303,85305],{"class":202,"line":14},[200,85290,443],{"class":379},[200,85292,318],{"class":214},[200,85294,23207],{"class":210},[200,85296,550],{"class":214},[200,85298,22457],{"class":210},[200,85300,413],{"class":214},[200,85302,510],{"class":214},[200,85304,23092],{"class":223},[200,85306,466],{"class":210},[200,85308,85309,85311,85313],{"class":202,"line":255},[200,85310,681],{"class":214},[200,85312,14892],{"class":235},[200,85314,648],{"class":214},[200,85316,85317],{"class":202,"line":272},[200,85318,529],{"emptyLinePlaceholder":20},[200,85320,85321,85323,85325],{"class":202,"line":283},[200,85322,600],{"class":214},[200,85324,21428],{"class":235},[200,85326,648],{"class":214},[200,85328,85329,85331],{"class":202,"line":300},[200,85330,657],{"class":214},[200,85332,23246],{"class":235},[200,85334,85335,85337,85339,85341,85343],{"class":202,"line":310},[200,85336,23251],{"class":379},[200,85338,449],{"class":214},[200,85340,611],{"class":214},[200,85342,23258],{"class":245},[200,85344,617],{"class":214},[200,85346,85347,85349,85351,85353,85355],{"class":202,"line":336},[200,85348,23265],{"class":379},[200,85350,449],{"class":214},[200,85352,611],{"class":214},[200,85354,23272],{"class":245},[200,85356,617],{"class":214},[200,85358,85359,85361,85363,85365,85367],{"class":202,"line":342},[200,85360,23279],{"class":379},[200,85362,449],{"class":214},[200,85364,611],{"class":214},[200,85366,23286],{"class":245},[200,85368,617],{"class":214},[200,85370,85371,85373,85375,85377,85379],{"class":202,"line":365},[200,85372,23293],{"class":379},[200,85374,449],{"class":214},[200,85376,611],{"class":214},[200,85378,23300],{"class":245},[200,85380,617],{"class":214},[200,85382,85383,85385,85387,85389,85391],{"class":202,"line":371},[200,85384,23307],{"class":379},[200,85386,449],{"class":214},[200,85388,611],{"class":214},[200,85390,23314],{"class":245},[200,85392,617],{"class":214},[200,85394,85395],{"class":202,"line":398},[200,85396,23321],{"class":214},[200,85398,85399,85401,85403],{"class":202,"line":405},[200,85400,681],{"class":214},[200,85402,21428],{"class":235},[200,85404,648],{"class":214},[939,85406,85407],{},[35,85408,85409,23336],{},[200,85410,945],{},[35,85412,23339],{},[51,85414,23343],{"id":23342},[35,85416,23346],{},[35,85418,21414],{},[192,85420,85421],{"className":21417,"code":23351,"filename":21995,"language":12060,"meta":13,"style":13},[156,85422,85423,85443,85457,85461,85473,85483,85487,85501,85509,85523,85535,85539,85547,85553,85561,85565,85573,85577,85585,85593,85623,85627,85635,85643],{"__ignoreMap":13},[200,85424,85425,85427,85429,85431,85433,85435,85437,85439,85441],{"class":202,"line":203},[200,85426,600],{"class":214},[200,85428,14892],{"class":235},[200,85430,14590],{"class":379},[200,85432,449],{"class":214},[200,85434,611],{"class":214},[200,85436,1373],{"class":245},[200,85438,611],{"class":214},[200,85440,21781],{"class":379},[200,85442,648],{"class":214},[200,85444,85445,85447,85449,85451,85453,85455],{"class":202,"line":14},[200,85446,973],{"class":206},[200,85448,23380],{"class":210},[200,85450,1472],{"class":206},[200,85452,242],{"class":214},[200,85454,23387],{"class":245},[200,85456,992],{"class":214},[200,85458,85459],{"class":202,"line":255},[200,85460,529],{"emptyLinePlaceholder":20},[200,85462,85463,85465,85467,85469,85471],{"class":202,"line":272},[200,85464,443],{"class":379},[200,85466,23087],{"class":210},[200,85468,449],{"class":214},[200,85470,23092],{"class":223},[200,85472,466],{"class":210},[200,85474,85475,85477,85479,85481],{"class":202,"line":283},[200,85476,14904],{"class":379},[200,85478,23412],{"class":223},[200,85480,1532],{"class":214},[200,85482,280],{"class":214},[200,85484,85485],{"class":202,"line":300},[200,85486,23116],{"class":210},[200,85488,85489,85491,85493,85495,85497,85499],{"class":202,"line":310},[200,85490,3755],{"class":214},[200,85492,23123],{"class":223},[200,85494,227],{"class":235},[200,85496,23431],{"class":210},[200,85498,550],{"class":214},[200,85500,280],{"class":214},[200,85502,85503,85505,85507],{"class":202,"line":336},[200,85504,23440],{"class":235},[200,85506,239],{"class":214},[200,85508,280],{"class":214},[200,85510,85511,85513,85515,85517,85519,85521],{"class":202,"line":342},[200,85512,23449],{"class":235},[200,85514,239],{"class":214},[200,85516,242],{"class":214},[200,85518,23456],{"class":245},[200,85520,249],{"class":214},[200,85522,252],{"class":214},[200,85524,85525,85527,85529,85531,85533],{"class":202,"line":365},[200,85526,19994],{"class":235},[200,85528,239],{"class":214},[200,85530,242],{"class":214},[200,85532,23471],{"class":245},[200,85534,992],{"class":214},[200,85536,85537],{"class":202,"line":371},[200,85538,12083],{"class":214},[200,85540,85541,85543,85545],{"class":202,"line":398},[200,85542,23482],{"class":235},[200,85544,239],{"class":214},[200,85546,22192],{"class":2423},[200,85548,85549,85551],{"class":202,"line":405},[200,85550,2096],{"class":214},[200,85552,416],{"class":235},[200,85554,85555,85557,85559],{"class":202,"line":410},[200,85556,3755],{"class":214},[200,85558,18904],{"class":223},[200,85560,466],{"class":235},[200,85562,85563],{"class":202,"line":861},[200,85564,1130],{"class":214},[200,85566,85567,85569,85571],{"class":202,"line":890},[200,85568,681],{"class":214},[200,85570,14892],{"class":235},[200,85572,648],{"class":214},[200,85574,85575],{"class":202,"line":900},[200,85576,529],{"emptyLinePlaceholder":20},[200,85578,85579,85581,85583],{"class":202,"line":905},[200,85580,600],{"class":214},[200,85582,21428],{"class":235},[200,85584,648],{"class":214},[200,85586,85587,85589,85591],{"class":202,"line":931},[200,85588,657],{"class":214},[200,85590,11386],{"class":235},[200,85592,648],{"class":214},[200,85594,85595,85597,85599,85601,85603,85605,85607,85609,85611,85613,85615,85617,85619,85621],{"class":202,"line":3640},[200,85596,804],{"class":214},[200,85598,21473],{"class":235},[200,85600,21476],{"class":379},[200,85602,449],{"class":214},[200,85604,611],{"class":214},[200,85606,23547],{"class":245},[200,85608,611],{"class":214},[200,85610,22373],{"class":214},[200,85612,22376],{"class":379},[200,85614,449],{"class":214},[200,85616,611],{"class":214},[200,85618,23560],{"class":210},[200,85620,611],{"class":214},[200,85622,21487],{"class":214},[200,85624,85625],{"class":202,"line":3645},[200,85626,529],{"emptyLinePlaceholder":20},[200,85628,85629,85631,85633],{"class":202,"line":4020},[200,85630,804],{"class":214},[200,85632,23575],{"class":235},[200,85634,21487],{"class":214},[200,85636,85637,85639,85641],{"class":202,"line":4475},[200,85638,893],{"class":214},[200,85640,11386],{"class":235},[200,85642,648],{"class":214},[200,85644,85645,85647,85649],{"class":202,"line":4481},[200,85646,681],{"class":214},[200,85648,21428],{"class":235},[200,85650,648],{"class":214},[192,85652,85653],{"className":21417,"code":23596,"filename":23597,"language":12060,"meta":13,"style":13},[156,85654,85655,85675,85689,85693,85705,85713,85721,85727,85731,85743,85753,85757,85771,85779,85793,85805,85809,85817,85823,85831,85835,85843,85847,85855,85861,85879,85897,85901,85913,85919,85931,85945,85949,85957,85965],{"__ignoreMap":13},[200,85656,85657,85659,85661,85663,85665,85667,85669,85671,85673],{"class":202,"line":203},[200,85658,600],{"class":214},[200,85660,14892],{"class":235},[200,85662,14590],{"class":379},[200,85664,449],{"class":214},[200,85666,611],{"class":214},[200,85668,1373],{"class":245},[200,85670,611],{"class":214},[200,85672,21781],{"class":379},[200,85674,648],{"class":214},[200,85676,85677,85679,85681,85683,85685,85687],{"class":202,"line":14},[200,85678,973],{"class":206},[200,85680,23626],{"class":210},[200,85682,1472],{"class":206},[200,85684,242],{"class":214},[200,85686,23633],{"class":245},[200,85688,992],{"class":214},[200,85690,85691],{"class":202,"line":255},[200,85692,529],{"emptyLinePlaceholder":20},[200,85694,85695,85697,85699,85701,85703],{"class":202,"line":272},[200,85696,443],{"class":379},[200,85698,23646],{"class":210},[200,85700,449],{"class":214},[200,85702,23651],{"class":223},[200,85704,23654],{"class":214},[200,85706,85707,85709,85711],{"class":202,"line":283},[200,85708,23659],{"class":235},[200,85710,239],{"class":214},[200,85712,23664],{"class":1978},[200,85714,85715,85717,85719],{"class":202,"line":300},[200,85716,258],{"class":235},[200,85718,239],{"class":214},[200,85720,23664],{"class":1978},[200,85722,85723,85725],{"class":202,"line":310},[200,85724,23677],{"class":214},[200,85726,466],{"class":210},[200,85728,85729],{"class":202,"line":336},[200,85730,529],{"emptyLinePlaceholder":20},[200,85732,85733,85735,85737,85739,85741],{"class":202,"line":342},[200,85734,443],{"class":379},[200,85736,23087],{"class":210},[200,85738,449],{"class":214},[200,85740,23092],{"class":223},[200,85742,466],{"class":210},[200,85744,85745,85747,85749,85751],{"class":202,"line":365},[200,85746,14904],{"class":379},[200,85748,23412],{"class":223},[200,85750,1532],{"class":214},[200,85752,280],{"class":214},[200,85754,85755],{"class":202,"line":371},[200,85756,23116],{"class":210},[200,85758,85759,85761,85763,85765,85767,85769],{"class":202,"line":398},[200,85760,3755],{"class":214},[200,85762,23123],{"class":223},[200,85764,227],{"class":235},[200,85766,23720],{"class":210},[200,85768,550],{"class":214},[200,85770,280],{"class":214},[200,85772,85773,85775,85777],{"class":202,"line":405},[200,85774,23440],{"class":235},[200,85776,239],{"class":214},[200,85778,280],{"class":214},[200,85780,85781,85783,85785,85787,85789,85791],{"class":202,"line":410},[200,85782,23449],{"class":235},[200,85784,239],{"class":214},[200,85786,242],{"class":214},[200,85788,23743],{"class":245},[200,85790,249],{"class":214},[200,85792,252],{"class":214},[200,85794,85795,85797,85799,85801,85803],{"class":202,"line":861},[200,85796,19994],{"class":235},[200,85798,239],{"class":214},[200,85800,242],{"class":214},[200,85802,23758],{"class":245},[200,85804,992],{"class":214},[200,85806,85807],{"class":202,"line":890},[200,85808,12083],{"class":214},[200,85810,85811,85813,85815],{"class":202,"line":900},[200,85812,23482],{"class":235},[200,85814,239],{"class":214},[200,85816,22192],{"class":2423},[200,85818,85819,85821],{"class":202,"line":905},[200,85820,2096],{"class":214},[200,85822,416],{"class":235},[200,85824,85825,85827,85829],{"class":202,"line":931},[200,85826,3755],{"class":214},[200,85828,18904],{"class":223},[200,85830,466],{"class":235},[200,85832,85833],{"class":202,"line":3640},[200,85834,1130],{"class":214},[200,85836,85837,85839,85841],{"class":202,"line":3645},[200,85838,681],{"class":214},[200,85840,14892],{"class":235},[200,85842,648],{"class":214},[200,85844,85845],{"class":202,"line":4020},[200,85846,529],{"emptyLinePlaceholder":20},[200,85848,85849,85851,85853],{"class":202,"line":4475},[200,85850,600],{"class":214},[200,85852,21428],{"class":235},[200,85854,648],{"class":214},[200,85856,85857,85859],{"class":202,"line":4481},[200,85858,657],{"class":214},[200,85860,21437],{"class":235},[200,85862,85863,85865,85867,85869,85871,85873,85875,85877],{"class":202,"line":4487},[200,85864,23821],{"class":214},[200,85866,3566],{"class":379},[200,85868,449],{"class":214},[200,85870,611],{"class":214},[200,85872,23830],{"class":210},[200,85874,215],{"class":214},[200,85876,3566],{"class":210},[200,85878,617],{"class":214},[200,85880,85881,85883,85885,85887,85889,85891,85893,85895],{"class":202,"line":4493},[200,85882,23821],{"class":214},[200,85884,3582],{"class":379},[200,85886,449],{"class":214},[200,85888,611],{"class":214},[200,85890,23830],{"class":210},[200,85892,215],{"class":214},[200,85894,3582],{"class":210},[200,85896,617],{"class":214},[200,85898,85899],{"class":202,"line":5454},[200,85900,11536],{"class":214},[200,85902,85903,85905,85907,85909,85911],{"class":202,"line":5459},[200,85904,804],{"class":214},[200,85906,21428],{"class":235},[200,85908,23867],{"class":214},[200,85910,23870],{"class":379},[200,85912,648],{"class":214},[200,85914,85915,85917],{"class":202,"line":13227},[200,85916,22027],{"class":214},[200,85918,23879],{"class":235},[200,85920,85921,85923,85925,85927,85929],{"class":202,"line":13253},[200,85922,23884],{"class":379},[200,85924,449],{"class":214},[200,85926,611],{"class":214},[200,85928,23891],{"class":245},[200,85930,617],{"class":214},[200,85932,85933,85935,85937,85939,85941,85943],{"class":202,"line":13269},[200,85934,23898],{"class":214},[200,85936,22376],{"class":379},[200,85938,449],{"class":214},[200,85940,611],{"class":214},[200,85942,23560],{"class":210},[200,85944,617],{"class":214},[200,85946,85947],{"class":202,"line":13313},[200,85948,23913],{"class":214},[200,85950,85951,85953,85955],{"class":202,"line":13319},[200,85952,15074],{"class":214},[200,85954,21428],{"class":235},[200,85956,648],{"class":214},[200,85958,85959,85961,85963],{"class":202,"line":13324},[200,85960,893],{"class":214},[200,85962,21494],{"class":235},[200,85964,648],{"class":214},[200,85966,85967,85969,85971],{"class":202,"line":13350},[200,85968,681],{"class":214},[200,85970,21428],{"class":235},[200,85972,648],{"class":214},[192,85974,85975],{"className":21417,"code":23940,"filename":23941,"language":12060,"meta":13,"style":13},[156,85976,85977,85997,86009,86017,86025,86031,86039,86043,86051,86057,86075,86093,86097],{"__ignoreMap":13},[200,85978,85979,85981,85983,85985,85987,85989,85991,85993,85995],{"class":202,"line":203},[200,85980,600],{"class":214},[200,85982,14892],{"class":235},[200,85984,14590],{"class":379},[200,85986,449],{"class":214},[200,85988,611],{"class":214},[200,85990,1373],{"class":245},[200,85992,611],{"class":214},[200,85994,21781],{"class":379},[200,85996,648],{"class":214},[200,85998,85999,86001,86003,86005,86007],{"class":202,"line":14},[200,86000,443],{"class":379},[200,86002,23646],{"class":210},[200,86004,449],{"class":214},[200,86006,23651],{"class":223},[200,86008,23654],{"class":214},[200,86010,86011,86013,86015],{"class":202,"line":255},[200,86012,23659],{"class":235},[200,86014,239],{"class":214},[200,86016,23664],{"class":1978},[200,86018,86019,86021,86023],{"class":202,"line":272},[200,86020,258],{"class":235},[200,86022,239],{"class":214},[200,86024,23664],{"class":1978},[200,86026,86027,86029],{"class":202,"line":283},[200,86028,23677],{"class":214},[200,86030,466],{"class":210},[200,86032,86033,86035,86037],{"class":202,"line":300},[200,86034,681],{"class":214},[200,86036,14892],{"class":235},[200,86038,648],{"class":214},[200,86040,86041],{"class":202,"line":310},[200,86042,529],{"emptyLinePlaceholder":20},[200,86044,86045,86047,86049],{"class":202,"line":336},[200,86046,600],{"class":214},[200,86048,21428],{"class":235},[200,86050,648],{"class":214},[200,86052,86053,86055],{"class":202,"line":342},[200,86054,657],{"class":214},[200,86056,21437],{"class":235},[200,86058,86059,86061,86063,86065,86067,86069,86071,86073],{"class":202,"line":365},[200,86060,23821],{"class":214},[200,86062,3566],{"class":379},[200,86064,449],{"class":214},[200,86066,611],{"class":214},[200,86068,23830],{"class":210},[200,86070,215],{"class":214},[200,86072,3566],{"class":210},[200,86074,617],{"class":214},[200,86076,86077,86079,86081,86083,86085,86087,86089,86091],{"class":202,"line":371},[200,86078,23821],{"class":214},[200,86080,3582],{"class":379},[200,86082,449],{"class":214},[200,86084,611],{"class":214},[200,86086,23830],{"class":210},[200,86088,215],{"class":214},[200,86090,3582],{"class":210},[200,86092,617],{"class":214},[200,86094,86095],{"class":202,"line":398},[200,86096,23321],{"class":214},[200,86098,86099,86101,86103],{"class":202,"line":405},[200,86100,681],{"class":214},[200,86102,21428],{"class":235},[200,86104,648],{"class":214},[35,86106,21590],{},[35,86108,24076,86109],{},[1582,86110,24080,86111,215],{"repo":24079},[39,86112,24085],{"href":24083,"rel":86113},[77],[130,86115,132,86116,132,86119],{},[24089,86117],{"src":24083,"width":24091,"height":24092,"frameBorder":3933,"scrolling":24093,"allowFullScreen":20,"className":86118},[24095],[143,86120,24098],{},[51,86122,24102],{"id":24101},[35,86124,24105],{},[35,86126,24108,86127,24114,86130,24122],{},[39,86128,24113],{"href":24111,"rel":86129},[77],[39,86131,86133],{"href":24117,"rel":86132},[77],[156,86134,24121],{},[35,86136,24125,86137,24131,86140,1638,86143,24142,86146,24145],{},[39,86138,24130],{"href":24128,"rel":86139},[77],[39,86141,24136],{"href":24134,"rel":86142},[77],[39,86144,24141],{"href":24139,"rel":86145},[77],[156,86147,23054],{},[35,86149,24148],{},[1171,86151,24151],{},{"title":13,"searchDepth":14,"depth":14,"links":86153},[86154,86155,86156,86157,86158,86159],{"id":21362,"depth":14,"text":21363},{"id":21657,"depth":14,"text":21658},{"id":21980,"depth":14,"text":21981},{"id":22864,"depth":14,"text":22865},{"id":23342,"depth":14,"text":23343},{"id":24101,"depth":14,"text":24102},{},{"title":21340,"description":24161},{"loc":24163},{"id":24170,"title":24171,"body":86164,"contentId":9252,"date":29190,"description":29191,"extension":18,"meta":90305,"navigation":20,"path":29193,"rawbody":29194,"robots":23,"schemaOrg":23,"seo":90306,"sitemap":90307,"stem":29204,"__hash__":29205},{"type":7,"value":86165,"toc":90292},[86166,86170,86179,86193,86197,86206,86212,86226,86228,86284,86286,86288,86290,86308,86316,86318,86324,86326,86334,86336,86338,86352,86356,86360,86566,86568,86570,86572,86578,86582,86584,86586,86593,86597,86599,86683,86693,86695,86699,86713,86829,86839,86841,86861,86953,86957,86976,87000,87005,87009,87018,87028,87030,87054,87058,87060,87064,87066,87068,87070,87091,87159,87161,87175,87355,87359,87363,87367,87369,87371,87375,87377,87383,87385,87393,87395,87397,87399,87405,87407,87409,87411,87419,87426,87430,87434,87441,87446,87451,87457,87462,87473,87517,87521,87577,87583,87585,87645,87647,87687,87689,87773,87782,87788,87792,87801,87811,87825,87835,87837,90290],[35,86167,24176,86168,5839],{},[6850,86169,24179],{},[35,86171,24182,86172,24188,86175,106,86177],{},[39,86173,24187],{"href":24185,"rel":86174},[77],[6850,86176,24191],{},[1217,86178,24194],{},[35,86180,24197,86181,6853,86184,24206,86187,1638,86189,24213,86191,24216],{},[39,86182,24202],{"href":24200,"rel":86183},[77],[39,86185,17864],{"href":17862,"rel":86186},[77],[6850,86188,24209],{},[6850,86190,24212],{},[6850,86192,19447],{},[35,86194,86195],{},[1690,86196],{"alt":24221,"src":24222},[35,86198,24225,86199,24233,86204,24236],{},[39,86200,86202],{"href":24228,"rel":86201},[77],[6850,86203,24232],{},[6850,86205,24209],{},[939,86207,86208],{},[35,86209,86210,24243],{},[200,86211,945],{},[35,86213,24246,86214,24252,86217,13731,86219,24258,86221,24264,86224,24268],{},[39,86215,24251],{"href":24249,"rel":86216},[77],[6850,86218,24255],{},[6850,86220,2918],{},[39,86222,24263],{"href":24261,"rel":86223},[77],[156,86225,24267],{},[35,86227,24271],{},[192,86229,86230],{"className":21417,"code":24274,"language":12060,"meta":13,"style":13},[156,86231,86232,86240,86244,86248,86256,86260,86268,86272,86276],{"__ignoreMap":13},[200,86233,86234,86236,86238],{"class":202,"line":203},[200,86235,600],{"class":214},[200,86237,19540],{"class":235},[200,86239,648],{"class":214},[200,86241,86242],{"class":202,"line":14},[200,86243,24289],{"class":210},[200,86245,86246],{"class":202,"line":255},[200,86247,24294],{"class":210},[200,86249,86250,86252,86254],{"class":202,"line":272},[200,86251,681],{"class":214},[200,86253,19540],{"class":235},[200,86255,648],{"class":214},[200,86257,86258],{"class":202,"line":283},[200,86259,529],{"emptyLinePlaceholder":20},[200,86261,86262,86264,86266],{"class":202,"line":300},[200,86263,600],{"class":214},[200,86265,21428],{"class":235},[200,86267,648],{"class":214},[200,86269,86270],{"class":202,"line":310},[200,86271,24319],{"class":401},[200,86273,86274],{"class":202,"line":336},[200,86275,24324],{"class":210},[200,86277,86278,86280,86282],{"class":202,"line":342},[200,86279,681],{"class":214},[200,86281,21428],{"class":235},[200,86283,648],{"class":214},[1159,86285],{},[35,86287,24337],{},[35,86289,24340],{},[68,86291,86292,86294],{},[71,86293,24345],{},[71,86295,24348,86296,3076,86298,3080,86300,24356,86302,24360,86305,24366],{},[6850,86297,19447],{},[6850,86299,17986],{},[6850,86301,24355],{},[39,86303,14039],{"href":14037,"rel":86304},[77],[39,86306,24365],{"href":24363,"rel":86307},[77],[939,86309,86310],{},[35,86311,86312,24373,86314,24376],{},[200,86313,945],{},[6850,86315,24355],{},[35,86317,24379],{},[939,86319,86320],{},[35,86321,86322,24386],{},[200,86323,945],{},[51,86325,24390],{"id":24389},[35,86327,24393,86328,24397,86331,24402],{},[39,86329,24187],{"href":24185,"rel":86330},[77],[39,86332,24401],{"href":13728,"rel":86333},[77],[35,86335,24405],{},[35,86337,24408],{},[130,86339,132,86340,132,86344],{},[134,86341,136,86342,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,86343],{"src":24415,"type":141},[143,86345,24418,86346,1638,86348,3076,86350,24428],{},[39,86347,17864],{"href":17862,"target":7144},[39,86349,24423],{"href":24261,"target":7144},[39,86351,24427],{"href":24426,"target":7144},[35,86353,24431,86354,24435],{},[156,86355,24434],{},[35,86357,24438,86358,24442],{},[156,86359,24441],{},[192,86361,86362],{"className":21417,"code":24445,"language":12060,"meta":13,"style":13},[156,86363,86364,86372,86376,86380,86384,86388,86392,86396,86404,86408,86428,86442,86450,86454,86462,86468,86484,86502,86514,86554,86558],{"__ignoreMap":13},[200,86365,86366,86368,86370],{"class":202,"line":203},[200,86367,600],{"class":214},[200,86369,19540],{"class":235},[200,86371,648],{"class":214},[200,86373,86374],{"class":202,"line":14},[200,86375,24460],{"class":210},[200,86377,86378],{"class":202,"line":255},[200,86379,24465],{"class":210},[200,86381,86382],{"class":202,"line":272},[200,86383,529],{"emptyLinePlaceholder":20},[200,86385,86386],{"class":202,"line":283},[200,86387,24474],{"class":210},[200,86389,86390],{"class":202,"line":300},[200,86391,529],{"emptyLinePlaceholder":20},[200,86393,86394],{"class":202,"line":310},[200,86395,24483],{"class":210},[200,86397,86398,86400,86402],{"class":202,"line":336},[200,86399,681],{"class":214},[200,86401,19540],{"class":235},[200,86403,648],{"class":214},[200,86405,86406],{"class":202,"line":342},[200,86407,529],{"emptyLinePlaceholder":20},[200,86409,86410,86412,86414,86416,86418,86420,86422,86424,86426],{"class":202,"line":365},[200,86411,600],{"class":214},[200,86413,14892],{"class":235},[200,86415,14590],{"class":379},[200,86417,449],{"class":214},[200,86419,611],{"class":214},[200,86421,1373],{"class":245},[200,86423,611],{"class":214},[200,86425,21781],{"class":379},[200,86427,648],{"class":214},[200,86429,86430,86432,86434,86436,86438,86440],{"class":202,"line":371},[200,86431,973],{"class":206},[200,86433,24522],{"class":210},[200,86435,1472],{"class":206},[200,86437,242],{"class":214},[200,86439,24529],{"class":245},[200,86441,992],{"class":214},[200,86443,86444,86446,86448],{"class":202,"line":398},[200,86445,681],{"class":214},[200,86447,14892],{"class":235},[200,86449,648],{"class":214},[200,86451,86452],{"class":202,"line":405},[200,86453,529],{"emptyLinePlaceholder":20},[200,86455,86456,86458,86460],{"class":202,"line":410},[200,86457,600],{"class":214},[200,86459,21428],{"class":235},[200,86461,648],{"class":214},[200,86463,86464,86466],{"class":202,"line":861},[200,86465,657],{"class":214},[200,86467,24558],{"class":235},[200,86469,86470,86472,86474,86476,86478,86480,86482],{"class":202,"line":890},[200,86471,23265],{"class":206},[200,86473,449],{"class":214},[200,86475,611],{"class":214},[200,86477,24569],{"class":210},[200,86479,24572],{"class":214},[200,86481,24575],{"class":210},[200,86483,617],{"class":214},[200,86485,86486,86488,86490,86492,86494,86496,86498,86500],{"class":202,"line":900},[200,86487,23821],{"class":214},[200,86489,24584],{"class":379},[200,86491,449],{"class":214},[200,86493,611],{"class":214},[200,86495,24569],{"class":210},[200,86497,215],{"class":214},[200,86499,3280],{"class":210},[200,86501,617],{"class":214},[200,86503,86504,86506,86508,86510,86512],{"class":202,"line":905},[200,86505,23293],{"class":379},[200,86507,449],{"class":214},[200,86509,611],{"class":214},[200,86511,24569],{"class":210},[200,86513,617],{"class":214},[200,86515,86516,86518,86520,86522,86524,86526,86528,86530,86532,86534,86536,86538,86540,86542,86544,86546,86548,86550,86552],{"class":202,"line":931},[200,86517,24613],{"class":214},[200,86519,24441],{"class":379},[200,86521,449],{"class":214},[200,86523,611],{"class":214},[200,86525,24441],{"class":223},[200,86527,24624],{"class":214},[200,86529,24569],{"class":210},[200,86531,24629],{"class":214},[200,86533,10929],{"class":223},[200,86535,227],{"class":214},[200,86537,24636],{"class":386},[200,86539,393],{"class":379},[200,86541,24641],{"class":210},[200,86543,215],{"class":214},[200,86545,24646],{"class":210},[200,86547,24649],{"class":214},[200,86549,24636],{"class":210},[200,86551,2089],{"class":214},[200,86553,617],{"class":214},[200,86555,86556],{"class":202,"line":3640},[200,86557,23321],{"class":214},[200,86559,86560,86562,86564],{"class":202,"line":3645},[200,86561,681],{"class":214},[200,86563,21428],{"class":235},[200,86565,648],{"class":214},[35,86567,24670],{},[35,86569,24673],{},[35,86571,24676],{},[68,86573,86574,86576],{},[71,86575,24681],{},[71,86577,24684],{},[68,86579,86580],{"start":272},[71,86581,24689],{},[35,86583,24692],{},[51,86585,24681],{"id":24695},[35,86587,24698,86588,106,86591],{},[39,86589,24355],{"href":24701,"rel":86590},[77],[1217,86592,24705],{},[35,86594,24708,86595,24711],{},[6850,86596,24355],{},[35,86598,24714],{},[192,86600,86601],{"className":21417,"code":24717,"language":12060,"meta":13,"style":13},[156,86602,86603,86613,86617,86625,86629,86637,86645,86649,86657,86661,86671,86675],{"__ignoreMap":13},[200,86604,86605,86607,86609,86611],{"class":202,"line":203},[200,86606,600],{"class":214},[200,86608,14892],{"class":235},[200,86610,21781],{"class":379},[200,86612,648],{"class":214},[200,86614,86615],{"class":202,"line":14},[200,86616,24734],{"class":401},[200,86618,86619,86621,86623],{"class":202,"line":255},[200,86620,681],{"class":214},[200,86622,14892],{"class":235},[200,86624,648],{"class":214},[200,86626,86627],{"class":202,"line":272},[200,86628,529],{"emptyLinePlaceholder":20},[200,86630,86631,86633,86635],{"class":202,"line":283},[200,86632,600],{"class":214},[200,86634,21428],{"class":235},[200,86636,648],{"class":214},[200,86638,86639,86641,86643],{"class":202,"line":300},[200,86640,657],{"class":214},[200,86642,11386],{"class":235},[200,86644,21487],{"class":214},[200,86646,86647],{"class":202,"line":310},[200,86648,24767],{"class":401},[200,86650,86651,86653,86655],{"class":202,"line":336},[200,86652,681],{"class":214},[200,86654,21428],{"class":235},[200,86656,648],{"class":214},[200,86658,86659],{"class":202,"line":342},[200,86660,529],{"emptyLinePlaceholder":20},[200,86662,86663,86665,86667,86669],{"class":202,"line":365},[200,86664,600],{"class":214},[200,86666,1171],{"class":235},[200,86668,24788],{"class":379},[200,86670,648],{"class":214},[200,86672,86673],{"class":202,"line":371},[200,86674,24795],{"class":401},[200,86676,86677,86679,86681],{"class":202,"line":398},[200,86678,681],{"class":214},[200,86680,1171],{"class":235},[200,86682,648],{"class":214},[35,86684,24806,86685,24809,86687,24813,86690,24817],{},[156,86686,11851],{},[39,86688,13730],{"href":13728,"rel":86689},[77],[39,86691,13569],{"href":13567,"rel":86692},[77],[35,86694,24820],{},[35,86696,86697],{},[1690,86698],{"alt":24825,"src":24826},[35,86700,24829,86701,24832,86703,24840,86708,24847],{},[156,86702,11851],{},[39,86704,86706],{"href":24835,"rel":86705},[77],[156,86707,24839],{},[39,86709,86711],{"href":24843,"rel":86710},[77],[156,86712,11770],{},[192,86714,86715],{"className":1370,"code":24850,"language":1373,"meta":13,"style":13},[156,86716,86717,86735,86761,86767,86771,86775,86787,86797,86803,86809,86813,86825],{"__ignoreMap":13},[200,86718,86719,86721,86723,86725,86727,86729,86731,86733],{"class":202,"line":203},[200,86720,14904],{"class":379},[200,86722,11815],{"class":223},[200,86724,227],{"class":214},[200,86726,156],{"class":386},[200,86728,550],{"class":214},[200,86730,24867],{"class":386},[200,86732,2089],{"class":214},[200,86734,280],{"class":214},[200,86736,86737,86739,86741,86743,86745,86747,86749,86751,86753,86755,86757,86759],{"class":202,"line":14},[200,86738,20075],{"class":206},[200,86740,1095],{"class":235},[200,86742,11837],{"class":214},[200,86744,24882],{"class":210},[200,86746,215],{"class":214},[200,86748,11844],{"class":223},[200,86750,227],{"class":235},[200,86752,249],{"class":214},[200,86754,11851],{"class":245},[200,86756,249],{"class":214},[200,86758,6466],{"class":235},[200,86760,230],{"class":214},[200,86762,86763,86765],{"class":202,"line":255},[200,86764,3296],{"class":206},[200,86766,24905],{"class":210},[200,86768,86769],{"class":202,"line":272},[200,86770,4490],{"class":214},[200,86772,86773],{"class":202,"line":283},[200,86774,529],{"emptyLinePlaceholder":20},[200,86776,86777,86779,86781,86783,86785],{"class":202,"line":300},[200,86778,3743],{"class":206},[200,86780,12997],{"class":214},[200,86782,24922],{"class":223},[200,86784,227],{"class":235},[200,86786,230],{"class":214},[200,86788,86789,86791,86793,86795],{"class":202,"line":310},[200,86790,24931],{"class":235},[200,86792,239],{"class":214},[200,86794,24936],{"class":210},[200,86796,252],{"class":214},[200,86798,86799,86801],{"class":202,"line":336},[200,86800,24943],{"class":210},[200,86802,252],{"class":214},[200,86804,86805,86807],{"class":202,"line":342},[200,86806,5104],{"class":210},[200,86808,252],{"class":214},[200,86810,86811],{"class":202,"line":365},[200,86812,24956],{"class":210},[200,86814,86815,86817,86819,86821,86823],{"class":202,"line":371},[200,86816,5080],{"class":214},[200,86818,2089],{"class":235},[200,86820,215],{"class":214},[200,86822,11770],{"class":223},[200,86824,466],{"class":235},[200,86826,86827],{"class":202,"line":398},[200,86828,1130],{"class":214},[35,86830,419,86831,24977,86833,24981,86835,24985,86837,24988],{},[156,86832,11770],{},[156,86834,24980],{},[156,86836,24984],{},[156,86838,11851],{},[35,86840,24991],{},[68,86842,86843],{},[71,86844,86845,25001,86848,25007,86851,25010,86853,25016,86856,25026],{},[39,86846,25000],{"href":24998,"rel":86847},[77],[39,86849,25006],{"href":25004,"rel":86850},[77],[156,86852,13730],{},[39,86854,25015],{"href":25013,"rel":86855},[77],[39,86857,25021,86859,25025],{"href":25019,"rel":86858},[77],[156,86860,25024],{},[192,86862,86863],{"className":25029,"code":25030,"language":25031,"meta":13,"style":13},[156,86864,86865,86883,86887,86913],{"__ignoreMap":13},[200,86866,86867,86869,86871,86873,86875,86877,86879,86881],{"class":202,"line":203},[200,86868,973],{"class":206},[200,86870,318],{"class":214},[200,86872,25042],{"class":210},[200,86874,981],{"class":214},[200,86876,984],{"class":206},[200,86878,242],{"class":214},[200,86880,25006],{"class":245},[200,86882,992],{"class":214},[200,86884,86885],{"class":202,"line":14},[200,86886,529],{"emptyLinePlaceholder":20},[200,86888,86889,86891,86893,86895,86897,86899,86901,86903,86905,86907,86909,86911],{"class":202,"line":255},[200,86890,443],{"class":379},[200,86892,25063],{"class":210},[200,86894,449],{"class":214},[200,86896,25042],{"class":223},[200,86898,25070],{"class":210},[200,86900,550],{"class":214},[200,86902,318],{"class":214},[200,86904,25077],{"class":210},[200,86906,413],{"class":214},[200,86908,2089],{"class":210},[200,86910,215],{"class":214},[200,86912,25086],{"class":210},[200,86914,86915,86917,86919,86921,86923,86925,86927,86929,86931,86933,86935,86937,86939,86941,86943,86945,86947,86949,86951],{"class":202,"line":272},[200,86916,443],{"class":379},[200,86918,25093],{"class":210},[200,86920,449],{"class":214},[200,86922,25098],{"class":210},[200,86924,215],{"class":214},[200,86926,25103],{"class":210},[200,86928,215],{"class":214},[200,86930,3266],{"class":223},[200,86932,227],{"class":210},[200,86934,25112],{"class":386},[200,86936,393],{"class":379},[200,86938,25117],{"class":210},[200,86940,215],{"class":214},[200,86942,25122],{"class":210},[200,86944,6546],{"class":214},[200,86946,242],{"class":214},[200,86948,19540],{"class":245},[200,86950,249],{"class":214},[200,86952,416],{"class":210},[35,86954,86955],{},[1690,86956],{"alt":25137,"src":25138},[68,86958,86959,86964,86971],{"start":14},[71,86960,86961,25148],{},[39,86962,25147],{"href":25145,"rel":86963},[77],[71,86965,86966,25156,86969,215],{},[39,86967,25155],{"href":25153,"rel":86968},[77],[39,86970,25160],{"href":25159},[71,86972,86973,25168],{},[39,86974,25167],{"href":25165,"rel":86975},[77],[192,86977,86978],{"className":1370,"code":25171,"language":1373,"meta":13,"style":13},[156,86979,86980],{"__ignoreMap":13},[200,86981,86982,86984,86986,86988,86990,86992,86994,86996,86998],{"class":202,"line":203},[200,86983,207],{"class":206},[200,86985,25180],{"class":223},[200,86987,25183],{"class":210},[200,86989,249],{"class":214},[200,86991,25188],{"class":245},[200,86993,249],{"class":214},[200,86995,550],{"class":214},[200,86997,25195],{"class":214},[200,86999,25198],{"class":210},[35,87001,25201,87002,25207],{},[39,87003,25206],{"href":25204,"rel":87004},[77],[35,87006,87007],{},[1690,87008],{"alt":25212,"src":25213},[35,87010,25216,87011,25222,87014,25226,87016,215],{},[39,87012,25221],{"href":25219,"rel":87013},[77],[156,87015,25225],{},[39,87017,25230],{"href":25229},[68,87019,87020],{"start":283},[71,87021,25235,87022,25241,87025,25247],{},[39,87023,25240],{"href":25238,"rel":87024},[77],[39,87026,25246],{"href":25244,"rel":87027},[77],[35,87029,25250],{},[192,87031,87032],{"className":1370,"code":25253,"language":1373,"meta":13,"style":13},[156,87033,87034],{"__ignoreMap":13},[200,87035,87036,87038,87040,87042,87044,87046,87048,87050,87052],{"class":202,"line":203},[200,87037,207],{"class":206},[200,87039,25180],{"class":223},[200,87041,25183],{"class":210},[200,87043,249],{"class":214},[200,87045,25268],{"class":245},[200,87047,249],{"class":214},[200,87049,550],{"class":214},[200,87051,25195],{"class":214},[200,87053,25198],{"class":210},[68,87055,87056],{"start":300},[71,87057,25281],{},[35,87059,25284],{},[35,87061,87062],{},[1690,87063],{"alt":25289,"src":25290},[35,87065,25293],{},[35,87067,25296],{},[35,87069,25299],{},[68,87071,87072,87083],{},[71,87073,419,87074,25309,87077,25315,87080,25321],{},[39,87075,25308],{"href":25306,"rel":87076},[77],[39,87078,25314],{"href":25312,"rel":87079},[77],[39,87081,25320],{"href":25318,"rel":87082},[77],[71,87084,25324,87085,25330,87088,215],{},[39,87086,25329],{"href":25327,"rel":87087},[77],[39,87089,25335],{"href":25333,"rel":87090},[77],[192,87092,87093],{"className":1370,"code":25338,"language":1373,"meta":13,"style":13},[156,87094,87095,87109,87145,87149,87155],{"__ignoreMap":13},[200,87096,87097,87099,87101,87103,87105,87107],{"class":202,"line":203},[200,87098,14904],{"class":379},[200,87100,25347],{"class":223},[200,87102,227],{"class":214},[200,87104,3108],{"class":386},[200,87106,2089],{"class":214},[200,87108,280],{"class":214},[200,87110,87111,87113,87115,87117,87119,87121,87123,87125,87127,87129,87131,87133,87135,87137,87139,87141,87143],{"class":202,"line":14},[200,87112,25360],{"class":210},[200,87114,215],{"class":214},[200,87116,25365],{"class":210},[200,87118,215],{"class":214},[200,87120,25370],{"class":223},[200,87122,227],{"class":235},[200,87124,249],{"class":214},[200,87126,25377],{"class":245},[200,87128,249],{"class":214},[200,87130,550],{"class":214},[200,87132,1095],{"class":214},[200,87134,25386],{"class":386},[200,87136,550],{"class":214},[200,87138,24867],{"class":386},[200,87140,2089],{"class":214},[200,87142,393],{"class":379},[200,87144,280],{"class":214},[200,87146,87147],{"class":202,"line":255},[200,87148,25401],{"class":401},[200,87150,87151,87153],{"class":202,"line":272},[200,87152,5080],{"class":214},[200,87154,416],{"class":235},[200,87156,87157],{"class":202,"line":283},[200,87158,1130],{"class":214},[35,87160,25414],{},[68,87162,87163,87168],{},[71,87164,87165,25424],{},[39,87166,25423],{"href":25421,"rel":87167},[77],[71,87169,87170,25432,87173,25435],{},[39,87171,25431],{"href":25429,"rel":87172},[77],[156,87174,11770],{},[192,87176,87177],{"className":1370,"code":25438,"language":1373,"meta":13,"style":13},[156,87178,87179,87193,87231,87255,87283,87287,87299,87309,87319,87325,87329,87341,87345,87351],{"__ignoreMap":13},[200,87180,87181,87183,87185,87187,87189,87191],{"class":202,"line":203},[200,87182,14904],{"class":379},[200,87184,25347],{"class":223},[200,87186,227],{"class":214},[200,87188,3108],{"class":386},[200,87190,2089],{"class":214},[200,87192,280],{"class":214},[200,87194,87195,87197,87199,87201,87203,87205,87207,87209,87211,87213,87215,87217,87219,87221,87223,87225,87227,87229],{"class":202,"line":14},[200,87196,25360],{"class":210},[200,87198,215],{"class":214},[200,87200,25365],{"class":210},[200,87202,215],{"class":214},[200,87204,25370],{"class":223},[200,87206,227],{"class":235},[200,87208,249],{"class":214},[200,87210,25377],{"class":245},[200,87212,249],{"class":214},[200,87214,550],{"class":214},[200,87216,380],{"class":379},[200,87218,1095],{"class":214},[200,87220,25386],{"class":386},[200,87222,550],{"class":214},[200,87224,24867],{"class":386},[200,87226,2089],{"class":214},[200,87228,393],{"class":379},[200,87230,280],{"class":214},[200,87232,87233,87235,87237,87239,87241,87243,87245,87247,87249,87251,87253],{"class":202,"line":255},[200,87234,5252],{"class":206},[200,87236,1095],{"class":235},[200,87238,24882],{"class":210},[200,87240,215],{"class":214},[200,87242,11844],{"class":223},[200,87244,227],{"class":235},[200,87246,249],{"class":214},[200,87248,11851],{"class":245},[200,87250,249],{"class":214},[200,87252,6466],{"class":235},[200,87254,230],{"class":214},[200,87256,87257,87259,87261,87263,87265,87267,87269,87271,87273,87275,87277,87279,87281],{"class":202,"line":272},[200,87258,20113],{"class":379},[200,87260,25523],{"class":210},[200,87262,510],{"class":214},[200,87264,25528],{"class":210},[200,87266,215],{"class":214},[200,87268,25533],{"class":223},[200,87270,227],{"class":235},[200,87272,24882],{"class":210},[200,87274,550],{"class":214},[200,87276,242],{"class":214},[200,87278,14746],{"class":245},[200,87280,249],{"class":214},[200,87282,416],{"class":235},[200,87284,87285],{"class":202,"line":283},[200,87286,529],{"emptyLinePlaceholder":20},[200,87288,87289,87291,87293,87295,87297],{"class":202,"line":300},[200,87290,25556],{"class":206},[200,87292,12997],{"class":214},[200,87294,24922],{"class":223},[200,87296,227],{"class":235},[200,87298,230],{"class":214},[200,87300,87301,87303,87305,87307],{"class":202,"line":310},[200,87302,25569],{"class":235},[200,87304,239],{"class":214},[200,87306,24936],{"class":210},[200,87308,252],{"class":214},[200,87310,87311,87313,87315,87317],{"class":202,"line":336},[200,87312,11884],{"class":235},[200,87314,239],{"class":214},[200,87316,25523],{"class":210},[200,87318,252],{"class":214},[200,87320,87321,87323],{"class":202,"line":342},[200,87322,25590],{"class":210},[200,87324,252],{"class":214},[200,87326,87327],{"class":202,"line":365},[200,87328,25597],{"class":210},[200,87330,87331,87333,87335,87337,87339],{"class":202,"line":371},[200,87332,2632],{"class":214},[200,87334,2089],{"class":235},[200,87336,215],{"class":214},[200,87338,11770],{"class":223},[200,87340,466],{"class":235},[200,87342,87343],{"class":202,"line":398},[200,87344,4962],{"class":214},[200,87346,87347,87349],{"class":202,"line":405},[200,87348,5080],{"class":214},[200,87350,416],{"class":235},[200,87352,87353],{"class":202,"line":410},[200,87354,1130],{"class":214},[35,87356,25626,87357,25629],{},[156,87358,11770],{},[35,87360,25632,87361,25636],{},[156,87362,25635],{},[35,87364,25639,87365,25642],{},[156,87366,11770],{},[35,87368,25645],{},[35,87370,25648],{},[35,87372,87373],{},[1690,87374],{"alt":25653,"src":25654},[35,87376,25657],{},[939,87378,87379],{},[35,87380,87381,25664],{},[200,87382,945],{},[35,87384,25667],{},[939,87386,87387],{},[35,87388,87389,25674,87391,25679],{},[200,87390,945],{},[39,87392,25678],{"href":25677},[51,87394,25683],{"id":25682},[35,87396,25686],{},[35,87398,25689],{},[68,87400,87401,87403],{},[71,87402,25694],{},[71,87404,25697],{},[35,87406,25700],{},[1913,87408,25704],{"id":25703},[35,87410,25707],{},[35,87412,25710,87413,25716,87416,25722],{},[39,87414,25715],{"href":25713,"rel":87415},[77],[39,87417,25721],{"href":25719,"rel":87418},[77],[35,87420,25725,87421,215],{},[39,87422,87424,25731],{"href":25165,"rel":87423},[77],[156,87425,25188],{},[1913,87427,419,87428,25731],{"id":25734},[156,87429,25188],{},[35,87431,25739,87432,25742],{},[156,87433,25188],{},[35,87435,25745,87436,25748,87438,215],{},[156,87437,11165],{},[39,87439,25753],{"href":25751,"rel":87440},[77],[35,87442,25756,87443,25762],{},[39,87444,25761],{"href":25759,"rel":87445},[77],[35,87447,25765,87448,25771],{},[39,87449,25770],{"href":25768,"rel":87450},[77],[68,87452,87453,87455],{},[71,87454,25776],{},[71,87456,25779],{},[35,87458,25782,87459,215],{},[39,87460,25787],{"href":25785,"rel":87461},[77],[35,87463,25790,87464,25793,87466,25797,87468,25801,87470,215],{},[156,87465,24980],{},[156,87467,25796],{},[156,87469,25800],{},[39,87471,25806],{"href":25804,"rel":87472},[77],[192,87474,87475],{"className":19537,"code":25809,"filename":25810,"language":19540,"meta":13,"style":13},[156,87476,87477,87481,87485,87489,87493,87497,87501,87505,87509,87513],{"__ignoreMap":13},[200,87478,87479],{"class":202,"line":203},[200,87480,19577],{},[200,87482,87483],{"class":202,"line":14},[200,87484,529],{"emptyLinePlaceholder":20},[200,87486,87487],{"class":202,"line":255},[200,87488,25825],{},[200,87490,87491],{"class":202,"line":272},[200,87492,529],{"emptyLinePlaceholder":20},[200,87494,87495],{"class":202,"line":283},[200,87496,25834],{},[200,87498,87499],{"class":202,"line":300},[200,87500,230],{},[200,87502,87503],{"class":202,"line":310},[200,87504,25843],{},[200,87506,87507],{"class":202,"line":336},[200,87508,25848],{},[200,87510,87511],{"class":202,"line":342},[200,87512,25853],{},[200,87514,87515],{"class":202,"line":365},[200,87516,1130],{},[35,87518,25860,87519,25864],{},[156,87520,25863],{},[192,87522,87523],{"className":19537,"code":25867,"filename":25868,"language":19540,"meta":13,"style":13},[156,87524,87525,87529,87533,87537,87541,87545,87549,87553,87557,87561,87565,87569,87573],{"__ignoreMap":13},[200,87526,87527],{"class":202,"line":203},[200,87528,19577],{},[200,87530,87531],{"class":202,"line":14},[200,87532,529],{"emptyLinePlaceholder":20},[200,87534,87535],{"class":202,"line":255},[200,87536,25825],{},[200,87538,87539],{"class":202,"line":272},[200,87540,529],{"emptyLinePlaceholder":20},[200,87542,87543],{"class":202,"line":283},[200,87544,25891],{},[200,87546,87547],{"class":202,"line":300},[200,87548,230],{},[200,87550,87551],{"class":202,"line":310},[200,87552,25900],{},[200,87554,87555],{"class":202,"line":336},[200,87556,19638],{},[200,87558,87559],{"class":202,"line":342},[200,87560,25909],{},[200,87562,87563],{"class":202,"line":365},[200,87564,25914],{},[200,87566,87567],{"class":202,"line":371},[200,87568,13459],{},[200,87570,87571],{"class":202,"line":398},[200,87572,4962],{},[200,87574,87575],{"class":202,"line":405},[200,87576,1130],{},[939,87578,87579],{},[35,87580,87581,25933],{},[200,87582,945],{},[35,87584,25936],{},[192,87586,87587],{"className":4277,"code":25939,"language":4279,"meta":13,"style":13},[156,87588,87589,87593,87597,87601,87605,87609,87613,87617,87621,87625,87629,87633,87637,87641],{"__ignoreMap":13},[200,87590,87591],{"class":202,"line":203},[200,87592,25946],{},[200,87594,87595],{"class":202,"line":14},[200,87596,25951],{},[200,87598,87599],{"class":202,"line":255},[200,87600,25956],{},[200,87602,87603],{"class":202,"line":272},[200,87604,25961],{},[200,87606,87607],{"class":202,"line":283},[200,87608,25966],{},[200,87610,87611],{"class":202,"line":300},[200,87612,25971],{},[200,87614,87615],{"class":202,"line":310},[200,87616,25976],{},[200,87618,87619],{"class":202,"line":336},[200,87620,25981],{},[200,87622,87623],{"class":202,"line":342},[200,87624,25986],{},[200,87626,87627],{"class":202,"line":365},[200,87628,25991],{},[200,87630,87631],{"class":202,"line":371},[200,87632,25996],{},[200,87634,87635],{"class":202,"line":398},[200,87636,26001],{},[200,87638,87639],{"class":202,"line":405},[200,87640,25951],{},[200,87642,87643],{"class":202,"line":410},[200,87644,26010],{},[35,87646,26013],{},[192,87648,87649],{"className":21417,"code":26016,"language":12060,"meta":13,"style":13},[156,87650,87651,87659,87663,87667,87671,87675,87679],{"__ignoreMap":13},[200,87652,87653,87655,87657],{"class":202,"line":203},[200,87654,600],{"class":214},[200,87656,19540],{"class":235},[200,87658,648],{"class":214},[200,87660,87661],{"class":202,"line":14},[200,87662,26031],{"class":210},[200,87664,87665],{"class":202,"line":255},[200,87666,529],{"emptyLinePlaceholder":20},[200,87668,87669],{"class":202,"line":272},[200,87670,26040],{"class":210},[200,87672,87673],{"class":202,"line":283},[200,87674,529],{"emptyLinePlaceholder":20},[200,87676,87677],{"class":202,"line":300},[200,87678,26049],{"class":210},[200,87680,87681,87683,87685],{"class":202,"line":310},[200,87682,681],{"class":214},[200,87684,19540],{"class":235},[200,87686,648],{"class":214},[35,87688,26060],{},[192,87690,87691],{"className":19537,"code":26063,"language":19540,"meta":13,"style":13},[156,87692,87693,87697,87701,87705,87709,87713,87717,87721,87725,87729,87733,87737,87741,87745,87749,87753,87757,87761,87765,87769],{"__ignoreMap":13},[200,87694,87695],{"class":202,"line":203},[200,87696,19577],{},[200,87698,87699],{"class":202,"line":14},[200,87700,529],{"emptyLinePlaceholder":20},[200,87702,87703],{"class":202,"line":255},[200,87704,26078],{},[200,87706,87707],{"class":202,"line":272},[200,87708,26083],{},[200,87710,87711],{"class":202,"line":283},[200,87712,26088],{},[200,87714,87715],{"class":202,"line":300},[200,87716,26093],{},[200,87718,87719],{"class":202,"line":310},[200,87720,26098],{},[200,87722,87723],{"class":202,"line":336},[200,87724,529],{"emptyLinePlaceholder":20},[200,87726,87727],{"class":202,"line":342},[200,87728,26107],{},[200,87730,87731],{"class":202,"line":365},[200,87732,230],{},[200,87734,87735],{"class":202,"line":371},[200,87736,26116],{},[200,87738,87739],{"class":202,"line":398},[200,87740,26121],{},[200,87742,87743],{"class":202,"line":405},[200,87744,26126],{},[200,87746,87747],{"class":202,"line":410},[200,87748,26131],{},[200,87750,87751],{"class":202,"line":861},[200,87752,19638],{},[200,87754,87755],{"class":202,"line":890},[200,87756,26140],{},[200,87758,87759],{"class":202,"line":900},[200,87760,26145],{},[200,87762,87763],{"class":202,"line":905},[200,87764,26150],{},[200,87766,87767],{"class":202,"line":931},[200,87768,4962],{},[200,87770,87771],{"class":202,"line":3640},[200,87772,1130],{},[35,87774,26161,87775,26165,87777,215],{},[156,87776,26164],{},[39,87778,87780,26173],{"href":26168,"rel":87779},[77],[156,87781,26172],{},[35,87783,419,87784,26179,87786,26183],{},[156,87785,26178],{},[6850,87787,26182],{},[35,87789,26186,87790,26190],{},[156,87791,26189],{},[35,87793,419,87794,26200,87799,26203],{},[39,87795,87797],{"href":26195,"rel":87796},[77],[156,87798,26199],{},[156,87800,23830],{},[939,87802,87803],{},[35,87804,87805,26210,87807,26214,87809,215],{},[200,87806,945],{},[156,87808,26213],{},[156,87810,26217],{},[939,87812,87813],{},[35,87814,87815,87817,26227,87819,26233,87822,215],{},[200,87816,945],{},[156,87818,26226],{},[39,87820,26232],{"href":26230,"rel":87821},[77],[39,87823,26238],{"href":26236,"rel":87824},[77],[35,87826,26241,87827,26245,87829,26251,87832,26256],{},[156,87828,26244],{},[39,87830,26250],{"href":26248,"rel":87831},[77],[39,87833,26255],{"href":25204,"rel":87834},[77],[35,87836,26259],{},[26261,87838,87839,87843,87845,87847,87849,87915,87922,87927,87933,87935,87937,87939,87946,87990,87996,88001,88045,88051,88053,88055,88057,88074,88098,88113,88129,88131,88143,88151,88153,88165,88169,88389,88393,88687,88702,88706,88862,88864,88866,88926,88928,89116,89135,89143,89149,89155,89505,89519,89521,89551,89555,89569,89571,89653,89659,89680,89682,89822,89831,89837,89839,89847,89851,89859,89861,89869,89871,89873,89875,89879,89881,89883,89887,89889,89891,89897,89899,89901,89907,89915,90253,90259,90263,90268,90273,90278,90286,90288],{"name":26263,":items":26264,":default-expanded":26265},[35,87840,26268,87841,215],{},[39,87842,26272],{"href":26271},[1913,87844,26276],{"id":26275},[35,87846,26279],{},[35,87848,26282],{},[26284,87850,87851,87867],{},[26287,87852,87853],{},[26290,87854,87855,87857,87859,87861,87863,87865],{},[26293,87856,3280],{},[26293,87858,11165],{},[26293,87860,26299],{},[26293,87862,26302],{},[26293,87864,26305],{},[26293,87866,26308],{},[26310,87868,87869,87883,87897],{},[26290,87870,87871,87873,87875,87877,87879,87881],{},[26315,87872,3188],{},[26315,87874,26319],{},[26315,87876,26322],{},[26315,87878,26325],{},[26315,87880,26328],{},[26315,87882,26331],{},[26290,87884,87885,87887,87889,87891,87893,87895],{},[26315,87886,4782],{},[26315,87888,26338],{},[26315,87890,26341],{},[26315,87892,26344],{},[26315,87894,26347],{},[26315,87896,26350],{},[26290,87898,87899,87901,87905,87907,87909,87913],{},[26315,87900,26355],{},[26315,87902,26358,87903,11851],{},[200,87904,26361],{},[26315,87906,26364],{},[26315,87908,26367],{},[26315,87910,26370,87911,26373],{},[200,87912,26361],{},[26315,87914,26376],{},[35,87916,26379,87917,26385,87920,26388],{},[39,87918,26384],{"href":26382,"rel":87919},[77],[156,87921,26308],{},[35,87923,26391,87924,26396],{},[39,87925,26395],{"href":25153,"rel":87926},[77],[939,87928,87929],{},[35,87930,87931,26403],{},[200,87932,945],{},[35,87934,26406],{},[35,87936,26409],{},[35,87938,26412],{},[35,87940,26415,87941,26424],{},[39,87942,26420,87944],{"href":26418,"rel":87943},[77],[156,87945,26423],{},[192,87947,87948],{"className":19537,"code":26427,"language":19540,"meta":13,"style":13},[156,87949,87950,87954,87958,87962,87966,87970,87974,87978,87982,87986],{"__ignoreMap":13},[200,87951,87952],{"class":202,"line":203},[200,87953,26434],{},[200,87955,87956],{"class":202,"line":14},[200,87957,230],{},[200,87959,87960],{"class":202,"line":255},[200,87961,26443],{},[200,87963,87964],{"class":202,"line":272},[200,87965,26448],{},[200,87967,87968],{"class":202,"line":283},[200,87969,26453],{},[200,87971,87972],{"class":202,"line":300},[200,87973,26458],{},[200,87975,87976],{"class":202,"line":310},[200,87977,26463],{},[200,87979,87980],{"class":202,"line":336},[200,87981,26468],{},[200,87983,87984],{"class":202,"line":342},[200,87985,26473],{},[200,87987,87988],{"class":202,"line":365},[200,87989,1130],{},[939,87991,87992],{},[35,87993,87994,26484],{},[200,87995,945],{},[35,87997,419,87998,26492],{},[39,87999,26491],{"href":26489,"rel":88000},[77],[192,88002,88003],{"className":19537,"code":26495,"language":19540,"meta":13,"style":13},[156,88004,88005,88009,88013,88017,88021,88025,88029,88033,88037,88041],{"__ignoreMap":13},[200,88006,88007],{"class":202,"line":203},[200,88008,19577],{},[200,88010,88011],{"class":202,"line":14},[200,88012,529],{"emptyLinePlaceholder":20},[200,88014,88015],{"class":202,"line":255},[200,88016,26510],{},[200,88018,88019],{"class":202,"line":272},[200,88020,529],{"emptyLinePlaceholder":20},[200,88022,88023],{"class":202,"line":283},[200,88024,26519],{},[200,88026,88027],{"class":202,"line":300},[200,88028,529],{"emptyLinePlaceholder":20},[200,88030,88031],{"class":202,"line":310},[200,88032,26528],{},[200,88034,88035],{"class":202,"line":336},[200,88036,230],{},[200,88038,88039],{"class":202,"line":342},[200,88040,26537],{},[200,88042,88043],{"class":202,"line":365},[200,88044,1130],{},[35,88046,26544,88047,26548,88049,26551],{},[156,88048,26547],{},[156,88050,26423],{},[1913,88052,26555],{"id":26554},[35,88054,26558],{},[35,88056,26561],{},[35,88058,26564,88059,26572,88064,26578,88067,26587,88072,215],{},[39,88060,88062,26571],{"href":26567,"rel":88061},[77],[156,88063,24232],{},[39,88065,26577],{"href":26575,"rel":88066},[77],[39,88068,26583,88070],{"href":26581,"rel":88069},[77],[156,88071,26586],{},[156,88073,26590],{},[192,88075,88076],{"className":19537,"code":26593,"language":19540,"meta":13,"style":13},[156,88077,88078,88082,88086,88090,88094],{"__ignoreMap":13},[200,88079,88080],{"class":202,"line":203},[200,88081,19577],{},[200,88083,88084],{"class":202,"line":14},[200,88085,529],{"emptyLinePlaceholder":20},[200,88087,88088],{"class":202,"line":255},[200,88089,26608],{},[200,88091,88092],{"class":202,"line":272},[200,88093,529],{"emptyLinePlaceholder":20},[200,88095,88096],{"class":202,"line":283},[200,88097,26617],{},[35,88099,26620,88100,3076,88102,26628,88105,26632,88107,26638,88110,26644],{},[156,88101,5351],{},[39,88103,26627],{"href":26625,"rel":88104},[77],[156,88106,26631],{},[39,88108,26637],{"href":26635,"rel":88109},[77],[39,88111,26643],{"href":26641,"rel":88112},[77],[192,88114,88115],{"className":19537,"code":26647,"language":19540,"meta":13,"style":13},[156,88116,88117,88121,88125],{"__ignoreMap":13},[200,88118,88119],{"class":202,"line":203},[200,88120,26654],{},[200,88122,88123],{"class":202,"line":14},[200,88124,26659],{},[200,88126,88127],{"class":202,"line":255},[200,88128,26664],{},[35,88130,26667],{},[68,88132,88133,88137],{},[71,88134,88135,26675],{},[156,88136,26674],{},[71,88138,88139,26681,88141,26684],{},[156,88140,26680],{},[156,88142,25188],{},[35,88144,26687,88145,26690,88147,26694,88149,26698],{},[156,88146,2882],{},[156,88148,26693],{},[156,88150,26697],{},[35,88152,26701],{},[68,88154,88155,88160],{},[71,88156,26706,88157,215],{},[39,88158,26711],{"href":26709,"rel":88159},[77],[71,88161,26714,88162,215],{},[39,88163,26719],{"href":26717,"rel":88164},[77],[35,88166,26722,88167,26726],{},[156,88168,26725],{},[192,88170,88171],{"className":591,"code":26729,"language":593,"meta":13,"style":13},[156,88172,88173],{"__ignoreMap":13},[200,88174,88175,88177,88179,88181,88183,88185,88187,88189,88191,88193,88195,88197,88199,88201,88203,88205,88207,88209,88211,88213,88215,88217,88219,88221,88223,88225,88227,88229,88231,88233,88235,88237,88239,88241,88243,88245,88247,88249,88251,88253,88255,88257,88259,88261,88263,88265,88267,88269,88271,88273,88275,88277,88279,88281,88283,88285,88287,88289,88291,88293,88295,88297,88299,88301,88303,88305,88307,88309,88311,88313,88315,88317,88319,88321,88323,88325,88327,88329,88331,88333,88335,88337,88339,88341,88343,88345,88347,88349,88351,88353,88355,88357,88359,88361,88363,88365,88367,88369,88371,88373,88375,88377,88379,88381,88383,88385,88387],{"class":202,"line":203},[200,88176,600],{"class":214},[200,88178,11386],{"class":235},[200,88180,3224],{"class":379},[200,88182,449],{"class":214},[200,88184,611],{"class":214},[200,88186,26746],{"class":245},[200,88188,611],{"class":214},[200,88190,26751],{"class":379},[200,88192,449],{"class":214},[200,88194,611],{"class":214},[200,88196,2752],{"class":245},[200,88198,26760],{"class":210},[200,88200,23163],{"class":245},[200,88202,26760],{"class":210},[200,88204,239],{"class":245},[200,88206,26760],{"class":210},[200,88208,26771],{"class":245},[200,88210,26760],{"class":210},[200,88212,550],{"class":245},[200,88214,26760],{"class":210},[200,88216,23830],{"class":245},[200,88218,26760],{"class":210},[200,88220,26784],{"class":245},[200,88222,26760],{"class":210},[200,88224,26789],{"class":245},[200,88226,26760],{"class":210},[200,88228,26794],{"class":245},[200,88230,26760],{"class":210},[200,88232,26799],{"class":245},[200,88234,26760],{"class":210},[200,88236,26784],{"class":245},[200,88238,26760],{"class":210},[200,88240,26808],{"class":245},[200,88242,26760],{"class":210},[200,88244,26813],{"class":245},[200,88246,26760],{"class":210},[200,88248,26818],{"class":245},[200,88250,26760],{"class":210},[200,88252,26784],{"class":245},[200,88254,26760],{"class":210},[200,88256,2015],{"class":245},[200,88258,26760],{"class":210},[200,88260,26831],{"class":245},[200,88262,26760],{"class":210},[200,88264,26836],{"class":245},[200,88266,26760],{"class":210},[200,88268,26784],{"class":245},[200,88270,26760],{"class":210},[200,88272,20089],{"class":245},[200,88274,26760],{"class":210},[200,88276,239],{"class":245},[200,88278,26760],{"class":210},[200,88280,26853],{"class":245},[200,88282,26760],{"class":210},[200,88284,26858],{"class":245},[200,88286,26760],{"class":210},[200,88288,26863],{"class":245},[200,88290,26760],{"class":210},[200,88292,26868],{"class":245},[200,88294,26760],{"class":210},[200,88296,26873],{"class":245},[200,88298,26760],{"class":210},[200,88300,239],{"class":245},[200,88302,26760],{"class":210},[200,88304,26882],{"class":245},[200,88306,26760],{"class":210},[200,88308,550],{"class":245},[200,88310,26760],{"class":210},[200,88312,26891],{"class":245},[200,88314,26760],{"class":210},[200,88316,26896],{"class":245},[200,88318,26760],{"class":210},[200,88320,26901],{"class":245},[200,88322,26760],{"class":210},[200,88324,239],{"class":245},[200,88326,26760],{"class":210},[200,88328,26910],{"class":245},[200,88330,26760],{"class":210},[200,88332,26915],{"class":245},[200,88334,26760],{"class":210},[200,88336,2020],{"class":245},[200,88338,26760],{"class":210},[200,88340,239],{"class":245},[200,88342,26760],{"class":210},[200,88344,26928],{"class":245},[200,88346,26760],{"class":210},[200,88348,550],{"class":245},[200,88350,26760],{"class":210},[200,88352,26937],{"class":245},[200,88354,26760],{"class":210},[200,88356,239],{"class":245},[200,88358,26760],{"class":210},[200,88360,26946],{"class":245},[200,88362,26760],{"class":210},[200,88364,550],{"class":245},[200,88366,26760],{"class":210},[200,88368,26955],{"class":245},[200,88370,26760],{"class":210},[200,88372,26960],{"class":245},[200,88374,26760],{"class":210},[200,88376,26965],{"class":245},[200,88378,26760],{"class":210},[200,88380,26970],{"class":245},[200,88382,611],{"class":214},[200,88384,10192],{"class":214},[200,88386,11386],{"class":235},[200,88388,648],{"class":214},[35,88390,419,88391,26983],{},[156,88392,26725],{},[192,88394,88395],{"className":26986,"code":26987,"language":26988,"meta":13,"style":13},[156,88396,88397,88401,88419,88431,88443,88455,88467,88471,88483,88495,88507,88523,88527,88539,88543,88561,88575,88607,88611,88615,88619,88623,88641,88659,88671,88683],{"__ignoreMap":13},[200,88398,88399],{"class":202,"line":203},[200,88400,230],{"class":214},[200,88402,88403,88405,88407,88409,88411,88413,88415,88417],{"class":202,"line":14},[200,88404,26999],{"class":214},[200,88406,23163],{"class":379},[200,88408,611],{"class":214},[200,88410,239],{"class":214},[200,88412,27008],{"class":214},[200,88414,26771],{"class":245},[200,88416,611],{"class":214},[200,88418,252],{"class":214},[200,88420,88421,88423,88425,88427,88429],{"class":202,"line":255},[200,88422,26999],{"class":214},[200,88424,23830],{"class":379},[200,88426,611],{"class":214},[200,88428,239],{"class":214},[200,88430,280],{"class":214},[200,88432,88433,88435,88437,88439,88441],{"class":202,"line":272},[200,88434,27031],{"class":214},[200,88436,26789],{"class":1978},[200,88438,611],{"class":214},[200,88440,239],{"class":214},[200,88442,12100],{"class":214},[200,88444,88445,88447,88449,88451,88453],{"class":202,"line":283},[200,88446,27031],{"class":214},[200,88448,26799],{"class":1978},[200,88450,611],{"class":214},[200,88452,239],{"class":214},[200,88454,280],{"class":214},[200,88456,88457,88459,88461,88463,88465],{"class":202,"line":300},[200,88458,27056],{"class":214},[200,88460,26808],{"class":2045},[200,88462,611],{"class":214},[200,88464,239],{"class":214},[200,88466,27065],{"class":214},[200,88468,88469],{"class":202,"line":310},[200,88470,339],{"class":214},[200,88472,88473,88475,88477,88479,88481],{"class":202,"line":336},[200,88474,27031],{"class":214},[200,88476,26818],{"class":1978},[200,88478,611],{"class":214},[200,88480,239],{"class":214},[200,88482,280],{"class":214},[200,88484,88485,88487,88489,88491,88493],{"class":202,"line":342},[200,88486,27056],{"class":214},[200,88488,2015],{"class":2045},[200,88490,611],{"class":214},[200,88492,239],{"class":214},[200,88494,27094],{"class":214},[200,88496,88497,88499,88501,88503,88505],{"class":202,"line":365},[200,88498,27056],{"class":214},[200,88500,26836],{"class":2045},[200,88502,611],{"class":214},[200,88504,239],{"class":214},[200,88506,280],{"class":214},[200,88508,88509,88511,88513,88515,88517,88519,88521],{"class":202,"line":371},[200,88510,27111],{"class":214},[200,88512,20089],{"class":235},[200,88514,611],{"class":214},[200,88516,239],{"class":214},[200,88518,27008],{"class":214},[200,88520,26853],{"class":245},[200,88522,617],{"class":214},[200,88524,88525],{"class":202,"line":398},[200,88526,12083],{"class":214},[200,88528,88529,88531,88533,88535,88537],{"class":202,"line":405},[200,88530,27056],{"class":214},[200,88532,26863],{"class":2045},[200,88534,611],{"class":214},[200,88536,239],{"class":214},[200,88538,1400],{"class":214},[200,88540,88541],{"class":202,"line":410},[200,88542,4909],{"class":214},[200,88544,88545,88547,88549,88551,88553,88555,88557,88559],{"class":202,"line":861},[200,88546,27148],{"class":214},[200,88548,26873],{"class":235},[200,88550,611],{"class":214},[200,88552,239],{"class":214},[200,88554,27008],{"class":214},[200,88556,26882],{"class":245},[200,88558,611],{"class":214},[200,88560,252],{"class":214},[200,88562,88563,88565,88567,88569,88571,88573],{"class":202,"line":890},[200,88564,27148],{"class":214},[200,88566,26891],{"class":235},[200,88568,611],{"class":214},[200,88570,239],{"class":214},[200,88572,27175],{"class":2045},[200,88574,252],{"class":214},[200,88576,88577,88579,88581,88583,88585,88587,88589,88591,88593,88595,88597,88599,88601,88603,88605],{"class":202,"line":900},[200,88578,27148],{"class":214},[200,88580,26901],{"class":235},[200,88582,611],{"class":214},[200,88584,239],{"class":214},[200,88586,27008],{"class":214},[200,88588,24232],{"class":245},[200,88590,27194],{"class":210},[200,88592,27197],{"class":245},[200,88594,27194],{"class":210},[200,88596,27202],{"class":245},[200,88598,27194],{"class":210},[200,88600,27207],{"class":245},[200,88602,27194],{"class":210},[200,88604,27212],{"class":245},[200,88606,617],{"class":214},[200,88608,88609],{"class":202,"line":905},[200,88610,13459],{"class":214},[200,88612,88613],{"class":202,"line":931},[200,88614,27223],{"class":214},[200,88616,88617],{"class":202,"line":3640},[200,88618,4962],{"class":214},[200,88620,88621],{"class":202,"line":3645},[200,88622,368],{"class":214},[200,88624,88625,88627,88629,88631,88633,88635,88637,88639],{"class":202,"line":4020},[200,88626,26999],{"class":214},[200,88628,2020],{"class":379},[200,88630,611],{"class":214},[200,88632,239],{"class":214},[200,88634,27008],{"class":214},[200,88636,2882],{"class":245},[200,88638,611],{"class":214},[200,88640,252],{"class":214},[200,88642,88643,88645,88647,88649,88651,88653,88655,88657],{"class":202,"line":4475},[200,88644,26999],{"class":214},[200,88646,26937],{"class":379},[200,88648,611],{"class":214},[200,88650,239],{"class":214},[200,88652,27008],{"class":214},[200,88654,26946],{"class":245},[200,88656,611],{"class":214},[200,88658,252],{"class":214},[200,88660,88661,88663,88665,88667,88669],{"class":202,"line":4481},[200,88662,26999],{"class":214},[200,88664,26955],{"class":379},[200,88666,611],{"class":214},[200,88668,239],{"class":214},[200,88670,27280],{"class":214},[200,88672,88673,88675,88677,88679,88681],{"class":202,"line":4487},[200,88674,26999],{"class":214},[200,88676,26965],{"class":379},[200,88678,611],{"class":214},[200,88680,239],{"class":214},[200,88682,18889],{"class":214},[200,88684,88685],{"class":202,"line":4493},[200,88686,1130],{"class":214},[35,88688,419,88689,27301,88691,27305,88693,1638,88695,27310,88697,27314,88699,215],{},[156,88690,26818],{},[6850,88692,27304],{},[6850,88694,26836],{},[6850,88696,26863],{},[156,88698,27313],{},[39,88700,27319],{"href":27317,"rel":88701},[77],[35,88703,27322,88704,27325],{},[156,88705,26882],{},[192,88707,88708],{"className":25029,"code":27328,"language":25031,"meta":13,"style":13},[156,88709,88710,88732,88736,88762,88796,88828,88834,88838,88858],{"__ignoreMap":13},[200,88711,88712,88714,88716,88718,88720,88722,88724,88726,88728,88730],{"class":202,"line":203},[200,88713,973],{"class":206},[200,88715,318],{"class":214},[200,88717,18685],{"class":210},[200,88719,550],{"class":214},[200,88721,27343],{"class":210},[200,88723,981],{"class":214},[200,88725,984],{"class":206},[200,88727,242],{"class":214},[200,88729,12060],{"class":245},[200,88731,992],{"class":214},[200,88733,88734],{"class":202,"line":14},[200,88735,529],{"emptyLinePlaceholder":20},[200,88737,88738,88740,88742,88744,88746,88748,88750,88752,88754,88756,88758,88760],{"class":202,"line":255},[200,88739,1380],{"class":206},[200,88741,1383],{"class":206},[200,88743,4987],{"class":379},[200,88745,27368],{"class":223},[200,88747,3951],{"class":214},[200,88749,27373],{"class":386},[200,88751,550],{"class":214},[200,88753,27378],{"class":386},[200,88755,27381],{"class":214},[200,88757,27384],{"class":386},[200,88759,2089],{"class":214},[200,88761,280],{"class":214},[200,88763,88764,88766,88768,88770,88772,88774,88776,88778,88780,88782,88784,88786,88788,88790,88792,88794],{"class":202,"line":272},[200,88765,27393],{"class":210},[200,88767,215],{"class":214},[200,88769,27398],{"class":223},[200,88771,227],{"class":235},[200,88773,26818],{"class":210},[200,88775,215],{"class":214},[200,88777,26836],{"class":210},[200,88779,2089],{"class":235},[200,88781,215],{"class":214},[200,88783,27413],{"class":223},[200,88785,227],{"class":235},[200,88787,227],{"class":214},[200,88789,24584],{"class":386},[200,88791,2089],{"class":214},[200,88793,393],{"class":379},[200,88795,280],{"class":214},[200,88797,88798,88800,88802,88804,88806,88808,88810,88812,88814,88816,88818,88820,88822,88824,88826],{"class":202,"line":283},[200,88799,27430],{"class":210},[200,88801,9858],{"class":235},[200,88803,24584],{"class":210},[200,88805,18836],{"class":235},[200,88807,449],{"class":214},[200,88809,18685],{"class":223},[200,88811,227],{"class":235},[200,88813,27445],{"class":223},[200,88815,227],{"class":235},[200,88817,26818],{"class":210},[200,88819,215],{"class":214},[200,88821,26836],{"class":210},[200,88823,9858],{"class":235},[200,88825,24584],{"class":210},[200,88827,27460],{"class":235},[200,88829,88830,88832],{"class":202,"line":300},[200,88831,5080],{"class":214},[200,88833,416],{"class":235},[200,88835,88836],{"class":202,"line":310},[200,88837,529],{"emptyLinePlaceholder":20},[200,88839,88840,88842,88844,88846,88848,88850,88852,88854,88856],{"class":202,"line":336},[200,88841,3743],{"class":206},[200,88843,27384],{"class":223},[200,88845,227],{"class":235},[200,88847,2752],{"class":214},[200,88849,27373],{"class":210},[200,88851,550],{"class":214},[200,88853,27378],{"class":210},[200,88855,981],{"class":214},[200,88857,416],{"class":235},[200,88859,88860],{"class":202,"line":342},[200,88861,1130],{"class":214},[35,88863,27497],{},[35,88865,27500],{},[192,88867,88868],{"className":21417,"code":27503,"language":12060,"meta":13,"style":13},[156,88869,88870,88878,88882,88886,88890,88894,88902,88906,88914,88918],{"__ignoreMap":13},[200,88871,88872,88874,88876],{"class":202,"line":203},[200,88873,600],{"class":214},[200,88875,19540],{"class":235},[200,88877,648],{"class":214},[200,88879,88880],{"class":202,"line":14},[200,88881,26031],{"class":210},[200,88883,88884],{"class":202,"line":255},[200,88885,529],{"emptyLinePlaceholder":20},[200,88887,88888],{"class":202,"line":272},[200,88889,27526],{"class":210},[200,88891,88892],{"class":202,"line":283},[200,88893,26049],{"class":210},[200,88895,88896,88898,88900],{"class":202,"line":300},[200,88897,681],{"class":214},[200,88899,19540],{"class":235},[200,88901,648],{"class":214},[200,88903,88904],{"class":202,"line":310},[200,88905,529],{"emptyLinePlaceholder":20},[200,88907,88908,88910,88912],{"class":202,"line":336},[200,88909,600],{"class":214},[200,88911,21428],{"class":235},[200,88913,648],{"class":214},[200,88915,88916],{"class":202,"line":342},[200,88917,24324],{"class":210},[200,88919,88920,88922,88924],{"class":202,"line":365},[200,88921,681],{"class":214},[200,88923,21428],{"class":235},[200,88925,648],{"class":214},[35,88927,27565],{},[192,88929,88930],{"className":21417,"code":27568,"language":12060,"meta":13,"style":13},[156,88931,88932,88942,88960,88974,88978,88982,89018,89022,89040,89044,89084,89092,89096,89104,89108],{"__ignoreMap":13},[200,88933,88934,88936,88938,88940],{"class":202,"line":203},[200,88935,600],{"class":214},[200,88937,14892],{"class":235},[200,88939,21781],{"class":379},[200,88941,648],{"class":214},[200,88943,88944,88946,88948,88950,88952,88954,88956,88958],{"class":202,"line":14},[200,88945,973],{"class":206},[200,88947,318],{"class":214},[200,88949,27589],{"class":210},[200,88951,981],{"class":214},[200,88953,984],{"class":206},[200,88955,242],{"class":214},[200,88957,27598],{"class":245},[200,88959,992],{"class":214},[200,88961,88962,88964,88966,88968,88970,88972],{"class":202,"line":255},[200,88963,973],{"class":206},[200,88965,27607],{"class":210},[200,88967,1472],{"class":206},[200,88969,242],{"class":214},[200,88971,27614],{"class":245},[200,88973,992],{"class":214},[200,88975,88976],{"class":202,"line":272},[200,88977,529],{"emptyLinePlaceholder":20},[200,88979,88980],{"class":202,"line":283},[200,88981,27625],{"class":401},[200,88983,88984,88986,88988,88990,88992,88994,88996,88998,89000,89002,89004,89006,89008,89010,89012,89014,89016],{"class":202,"line":300},[200,88985,443],{"class":379},[200,88987,27632],{"class":210},[200,88989,449],{"class":214},[200,88991,27589],{"class":223},[200,88993,25183],{"class":210},[200,88995,249],{"class":214},[200,88997,20089],{"class":245},[200,88999,249],{"class":214},[200,89001,550],{"class":214},[200,89003,242],{"class":214},[200,89005,26818],{"class":245},[200,89007,249],{"class":214},[200,89009,360],{"class":210},[200,89011,550],{"class":214},[200,89013,27659],{"class":210},[200,89015,215],{"class":214},[200,89017,27664],{"class":210},[200,89019,89020],{"class":202,"line":310},[200,89021,529],{"emptyLinePlaceholder":20},[200,89023,89024,89026,89028,89030,89032,89034,89036,89038],{"class":202,"line":336},[200,89025,443],{"class":379},[200,89027,318],{"class":214},[200,89029,701],{"class":210},[200,89031,550],{"class":214},[200,89033,27681],{"class":210},[200,89035,413],{"class":214},[200,89037,510],{"class":214},[200,89039,27688],{"class":210},[200,89041,89042],{"class":202,"line":342},[200,89043,529],{"emptyLinePlaceholder":20},[200,89045,89046,89048,89050,89052,89054,89056,89058,89060,89062,89064,89066,89068,89070,89072,89074,89076,89078,89080,89082],{"class":202,"line":365},[200,89047,27697],{"class":223},[200,89049,27700],{"class":210},[200,89051,550],{"class":214},[200,89053,318],{"class":214},[200,89055,3224],{"class":235},[200,89057,239],{"class":214},[200,89059,242],{"class":214},[200,89061,27713],{"class":245},[200,89063,249],{"class":214},[200,89065,550],{"class":214},[200,89067,27720],{"class":235},[200,89069,239],{"class":214},[200,89071,27725],{"class":206},[200,89073,215],{"class":214},[200,89075,2015],{"class":210},[200,89077,6524],{"class":214},[200,89079,27734],{"class":210},[200,89081,413],{"class":214},[200,89083,416],{"class":210},[200,89085,89086,89088,89090],{"class":202,"line":371},[200,89087,681],{"class":214},[200,89089,14892],{"class":235},[200,89091,648],{"class":214},[200,89093,89094],{"class":202,"line":398},[200,89095,529],{"emptyLinePlaceholder":20},[200,89097,89098,89100,89102],{"class":202,"line":405},[200,89099,600],{"class":214},[200,89101,21428],{"class":235},[200,89103,648],{"class":214},[200,89105,89106],{"class":202,"line":410},[200,89107,24324],{"class":210},[200,89109,89110,89112,89114],{"class":202,"line":861},[200,89111,681],{"class":214},[200,89113,21428],{"class":235},[200,89115,648],{"class":214},[35,89117,27773,89118,27781,89123,27785,89125,27788,89127,27791,89129,27795,89131,27799,89133,27802],{},[39,89119,89121,25117],{"href":27776,"rel":89120},[77],[156,89122,27780],{},[156,89124,27784],{},[6850,89126,27304],{},[156,89128,27304],{},[156,89130,27794],{},[156,89132,27798],{},[156,89134,27780],{},[35,89136,27805,89137,1638,89139,27810,89141,27813],{},[156,89138,20089],{},[156,89140,26818],{},[156,89142,27780],{},[35,89144,27816,89145,27819,89147,27823],{},[156,89146,27784],{},[156,89148,27822],{},[35,89150,27826,89151,27829,89153,27832],{},[156,89152,27822],{},[156,89154,25268],{},[192,89156,89157],{"className":25029,"code":27835,"language":25031,"meta":13,"style":13},[156,89158,89159,89173,89187,89191,89211,89223,89243,89247,89253,89257,89291,89331,89335,89349,89357,89361,89365,89375,89383,89411,89415,89419,89429,89447,89463,89483,89487,89491,89495,89501],{"__ignoreMap":13},[200,89160,89161,89163,89165,89167,89169,89171],{"class":202,"line":203},[200,89162,973],{"class":206},[200,89164,27844],{"class":210},[200,89166,1472],{"class":206},[200,89168,242],{"class":214},[200,89170,27851],{"class":245},[200,89172,992],{"class":214},[200,89174,89175,89177,89179,89181,89183,89185],{"class":202,"line":14},[200,89176,973],{"class":206},[200,89178,27860],{"class":210},[200,89180,1472],{"class":206},[200,89182,242],{"class":214},[200,89184,27867],{"class":245},[200,89186,992],{"class":214},[200,89188,89189],{"class":202,"line":255},[200,89190,529],{"emptyLinePlaceholder":20},[200,89192,89193,89195,89197,89199,89201,89203,89205,89207,89209],{"class":202,"line":272},[200,89194,1380],{"class":206},[200,89196,14301],{"class":379},[200,89198,18946],{"class":210},[200,89200,449],{"class":214},[200,89202,350],{"class":210},[200,89204,249],{"class":214},[200,89206,20089],{"class":245},[200,89208,249],{"class":214},[200,89210,4496],{"class":210},[200,89212,89213,89215,89217,89219,89221],{"class":202,"line":283},[200,89214,1380],{"class":206},[200,89216,14301],{"class":379},[200,89218,27902],{"class":210},[200,89220,449],{"class":214},[200,89222,6421],{"class":210},[200,89224,89225,89227,89229,89231,89233,89235,89237,89239,89241],{"class":202,"line":300},[200,89226,1380],{"class":206},[200,89228,14301],{"class":379},[200,89230,27915],{"class":210},[200,89232,449],{"class":214},[200,89234,350],{"class":210},[200,89236,249],{"class":214},[200,89238,27924],{"class":245},[200,89240,249],{"class":214},[200,89242,4496],{"class":210},[200,89244,89245],{"class":202,"line":310},[200,89246,529],{"emptyLinePlaceholder":20},[200,89248,89249,89251],{"class":202,"line":336},[200,89250,12327],{"class":379},[200,89252,27939],{"class":210},[200,89254,89255],{"class":202,"line":342},[200,89256,529],{"emptyLinePlaceholder":20},[200,89258,89259,89261,89263,89265,89267,89269,89271,89273,89275,89277,89279,89281,89283,89285,89287,89289],{"class":202,"line":365},[200,89260,1380],{"class":206},[200,89262,4987],{"class":379},[200,89264,27589],{"class":223},[200,89266,227],{"class":214},[200,89268,27398],{"class":386},[200,89270,510],{"class":214},[200,89272,27960],{"class":210},[200,89274,550],{"class":214},[200,89276,27965],{"class":386},[200,89278,510],{"class":214},[200,89280,27970],{"class":214},[200,89282,27973],{"class":386},[200,89284,510],{"class":214},[200,89286,16166],{"class":2423},[200,89288,2089],{"class":214},[200,89290,280],{"class":214},[200,89292,89293,89295,89297,89299,89301,89303,89305,89307,89309,89311,89313,89315,89317,89319,89321,89323,89325,89327,89329],{"class":202,"line":371},[200,89294,3723],{"class":379},[200,89296,27378],{"class":210},[200,89298,510],{"class":214},[200,89300,1095],{"class":235},[200,89302,27994],{"class":210},[200,89304,14928],{"class":214},[200,89306,27999],{"class":210},[200,89308,5267],{"class":235},[200,89310,28004],{"class":214},[200,89312,27999],{"class":210},[200,89314,28009],{"class":214},[200,89316,12997],{"class":214},[200,89318,28014],{"class":223},[200,89320,227],{"class":235},[200,89322,23830],{"class":210},[200,89324,2089],{"class":235},[200,89326,215],{"class":214},[200,89328,28025],{"class":223},[200,89330,466],{"class":235},[200,89332,89333],{"class":202,"line":398},[200,89334,529],{"emptyLinePlaceholder":20},[200,89336,89337,89339,89341,89343,89345,89347],{"class":202,"line":405},[200,89338,20075],{"class":206},[200,89340,1095],{"class":235},[200,89342,11837],{"class":214},[200,89344,27994],{"class":210},[200,89346,5267],{"class":235},[200,89348,230],{"class":214},[200,89350,89351,89353,89355],{"class":202,"line":410},[200,89352,28050],{"class":210},[200,89354,510],{"class":214},[200,89356,28055],{"class":210},[200,89358,89359],{"class":202,"line":861},[200,89360,4490],{"class":214},[200,89362,89363],{"class":202,"line":890},[200,89364,529],{"emptyLinePlaceholder":20},[200,89366,89367,89369,89371,89373],{"class":202,"line":900},[200,89368,3723],{"class":379},[200,89370,28070],{"class":210},[200,89372,510],{"class":214},[200,89374,280],{"class":214},[200,89376,89377,89379,89381],{"class":202,"line":905},[200,89378,28079],{"class":214},[200,89380,26836],{"class":210},[200,89382,252],{"class":214},[200,89384,89385,89387,89389,89391,89393,89395,89397,89399,89401,89403,89405,89407,89409],{"class":202,"line":931},[200,89386,28088],{"class":214},[200,89388,28091],{"class":223},[200,89390,25183],{"class":235},[200,89392,574],{"class":214},[200,89394,26863],{"class":210},[200,89396,550],{"class":214},[200,89398,28102],{"class":214},[200,89400,28105],{"class":210},[200,89402,360],{"class":235},[200,89404,550],{"class":214},[200,89406,27378],{"class":210},[200,89408,2089],{"class":235},[200,89410,252],{"class":214},[200,89412,89413],{"class":202,"line":3640},[200,89414,4490],{"class":214},[200,89416,89417],{"class":202,"line":3645},[200,89418,529],{"emptyLinePlaceholder":20},[200,89420,89421,89423,89425,89427],{"class":202,"line":4020},[200,89422,3723],{"class":379},[200,89424,28130],{"class":210},[200,89426,510],{"class":214},[200,89428,19891],{"class":214},[200,89430,89431,89433,89435,89437,89439,89441,89443,89445],{"class":202,"line":4475},[200,89432,6426],{"class":206},[200,89434,1095],{"class":235},[200,89436,443],{"class":379},[200,89438,18822],{"class":210},[200,89440,6445],{"class":214},[200,89442,28149],{"class":210},[200,89444,5267],{"class":235},[200,89446,230],{"class":214},[200,89448,89449,89451,89453,89455,89457,89459,89461],{"class":202,"line":4481},[200,89450,5252],{"class":206},[200,89452,1095],{"class":235},[200,89454,24584],{"class":210},[200,89456,28164],{"class":214},[200,89458,28070],{"class":210},[200,89460,5267],{"class":235},[200,89462,230],{"class":214},[200,89464,89465,89467,89469,89471,89473,89475,89477,89479,89481],{"class":202,"line":4487},[200,89466,28175],{"class":210},[200,89468,9858],{"class":235},[200,89470,24584],{"class":210},[200,89472,18836],{"class":235},[200,89474,449],{"class":214},[200,89476,28070],{"class":210},[200,89478,9858],{"class":235},[200,89480,24584],{"class":210},[200,89482,4496],{"class":235},[200,89484,89485],{"class":202,"line":4493},[200,89486,4962],{"class":214},[200,89488,89489],{"class":202,"line":5454},[200,89490,4490],{"class":214},[200,89492,89493],{"class":202,"line":5459},[200,89494,529],{"emptyLinePlaceholder":20},[200,89496,89497,89499],{"class":202,"line":13227},[200,89498,3743],{"class":206},[200,89500,28210],{"class":210},[200,89502,89503],{"class":202,"line":13253},[200,89504,1130],{"class":214},[35,89506,419,89507,1638,89509,28221,89511,1638,89513,28227,89515,28231,89517,28234],{},[156,89508,26836],{},[156,89510,26818],{},[6850,89512,27304],{},[6850,89514,28226],{},[156,89516,28230],{},[156,89518,26882],{},[35,89520,28237],{},[1576,89522,89523,89530,89537,89544],{},[71,89524,89525,28248],{},[39,89526,89528],{"href":28244,"rel":89527},[77],[156,89529,26882],{},[71,89531,89532,28258],{},[39,89533,89535],{"href":28253,"rel":89534},[77],[156,89536,28257],{},[71,89538,89539,28267],{},[39,89540,89542],{"href":28263,"rel":89541},[77],[156,89543,1118],{},[71,89545,89546,28277],{},[39,89547,89549],{"href":28272,"rel":89548},[77],[156,89550,28276],{},[35,89552,28280,89553,28283],{},[156,89554,28226],{},[939,89556,89557],{},[35,89558,89559,13519,89561,28292,89563,1638,89566,215],{},[200,89560,945],{},[156,89562,28226],{},[39,89564,28297],{"href":28295,"rel":89565},[77],[39,89567,28302],{"href":28300,"rel":89568},[77],[35,89570,28305],{},[192,89572,89573],{"className":21417,"code":28308,"language":12060,"meta":13,"style":13},[156,89574,89575,89583,89587,89591,89595,89603,89607,89615,89633,89637,89645],{"__ignoreMap":13},[200,89576,89577,89579,89581],{"class":202,"line":203},[200,89578,600],{"class":214},[200,89580,19540],{"class":235},[200,89582,648],{"class":214},[200,89584,89585],{"class":202,"line":14},[200,89586,28323],{"class":210},[200,89588,89589],{"class":202,"line":255},[200,89590,28328],{"class":210},[200,89592,89593],{"class":202,"line":272},[200,89594,28333],{"class":210},[200,89596,89597,89599,89601],{"class":202,"line":283},[200,89598,681],{"class":214},[200,89600,19540],{"class":235},[200,89602,648],{"class":214},[200,89604,89605],{"class":202,"line":300},[200,89606,529],{"emptyLinePlaceholder":20},[200,89608,89609,89611,89613],{"class":202,"line":310},[200,89610,600],{"class":214},[200,89612,21428],{"class":235},[200,89614,648],{"class":214},[200,89616,89617,89619,89621,89623,89625,89627,89629,89631],{"class":202,"line":336},[200,89618,657],{"class":214},[200,89620,910],{"class":235},[200,89622,28362],{"class":379},[200,89624,449],{"class":214},[200,89626,611],{"class":214},[200,89628,28369],{"class":245},[200,89630,611],{"class":214},[200,89632,648],{"class":214},[200,89634,89635],{"class":202,"line":342},[200,89636,28378],{"class":210},[200,89638,89639,89641,89643],{"class":202,"line":365},[200,89640,893],{"class":214},[200,89642,910],{"class":235},[200,89644,648],{"class":214},[200,89646,89647,89649,89651],{"class":202,"line":371},[200,89648,681],{"class":214},[200,89650,21428],{"class":235},[200,89652,648],{"class":214},[35,89654,28397,89655,28400,89657,28403],{},[156,89656,24441],{},[156,89658,27784],{},[68,89660,89661,89670,89675],{},[71,89662,28408,89663,28414,89666,1638,89668,215],{},[39,89664,28413],{"href":28411,"rel":89665},[77],[156,89667,28417],{},[156,89669,28420],{},[71,89671,28423,89672,215],{},[39,89673,28428],{"href":28426,"rel":89674},[77],[71,89676,28431,89677,215],{},[39,89678,28436],{"href":28434,"rel":89679},[77],[35,89681,28439],{},[192,89683,89684],{"className":28442,"code":28443,"language":28444,"meta":13,"style":13},[156,89685,89686,89690,89694,89698,89702,89706,89710,89714,89718,89722,89726,89730,89734,89738,89742,89746,89750,89754,89758,89762,89766,89770,89774,89778,89782,89786,89790,89794,89798,89802,89806,89810,89814,89818],{"__ignoreMap":13},[200,89687,89688],{"class":202,"line":203},[200,89689,28451],{},[200,89691,89692],{"class":202,"line":14},[200,89693,28456],{},[200,89695,89696],{"class":202,"line":255},[200,89697,28461],{},[200,89699,89700],{"class":202,"line":272},[200,89701,28466],{},[200,89703,89704],{"class":202,"line":283},[200,89705,529],{"emptyLinePlaceholder":20},[200,89707,89708],{"class":202,"line":300},[200,89709,230],{},[200,89711,89712],{"class":202,"line":310},[200,89713,28479],{},[200,89715,89716],{"class":202,"line":336},[200,89717,28484],{},[200,89719,89720],{"class":202,"line":342},[200,89721,28489],{},[200,89723,89724],{"class":202,"line":365},[200,89725,28494],{},[200,89727,89728],{"class":202,"line":371},[200,89729,28499],{},[200,89731,89732],{"class":202,"line":398},[200,89733,28504],{},[200,89735,89736],{"class":202,"line":405},[200,89737,28509],{},[200,89739,89740],{"class":202,"line":410},[200,89741,28514],{},[200,89743,89744],{"class":202,"line":861},[200,89745,28519],{},[200,89747,89748],{"class":202,"line":890},[200,89749,28524],{},[200,89751,89752],{"class":202,"line":900},[200,89753,4478],{},[200,89755,89756],{"class":202,"line":905},[200,89757,339],{},[200,89759,89760],{"class":202,"line":931},[200,89761,28537],{},[200,89763,89764],{"class":202,"line":3640},[200,89765,28542],{},[200,89767,89768],{"class":202,"line":3645},[200,89769,28547],{},[200,89771,89772],{"class":202,"line":4020},[200,89773,4909],{},[200,89775,89776],{"class":202,"line":4475},[200,89777,28556],{},[200,89779,89780],{"class":202,"line":4481},[200,89781,28561],{},[200,89783,89784],{"class":202,"line":4487},[200,89785,28566],{},[200,89787,89788],{"class":202,"line":4493},[200,89789,28571],{},[200,89791,89792],{"class":202,"line":5454},[200,89793,28576],{},[200,89795,89796],{"class":202,"line":5459},[200,89797,28581],{},[200,89799,89800],{"class":202,"line":13227},[200,89801,28586],{},[200,89803,89804],{"class":202,"line":13253},[200,89805,13459],{},[200,89807,89808],{"class":202,"line":13269},[200,89809,27223],{},[200,89811,89812],{"class":202,"line":13313},[200,89813,4962],{},[200,89815,89816],{"class":202,"line":13319},[200,89817,4490],{},[200,89819,89820],{"class":202,"line":13324},[200,89821,1130],{},[35,89823,28609,89824,1638,89826,28614,89828,215],{},[156,89825,28417],{},[156,89827,28420],{},[39,89829,28619],{"href":28617,"rel":89830},[77],[35,89832,28622,89833,28626,89835,28629],{},[156,89834,28625],{},[156,89836,26836],{},[35,89838,28632],{},[192,89840,89841],{"className":26986,"code":28635,"language":26988,"meta":13,"style":13},[156,89842,89843],{"__ignoreMap":13},[200,89844,89845],{"class":202,"line":203},[200,89846,28635],{"class":214},[35,89848,28644,89849,28647],{},[156,89850,24441],{},[35,89852,28650,89853,28653,89855,28656,89857,28659],{},[156,89854,24434],{},[156,89856,24434],{},[156,89858,27924],{},[51,89860,28663],{"id":28662},[35,89862,28666,89863,28671,89866,28677],{},[39,89864,28670],{"href":26717,"rel":89865},[77],[39,89867,28676],{"href":28674,"rel":89868},[77],[35,89870,28680],{},[51,89872,24102],{"id":24101},[35,89874,28685],{},[35,89876,28688,89877,28692],{},[156,89878,28691],{},[35,89880,28695],{},[35,89882,28698],{},[35,89884,28701,89885,28705],{},[39,89886,28704],{"href":25677},[35,89888,28708],{},[51,89890,28712],{"id":28711},[35,89892,28715,89893,28719,89895,28723],{},[6850,89894,28718],{},[6850,89896,28722],{},[35,89898,28726],{},[35,89900,28729],{},[939,89902,89903],{},[35,89904,89905,28736],{},[200,89906,945],{},[35,89908,28739,89909,28745,89912,28751],{},[39,89910,28744],{"href":28742,"rel":89911},[77],[39,89913,28750],{"href":28748,"rel":89914},[77],[192,89916,89917],{"className":26986,"code":28754,"language":26988,"meta":13,"style":13},[156,89918,89919,89923,89945,89957,89969,89985,89989,89993,90019,90031,90043,90059,90063,90075,90091,90095,90099,90111,90115,90131,90135,90139,90157,90183,90201,90213,90217,90233,90237,90241,90245,90249],{"__ignoreMap":13},[200,89920,89921],{"class":202,"line":203},[200,89922,230],{"class":214},[200,89924,89925,89927,89929,89931,89933,89935,89937,89939,89941,89943],{"class":202,"line":14},[200,89926,26999],{"class":214},[200,89928,28767],{"class":379},[200,89930,611],{"class":214},[200,89932,239],{"class":214},[200,89934,27008],{"class":214},[200,89936,28776],{"class":245},[200,89938,27194],{"class":210},[200,89940,3884],{"class":245},[200,89942,611],{"class":214},[200,89944,252],{"class":214},[200,89946,89947,89949,89951,89953,89955],{"class":202,"line":255},[200,89948,26999],{"class":214},[200,89950,28791],{"class":379},[200,89952,611],{"class":214},[200,89954,239],{"class":214},[200,89956,280],{"class":214},[200,89958,89959,89961,89963,89965,89967],{"class":202,"line":272},[200,89960,27031],{"class":214},[200,89962,3188],{"class":1978},[200,89964,611],{"class":214},[200,89966,239],{"class":214},[200,89968,280],{"class":214},[200,89970,89971,89973,89975,89977,89979,89981,89983],{"class":202,"line":283},[200,89972,27056],{"class":214},[200,89974,20089],{"class":2045},[200,89976,611],{"class":214},[200,89978,239],{"class":214},[200,89980,27008],{"class":214},[200,89982,28824],{"class":245},[200,89984,617],{"class":214},[200,89986,89987],{"class":202,"line":300},[200,89988,4962],{"class":214},[200,89990,89991],{"class":202,"line":310},[200,89992,368],{"class":214},[200,89994,89995,89997,89999,90001,90003,90005,90007,90009,90011,90013,90015,90017],{"class":202,"line":336},[200,89996,26999],{"class":214},[200,89998,28841],{"class":379},[200,90000,611],{"class":214},[200,90002,239],{"class":214},[200,90004,27008],{"class":214},[200,90006,28850],{"class":245},[200,90008,27194],{"class":210},[200,90010,28855],{"class":245},[200,90012,27194],{"class":210},[200,90014,28860],{"class":245},[200,90016,611],{"class":214},[200,90018,252],{"class":214},[200,90020,90021,90023,90025,90027,90029],{"class":202,"line":342},[200,90022,26999],{"class":214},[200,90024,28871],{"class":379},[200,90026,611],{"class":214},[200,90028,239],{"class":214},[200,90030,280],{"class":214},[200,90032,90033,90035,90037,90039,90041],{"class":202,"line":365},[200,90034,27031],{"class":214},[200,90036,3188],{"class":1978},[200,90038,611],{"class":214},[200,90040,239],{"class":214},[200,90042,280],{"class":214},[200,90044,90045,90047,90049,90051,90053,90055,90057],{"class":202,"line":371},[200,90046,27056],{"class":214},[200,90048,20089],{"class":2045},[200,90050,611],{"class":214},[200,90052,239],{"class":214},[200,90054,27008],{"class":214},[200,90056,28904],{"class":245},[200,90058,617],{"class":214},[200,90060,90061],{"class":202,"line":398},[200,90062,339],{"class":214},[200,90064,90065,90067,90069,90071,90073],{"class":202,"line":405},[200,90066,27031],{"class":214},[200,90068,4782],{"class":1978},[200,90070,611],{"class":214},[200,90072,239],{"class":214},[200,90074,280],{"class":214},[200,90076,90077,90079,90081,90083,90085,90087,90089],{"class":202,"line":410},[200,90078,27056],{"class":214},[200,90080,20089],{"class":2045},[200,90082,611],{"class":214},[200,90084,239],{"class":214},[200,90086,27008],{"class":214},[200,90088,28937],{"class":245},[200,90090,617],{"class":214},[200,90092,90093],{"class":202,"line":861},[200,90094,4962],{"class":214},[200,90096,90097],{"class":202,"line":890},[200,90098,368],{"class":214},[200,90100,90101,90103,90105,90107,90109],{"class":202,"line":900},[200,90102,26999],{"class":214},[200,90104,28954],{"class":379},[200,90106,611],{"class":214},[200,90108,239],{"class":214},[200,90110,1400],{"class":214},[200,90112,90113],{"class":202,"line":905},[200,90114,19638],{"class":214},[200,90116,90117,90119,90121,90123,90125,90127,90129],{"class":202,"line":931},[200,90118,27056],{"class":214},[200,90120,28971],{"class":1978},[200,90122,611],{"class":214},[200,90124,239],{"class":214},[200,90126,27008],{"class":214},[200,90128,28980],{"class":245},[200,90130,617],{"class":214},[200,90132,90133],{"class":202,"line":3640},[200,90134,339],{"class":214},[200,90136,90137],{"class":202,"line":3645},[200,90138,19638],{"class":214},[200,90140,90141,90143,90145,90147,90149,90151,90153,90155],{"class":202,"line":4020},[200,90142,27056],{"class":214},[200,90144,28767],{"class":1978},[200,90146,611],{"class":214},[200,90148,239],{"class":214},[200,90150,27008],{"class":214},[200,90152,29005],{"class":245},[200,90154,611],{"class":214},[200,90156,252],{"class":214},[200,90158,90159,90161,90163,90165,90167,90169,90171,90173,90175,90177,90179,90181],{"class":202,"line":4475},[200,90160,27056],{"class":214},[200,90162,28841],{"class":1978},[200,90164,611],{"class":214},[200,90166,239],{"class":214},[200,90168,27008],{"class":214},[200,90170,29024],{"class":245},[200,90172,27194],{"class":210},[200,90174,29029],{"class":245},[200,90176,27194],{"class":210},[200,90178,29034],{"class":245},[200,90180,611],{"class":214},[200,90182,252],{"class":214},[200,90184,90185,90187,90189,90191,90193,90195,90197,90199],{"class":202,"line":4481},[200,90186,27056],{"class":214},[200,90188,20089],{"class":1978},[200,90190,611],{"class":214},[200,90192,239],{"class":214},[200,90194,27008],{"class":214},[200,90196,29053],{"class":245},[200,90198,611],{"class":214},[200,90200,252],{"class":214},[200,90202,90203,90205,90207,90209,90211],{"class":202,"line":4487},[200,90204,27056],{"class":214},[200,90206,28954],{"class":1978},[200,90208,611],{"class":214},[200,90210,239],{"class":214},[200,90212,1400],{"class":214},[200,90214,90215],{"class":202,"line":4493},[200,90216,4909],{"class":214},[200,90218,90219,90221,90223,90225,90227,90229,90231],{"class":202,"line":5454},[200,90220,27148],{"class":214},[200,90222,28971],{"class":2045},[200,90224,611],{"class":214},[200,90226,239],{"class":214},[200,90228,27008],{"class":214},[200,90230,29053],{"class":245},[200,90232,617],{"class":214},[200,90234,90235],{"class":202,"line":5459},[200,90236,13459],{"class":214},[200,90238,90239],{"class":202,"line":13227},[200,90240,27223],{"class":214},[200,90242,90243],{"class":202,"line":13253},[200,90244,4962],{"class":214},[200,90246,90247],{"class":202,"line":13269},[200,90248,13674],{"class":214},[200,90250,90251],{"class":202,"line":13313},[200,90252,1130],{"class":214},[35,90254,29112,90255,29116,90257,29119],{},[156,90256,29115],{},[156,90258,28971],{},[35,90260,29122,90261,29126],{},[156,90262,29125],{},[35,90264,29129,90265,29135],{},[39,90266,29134],{"href":29132,"rel":90267},[77],[35,90269,29138,90270,29144],{},[39,90271,29143],{"href":29141,"rel":90272},[77],[35,90274,29147,90275,215],{},[39,90276,29152],{"href":29150,"rel":90277},[77],[35,90279,29155,90280,29161,90283,215],{},[39,90281,29160],{"href":29158,"rel":90282},[77],[39,90284,29166],{"href":29164,"rel":90285},[77],[35,90287,29169],{},[35,90289,29172],{},[1171,90291,29175],{},{"title":13,"searchDepth":14,"depth":14,"links":90293},[90294,90295,90296,90302,90303,90304],{"id":24389,"depth":14,"text":24390},{"id":24695,"depth":14,"text":24681},{"id":25682,"depth":14,"text":25683,"children":90297},[90298,90299,90300,90301],{"id":25703,"depth":255,"text":25704},{"id":25734,"depth":255,"text":29184},{"id":26275,"depth":255,"text":26276},{"id":26554,"depth":255,"text":26555},{"id":28662,"depth":14,"text":28663},{"id":24101,"depth":14,"text":24102},{"id":28711,"depth":14,"text":28712},{},{"title":24171,"description":29191},{"loc":29193,"images":90308},[90309,90310,90311,90312,90313,90314],{"loc":24222},{"loc":24826},{"loc":25138},{"loc":25213},{"loc":25290},{"loc":25654},{"id":29207,"title":29208,"body":90316,"contentId":9247,"date":30701,"description":30702,"extension":18,"meta":91569,"navigation":20,"path":30704,"rawbody":30705,"robots":23,"schemaOrg":23,"seo":91570,"sitemap":91571,"stem":30713,"__hash__":30714},{"type":7,"value":90317,"toc":91562},[90318,90322,90326,90335,90337,90343,90347,90353,90359,90361,90365,90367,90373,90409,90415,90419,90421,90423,90430,90434,90436,90438,90440,90450,90452,90564,90566,90688,90696,90698,90707,90709,90711,90713,90719,90725,90731,90733,90775,90777,90819,90821,90831,90833,90883,90885,90887,90889,91041,91043,91052,91060,91062,91064,91066,91074,91078,91162,91170,91172,91180,91184,91186,91194,91196,91198,91202,91424,91434,91436,91438,91440,91442,91450,91466,91470,91532,91538,91540,91544,91546,91548,91552,91554,91556,91560],[35,90319,90320],{},[1217,90321,29215],{},[35,90323,29218,90324,29221],{},[6850,90325,19447],{},[35,90327,29224,90328,1638,90330,29229,90332,215],{},[6850,90329,19447],{},[6850,90331,24355],{},[39,90333,29234],{"href":29232,"rel":90334},[77],[51,90336,29238],{"id":29237},[35,90338,29241,90339,29244,90341,29247],{},[6850,90340,24355],{},[6850,90342,19447],{},[35,90344,29250,90345,29253],{},[6850,90346,24355],{},[35,90348,29256,90349,29259,90351],{},[6850,90350,19447],{},[1217,90352,29262],{},[35,90354,29265,90355,1638,90357,29270],{},[6850,90356,19447],{},[6850,90358,24355],{},[35,90360,29273],{},[35,90362,90363],{},[1690,90364],{"alt":29278,"src":29279},[35,90366,29282],{},[68,90368,90369,90371],{},[71,90370,29287],{},[71,90372,29290],{},[192,90374,90375],{"className":591,"code":29293,"language":593,"meta":13,"style":13},[156,90376,90377],{"__ignoreMap":13},[200,90378,90379,90381,90383,90385,90387,90389,90391,90393,90395,90397,90399,90401,90403,90405,90407],{"class":202,"line":203},[200,90380,600],{"class":214},[200,90382,14892],{"class":235},[200,90384,321],{"class":379},[200,90386,449],{"class":214},[200,90388,611],{"class":214},[200,90390,15093],{"class":245},[200,90392,611],{"class":214},[200,90394,10106],{"class":379},[200,90396,449],{"class":214},[200,90398,611],{"class":214},[200,90400,29320],{"class":245},[200,90402,611],{"class":214},[200,90404,10192],{"class":214},[200,90406,14892],{"class":235},[200,90408,648],{"class":214},[68,90410,90411,90413],{"start":255},[71,90412,29333],{},[71,90414,29336],{},[35,90416,29339,90417],{},[1217,90418,29342],{},[35,90420,29345],{},[51,90422,29349],{"id":29348},[35,90424,29352,90425,29358,90428],{},[39,90426,29357],{"href":29355,"rel":90427},[77],[1217,90429,29361],{},[35,90431,90432],{},[1690,90433],{"alt":29366,"src":29367},[35,90435,29370],{},[35,90437,29373],{},[35,90439,29376],{},[1576,90441,90442,90446],{},[71,90443,90444,29384],{},[6850,90445,29383],{},[71,90447,90448,29390],{},[6850,90449,29389],{},[35,90451,29393],{},[192,90453,90454],{"className":1370,"code":29396,"filename":29397,"language":1373,"meta":13,"style":13},[156,90455,90456,90476,90494,90532,90550,90560],{"__ignoreMap":13},[200,90457,90458,90460,90462,90464,90466,90468,90470,90472,90474],{"class":202,"line":203},[200,90459,29404],{"class":210},[200,90461,215],{"class":214},[200,90463,29409],{"class":223},[200,90465,510],{"class":214},[200,90467,4987],{"class":379},[200,90469,1095],{"class":214},[200,90471,29418],{"class":386},[200,90473,2089],{"class":214},[200,90475,280],{"class":214},[200,90477,90478,90480,90482,90484,90486,90488,90490,90492],{"class":202,"line":14},[200,90479,1113],{"class":210},[200,90481,215],{"class":214},[200,90483,1118],{"class":223},[200,90485,227],{"class":235},[200,90487,249],{"class":214},[200,90489,29437],{"class":245},[200,90491,249],{"class":214},[200,90493,416],{"class":235},[200,90495,90496,90498,90500,90502,90504,90506,90508,90510,90512,90514,90516,90518,90520,90522,90524,90526,90528,90530],{"class":202,"line":255},[200,90497,3723],{"class":379},[200,90499,29448],{"class":210},[200,90501,510],{"class":214},[200,90503,11139],{"class":214},[200,90505,29455],{"class":245},[200,90507,4853],{"class":214},[200,90509,29418],{"class":210},[200,90511,215],{"class":214},[200,90513,29464],{"class":210},[200,90515,3933],{"class":2045},[200,90517,18836],{"class":210},[200,90519,29471],{"class":214},[200,90521,29474],{"class":210},[200,90523,215],{"class":214},[200,90525,29464],{"class":210},[200,90527,3188],{"class":2045},[200,90529,360],{"class":210},[200,90531,29485],{"class":214},[200,90533,90534,90536,90538,90540,90542,90544,90546,90548],{"class":202,"line":272},[200,90535,1113],{"class":210},[200,90537,215],{"class":214},[200,90539,1118],{"class":223},[200,90541,227],{"class":235},[200,90543,249],{"class":214},[200,90545,29500],{"class":245},[200,90547,249],{"class":214},[200,90549,416],{"class":235},[200,90551,90552,90554,90556,90558],{"class":202,"line":283},[200,90553,29509],{"class":223},[200,90555,227],{"class":235},[200,90557,29514],{"class":210},[200,90559,416],{"class":235},[200,90561,90562],{"class":202,"line":300},[200,90563,1130],{"class":214},[35,90565,29523],{},[192,90567,90568],{"className":1370,"code":29526,"filename":29527,"language":1373,"meta":13,"style":13},[156,90569,90570,90592,90596,90624,90642,90660,90666,90670],{"__ignoreMap":13},[200,90571,90572,90574,90576,90578,90580,90582,90584,90586,90588,90590],{"class":202,"line":203},[200,90573,443],{"class":379},[200,90575,29536],{"class":210},[200,90577,449],{"class":214},[200,90579,12997],{"class":214},[200,90581,29543],{"class":223},[200,90583,227],{"class":210},[200,90585,249],{"class":214},[200,90587,29397],{"class":245},[200,90589,249],{"class":214},[200,90591,416],{"class":210},[200,90593,90594],{"class":202,"line":14},[200,90595,529],{"emptyLinePlaceholder":20},[200,90597,90598,90600,90602,90604,90606,90608,90610,90612,90614,90616,90618,90620,90622],{"class":202,"line":255},[200,90599,29562],{"class":210},[200,90601,215],{"class":214},[200,90603,29567],{"class":223},[200,90605,227],{"class":210},[200,90607,249],{"class":214},[200,90609,29574],{"class":245},[200,90611,249],{"class":214},[200,90613,550],{"class":214},[200,90615,1095],{"class":214},[200,90617,29418],{"class":386},[200,90619,2089],{"class":214},[200,90621,393],{"class":379},[200,90623,280],{"class":214},[200,90625,90626,90628,90630,90632,90634,90636,90638,90640],{"class":202,"line":272},[200,90627,1113],{"class":210},[200,90629,215],{"class":214},[200,90631,1118],{"class":223},[200,90633,227],{"class":235},[200,90635,249],{"class":214},[200,90637,29603],{"class":245},[200,90639,249],{"class":214},[200,90641,416],{"class":235},[200,90643,90644,90646,90648,90650,90652,90654,90656,90658],{"class":202,"line":283},[200,90645,1113],{"class":210},[200,90647,215],{"class":214},[200,90649,1118],{"class":223},[200,90651,227],{"class":235},[200,90653,29418],{"class":210},[200,90655,215],{"class":214},[200,90657,12726],{"class":210},[200,90659,416],{"class":235},[200,90661,90662,90664],{"class":202,"line":300},[200,90663,413],{"class":214},[200,90665,416],{"class":210},[200,90667,90668],{"class":202,"line":310},[200,90669,529],{"emptyLinePlaceholder":20},[200,90671,90672,90674,90676,90678,90680,90682,90684,90686],{"class":202,"line":336},[200,90673,29562],{"class":210},[200,90675,215],{"class":214},[200,90677,29644],{"class":223},[200,90679,25183],{"class":210},[200,90681,4782],{"class":2045},[200,90683,550],{"class":214},[200,90685,29653],{"class":2045},[200,90687,29656],{"class":210},[35,90689,29659,90690,29662,90692,29665,90694,215],{},[156,90691,29644],{},[156,90693,29644],{},[156,90695,29567],{},[35,90697,29670],{},[939,90699,90700],{},[35,90701,90702,29677,90704,29683],{},[200,90703,945],{},[39,90705,29682],{"href":29680,"rel":90706},[77],[51,90708,29687],{"id":29686},[35,90710,29690],{},[35,90712,29693],{},[939,90714,90715],{},[35,90716,90717,29701],{},[200,90718,29700],{},[35,90720,29704,90721,1638,90723,29711],{},[156,90722,29707],{},[156,90724,29710],{},[35,90726,29714,90727,29717,90729,215],{},[156,90728,29707],{},[156,90730,29707],{},[35,90732,29722],{},[192,90734,90735],{"className":591,"code":29725,"language":593,"meta":13,"style":13},[156,90736,90737,90745,90767],{"__ignoreMap":13},[200,90738,90739,90741,90743],{"class":202,"line":203},[200,90740,600],{"class":214},[200,90742,14892],{"class":235},[200,90744,648],{"class":214},[200,90746,90747,90749,90751,90753,90755,90757,90759,90761,90763,90765],{"class":202,"line":14},[200,90748,3723],{"class":379},[200,90750,29536],{"class":210},[200,90752,449],{"class":214},[200,90754,12997],{"class":214},[200,90756,29543],{"class":223},[200,90758,227],{"class":210},[200,90760,249],{"class":214},[200,90762,29397],{"class":245},[200,90764,249],{"class":214},[200,90766,416],{"class":210},[200,90768,90769,90771,90773],{"class":202,"line":255},[200,90770,681],{"class":214},[200,90772,14892],{"class":235},[200,90774,648],{"class":214},[35,90776,29768],{},[192,90778,90779],{"className":591,"code":29771,"language":593,"meta":13,"style":13},[156,90780,90781,90789,90811],{"__ignoreMap":13},[200,90782,90783,90785,90787],{"class":202,"line":203},[200,90784,600],{"class":214},[200,90786,14892],{"class":235},[200,90788,648],{"class":214},[200,90790,90791,90793,90795,90797,90799,90801,90803,90805,90807,90809],{"class":202,"line":14},[200,90792,3723],{"class":379},[200,90794,29536],{"class":210},[200,90796,449],{"class":214},[200,90798,12997],{"class":214},[200,90800,29543],{"class":223},[200,90802,227],{"class":210},[200,90804,249],{"class":214},[200,90806,29800],{"class":245},[200,90808,249],{"class":214},[200,90810,416],{"class":210},[200,90812,90813,90815,90817],{"class":202,"line":255},[200,90814,681],{"class":214},[200,90816,14892],{"class":235},[200,90818,648],{"class":214},[35,90820,29815],{},[35,90822,29818,90823,29822,90825,106,90827,1638,90829,29830],{},[156,90824,29821],{},[156,90826,29825],{},[156,90828,29821],{},[156,90830,29825],{},[35,90832,29833],{},[192,90834,90835],{"className":1355,"code":29836,"language":1358,"meta":13,"style":13},[156,90836,90837],{"__ignoreMap":13},[200,90838,90839,90841,90843,90845,90847,90849,90851,90853,90855,90857,90859,90861,90863,90865,90867,90869,90871,90873,90875,90877,90879,90881],{"class":202,"line":203},[200,90840,29843],{"class":1978},[200,90842,29846],{"class":245},[200,90844,29849],{"class":245},[200,90846,29852],{"class":245},[200,90848,242],{"class":214},[200,90850,29857],{"class":245},[200,90852,249],{"class":214},[200,90854,239],{"class":245},[200,90856,29864],{"class":245},[200,90858,29867],{"class":245},[200,90860,242],{"class":214},[200,90862,29872],{"class":245},[200,90864,249],{"class":214},[200,90866,29877],{"class":245},[200,90868,29880],{"class":245},[200,90870,29883],{"class":245},[200,90872,984],{"class":245},[200,90874,29888],{"class":245},[200,90876,242],{"class":214},[200,90878,29893],{"class":245},[200,90880,249],{"class":214},[200,90882,29898],{"class":245},[35,90884,29901],{},[35,90886,29904],{},[35,90888,29907],{},[192,90890,90891],{"className":1370,"code":29910,"language":1373,"meta":13,"style":13},[156,90892,90893,90911,90925,90939,90943,90955,90971,90983,90995,91005,91011,91021,91025,91029,91033,91037],{"__ignoreMap":13},[200,90894,90895,90897,90899,90901,90903,90905,90907,90909],{"class":202,"line":203},[200,90896,973],{"class":206},[200,90898,29919],{"class":214},[200,90900,14674],{"class":206},[200,90902,29924],{"class":210},[200,90904,1472],{"class":206},[200,90906,242],{"class":214},[200,90908,29931],{"class":245},[200,90910,992],{"class":214},[200,90912,90913,90915,90917,90919,90921,90923],{"class":202,"line":14},[200,90914,973],{"class":206},[200,90916,29940],{"class":210},[200,90918,1472],{"class":206},[200,90920,242],{"class":214},[200,90922,29947],{"class":245},[200,90924,992],{"class":214},[200,90926,90927,90929,90931,90933,90935,90937],{"class":202,"line":255},[200,90928,973],{"class":206},[200,90930,29956],{"class":210},[200,90932,1472],{"class":206},[200,90934,242],{"class":214},[200,90936,29963],{"class":245},[200,90938,992],{"class":214},[200,90940,90941],{"class":202,"line":272},[200,90942,529],{"emptyLinePlaceholder":20},[200,90944,90945,90947,90949,90951,90953],{"class":202,"line":283},[200,90946,29974],{"class":210},[200,90948,215],{"class":214},[200,90950,29979],{"class":210},[200,90952,449],{"class":214},[200,90954,280],{"class":214},[200,90956,90957,90959,90961,90963,90965,90967,90969],{"class":202,"line":300},[200,90958,29988],{"class":235},[200,90960,227],{"class":214},[200,90962,29993],{"class":386},[200,90964,550],{"class":214},[200,90966,21476],{"class":386},[200,90968,2089],{"class":214},[200,90970,280],{"class":214},[200,90972,90973,90975,90977,90979,90981],{"class":202,"line":310},[200,90974,30006],{"class":206},[200,90976,1095],{"class":235},[200,90978,660],{"class":210},[200,90980,5267],{"class":235},[200,90982,230],{"class":214},[200,90984,90985,90987,90989,90991,90993],{"class":202,"line":336},[200,90986,30019],{"class":206},[200,90988,242],{"class":214},[200,90990,26988],{"class":245},[200,90992,249],{"class":214},[200,90994,30028],{"class":214},[200,90996,90997,90999,91001,91003],{"class":202,"line":342},[200,90998,13403],{"class":206},[200,91000,12997],{"class":214},[200,91002,30037],{"class":223},[200,91004,466],{"class":235},[200,91006,91007,91009],{"class":202,"line":365},[200,91008,30044],{"class":206},[200,91010,30028],{"class":214},[200,91012,91013,91015,91017,91019],{"class":202,"line":371},[200,91014,13403],{"class":206},[200,91016,12997],{"class":214},[200,91018,30055],{"class":223},[200,91020,466],{"class":235},[200,91022,91023],{"class":202,"line":398},[200,91024,4962],{"class":214},[200,91026,91027],{"class":202,"line":405},[200,91028,368],{"class":214},[200,91030,91031],{"class":202,"line":410},[200,91032,1130],{"class":214},[200,91034,91035],{"class":202,"line":861},[200,91036,529],{"emptyLinePlaceholder":20},[200,91038,91039],{"class":202,"line":890},[200,91040,30078],{"class":401},[35,91042,30081],{},[939,91044,91045],{},[35,91046,91047,30088,91049,30094],{},[200,91048,945],{},[39,91050,30093],{"href":30091,"rel":91051},[77],[939,91053,91054],{},[35,91055,91056,30101,91058,30105],{},[200,91057,945],{},[156,91059,30104],{},[51,91061,30109],{"id":30108},[35,91063,30112],{},[35,91065,30115],{},[192,91067,91068],{"className":4277,"code":30118,"language":4279,"meta":13,"style":13},[156,91069,91070],{"__ignoreMap":13},[200,91071,91072],{"class":202,"line":203},[200,91073,30118],{},[35,91075,30127,91076,30131],{},[156,91077,30130],{},[192,91079,91080],{"className":25029,"code":30134,"language":25031,"meta":13,"style":13},[156,91081,91082,91100,91110,91120,91124,91138,91150,91154,91158],{"__ignoreMap":13},[200,91083,91084,91086,91088,91090,91092,91094,91096,91098],{"class":202,"line":203},[200,91085,1380],{"class":206},[200,91087,1383],{"class":206},[200,91089,4987],{"class":379},[200,91091,30147],{"class":223},[200,91093,227],{"class":214},[200,91095,30152],{"class":386},[200,91097,2089],{"class":214},[200,91099,280],{"class":214},[200,91101,91102,91104,91106,91108],{"class":202,"line":14},[200,91103,3743],{"class":206},[200,91105,12997],{"class":214},[200,91107,29543],{"class":223},[200,91109,3115],{"class":235},[200,91111,91112,91114,91116,91118],{"class":202,"line":255},[200,91113,1405],{"class":214},[200,91115,30173],{"class":245},[200,91117,249],{"class":214},[200,91119,252],{"class":214},[200,91121,91122],{"class":202,"line":272},[200,91123,19638],{"class":214},[200,91125,91126,91128,91130,91132,91134,91136],{"class":202,"line":283},[200,91127,30186],{"class":235},[200,91129,239],{"class":214},[200,91131,242],{"class":214},[200,91133,15093],{"class":245},[200,91135,249],{"class":214},[200,91137,252],{"class":214},[200,91139,91140,91142,91144,91146,91148],{"class":202,"line":300},[200,91141,2053],{"class":235},[200,91143,239],{"class":214},[200,91145,9828],{"class":210},[200,91147,6524],{"class":214},[200,91149,30209],{"class":210},[200,91151,91152],{"class":202,"line":310},[200,91153,4962],{"class":214},[200,91155,91156],{"class":202,"line":336},[200,91157,30218],{"class":235},[200,91159,91160],{"class":202,"line":342},[200,91161,1130],{"class":214},[35,91163,30225,91164,30228,91166,30231,91168,30234],{},[156,91165,30130],{},[156,91167,29857],{},[156,91169,29857],{},[35,91171,30237],{},[192,91173,91174],{"className":4277,"code":30240,"language":4279,"meta":13,"style":13},[156,91175,91176],{"__ignoreMap":13},[200,91177,91178],{"class":202,"line":203},[200,91179,30240],{},[35,91181,419,91182,30251],{},[156,91183,29825],{},[35,91185,30254],{},[192,91187,91188],{"className":4277,"code":30257,"language":4279,"meta":13,"style":13},[156,91189,91190],{"__ignoreMap":13},[200,91191,91192],{"class":202,"line":203},[200,91193,30257],{},[35,91195,30266],{},[35,91197,30269],{},[35,91199,30272,91200,30275],{},[156,91201,1460],{},[192,91203,91204],{"className":1370,"code":30278,"language":1373,"meta":13,"style":13},[156,91205,91206,91224,91228,91240,91248,91258,91264,91278,91294,91320,91324,91328,91332,91356,91368,91378,91388,91392,91396,91400,91404,91412,91418],{"__ignoreMap":13},[200,91207,91208,91210,91212,91214,91216,91218,91220,91222],{"class":202,"line":203},[200,91209,973],{"class":206},[200,91211,318],{"class":214},[200,91213,1488],{"class":210},[200,91215,981],{"class":214},[200,91217,984],{"class":206},[200,91219,242],{"class":214},[200,91221,1497],{"class":245},[200,91223,992],{"class":214},[200,91225,91226],{"class":202,"line":14},[200,91227,529],{"emptyLinePlaceholder":20},[200,91229,91230,91232,91234,91236,91238],{"class":202,"line":255},[200,91231,1380],{"class":206},[200,91233,1383],{"class":206},[200,91235,1488],{"class":223},[200,91237,227],{"class":210},[200,91239,230],{"class":214},[200,91241,91242,91244,91246],{"class":202,"line":272},[200,91243,1520],{"class":235},[200,91245,239],{"class":214},[200,91247,1400],{"class":210},[200,91249,91250,91252,91254,91256],{"class":202,"line":283},[200,91251,11704],{"class":210},[200,91253,1532],{"class":214},[200,91255,393],{"class":379},[200,91257,280],{"class":214},[200,91259,91260,91262],{"class":202,"line":300},[200,91261,11715],{"class":206},[200,91263,280],{"class":214},[200,91265,91266,91268,91270,91272,91274,91276],{"class":202,"line":310},[200,91267,11722],{"class":235},[200,91269,239],{"class":214},[200,91271,242],{"class":214},[200,91273,30349],{"class":245},[200,91275,249],{"class":214},[200,91277,252],{"class":214},[200,91279,91280,91282,91284,91286,91288,91290,91292],{"class":202,"line":336},[200,91281,30358],{"class":235},[200,91283,227],{"class":214},[200,91285,156],{"class":386},[200,91287,550],{"class":214},[200,91289,3224],{"class":386},[200,91291,2089],{"class":214},[200,91293,280],{"class":214},[200,91295,91296,91298,91300,91302,91304,91306,91308,91310,91312,91314,91316,91318],{"class":202,"line":342},[200,91297,30375],{"class":206},[200,91299,1095],{"class":235},[200,91301,4726],{"class":214},[200,91303,30382],{"class":210},[200,91305,215],{"class":214},[200,91307,30387],{"class":210},[200,91309,13388],{"class":214},[200,91311,242],{"class":214},[200,91313,30394],{"class":245},[200,91315,249],{"class":214},[200,91317,5267],{"class":235},[200,91319,230],{"class":214},[200,91321,91322],{"class":202,"line":365},[200,91323,30405],{"class":206},[200,91325,91326],{"class":202,"line":371},[200,91327,30410],{"class":214},[200,91329,91330],{"class":202,"line":398},[200,91331,529],{"emptyLinePlaceholder":20},[200,91333,91334,91336,91338,91340,91342,91344,91346,91348,91350,91352,91354],{"class":202,"line":405},[200,91335,30375],{"class":206},[200,91337,1095],{"class":235},[200,91339,3280],{"class":210},[200,91341,215],{"class":214},[200,91343,3841],{"class":223},[200,91345,227],{"class":235},[200,91347,249],{"class":214},[200,91349,29562],{"class":245},[200,91351,249],{"class":214},[200,91353,6466],{"class":235},[200,91355,230],{"class":214},[200,91357,91358,91360,91362,91364,91366],{"class":202,"line":410},[200,91359,30443],{"class":206},[200,91361,4164],{"class":210},[200,91363,215],{"class":214},[200,91365,6136],{"class":223},[200,91367,3115],{"class":235},[200,91369,91370,91372,91374,91376],{"class":202,"line":861},[200,91371,30456],{"class":214},[200,91373,30459],{"class":245},[200,91375,249],{"class":214},[200,91377,252],{"class":214},[200,91379,91380,91382,91384,91386],{"class":202,"line":890},[200,91381,30456],{"class":214},[200,91383,30470],{"class":245},[200,91385,249],{"class":214},[200,91387,252],{"class":214},[200,91389,91390],{"class":202,"line":900},[200,91391,30479],{"class":235},[200,91393,91394],{"class":202,"line":905},[200,91395,30410],{"class":214},[200,91397,91398],{"class":202,"line":931},[200,91399,4950],{"class":214},[200,91401,91402],{"class":202,"line":3640},[200,91403,4478],{"class":214},[200,91405,91406,91408,91410],{"class":202,"line":3645},[200,91407,2096],{"class":214},[200,91409,30498],{"class":210},[200,91411,252],{"class":214},[200,91413,91414,91416],{"class":202,"line":4020},[200,91415,1417],{"class":210},[200,91417,252],{"class":214},[200,91419,91420,91422],{"class":202,"line":4475},[200,91421,413],{"class":214},[200,91423,416],{"class":210},[939,91425,91426],{},[35,91427,91428,30519,91430,30522,91432,30525],{},[200,91429,945],{},[156,91431,30459],{},[156,91433,29825],{},[35,91435,30528],{},[35,91437,30531],{},[35,91439,30534],{},[35,91441,30537],{},[35,91443,30540,91444,30544,91446,30548,91448,30551],{},[156,91445,30543],{},[156,91447,30547],{},[156,91449,26590],{},[192,91451,91452],{"className":19537,"code":30554,"language":19540,"meta":13,"style":13},[156,91453,91454,91458,91462],{"__ignoreMap":13},[200,91455,91456],{"class":202,"line":203},[200,91457,30561],{},[200,91459,91460],{"class":202,"line":14},[200,91461,30566],{},[200,91463,91464],{"class":202,"line":255},[200,91465,1130],{},[35,91467,30573,91468,239],{},[156,91469,30543],{},[192,91471,91472],{"className":19537,"code":30578,"language":19540,"meta":13,"style":13},[156,91473,91474,91478,91482,91486,91490,91494,91498,91502,91506,91510,91514,91518,91524,91528],{"__ignoreMap":13},[200,91475,91476],{"class":202,"line":203},[200,91477,19577],{},[200,91479,91480],{"class":202,"line":14},[200,91481,529],{"emptyLinePlaceholder":20},[200,91483,91484],{"class":202,"line":255},[200,91485,19552],{},[200,91487,91488],{"class":202,"line":272},[200,91489,529],{"emptyLinePlaceholder":20},[200,91491,91492],{"class":202,"line":283},[200,91493,30601],{},[200,91495,91496],{"class":202,"line":300},[200,91497,30606],{},[200,91499,91500],{"class":202,"line":310},[200,91501,30611],{},[200,91503,91504],{"class":202,"line":336},[200,91505,30616],{},[200,91507,91508],{"class":202,"line":342},[200,91509,30621],{},[200,91511,91512],{"class":202,"line":365},[200,91513,30626],{},[200,91515,91516],{"class":202,"line":371},[200,91517,30631],{},[200,91519,91520,91522],{"class":202,"line":398},[200,91521,30636],{},[200,91523,30639],{},[200,91525,91526],{"class":202,"line":405},[200,91527,30644],{},[200,91529,91530],{"class":202,"line":410},[200,91531,30649],{},[939,91533,91534],{},[35,91535,91536,30656],{},[200,91537,945],{},[35,91539,30659],{},[35,91541,91542],{},[1690,91543],{"alt":30664,"src":30665},[35,91545,30668],{},[35,91547,30671],{},[35,91549,91550],{},[1690,91551],{"alt":30676,"src":30677},[51,91553,30681],{"id":30680},[35,91555,30684],{},[35,91557,30687,91558,30690],{},[6850,91559,24355],{},[1171,91561,30693],{},{"title":13,"searchDepth":14,"depth":14,"links":91563},[91564,91565,91566,91567,91568],{"id":29237,"depth":14,"text":29238},{"id":29348,"depth":14,"text":29349},{"id":29686,"depth":14,"text":29687},{"id":30108,"depth":14,"text":30109},{"id":30680,"depth":14,"text":30681},{},{"title":29208,"description":30702},{"loc":30704,"images":91572},[91573,91574,91575,91576],{"loc":29279},{"loc":29367},{"loc":30665},{"loc":30677},{"id":30716,"title":30717,"body":91578,"contentId":8782,"date":31188,"description":31189,"extension":18,"meta":91882,"navigation":20,"path":8951,"rawbody":31191,"robots":23,"schemaOrg":23,"seo":91883,"sitemap":91884,"stem":31202,"__hash__":31203},{"type":7,"value":91579,"toc":91871},[91580,91582,91589,91591,91595,91597,91599,91609,91615,91619,91621,91623,91634,91640,91644,91646,91650,91656,91658,91668,91673,91677,91679,91683,91691,91695,91703,91707,91717,91721,91725,91731,91735,91739,91743,91745,91751,91757,91759,91769,91773,91775,91777,91779,91789,91795,91801,91811,91816,91820,91822,91824,91826,91838,91847,91851,91859,91861,91863,91865,91867],[35,91581,30722],{},[35,91583,30725,91584,30729,91586,30734],{},[6850,91585,30728],{},[39,91587,30733],{"href":8785,"rel":91588},[77],[35,91590,30737],{},[939,91592,91593],{},[35,91594,30742],{},[35,91596,30745],{},[51,91598,30749],{"id":30748},[35,91600,30752,91601,30755,91603,30758,91605,30761,91607,30764],{},[6850,91602,21117],{},[6850,91604,30749],{},[6850,91606,22878],{},[6850,91608,22878],{},[35,91610,30767,91611,13731,91613,30772],{},[6850,91612,17991],{},[6850,91614,17986],{},[35,91616,91617],{},[1690,91618],{"alt":30777,"src":30778},[35,91620,30781],{},[1913,91622,30785],{"id":30784},[35,91624,30788,91625,30792,91627,30798,91630,30802,91632,30806],{},[6850,91626,30791],{},[39,91628,30797],{"href":30795,"rel":91629},[77],[6850,91631,30801],{},[6850,91633,30805],{},[35,91635,30809,91636,30812,91638],{},[6850,91637,30805],{},[3029,91639,215],{"id":30815},[35,91641,30818,91642],{},[3029,91643,30822],{"id":30821},[1913,91645,18569],{"id":30825},[35,91647,30828,91648,30831],{},[6850,91649,18569],{},[35,91651,30834,91652,30837,91654],{},[6850,91653,2918],{},[3029,91655,215],{"id":30840},[35,91657,30843],{},[1576,91659,91660,91664],{},[71,91661,91662],{},[3029,91663],{"id":30850},[71,91665,91666],{},[3029,91667],{"id":30855},[35,91669,30858,91670,215],{},[39,91671,9221],{"href":30861,"rel":91672},[77],[35,91674,30865,91675,30869],{},[6850,91676,30868],{},[51,91678,8945],{"id":8944},[35,91680,30874,91681,30877],{},[6850,91682,7142],{},[35,91684,30880,91685,30884,91687,30887,91689],{},[6850,91686,30883],{},[6850,91688,30791],{},[3029,91690,215],{"id":30890},[35,91692,91693],{},[1690,91694],{"alt":30895,"src":30896},[35,91696,30899,91697,30902,91699,30905,91701],{},[6850,91698,7142],{},[6850,91700,30791],{},[3029,91702,215],{"id":20826},[35,91704,91705],{},[1690,91706],{"alt":30912,"src":30913},[35,91708,30916,91709,30920,91711,30923,91713,30926,91715],{},[6850,91710,30919],{},[6850,91712,2918],{},[6850,91714,17986],{},[3029,91716,215],{"id":30929},[35,91718,91719],{},[1690,91720],{"alt":30934,"src":30935},[35,91722,30938,91723,30942],{},[6850,91724,30941],{},[35,91726,30945,91727,30949,91729,30953],{},[6850,91728,30948],{},[6850,91730,30952],{},[35,91732,91733],{},[1690,91734],{"alt":30958,"src":30959},[35,91736,30962,91737,30966],{},[6850,91738,30965],{},[35,91740,91741],{},[1690,91742],{"alt":30971,"src":30972},[51,91744,30976],{"id":30975},[35,91746,30979,91747,30983,91749],{},[6850,91748,30982],{},[3029,91750,215],{"id":30986},[35,91752,30989,91753,30993,91755,30997],{},[6850,91754,30992],{},[6850,91756,30996],{},[51,91758,31001],{"id":31000},[35,91760,31004,91761,31007,91763,3076,91765,3080,91767,31017],{},[6850,91762,21348],{},[6850,91764,31010],{},[6850,91766,31013],{},[6850,91768,31016],{},[35,91770,31020,91771,31023],{},[6850,91772,19447],{},[35,91774,31026],{},[51,91776,31030],{"id":31029},[35,91778,31033],{},[35,91780,31036,91781,3076,91783,3076,91785,3080,91787,31045],{},[6850,91782,2918],{},[6850,91784,17986],{},[6850,91786,30791],{},[6850,91788,19447],{},[35,91790,31048,91791,31051,91793],{},[6850,91792,2913],{},[3029,91794,31055],{"id":31054},[35,91796,31058,91797],{},[3029,91798,31062,91799,31065],{"id":31061},[6850,91800,17986],{},[35,91802,31068,91803,31072,91805],{},[6850,91804,31071],{},[3029,91806,3076,91807],{"id":31075},[3029,91808,3080,91809],{"id":31078},[3029,91810,215],{"id":31081},[35,91812,31084,91813,31090],{},[39,91814,31089],{"href":31087,"rel":91815},[77],[35,91817,31093,91818,31096],{},[6850,91819,11622],{},[35,91821,31099],{},[51,91823,9336],{"id":9335},[35,91825,31104],{},[35,91827,31107,91828,31110,91830,31114,91832,14177,91834,1638,91836,31124],{},[6850,91829,19447],{},[6850,91831,31113],{},[6850,91833,31117],{},[6850,91835,31120],{},[6850,91837,31123],{},[35,91839,31127,91840,31131,91842,31135,91844,31139],{},[6850,91841,31130],{},[6850,91843,31134],{},[39,91845,8864],{"href":8862,"rel":91846},[77],[35,91848,31142,91849,31145],{},[6850,91850,7881],{},[35,91852,31148,91853,31152,91855,1638,91857,31158],{},[6850,91854,31151],{},[6850,91856,7142],{},[6850,91858,31157],{},[35,91860,31161],{},[35,91862,31164],{},[35,91864,31167],{},[35,91866,31170],{},[35,91868,91869],{},[1690,91870],{"alt":31175,"src":31176},{"title":13,"searchDepth":14,"depth":14,"links":91872},[91873,91877,91878,91879,91880,91881],{"id":30748,"depth":14,"text":30749,"children":91874},[91875,91876],{"id":30784,"depth":255,"text":30785},{"id":30825,"depth":255,"text":18569},{"id":8944,"depth":14,"text":8945},{"id":30975,"depth":14,"text":30976},{"id":31000,"depth":14,"text":31001},{"id":31029,"depth":14,"text":31030},{"id":9335,"depth":14,"text":9336},{},{"title":30717,"description":31189},{"loc":8951,"images":91885},[91886,91887,91888,91889,91890,91891,91892],{"loc":30778},{"loc":30896},{"loc":30913},{"loc":30935},{"loc":30959},{"loc":30972},{"loc":31176},{"id":31205,"title":31206,"body":91894,"contentId":33468,"date":33469,"description":33470,"extension":18,"meta":93789,"navigation":20,"path":33472,"rawbody":33473,"robots":23,"schemaOrg":23,"seo":93790,"sitemap":93791,"stem":33476,"__hash__":33477},{"type":7,"value":91895,"toc":93780},[91896,91902,91906,91908,91912,91932,91942,91952,91958,91960,91962,91974,91985,91987,91991,92035,92039,92045,92063,92071,93778],[35,91897,31211,91898,31214,91900,31217],{},[6850,91899,2918],{},[6850,91901,2918],{},[35,91903,31220,91904,31224],{},[6850,91905,31223],{},[51,91907,31228],{"id":31227},[35,91909,31231,91910,31234],{},[6850,91911,2918],{},[192,91913,91914],{"className":2151,"code":31237,"language":2153,"meta":13,"style":13},[156,91915,91916],{"__ignoreMap":13},[200,91917,91918,91920,91922,91924,91926,91928,91930],{"class":202,"line":203},[200,91919,31244],{"class":1978},[200,91921,31247],{"class":245},[200,91923,31250],{"class":245},[200,91925,31253],{"class":214},[200,91927,31256],{"class":245},[200,91929,29418],{"class":210},[200,91931,648],{"class":214},[939,91933,91934],{},[35,91935,91936,31268,91938,31272,91940,215],{},[200,91937,31267],{},[156,91939,31271],{},[39,91941,31276],{"href":31275},[939,91943,91944],{},[35,91945,91946,31283,91948,31286,91950,215],{},[200,91947,945],{},[6850,91949,2160],{},[6850,91951,2160],{},[35,91953,31291,91954,31295,91956,31299],{},[156,91955,31294],{},[156,91957,31298],{},[51,91959,31303],{"id":31302},[35,91961,31306],{},[192,91963,91964],{"className":2151,"code":31309,"language":2153,"meta":13,"style":13},[156,91965,91966],{"__ignoreMap":13},[200,91967,91968,91970,91972],{"class":202,"line":203},[200,91969,31244],{"class":1978},[200,91971,31318],{"class":245},[200,91973,31321],{"class":245},[35,91975,419,91976,31327,91978,31332,91981,31336,91983,31340],{},[6850,91977,31326],{},[39,91979,24113],{"href":31330,"rel":91980},[77],[156,91982,31335],{},[6850,91984,31339],{},[51,91986,31276],{"id":31343},[35,91988,31346,91989,239],{},[156,91990,31349],{},[192,91992,91993],{"className":1370,"code":31352,"language":1373,"meta":13,"style":13},[156,91994,91995,92007,92015,92025,92029],{"__ignoreMap":13},[200,91996,91997,91999,92001,92003,92005],{"class":202,"line":203},[200,91998,1380],{"class":206},[200,92000,1383],{"class":206},[200,92002,1386],{"class":223},[200,92004,227],{"class":210},[200,92006,230],{"class":214},[200,92008,92009,92011,92013],{"class":202,"line":14},[200,92010,31371],{"class":235},[200,92012,239],{"class":214},[200,92014,280],{"class":214},[200,92016,92017,92019,92021,92023],{"class":202,"line":255},[200,92018,31380],{"class":235},[200,92020,239],{"class":214},[200,92022,11018],{"class":2045},[200,92024,252],{"class":214},[200,92026,92027],{"class":202,"line":272},[200,92028,4490],{"class":214},[200,92030,92031,92033],{"class":202,"line":283},[200,92032,413],{"class":214},[200,92034,416],{"class":210},[35,92036,31399,92037,31403],{},[6850,92038,31402],{},[35,92040,31406,92041,31409,92043,31412],{},[156,92042,26746],{},[156,92044,31298],{},[192,92046,92047],{"className":2151,"code":31415,"language":2153,"meta":13,"style":13},[156,92048,92049],{"__ignoreMap":13},[200,92050,92051,92053,92055,92057,92059,92061],{"class":202,"line":203},[200,92052,31422],{"class":1978},[200,92054,14330],{"class":245},[200,92056,14928],{"class":214},[200,92058,31429],{"class":1978},[200,92060,31432],{"class":245},[200,92062,31435],{"class":245},[939,92064,92065],{},[35,92066,92067,31442,92069,26684],{},[200,92068,31267],{},[156,92070,31271],{},[7905,92072,92073,92075,92077,92103,92112,92167,92171,92195,92199,92261,92267,92275,92277,92281,92283,92291],{"text":31447,"href":31448},[51,92074,31452],{"id":31451},[35,92076,31455],{},[192,92078,92079],{"className":2151,"code":31458,"language":2153,"meta":13,"style":13},[156,92080,92081],{"__ignoreMap":13},[200,92082,92083,92085,92087,92089,92091,92093,92095,92097,92099,92101],{"class":202,"line":203},[200,92084,31244],{"class":1978},[200,92086,31467],{"class":245},[200,92088,31470],{"class":245},[200,92090,2163],{"class":245},[200,92092,31475],{"class":245},[200,92094,14928],{"class":214},[200,92096,31480],{"class":1978},[200,92098,31483],{"class":245},[200,92100,12291],{"class":245},[200,92102,31488],{"class":245},[35,92104,31491,92105,31495,92107,31501,92110,239],{},[156,92106,31494],{},[39,92108,31500],{"href":31498,"rel":92109},[77],[156,92111,1372],{},[192,92113,92114],{"className":9741,"code":31506,"language":1373,"meta":13,"style":13},[156,92115,92116,92128,92136,92144,92153,92157,92161],{"__ignoreMap":13},[200,92117,92118,92120,92122,92124,92126],{"class":202,"line":203},[200,92119,1380],{"class":206},[200,92121,1383],{"class":206},[200,92123,1386],{"class":223},[200,92125,227],{"class":210},[200,92127,230],{"class":214},[200,92129,92130,92132,92134],{"class":202,"line":14},[200,92131,31525],{"class":235},[200,92133,239],{"class":214},[200,92135,280],{"class":214},[200,92137,92138,92140,92142],{"class":202,"line":255},[200,92139,24931],{"class":235},[200,92141,239],{"class":214},[200,92143,280],{"class":214},[200,92145,92147,92149,92151],{"class":92146,"line":272},[202,9871],[200,92148,31543],{"class":235},[200,92150,239],{"class":214},[200,92152,2424],{"class":2423},[200,92154,92155],{"class":202,"line":283},[200,92156,4962],{"class":214},[200,92158,92159],{"class":202,"line":300},[200,92160,4490],{"class":214},[200,92162,92163,92165],{"class":202,"line":310},[200,92164,413],{"class":214},[200,92166,416],{"class":210},[35,92168,31564,92169,31568],{},[156,92170,31567],{},[192,92172,92173],{"className":26986,"code":31571,"language":26988,"meta":13,"style":13},[156,92174,92175,92179,92191],{"__ignoreMap":13},[200,92176,92177],{"class":202,"line":203},[200,92178,230],{"class":214},[200,92180,92181,92183,92185,92187,92189],{"class":202,"line":14},[200,92182,26999],{"class":214},[200,92184,31584],{"class":379},[200,92186,611],{"class":214},[200,92188,239],{"class":214},[200,92190,18889],{"class":214},[200,92192,92193],{"class":202,"line":255},[200,92194,1130],{"class":214},[35,92196,31597,92197,13591],{},[156,92198,31600],{},[192,92200,92201],{"className":26986,"code":31603,"language":26988,"meta":13,"style":13},[156,92202,92203,92207,92219,92237,92253,92257],{"__ignoreMap":13},[200,92204,92205],{"class":202,"line":203},[200,92206,230],{"class":214},[200,92208,92209,92211,92213,92215,92217],{"class":202,"line":14},[200,92210,26999],{"class":214},[200,92212,31616],{"class":379},[200,92214,611],{"class":214},[200,92216,239],{"class":214},[200,92218,280],{"class":214},[200,92220,92221,92223,92225,92227,92229,92231,92233,92235],{"class":202,"line":255},[200,92222,27031],{"class":214},[200,92224,31629],{"class":1978},[200,92226,611],{"class":214},[200,92228,239],{"class":214},[200,92230,27008],{"class":214},[200,92232,31638],{"class":245},[200,92234,611],{"class":214},[200,92236,252],{"class":214},[200,92238,92239,92241,92243,92245,92247,92249,92251],{"class":202,"line":272},[200,92240,27031],{"class":214},[200,92242,31649],{"class":1978},[200,92244,611],{"class":214},[200,92246,239],{"class":214},[200,92248,27008],{"class":214},[200,92250,31658],{"class":245},[200,92252,617],{"class":214},[200,92254,92255],{"class":202,"line":283},[200,92256,4490],{"class":214},[200,92258,92259],{"class":202,"line":300},[200,92260,1130],{"class":214},[35,92262,31671,92263,31675,92265,31679],{},[156,92264,31674],{},[156,92266,31678],{},[35,92268,31682,92269,14177,92271,1638,92273,31690],{},[6850,92270,31685],{},[6850,92272,26232],{},[6850,92274,17986],{},[51,92276,31694],{"id":31693},[35,92278,31697,92279,31700],{},[6850,92280,31402],{},[1913,92282,31704],{"id":31703},[35,92284,31707,92285,31711,92288,31716],{},[39,92286,9190],{"href":14203,"rel":92287},[77],[39,92289,31715],{"href":9188,"rel":92290},[77],[7905,92292,92293,92299,92301,92315,92319,92357,92361,92389,92393,92431,92438,92514,92520,92564,92570,92572,92579,92581,92595,92599,92615,92619,92707,92714,92790,92792,92794,92799,92801,92803,92810,92826,92828,92835,92849,92853,92957,92961,92985,92990,92992,92999,93015,93017,93019,93023,93029,93229,93234,93444,93446,93451,93455,93491,93493,93495,93497,93499,93505,93563,93565,93573,93609,93617,93623,93633,93675,93687,93691,93696,93702,93720,93724,93729],{"id":31061},[939,92294,92295],{},[35,92296,92297,31725],{},[200,92298,945],{},[35,92300,31728],{},[192,92302,92303],{"className":2151,"code":31731,"language":2153,"meta":13,"style":13},[156,92304,92305],{"__ignoreMap":13},[200,92306,92307,92309,92311,92313],{"class":202,"line":203},[200,92308,2160],{"class":1978},[200,92310,2163],{"class":245},[200,92312,31742],{"class":245},[200,92314,31745],{"class":245},[35,92316,31748,92317,13591],{},[156,92318,1372],{},[192,92320,92321],{"className":1370,"code":31753,"language":1373,"meta":13,"style":13},[156,92322,92323,92335,92351],{"__ignoreMap":13},[200,92324,92325,92327,92329,92331,92333],{"class":202,"line":203},[200,92326,1380],{"class":206},[200,92328,1383],{"class":206},[200,92330,1386],{"class":223},[200,92332,227],{"class":210},[200,92334,230],{"class":214},[200,92336,92337,92339,92341,92343,92345,92347,92349],{"class":202,"line":14},[200,92338,1395],{"class":235},[200,92340,239],{"class":214},[200,92342,350],{"class":210},[200,92344,249],{"class":214},[200,92346,31780],{"class":245},[200,92348,249],{"class":214},[200,92350,4496],{"class":210},[200,92352,92353,92355],{"class":202,"line":255},[200,92354,413],{"class":214},[200,92356,416],{"class":210},[35,92358,31793,92359,13591],{},[156,92360,31796],{},[192,92362,92363],{"className":31799,"code":31800,"language":31801,"meta":13,"style":13},[156,92364,92365,92377],{"__ignoreMap":13},[200,92366,92367,92369,92371,92373,92375],{"class":202,"line":203},[200,92368,31808],{"class":206},[200,92370,27008],{"class":214},[200,92372,31813],{"class":245},[200,92374,611],{"class":214},[200,92376,5932],{"class":214},[200,92378,92379,92381,92383,92385,92387],{"class":202,"line":14},[200,92380,31808],{"class":206},[200,92382,27008],{"class":214},[200,92384,31780],{"class":245},[200,92386,611],{"class":214},[200,92388,5932],{"class":214},[35,92390,31832,92391,13591],{},[156,92392,1372],{},[192,92394,92395],{"className":1370,"code":31837,"language":1373,"meta":13,"style":13},[156,92396,92397,92409,92425],{"__ignoreMap":13},[200,92398,92399,92401,92403,92405,92407],{"class":202,"line":203},[200,92400,1380],{"class":206},[200,92402,1383],{"class":206},[200,92404,1386],{"class":223},[200,92406,227],{"class":210},[200,92408,230],{"class":214},[200,92410,92411,92413,92415,92417,92419,92421,92423],{"class":202,"line":14},[200,92412,31856],{"class":235},[200,92414,239],{"class":214},[200,92416,350],{"class":210},[200,92418,249],{"class":214},[200,92420,31865],{"class":245},[200,92422,249],{"class":214},[200,92424,4496],{"class":210},[200,92426,92427,92429],{"class":202,"line":255},[200,92428,413],{"class":214},[200,92430,416],{"class":210},[35,92432,31878,92433,31881,92435,239],{},[156,92434,31567],{},[39,92436,31886],{"href":31884,"rel":92437},[77],[192,92439,92440],{"className":26986,"code":31889,"language":26988,"meta":13,"style":13},[156,92441,92442,92446,92458,92474,92478,92490,92506,92510],{"__ignoreMap":13},[200,92443,92444],{"class":202,"line":203},[200,92445,230],{"class":214},[200,92447,92448,92450,92452,92454,92456],{"class":202,"line":14},[200,92449,26999],{"class":214},[200,92451,31902],{"class":379},[200,92453,611],{"class":214},[200,92455,239],{"class":214},[200,92457,280],{"class":214},[200,92459,92460,92462,92464,92466,92468,92470,92472],{"class":202,"line":255},[200,92461,27031],{"class":214},[200,92463,31915],{"class":1978},[200,92465,611],{"class":214},[200,92467,239],{"class":214},[200,92469,27008],{"class":214},[200,92471,31813],{"class":245},[200,92473,617],{"class":214},[200,92475,92476],{"class":202,"line":272},[200,92477,368],{"class":214},[200,92479,92480,92482,92484,92486,92488],{"class":202,"line":283},[200,92481,26999],{"class":214},[200,92483,31936],{"class":379},[200,92485,611],{"class":214},[200,92487,239],{"class":214},[200,92489,280],{"class":214},[200,92491,92492,92494,92496,92498,92500,92502,92504],{"class":202,"line":300},[200,92493,27031],{"class":214},[200,92495,31949],{"class":1978},[200,92497,611],{"class":214},[200,92499,239],{"class":214},[200,92501,27008],{"class":214},[200,92503,25370],{"class":245},[200,92505,617],{"class":214},[200,92507,92508],{"class":202,"line":310},[200,92509,4490],{"class":214},[200,92511,92512],{"class":202,"line":336},[200,92513,1130],{"class":214},[35,92515,31970,92516,31974,92518,31978],{},[156,92517,31973],{},[156,92519,31977],{},[192,92521,92522],{"className":21417,"code":31981,"language":12060,"meta":13,"style":13},[156,92523,92524,92532,92540,92548,92556],{"__ignoreMap":13},[200,92525,92526,92528,92530],{"class":202,"line":203},[200,92527,600],{"class":214},[200,92529,21428],{"class":235},[200,92531,648],{"class":214},[200,92533,92534,92536,92538],{"class":202,"line":14},[200,92535,657],{"class":214},[200,92537,31998],{"class":235},[200,92539,648],{"class":214},[200,92541,92542,92544,92546],{"class":202,"line":255},[200,92543,804],{"class":214},[200,92545,32007],{"class":235},[200,92547,21487],{"class":214},[200,92549,92550,92552,92554],{"class":202,"line":272},[200,92551,893],{"class":214},[200,92553,31998],{"class":235},[200,92555,648],{"class":214},[200,92557,92558,92560,92562],{"class":202,"line":283},[200,92559,681],{"class":214},[200,92561,21428],{"class":235},[200,92563,648],{"class":214},[939,92565,92566],{},[35,92567,92568,32032],{},[200,92569,945],{},[1913,92571,32036],{"id":32035},[35,92573,32039,92574,32044,92577,32047],{},[39,92575,32036],{"href":32042,"rel":92576},[77],[6850,92578,24355],{},[35,92580,32050],{},[192,92582,92583],{"className":2151,"code":32053,"language":2153,"meta":13,"style":13},[156,92584,92585],{"__ignoreMap":13},[200,92586,92587,92589,92591,92593],{"class":202,"line":203},[200,92588,2160],{"class":1978},[200,92590,2163],{"class":245},[200,92592,32064],{"class":245},[200,92594,32067],{"class":245},[35,92596,32070,92597,32073],{},[156,92598,31796],{},[192,92600,92601],{"className":31799,"code":32076,"language":31801,"meta":13,"style":13},[156,92602,92603],{"__ignoreMap":13},[200,92604,92605,92607,92609,92611,92613],{"class":202,"line":203},[200,92606,31808],{"class":206},[200,92608,27008],{"class":214},[200,92610,31813],{"class":245},[200,92612,611],{"class":214},[200,92614,5932],{"class":214},[35,92616,31878,92617,32095],{},[156,92618,1372],{},[192,92620,92621],{"className":1370,"code":32098,"language":1373,"meta":13,"style":13},[156,92622,92623,92637,92641,92653,92671,92679,92687,92693,92697,92701],{"__ignoreMap":13},[200,92624,92625,92627,92629,92631,92633,92635],{"class":202,"line":203},[200,92626,973],{"class":206},[200,92628,32107],{"class":210},[200,92630,1472],{"class":206},[200,92632,242],{"class":214},[200,92634,32114],{"class":245},[200,92636,992],{"class":214},[200,92638,92639],{"class":202,"line":14},[200,92640,529],{"emptyLinePlaceholder":20},[200,92642,92643,92645,92647,92649,92651],{"class":202,"line":255},[200,92644,1380],{"class":206},[200,92646,1383],{"class":206},[200,92648,1386],{"class":223},[200,92650,227],{"class":210},[200,92652,230],{"class":214},[200,92654,92655,92657,92659,92661,92663,92665,92667,92669],{"class":202,"line":272},[200,92656,31856],{"class":235},[200,92658,239],{"class":214},[200,92660,350],{"class":210},[200,92662,249],{"class":214},[200,92664,31865],{"class":245},[200,92666,249],{"class":214},[200,92668,360],{"class":210},[200,92670,252],{"class":214},[200,92672,92673,92675,92677],{"class":202,"line":283},[200,92674,2590],{"class":235},[200,92676,239],{"class":214},[200,92678,280],{"class":214},[200,92680,92681,92683,92685],{"class":202,"line":300},[200,92682,2599],{"class":235},[200,92684,239],{"class":214},[200,92686,1400],{"class":210},[200,92688,92689,92691],{"class":202,"line":310},[200,92690,32171],{"class":223},[200,92692,466],{"class":210},[200,92694,92695],{"class":202,"line":336},[200,92696,4484],{"class":210},[200,92698,92699],{"class":202,"line":342},[200,92700,4490],{"class":214},[200,92702,92703,92705],{"class":202,"line":365},[200,92704,413],{"class":214},[200,92706,416],{"class":210},[35,92708,32190,92709,32193,92711,239],{},[156,92710,31567],{},[39,92712,31886],{"href":31884,"rel":92713},[77],[192,92715,92716],{"className":26986,"code":31889,"language":26988,"meta":13,"style":13},[156,92717,92718,92722,92734,92750,92754,92766,92782,92786],{"__ignoreMap":13},[200,92719,92720],{"class":202,"line":203},[200,92721,230],{"class":214},[200,92723,92724,92726,92728,92730,92732],{"class":202,"line":14},[200,92725,26999],{"class":214},[200,92727,31902],{"class":379},[200,92729,611],{"class":214},[200,92731,239],{"class":214},[200,92733,280],{"class":214},[200,92735,92736,92738,92740,92742,92744,92746,92748],{"class":202,"line":255},[200,92737,27031],{"class":214},[200,92739,31915],{"class":1978},[200,92741,611],{"class":214},[200,92743,239],{"class":214},[200,92745,27008],{"class":214},[200,92747,31813],{"class":245},[200,92749,617],{"class":214},[200,92751,92752],{"class":202,"line":272},[200,92753,368],{"class":214},[200,92755,92756,92758,92760,92762,92764],{"class":202,"line":283},[200,92757,26999],{"class":214},[200,92759,31936],{"class":379},[200,92761,611],{"class":214},[200,92763,239],{"class":214},[200,92765,280],{"class":214},[200,92767,92768,92770,92772,92774,92776,92778,92780],{"class":202,"line":300},[200,92769,27031],{"class":214},[200,92771,31949],{"class":1978},[200,92773,611],{"class":214},[200,92775,239],{"class":214},[200,92777,27008],{"class":214},[200,92779,25370],{"class":245},[200,92781,617],{"class":214},[200,92783,92784],{"class":202,"line":310},[200,92785,4490],{"class":214},[200,92787,92788],{"class":202,"line":336},[200,92789,1130],{"class":214},[35,92791,32275],{},[51,92793,32279],{"id":32278},[35,92795,32282,92796,32288],{},[39,92797,32287],{"href":32285,"rel":92798},[77],[35,92800,32291],{},[35,92802,32294],{},[68,92804,92805],{},[71,92806,12142,92807,32304],{},[39,92808,32303],{"href":32301,"rel":92809},[77],[192,92811,92812],{"className":2151,"code":32307,"language":2153,"meta":13,"style":13},[156,92813,92814],{"__ignoreMap":13},[200,92815,92816,92818,92820,92822,92824],{"class":202,"line":203},[200,92817,31244],{"class":1978},[200,92819,31467],{"class":245},[200,92821,31470],{"class":245},[200,92823,2163],{"class":245},[200,92825,32322],{"class":245},[35,92827,32325],{},[68,92829,92830],{"start":14},[71,92831,32330,92832,32336],{},[39,92833,32335],{"href":32333,"rel":92834},[77],[192,92836,92837],{"className":2151,"code":32339,"language":2153,"meta":13,"style":13},[156,92838,92839],{"__ignoreMap":13},[200,92840,92841,92843,92845,92847],{"class":202,"line":203},[200,92842,31244],{"class":1978},[200,92844,31483],{"class":245},[200,92846,12291],{"class":245},[200,92848,32352],{"class":245},[35,92850,32355,92851,32359],{},[156,92852,32358],{},[192,92854,92855],{"className":25029,"code":32362,"language":25031,"meta":13,"style":13},[156,92856,92857,92879,92883,92899,92907,92915,92923,92939,92947,92953],{"__ignoreMap":13},[200,92858,92859,92861,92863,92865,92867,92869,92871,92873,92875,92877],{"class":202,"line":203},[200,92860,973],{"class":206},[200,92862,318],{"class":214},[200,92864,32373],{"class":210},[200,92866,550],{"class":214},[200,92868,32378],{"class":210},[200,92870,981],{"class":214},[200,92872,984],{"class":206},[200,92874,242],{"class":214},[200,92876,32387],{"class":245},[200,92878,992],{"class":214},[200,92880,92881],{"class":202,"line":14},[200,92882,529],{"emptyLinePlaceholder":20},[200,92884,92885,92887,92889,92891,92893,92895,92897],{"class":202,"line":255},[200,92886,32398],{"class":401},[200,92888,32401],{"class":206},[200,92890,3771],{"class":32404},[200,92892,318],{"class":206},[200,92894,32410],{"class":32409},[200,92896,413],{"class":206},[200,92898,26093],{"class":401},[200,92900,92901,92903,92905],{"class":202,"line":272},[200,92902,32419],{"class":214},[200,92904,510],{"class":214},[200,92906,280],{"class":214},[200,92908,92909,92911,92913],{"class":202,"line":283},[200,92910,1520],{"class":235},[200,92912,239],{"class":214},[200,92914,1400],{"class":210},[200,92916,92917,92919,92921],{"class":202,"line":300},[200,92918,32436],{"class":223},[200,92920,227],{"class":210},[200,92922,230],{"class":214},[200,92924,92925,92927,92929,92931,92933,92935,92937],{"class":202,"line":310},[200,92926,32445],{"class":235},[200,92928,239],{"class":214},[200,92930,32373],{"class":223},[200,92932,25183],{"class":210},[200,92934,32454],{"class":401},[200,92936,32457],{"class":210},[200,92938,252],{"class":214},[200,92940,92941,92943,92945],{"class":202,"line":336},[200,92942,2096],{"class":214},[200,92944,2089],{"class":210},[200,92946,252],{"class":214},[200,92948,92949,92951],{"class":202,"line":342},[200,92950,1417],{"class":210},[200,92952,252],{"class":214},[200,92954,92955],{"class":202,"line":365},[200,92956,1130],{"class":214},[35,92958,32480,92959,13591],{},[156,92960,31796],{},[192,92962,92963],{"className":31799,"code":32485,"language":31801,"meta":13,"style":13},[156,92964,92965,92977],{"__ignoreMap":13},[200,92966,92967,92969,92971,92973,92975],{"class":202,"line":203},[200,92968,31808],{"class":206},[200,92970,27008],{"class":214},[200,92972,31813],{"class":245},[200,92974,611],{"class":214},[200,92976,5932],{"class":214},[200,92978,92979,92981,92983],{"class":202,"line":14},[200,92980,32504],{"class":206},[200,92982,32507],{"class":210},[200,92984,5932],{"class":214},[35,92986,32512,92987,215],{},[39,92988,32303],{"href":32301,"rel":92989},[77],[51,92991,32519],{"id":32518},[35,92993,32522,92994,32525,92996,32531],{},[6850,92995,2918],{},[39,92997,32530],{"href":32528,"rel":92998},[77],[192,93000,93001],{"className":2151,"code":32534,"language":2153,"meta":13,"style":13},[156,93002,93003],{"__ignoreMap":13},[200,93004,93005,93007,93009,93011,93013],{"class":202,"line":203},[200,93006,31244],{"class":1978},[200,93008,31467],{"class":245},[200,93010,31470],{"class":245},[200,93012,2163],{"class":245},[200,93014,32549],{"class":245},[35,93016,32552],{},[51,93018,32556],{"id":32555},[35,93020,32559,93021,32562],{},[6850,93022,11622],{},[35,93024,32565,93025,32569,93027,13591],{},[6850,93026,32568],{},[156,93028,32572],{},[192,93030,93031],{"className":32575,"code":32576,"language":32577,"meta":13,"style":13},[156,93032,93033,93041,93045,93051,93057,93063,93069,93075,93081,93087,93091,93097,93103,93111,93119,93123,93129,93141,93151,93163,93169,93177,93185,93189,93199,93207,93211,93221],{"__ignoreMap":13},[200,93034,93035,93037,93039],{"class":202,"line":203},[200,93036,20089],{"class":235},[200,93038,239],{"class":214},[200,93040,32588],{"class":245},[200,93042,93043],{"class":202,"line":14},[200,93044,529],{"emptyLinePlaceholder":20},[200,93046,93047,93049],{"class":202,"line":255},[200,93048,25370],{"class":2423},[200,93050,30028],{"class":214},[200,93052,93053,93055],{"class":202,"line":272},[200,93054,32603],{"class":235},[200,93056,30028],{"class":214},[200,93058,93059,93061],{"class":202,"line":283},[200,93060,32610],{"class":235},[200,93062,30028],{"class":214},[200,93064,93065,93067],{"class":202,"line":300},[200,93066,32617],{"class":214},[200,93068,32620],{"class":245},[200,93070,93071,93073],{"class":202,"line":310},[200,93072,32625],{"class":235},[200,93074,30028],{"class":214},[200,93076,93077,93079],{"class":202,"line":336},[200,93078,32610],{"class":235},[200,93080,30028],{"class":214},[200,93082,93083,93085],{"class":202,"line":342},[200,93084,32617],{"class":214},[200,93086,32620],{"class":245},[200,93088,93089],{"class":202,"line":365},[200,93090,529],{"emptyLinePlaceholder":20},[200,93092,93093,93095],{"class":202,"line":371},[200,93094,32648],{"class":235},[200,93096,30028],{"class":214},[200,93098,93099,93101],{"class":202,"line":398},[200,93100,32655],{"class":235},[200,93102,30028],{"class":214},[200,93104,93105,93107,93109],{"class":202,"line":405},[200,93106,11797],{"class":235},[200,93108,239],{"class":214},[200,93110,32666],{"class":245},[200,93112,93113,93115,93117],{"class":202,"line":410},[200,93114,32671],{"class":235},[200,93116,239],{"class":214},[200,93118,32676],{"class":245},[200,93120,93121],{"class":202,"line":861},[200,93122,529],{"emptyLinePlaceholder":20},[200,93124,93125,93127],{"class":202,"line":890},[200,93126,32685],{"class":235},[200,93128,30028],{"class":214},[200,93130,93131,93133,93135,93137,93139],{"class":202,"line":900},[200,93132,32617],{"class":214},[200,93134,32694],{"class":235},[200,93136,239],{"class":214},[200,93138,32699],{"class":245},[200,93140,32702],{"class":401},[200,93142,93143,93145,93147,93149],{"class":202,"line":905},[200,93144,32617],{"class":214},[200,93146,32709],{"class":235},[200,93148,239],{"class":214},[200,93150,32714],{"class":245},[200,93152,93153,93155,93157,93159,93161],{"class":202,"line":931},[200,93154,32617],{"class":214},[200,93156,32694],{"class":235},[200,93158,239],{"class":214},[200,93160,32725],{"class":245},[200,93162,32702],{"class":401},[200,93164,93165,93167],{"class":202,"line":3640},[200,93166,32732],{"class":235},[200,93168,30028],{"class":214},[200,93170,93171,93173,93175],{"class":202,"line":3645},[200,93172,32739],{"class":235},[200,93174,239],{"class":214},[200,93176,32744],{"class":2045},[200,93178,93179,93181,93183],{"class":202,"line":4020},[200,93180,32749],{"class":235},[200,93182,239],{"class":214},[200,93184,32754],{"class":245},[200,93186,93187],{"class":202,"line":4475},[200,93188,529],{"emptyLinePlaceholder":20},[200,93190,93191,93193,93195,93197],{"class":202,"line":4481},[200,93192,32617],{"class":214},[200,93194,701],{"class":235},[200,93196,239],{"class":214},[200,93198,32769],{"class":245},[200,93200,93201,93203,93205],{"class":202,"line":4487},[200,93202,32774],{"class":235},[200,93204,239],{"class":214},[200,93206,32779],{"class":245},[200,93208,93209],{"class":202,"line":4493},[200,93210,529],{"emptyLinePlaceholder":20},[200,93212,93213,93215,93217,93219],{"class":202,"line":5454},[200,93214,32617],{"class":214},[200,93216,701],{"class":235},[200,93218,239],{"class":214},[200,93220,32794],{"class":245},[200,93222,93223,93225,93227],{"class":202,"line":5459},[200,93224,32774],{"class":235},[200,93226,239],{"class":214},[200,93228,32803],{"class":245},[35,93230,32806,93231,239],{},[39,93232,32811],{"href":32809,"rel":93233},[77],[192,93235,93236],{"className":32575,"code":32814,"language":32577,"meta":13,"style":13},[156,93237,93238,93248,93252,93258,93264,93270,93282,93286,93292,93300,93304,93310,93316,93324,93330,93342,93352,93364,93370,93378,93386,93390,93400,93408,93412,93422,93430,93434],{"__ignoreMap":13},[200,93239,93240,93242,93244,93246],{"class":202,"line":203},[200,93241,20089],{"class":235},[200,93243,239],{"class":214},[200,93245,32825],{"class":245},[200,93247,32828],{"class":401},[200,93249,93250],{"class":202,"line":14},[200,93251,529],{"emptyLinePlaceholder":20},[200,93253,93254,93256],{"class":202,"line":255},[200,93255,25370],{"class":2423},[200,93257,30028],{"class":214},[200,93259,93260,93262],{"class":202,"line":272},[200,93261,32625],{"class":235},[200,93263,30028],{"class":214},[200,93265,93266,93268],{"class":202,"line":283},[200,93267,32603],{"class":235},[200,93269,30028],{"class":214},[200,93271,93272,93274,93276,93278,93280],{"class":202,"line":300},[200,93273,32610],{"class":235},[200,93275,239],{"class":214},[200,93277,350],{"class":214},[200,93279,32861],{"class":245},[200,93281,4496],{"class":214},[200,93283,93284],{"class":202,"line":310},[200,93285,529],{"emptyLinePlaceholder":20},[200,93287,93288,93290],{"class":202,"line":336},[200,93289,32872],{"class":235},[200,93291,30028],{"class":214},[200,93293,93294,93296,93298],{"class":202,"line":342},[200,93295,32879],{"class":235},[200,93297,239],{"class":214},[200,93299,32884],{"class":245},[200,93301,93302],{"class":202,"line":365},[200,93303,529],{"emptyLinePlaceholder":20},[200,93305,93306,93308],{"class":202,"line":371},[200,93307,32648],{"class":235},[200,93309,30028],{"class":214},[200,93311,93312,93314],{"class":202,"line":398},[200,93313,32899],{"class":235},[200,93315,30028],{"class":214},[200,93317,93318,93320,93322],{"class":202,"line":405},[200,93319,32671],{"class":235},[200,93321,239],{"class":214},[200,93323,32676],{"class":245},[200,93325,93326,93328],{"class":202,"line":410},[200,93327,32685],{"class":235},[200,93329,30028],{"class":214},[200,93331,93332,93334,93336,93338,93340],{"class":202,"line":861},[200,93333,32617],{"class":214},[200,93335,32694],{"class":235},[200,93337,239],{"class":214},[200,93339,32926],{"class":245},[200,93341,32702],{"class":401},[200,93343,93344,93346,93348,93350],{"class":202,"line":890},[200,93345,32617],{"class":214},[200,93347,32709],{"class":235},[200,93349,239],{"class":214},[200,93351,32714],{"class":245},[200,93353,93354,93356,93358,93360,93362],{"class":202,"line":900},[200,93355,32617],{"class":214},[200,93357,32694],{"class":235},[200,93359,239],{"class":214},[200,93361,32949],{"class":245},[200,93363,32702],{"class":401},[200,93365,93366,93368],{"class":202,"line":905},[200,93367,32732],{"class":235},[200,93369,30028],{"class":214},[200,93371,93372,93374,93376],{"class":202,"line":931},[200,93373,32739],{"class":235},[200,93375,239],{"class":214},[200,93377,32966],{"class":2045},[200,93379,93380,93382,93384],{"class":202,"line":3640},[200,93381,32749],{"class":235},[200,93383,239],{"class":214},[200,93385,32754],{"class":245},[200,93387,93388],{"class":202,"line":3645},[200,93389,529],{"emptyLinePlaceholder":20},[200,93391,93392,93394,93396,93398],{"class":202,"line":4020},[200,93393,32617],{"class":214},[200,93395,701],{"class":235},[200,93397,239],{"class":214},[200,93399,32769],{"class":245},[200,93401,93402,93404,93406],{"class":202,"line":4475},[200,93403,32774],{"class":235},[200,93405,239],{"class":214},[200,93407,32779],{"class":245},[200,93409,93410],{"class":202,"line":4481},[200,93411,529],{"emptyLinePlaceholder":20},[200,93413,93414,93416,93418,93420],{"class":202,"line":4487},[200,93415,32617],{"class":214},[200,93417,701],{"class":235},[200,93419,239],{"class":214},[200,93421,33011],{"class":245},[200,93423,93424,93426,93428],{"class":202,"line":4493},[200,93425,32774],{"class":235},[200,93427,239],{"class":214},[200,93429,33020],{"class":245},[200,93431,93432],{"class":202,"line":5454},[200,93433,529],{"emptyLinePlaceholder":20},[200,93435,93436,93438,93440,93442],{"class":202,"line":5459},[200,93437,32617],{"class":214},[200,93439,32694],{"class":235},[200,93441,239],{"class":214},[200,93443,33035],{"class":245},[51,93445,33039],{"id":33038},[35,93447,33042,93448,33048],{},[39,93449,33047],{"href":33045,"rel":93450},[77],[35,93452,32355,93453,33054],{},[156,93454,33053],{},[192,93456,93457],{"className":26986,"code":33057,"language":26988,"meta":13,"style":13},[156,93458,93459,93463,93475,93483,93487],{"__ignoreMap":13},[200,93460,93461],{"class":202,"line":203},[200,93462,230],{"class":214},[200,93464,93465,93467,93469,93471,93473],{"class":202,"line":14},[200,93466,26999],{"class":214},[200,93468,33070],{"class":379},[200,93470,611],{"class":214},[200,93472,239],{"class":214},[200,93474,1400],{"class":214},[200,93476,93477,93479,93481],{"class":202,"line":255},[200,93478,27031],{"class":214},[200,93480,33083],{"class":245},[200,93482,617],{"class":214},[200,93484,93485],{"class":202,"line":272},[200,93486,13674],{"class":214},[200,93488,93489],{"class":202,"line":283},[200,93490,1130],{"class":214},[35,93492,33096],{},[51,93494,33100],{"id":33099},[35,93496,33103],{},[1913,93498,33107],{"id":33106},[35,93500,33110,93501,33113,93503,33116],{},[156,93502,12060],{},[156,93504,14892],{},[192,93506,93507],{"className":21417,"code":33119,"language":12060,"meta":13,"style":13},[156,93508,93509,93527,93531,93539,93543,93551,93555],{"__ignoreMap":13},[200,93510,93511,93513,93515,93517,93519,93521,93523,93525],{"class":202,"line":203},[200,93512,600],{"class":214},[200,93514,14892],{"class":235},[200,93516,14590],{"class":379},[200,93518,449],{"class":214},[200,93520,611],{"class":214},[200,93522,1373],{"class":245},[200,93524,611],{"class":214},[200,93526,648],{"class":214},[200,93528,93529],{"class":202,"line":14},[200,93530,33144],{"class":401},[200,93532,93533,93535,93537],{"class":202,"line":255},[200,93534,681],{"class":214},[200,93536,14892],{"class":235},[200,93538,648],{"class":214},[200,93540,93541],{"class":202,"line":272},[200,93542,529],{"emptyLinePlaceholder":20},[200,93544,93545,93547,93549],{"class":202,"line":283},[200,93546,600],{"class":214},[200,93548,21428],{"class":235},[200,93550,648],{"class":214},[200,93552,93553],{"class":202,"line":300},[200,93554,10634],{"class":210},[200,93556,93557,93559,93561],{"class":202,"line":310},[200,93558,681],{"class":214},[200,93560,21428],{"class":235},[200,93562,648],{"class":214},[1913,93564,33180],{"id":33179},[35,93566,33183,93567,33186,93569,33189,93571,33193],{},[6850,93568,26232],{},[156,93570,12060],{},[156,93572,33192],{},[192,93574,93575],{"className":21417,"code":33196,"language":12060,"meta":13,"style":13},[156,93576,93577,93597,93601],{"__ignoreMap":13},[200,93578,93579,93581,93583,93585,93587,93589,93591,93593,93595],{"class":202,"line":203},[200,93580,600],{"class":214},[200,93582,14892],{"class":235},[200,93584,14590],{"class":379},[200,93586,449],{"class":214},[200,93588,611],{"class":214},[200,93590,1373],{"class":245},[200,93592,611],{"class":214},[200,93594,21781],{"class":379},[200,93596,648],{"class":214},[200,93598,93599],{"class":202,"line":14},[200,93600,33144],{"class":401},[200,93602,93603,93605,93607],{"class":202,"line":255},[200,93604,681],{"class":214},[200,93606,14892],{"class":235},[200,93608,648],{"class":214},[939,93610,93611],{},[35,93612,93613,33237,93615,33241],{},[200,93614,945],{},[156,93616,33240],{},[1913,93618,33245,93619,24572,93621],{"id":33244},[156,93620,33248],{},[156,93622,1372],{},[35,93624,33253,93625,24572,93627,33258,93629,33262,93631,239],{},[156,93626,33248],{},[156,93628,1372],{},[156,93630,33261],{},[156,93632,33248],{},[192,93634,93635],{"className":1370,"code":33267,"language":1373,"meta":13,"style":13},[156,93636,93637,93649,93657,93665,93669],{"__ignoreMap":13},[200,93638,93639,93641,93643,93645,93647],{"class":202,"line":203},[200,93640,1380],{"class":206},[200,93642,1383],{"class":206},[200,93644,1386],{"class":223},[200,93646,227],{"class":210},[200,93648,230],{"class":214},[200,93650,93651,93653,93655],{"class":202,"line":14},[200,93652,33286],{"class":235},[200,93654,239],{"class":214},[200,93656,280],{"class":214},[200,93658,93659,93661,93663],{"class":202,"line":255},[200,93660,33295],{"class":235},[200,93662,239],{"class":214},[200,93664,18875],{"class":214},[200,93666,93667],{"class":202,"line":272},[200,93668,4490],{"class":214},[200,93670,93671,93673],{"class":202,"line":283},[200,93672,413],{"class":214},[200,93674,416],{"class":210},[939,93676,93677],{},[35,93678,93679,33316,93681,33320,93683,33323,93685,215],{},[200,93680,31267],{},[156,93682,33319],{},[6850,93684,2918],{},[156,93686,33326],{},[35,93688,32070,93689,33332],{},[156,93690,33331],{},[192,93692,93694],{"className":93693,"code":33337,"language":698},[33336],[156,93695,33337],{"__ignoreMap":13},[35,93697,33342,93698,33345,93700,239],{},[156,93699,33331],{},[156,93701,33348],{},[192,93703,93704],{"className":2151,"code":33351,"language":2153,"meta":13,"style":13},[156,93705,93706],{"__ignoreMap":13},[200,93707,93708,93710,93712,93714,93716,93718],{"class":202,"line":203},[200,93709,33358],{"class":223},[200,93711,27008],{"class":214},[200,93713,33331],{"class":245},[200,93715,611],{"class":214},[200,93717,33367],{"class":214},[200,93719,33370],{"class":245},[35,93721,33373,93722,33377],{},[156,93723,33376],{},[192,93725,93727],{"className":93726,"code":33337,"language":698},[33336],[156,93728,33337],{"__ignoreMap":13},[7905,93730,93731,93733,93737,93743,93754,93764,93766,93770,93776],{"text":33385,"href":33386},[1913,93732,33390],{"id":33389},[35,93734,33393,93735,33396],{},[6850,93736,2918],{},[1913,93738,33400,93739,1638,93741],{"id":33399},[156,93740,33403],{},[156,93742,33406],{},[35,93744,33245,93745,33412,93747,33415,93749,33422],{},[156,93746,33411],{},[156,93748,33406],{},[39,93750,93752],{"href":33418,"rel":93751},[77],[6850,93753,19771],{},[68,93755,93756,93760],{},[71,93757,33427,93758,33430],{},[156,93759,33406],{},[71,93761,33427,93762,33435],{},[156,93763,33403],{},[51,93765,33439],{"id":33438},[35,93767,33442,93768,33445],{},[6850,93769,2918],{},[939,93771,93772],{},[35,93773,93774,33452],{},[200,93775,29700],{},[35,93777,33455],{},[1171,93779,33458],{},{"title":13,"searchDepth":14,"depth":14,"links":93781},[93782,93783,93784,93785,93786],{"id":31227,"depth":14,"text":31228},{"id":31302,"depth":14,"text":31303},{"id":31343,"depth":14,"text":31276},{"id":31451,"depth":14,"text":31452},{"id":31693,"depth":14,"text":31694,"children":93787},[93788],{"id":31703,"depth":255,"text":31704},{},{"title":31206,"description":33470},{"loc":33472},{"id":33479,"title":33480,"body":93793,"contentId":33873,"date":33874,"description":33875,"extension":18,"meta":94055,"navigation":20,"path":33877,"rawbody":33878,"robots":23,"schemaOrg":23,"seo":94056,"sitemap":94057,"stem":33881,"__hash__":33882},{"type":7,"value":93794,"toc":94047},[93795,93807,93813,93819,93821,93831,93833,93835,93837,93839,93847,93879,93883,93885,93897,93903,93909,93911,93913,93931],[35,93796,33485,93797,33489,93799,3076,93801,3080,93803,33498,93805,33501],{},[6850,93798,33488],{},[6850,93800,33492],{},[6850,93802,31123],{},[6850,93804,33497],{},[6850,93806,21348],{},[35,93808,33504,93809,33507,93811,33511],{},[6850,93810,31016],{},[6850,93812,33510],{},[939,93814,93815],{},[35,93816,93817,33519],{},[200,93818,33518],{},[35,93820,33522],{},[68,93822,93823,93827],{},[71,93824,93825,33530],{},[6850,93826,33529],{},[71,93828,93829,33536],{},[6850,93830,33535],{},[35,93832,33539],{},[35,93834,33542],{},[35,93836,33545],{},[51,93838,33549],{"id":33548},[35,93840,33552,93841,3076,93843,3080,93845,33559],{},[6850,93842,31010],{},[6850,93844,31013],{},[6850,93846,31016],{},[1576,93848,93849,93854,93859,93864,93869,93874],{},[71,93850,93851,33569],{},[39,93852,33568],{"href":33566,"rel":93853},[77],[71,93855,93856,33569],{},[39,93857,33576],{"href":33574,"rel":93858},[77],[71,93860,93861,33569],{},[39,93862,33583],{"href":33581,"rel":93863},[77],[71,93865,93866,33591],{},[39,93867,33590],{"href":33588,"rel":93868},[77],[71,93870,93871,33591],{},[39,93872,33598],{"href":33596,"rel":93873},[77],[71,93875,93876,33591],{},[39,93877,33605],{"href":33603,"rel":93878},[77],[35,93880,33608,93881,33612],{},[6850,93882,33611],{},[51,93884,33616],{"id":33615},[35,93886,33619,93887,3076,93889,33624,93891,33627,93893,33631,93895,33635],{},[6850,93888,17991],{},[6850,93890,17986],{},[6850,93892,22878],{},[6850,93894,33630],{},[6850,93896,33634],{},[35,93898,33638,93899,1638,93901,215],{},[6850,93900,33641],{},[6850,93902,19447],{},[939,93904,93905],{},[35,93906,93907,33650],{},[200,93908,945],{},[35,93910,33653],{},[35,93912,33656],{},[1576,93914,93915,93922,93926],{},[71,93916,93917,17865,93919,33669],{},[6850,93918,33663],{},[39,93920,33668],{"href":33666,"rel":93921},[77],[71,93923,419,93924,33675],{},[6850,93925,33674],{},[71,93927,33678,93928,33684],{},[39,93929,33683],{"href":33681,"rel":93930},[77],[7139,93932,93933,93935,93940,93957,93959,93961,93967,93979,93981,93983,93985,93991,93998,94000,94005,94010],{"icon":33687,"text":33688,"to":33689},[51,93934,33693],{"id":33692},[35,93936,33696,93937,33702],{},[39,93938,33701],{"href":33699,"rel":93939},[77],[1576,93941,93942,93947,93952],{},[71,93943,93944,215],{},[39,93945,33711],{"href":33709,"rel":93946},[77],[71,93948,93949,215],{},[39,93950,33718],{"href":33716,"rel":93951},[77],[71,93953,93954,33726],{},[39,93955,33725],{"href":33723,"rel":93956},[77],[51,93958,33730],{"id":33729},[35,93960,33733],{},[35,93962,33736,93963,1638,93965,33741],{},[6850,93964,32036],{},[6850,93966,18624],{},[1576,93968,93969,93974],{},[71,93970,93971,33750],{},[39,93972,32036],{"href":33748,"rel":93973},[77],[71,93975,93976,33757],{},[39,93977,18624],{"href":33755,"rel":93978},[77],[35,93980,33760],{},[51,93982,33764],{"id":33763},[35,93984,33767],{},[35,93986,33770,93987,33774,93989,33778],{},[6850,93988,33773],{},[6850,93990,33777],{},[35,93992,33781,93993,33784,93995,33790],{},[6850,93994,24209],{},[39,93996,33789],{"href":33787,"rel":93997},[77],[51,93999,33794],{"id":33793},[35,94001,33797,94002,33802],{},[39,94003,2160],{"href":33800,"rel":94004},[77],[35,94006,33805,94007,33810],{},[39,94008,2913],{"href":33808,"rel":94009},[77],[7905,94011,94012,94017,94019,94021,94033,94035,94043,94045],{"id":30815},[35,94013,33815,94014,33819],{},[39,94015,2918],{"href":13818,"rel":94016},[77],[35,94018,33822],{},[35,94020,33825],{},[1576,94022,94023,94028],{},[71,94024,94025,33834],{},[39,94026,30791],{"href":33832,"rel":94027},[77],[71,94029,94030,33842],{},[39,94031,33841],{"href":33839,"rel":94032},[77],[51,94034,33846],{"id":33845},[35,94036,33849,94037,14177,94039,1638,94041,33858],{},[6850,94038,33852],{},[6850,94040,33855],{},[6850,94042,22878],{},[35,94044,33861],{},[35,94046,33864],{},{"title":13,"searchDepth":14,"depth":14,"links":94048},[94049,94050,94051,94052,94053,94054],{"id":33548,"depth":14,"text":33549},{"id":33615,"depth":14,"text":33616},{"id":33692,"depth":14,"text":33693},{"id":33729,"depth":14,"text":33730},{"id":33763,"depth":14,"text":33764},{"id":33793,"depth":14,"text":33794},{},{"title":33480,"description":33875},{"loc":33877},{"id":33884,"title":33885,"body":94059,"contentId":31081,"date":34072,"description":34073,"extension":18,"meta":94173,"navigation":20,"path":34075,"rawbody":34076,"robots":23,"schemaOrg":23,"seo":94174,"sitemap":94175,"stem":34079,"__hash__":34080},{"type":7,"value":94060,"toc":94171},[94061,94067,94073],[35,94062,33890,94063,33894,94065,33898],{},[6850,94064,33893],{},[6850,94066,33897],{},[35,94068,33901,94069,33905,94071,33908],{},[6850,94070,33904],{},[6850,94072,18831],{},[7905,94074,94075],{"id":31075},[7905,94076,94077,94089,94091,94095,94099],{"id":31078},[35,94078,33915,94079,1638,94081,33923,94083,1638,94085,33930,94087,33934],{},[39,94080,33918],{"href":21},[39,94082,33922],{"href":33921},[6850,94084,33926],{},[6850,94086,33929],{},[6850,94088,33933],{},[51,94090,33938],{"id":33937},[35,94092,33941,94093,215],{},[6850,94094,33944],{},[134,94096,132,94097],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,94098],{"src":33949,"type":141},[33951,94100,94101,94107,94111,94113,94115,94117,94125,94131,94139,94143,94147,94149,94155,94157,94159],{},[35,94102,33955,94103,33959,94105,33963],{},[6850,94104,33958],{},[6850,94106,33962],{},[35,94108,33966,94109,33970],{},[6850,94110,33969],{},[35,94112,33973],{},[51,94114,33977],{"id":33976},[35,94116,33980],{},[35,94118,33983,94119,33987,94121,33991,94123,215],{},[6850,94120,33986],{},[6850,94122,33990],{},[6850,94124,33994],{},[35,94126,33997,94127,34001,94129,34005],{},[6850,94128,34000],{},[6850,94130,34004],{},[35,94132,34008,94133,34012,94135,34017,94137,34021],{},[6850,94134,34011],{},[34014,94136,34016],{},[34014,94138,34020],{},[35,94140,34008,94141,34027],{},[6850,94142,34026],{},[134,94144,132,94145],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,94146],{"src":34032,"type":141},[51,94148,34036],{"id":34035},[35,94150,34039,94151,33991,94153,34045],{},[6850,94152,34042],{},[6850,94154,33958],{},[35,94156,34048],{},[1159,94158],{},[35,94160,34053,94161,34056,94163,34060,94166,34066,94169,34069],{},[6850,94162,31071],{},[39,94164,19447],{"href":19443,"rel":94165},[77],[39,94167,34065],{"href":34063,"rel":94168},[77],[6850,94170,33969],{},{"title":13,"searchDepth":14,"depth":14,"links":94172},[],{},{"title":33885,"description":34073},{"loc":34075},{"id":34082,"title":34083,"body":94177,"contentId":31078,"date":34294,"description":34295,"extension":18,"meta":94315,"navigation":20,"path":34297,"rawbody":34298,"robots":23,"schemaOrg":23,"seo":94316,"sitemap":94317,"stem":34301,"__hash__":34302},{"type":7,"value":94178,"toc":94311},[94179,94185],[35,94180,34088,94181,34093,94183,215],{},[39,94182,34092],{"href":34091},[6850,94184,34096],{},[7905,94186,94187,94189,94191,94198,94200,94207,94209],{"id":31075},[51,94188,34102],{"id":34101},[35,94190,34105],{},[35,94192,34108,94193,34112,94196],{},[39,94194,19447],{"href":19443,"rel":94195},[77],[1217,94197,34115],{},[51,94199,34119],{"id":34118},[35,94201,34122,94202,34128,94205,34132],{},[39,94203,34127],{"href":34125,"rel":94204},[77],[1217,94206,34131],{},[35,94208,34135],{},[7905,94210,94211,94213,94215,94217,94221,94225,94259,94263,94265,94291,94293,94301,94303,94307],{"id":21352},[35,94212,34140],{},[51,94214,34144],{"id":34143},[35,94216,34147],{},[134,94218,132,94219],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,94220],{"src":34152,"type":141},[35,94222,34155,94223],{},[1217,94224,34158],{},[1576,94226,94227,94231,94235,94241,94245,94249,94253],{},[71,94228,94229,34166],{},[6850,94230,34165],{},[71,94232,94233,34172],{},[6850,94234,34171],{},[71,94236,94237,34178,94239],{},[6850,94238,34177],{},[1217,94240,34181],{},[71,94242,94243,34187],{},[6850,94244,34186],{},[71,94246,94247,34193],{},[6850,94248,34192],{},[71,94250,94251,34199],{},[6850,94252,34198],{},[71,94254,94255,34205,94257],{},[6850,94256,34204],{},[1217,94258,34208],{},[35,94260,34211,94261],{},[1217,94262,34214],{},[35,94264,34217],{},[1576,94266,94267,94277,94287],{},[71,94268,94269,34225,94271,1638,94274,34236],{},[6850,94270,34224],{},[39,94272,34230],{"href":34228,"rel":94273},[77],[39,94275,34235],{"href":34233,"rel":94276},[77],[71,94278,94279,34242,94281,1638,94284,34253],{},[6850,94280,34241],{},[39,94282,34247],{"href":34245,"rel":94283},[77],[39,94285,34252],{"href":34250,"rel":94286},[77],[71,94288,94289,34259],{},[6850,94290,34258],{},[35,94292,34262],{},[35,94294,34265,94295,106,94297,34272,94299,34276],{},[6850,94296,34268],{},[1217,94298,34271],{},[6850,94300,34275],{},[1159,94302],{},[35,94304,94305,34284],{},[6850,94306,34283],{},[35,94308,94309],{},[1217,94310,34289],{},{"title":13,"searchDepth":14,"depth":14,"links":94312},[94313,94314],{"id":34101,"depth":14,"text":34102},{"id":34118,"depth":14,"text":34119},{},{"title":34083,"description":34295},{"loc":34297},{"id":34304,"title":34305,"body":94319,"contentId":31075,"date":34527,"description":34528,"extension":18,"meta":94465,"navigation":20,"path":34530,"rawbody":34531,"robots":23,"schemaOrg":23,"seo":94466,"sitemap":94467,"stem":34534,"__hash__":34535},{"type":7,"value":94320,"toc":94459},[94321,94323,94325,94332],[35,94322,34310],{},[51,94324,34314],{"id":34313},[35,94326,34317,94327,34321,94330],{},[39,94328,2913],{"href":33808,"rel":94329},[77],[1217,94331,34324],{},[7905,94333,94334,94336,94341,94345,94350,94360,94362,94369,94371,94375,94377,94389,94393,94395,94445,94449,94453,94457],{"id":30815},[51,94335,34330],{"id":34329},[35,94337,34333,94338,34337],{},[39,94339,19447],{"href":19443,"rel":94340},[77],[35,94342,34340,94343],{},[1217,94344,34343],{},[35,94346,34346,94347,34351],{},[39,94348,22878],{"href":34349,"rel":94349},[77],[35,94351,94352],{},[1217,94353,34356,94354,34362,94357,34366],{},[39,94355,34361],{"href":34359,"rel":94356},[77],[39,94358,33683],{"href":33689,"rel":94359},[77],[51,94361,34370],{"id":34369},[35,94363,34373,94364],{},[1217,94365,34376,94366,215],{},[39,94367,34381],{"href":34379,"rel":94368},[77],[35,94370,34384],{},[35,94372,34387,94373],{},[1217,94374,34390],{},[51,94376,34394],{"id":34393},[35,94378,34397,94379,34401,94381,106,94384],{},[6850,94380,34400],{},[39,94382,19447],{"href":19443,"rel":94383},[77],[1217,94385,34407,94386,215],{},[39,94387,2913],{"href":19430,"rel":94388},[77],[134,94390,132,94391],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,94392],{"src":34415,"type":141},[35,94394,34418],{},[1576,94396,94397,94402,94407,94418,94423,94428,94433,94438],{},[71,94398,94399,34428],{},[39,94400,34427],{"href":34425,"rel":94401},[77],[71,94403,94404,34436],{},[39,94405,34435],{"href":34433,"rel":94406},[77],[71,94408,94409,3076,94412,3076,94415,34454],{},[39,94410,34443],{"href":34441,"rel":94411},[77],[39,94413,34448],{"href":34446,"rel":94414},[77],[39,94416,34453],{"href":34451,"rel":94417},[77],[71,94419,94420,34462],{},[39,94421,34461],{"href":34459,"rel":94422},[77],[71,94424,94425,34470],{},[39,94426,34469],{"href":34467,"rel":94427},[77],[71,94429,34473,94430,34479],{},[39,94431,34478],{"href":34476,"rel":94432},[77],[71,94434,94435,34487],{},[39,94436,34486],{"href":34484,"rel":94437},[77],[71,94439,34490,94440],{},[1217,94441,34493,94442,34045],{},[39,94443,34498],{"href":34496,"rel":94444},[77],[35,94446,34501,94447],{},[1217,94448,34504],{},[35,94450,34507,94451],{},[1582,94452,34512],{"repo":34510,"name":34511},[35,94454,94455],{},[1217,94456,34517],{},[35,94458,34520],{},{"title":13,"searchDepth":14,"depth":14,"links":94460},[94461,94462,94463,94464],{"id":34313,"depth":14,"text":34314},{"id":34329,"depth":14,"text":34330},{"id":34369,"depth":14,"text":34370},{"id":34393,"depth":14,"text":34394},{},{"title":34305,"description":34528},{"loc":34530},{"id":34537,"title":34538,"body":94469,"contentId":21352,"date":36541,"description":36542,"extension":18,"meta":96118,"navigation":20,"path":36544,"rawbody":36545,"robots":23,"schemaOrg":23,"seo":96119,"sitemap":96120,"stem":36548,"__hash__":36549},{"type":7,"value":94470,"toc":96107},[94471,94477,94481,94495,96105],[35,94472,34543,94473],{},[1582,94474,34547,94475],{"repo":34546},[1217,94476,34550],{},[35,94478,34553,94479],{},[1217,94480,34556],{},[35,94482,34559,94483,34564,94486,34570,94489,13731,94492,215],{},[39,94484,18624],{"href":34562,"rel":94485},[77],[39,94487,34569],{"href":34567,"rel":94488},[77],[39,94490,34575],{"href":34573,"rel":94491},[77],[39,94493,24355],{"href":34578,"rel":94494},[77],[7905,94496,94497,94507,94509,94511,94631,94633,94721,94725,94727,94732,94736,94790,94796,94926,94928,94934,94938,94943,94959,94963,94970,94979,94981,94991,94993,94995,95005,95007,95015,95019,95023,95045,95049,95159,95165,95167,95177,95191,95197,95359,95363,95365,95369,95419,95423,95428,95430,95436,95438,95440,95456,95458,95466,95482,95484,95486,95492,95564,95582,95590,95594,95728,95732,95737,95745,95747,95751,95753,95755,95760,95764,95786,95790,95806,95808,95818,95826,95830,95836,95914,95916,95932,95938,95940,95942,95946,95953,95958,95972,95976,96038,96046,96054,96056,96068,96070,96072,96074,96088,96094,96099,96103],{"id":31061},[939,94498,94499],{},[35,94500,94501,34588,94503,34592,94505],{},[200,94502,945],{},[6850,94504,34591],{},[1217,94506,34595],{},[35,94508,34598],{},[35,94510,34601],{},[192,94512,94513],{"className":21417,"code":34604,"language":12060,"meta":13,"style":13},[156,94514,94515,94535,94553,94557,94573,94581,94585,94593,94611,94615,94623],{"__ignoreMap":13},[200,94516,94517,94519,94521,94523,94525,94527,94529,94531,94533],{"class":202,"line":203},[200,94518,600],{"class":214},[200,94520,14892],{"class":235},[200,94522,14590],{"class":379},[200,94524,449],{"class":214},[200,94526,611],{"class":214},[200,94528,1373],{"class":245},[200,94530,611],{"class":214},[200,94532,21781],{"class":379},[200,94534,648],{"class":214},[200,94536,94537,94539,94541,94543,94545,94547,94549,94551],{"class":202,"line":14},[200,94538,973],{"class":206},[200,94540,318],{"class":214},[200,94542,34635],{"class":210},[200,94544,981],{"class":214},[200,94546,984],{"class":206},[200,94548,242],{"class":214},[200,94550,34644],{"class":245},[200,94552,992],{"class":214},[200,94554,94555],{"class":202,"line":255},[200,94556,529],{"emptyLinePlaceholder":20},[200,94558,94559,94561,94563,94565,94567,94569,94571],{"class":202,"line":272},[200,94560,443],{"class":379},[200,94562,318],{"class":214},[200,94564,34659],{"class":210},[200,94566,413],{"class":214},[200,94568,510],{"class":214},[200,94570,34635],{"class":223},[200,94572,466],{"class":210},[200,94574,94575,94577,94579],{"class":202,"line":283},[200,94576,681],{"class":214},[200,94578,14892],{"class":235},[200,94580,648],{"class":214},[200,94582,94583],{"class":202,"line":300},[200,94584,529],{"emptyLinePlaceholder":20},[200,94586,94587,94589,94591],{"class":202,"line":310},[200,94588,600],{"class":214},[200,94590,21428],{"class":235},[200,94592,648],{"class":214},[200,94594,94595,94597,94599,94601,94603,94605,94607,94609],{"class":202,"line":336},[200,94596,657],{"class":214},[200,94598,11386],{"class":235},[200,94600,34696],{"class":379},[200,94602,449],{"class":214},[200,94604,611],{"class":214},[200,94606,26808],{"class":245},[200,94608,611],{"class":214},[200,94610,648],{"class":214},[200,94612,94613],{"class":202,"line":342},[200,94614,34711],{"class":210},[200,94616,94617,94619,94621],{"class":202,"line":365},[200,94618,893],{"class":214},[200,94620,11386],{"class":235},[200,94622,648],{"class":214},[200,94624,94625,94627,94629],{"class":202,"line":371},[200,94626,681],{"class":214},[200,94628,21428],{"class":235},[200,94630,648],{"class":214},[35,94632,34730],{},[192,94634,94635],{"className":1370,"code":34733,"language":1373,"meta":13,"style":13},[156,94636,94637,94655,94659,94671,94699,94703,94709,94713,94717],{"__ignoreMap":13},[200,94638,94639,94641,94643,94645,94647,94649,94651,94653],{"class":202,"line":203},[200,94640,973],{"class":206},[200,94642,318],{"class":214},[200,94644,18685],{"class":210},[200,94646,981],{"class":214},[200,94648,984],{"class":206},[200,94650,242],{"class":214},[200,94652,12060],{"class":245},[200,94654,992],{"class":214},[200,94656,94657],{"class":202,"line":14},[200,94658,529],{"emptyLinePlaceholder":20},[200,94660,94661,94663,94665,94667,94669],{"class":202,"line":255},[200,94662,1380],{"class":206},[200,94664,4987],{"class":379},[200,94666,34635],{"class":223},[200,94668,1532],{"class":214},[200,94670,280],{"class":214},[200,94672,94673,94675,94677,94679,94681,94683,94685,94687,94689,94691,94693,94695,94697],{"class":202,"line":272},[200,94674,3723],{"class":379},[200,94676,34776],{"class":210},[200,94678,510],{"class":214},[200,94680,18685],{"class":223},[200,94682,227],{"class":235},[200,94684,2752],{"class":214},[200,94686,701],{"class":235},[200,94688,239],{"class":214},[200,94690,242],{"class":214},[200,94692,34793],{"class":245},[200,94694,249],{"class":214},[200,94696,981],{"class":214},[200,94698,416],{"class":235},[200,94700,94701],{"class":202,"line":283},[200,94702,529],{"emptyLinePlaceholder":20},[200,94704,94705,94707],{"class":202,"line":300},[200,94706,3743],{"class":206},[200,94708,280],{"class":214},[200,94710,94711],{"class":202,"line":310},[200,94712,34814],{"class":210},[200,94714,94715],{"class":202,"line":336},[200,94716,4490],{"class":214},[200,94718,94719],{"class":202,"line":342},[200,94720,1130],{"class":214},[35,94722,34825,94723],{},[1217,94724,34828],{},[35,94726,34831],{},[192,94728,94730],{"className":94729,"code":34835,"language":698},[33336],[156,94731,34835],{"__ignoreMap":13},[35,94733,419,94734,34843],{},[156,94735,34842],{},[192,94737,94738],{"className":1370,"code":34846,"language":1373,"meta":13,"style":13},[156,94739,94740,94754,94758,94768,94772],{"__ignoreMap":13},[200,94741,94742,94744,94746,94748,94750,94752],{"class":202,"line":203},[200,94743,973],{"class":206},[200,94745,34855],{"class":210},[200,94747,1472],{"class":206},[200,94749,242],{"class":214},[200,94751,34862],{"class":245},[200,94753,992],{"class":214},[200,94755,94756],{"class":202,"line":14},[200,94757,529],{"emptyLinePlaceholder":20},[200,94759,94760,94762,94764,94766],{"class":202,"line":255},[200,94761,1380],{"class":206},[200,94763,318],{"class":214},[200,94765,34877],{"class":210},[200,94767,34880],{"class":214},[200,94769,94770],{"class":202,"line":272},[200,94771,529],{"emptyLinePlaceholder":20},[200,94773,94774,94776,94778,94780,94782,94784,94786,94788],{"class":202,"line":283},[200,94775,1380],{"class":206},[200,94777,318],{"class":214},[200,94779,34635],{"class":210},[200,94781,981],{"class":214},[200,94783,984],{"class":206},[200,94785,242],{"class":214},[200,94787,34901],{"class":245},[200,94789,992],{"class":214},[35,94791,34906,94792,34909,94794,239],{},[156,94793,31600],{},[156,94795,34912],{},[192,94797,94798],{"className":26986,"code":34915,"language":26988,"meta":13,"style":13},[156,94799,94800,94804,94822,94834,94846,94864,94880,94884,94888,94906,94922],{"__ignoreMap":13},[200,94801,94802],{"class":202,"line":203},[200,94803,230],{"class":214},[200,94805,94806,94808,94810,94812,94814,94816,94818,94820],{"class":202,"line":14},[200,94807,26999],{"class":214},[200,94809,3771],{"class":379},[200,94811,611],{"class":214},[200,94813,239],{"class":214},[200,94815,27008],{"class":214},[200,94817,15093],{"class":245},[200,94819,611],{"class":214},[200,94821,252],{"class":214},[200,94823,94824,94826,94828,94830,94832],{"class":202,"line":255},[200,94825,26999],{"class":214},[200,94827,34912],{"class":379},[200,94829,611],{"class":214},[200,94831,239],{"class":214},[200,94833,280],{"class":214},[200,94835,94836,94838,94840,94842,94844],{"class":202,"line":272},[200,94837,27031],{"class":214},[200,94839,215],{"class":1978},[200,94841,611],{"class":214},[200,94843,239],{"class":214},[200,94845,280],{"class":214},[200,94847,94848,94850,94852,94854,94856,94858,94860,94862],{"class":202,"line":283},[200,94849,27056],{"class":214},[200,94851,34970],{"class":2045},[200,94853,611],{"class":214},[200,94855,239],{"class":214},[200,94857,27008],{"class":214},[200,94859,34979],{"class":245},[200,94861,611],{"class":214},[200,94863,252],{"class":214},[200,94865,94866,94868,94870,94872,94874,94876,94878],{"class":202,"line":300},[200,94867,27056],{"class":214},[200,94869,973],{"class":2045},[200,94871,611],{"class":214},[200,94873,239],{"class":214},[200,94875,27008],{"class":214},[200,94877,34998],{"class":245},[200,94879,617],{"class":214},[200,94881,94882],{"class":202,"line":310},[200,94883,4962],{"class":214},[200,94885,94886],{"class":202,"line":336},[200,94887,368],{"class":214},[200,94889,94890,94892,94894,94896,94898,94900,94902,94904],{"class":202,"line":342},[200,94891,26999],{"class":214},[200,94893,32861],{"class":379},[200,94895,611],{"class":214},[200,94897,239],{"class":214},[200,94899,27008],{"class":214},[200,94901,35023],{"class":245},[200,94903,611],{"class":214},[200,94905,252],{"class":214},[200,94907,94908,94910,94912,94914,94916,94918,94920],{"class":202,"line":365},[200,94909,26999],{"class":214},[200,94911,34970],{"class":379},[200,94913,611],{"class":214},[200,94915,239],{"class":214},[200,94917,27008],{"class":214},[200,94919,35042],{"class":245},[200,94921,617],{"class":214},[200,94923,94924],{"class":202,"line":371},[200,94925,1130],{"class":214},[51,94927,35052],{"id":35051},[939,94929,94930],{},[35,94931,94932,35059],{},[200,94933,945],{},[35,94935,35062,94936,35065],{},[156,94937,5139],{},[192,94939,94941],{"className":94940,"code":35069,"language":698},[33336],[156,94942,35069],{"__ignoreMap":13},[35,94944,35074,94945,35078,94947,35082,94949],{},[156,94946,35077],{},[156,94948,35081],{},[1217,94950,35085,94951,35089,94953,13731,94956,35100],{},[156,94952,35088],{},[39,94954,35094],{"href":35092,"rel":94955},[77],[39,94957,35099],{"href":35097,"rel":94958},[77],[35,94960,35103,94961,35107],{},[156,94962,35106],{},[35,94964,35110,94965,35116,94968],{},[39,94966,35115],{"href":35113,"rel":94967},[77],[1217,94969,35119],{},[35,94971,35122,94972,34547,94974,35133],{},[6850,94973,34591],{},[1217,94975,35127,94976],{},[39,94977,35132],{"href":35130,"rel":94978},[77],[51,94980,35137],{"id":35136},[35,94982,35140,94983,35143,94985,35149,94988,35154],{},[156,94984,11851],{},[39,94986,35148],{"href":35146,"rel":94987},[77],[39,94989,30791],{"href":35152,"rel":94990},[77],[35,94992,35157],{},[35,94994,35160],{},[192,94996,94997],{"className":1355,"code":35163,"language":1358,"meta":13,"style":13},[156,94998,94999],{"__ignoreMap":13},[200,95000,95001,95003],{"class":202,"line":203},[200,95002,31244],{"class":1978},[200,95004,35172],{"class":245},[35,95006,35175],{},[192,95008,95009],{"className":4277,"code":35178,"language":4279,"meta":13,"style":13},[156,95010,95011],{"__ignoreMap":13},[200,95012,95013],{"class":202,"line":203},[200,95014,35178],{},[35,95016,95017],{},[1217,95018,35189],{},[35,95020,35192,95021,35195],{},[156,95022,34842],{},[192,95024,95025],{"className":1370,"code":35198,"language":1373,"meta":13,"style":13},[156,95026,95027],{"__ignoreMap":13},[200,95028,95029,95031,95033,95035,95037,95039,95041,95043],{"class":202,"line":203},[200,95030,1380],{"class":206},[200,95032,318],{"class":214},[200,95034,34635],{"class":210},[200,95036,981],{"class":214},[200,95038,984],{"class":206},[200,95040,242],{"class":214},[200,95042,34901],{"class":245},[200,95044,992],{"class":214},[35,95046,35221,95047,35225],{},[156,95048,35224],{},[192,95050,95051],{"className":1355,"code":35228,"language":1358,"meta":13,"style":13},[156,95052,95053,95061,95081,95089,95101,95113,95139,95143],{"__ignoreMap":13},[200,95054,95055,95057,95059],{"class":202,"line":203},[200,95056,35235],{"class":1978},[200,95058,31480],{"class":245},[200,95060,35172],{"class":245},[200,95062,95063,95065,95067,95069,95071,95073,95075,95077,95079],{"class":202,"line":14},[200,95064,35244],{"class":1978},[200,95066,35247],{"class":245},[200,95068,35250],{"class":245},[200,95070,35253],{"class":245},[200,95072,35256],{"class":245},[200,95074,35259],{"class":210},[200,95076,9858],{"class":214},[200,95078,35264],{"class":210},[200,95080,4496],{"class":214},[200,95082,95083,95085,95087],{"class":202,"line":255},[200,95084,35244],{"class":1978},[200,95086,35273],{"class":245},[200,95088,35276],{"class":245},[200,95090,95091,95093,95095,95097,95099],{"class":202,"line":272},[200,95092,35244],{"class":1978},[200,95094,35283],{"class":245},[200,95096,35286],{"class":245},[200,95098,35289],{"class":245},[200,95100,35292],{"class":245},[200,95102,95103,95105,95107,95109,95111],{"class":202,"line":283},[200,95104,35297],{"class":1978},[200,95106,35300],{"class":245},[200,95108,35303],{"class":245},[200,95110,663],{"class":245},[200,95112,35276],{"class":245},[200,95114,95115,95117,95119,95121,95123,95125,95127,95129,95131,95133,95135,95137],{"class":202,"line":300},[200,95116,35312],{"class":1978},[200,95118,35315],{"class":210},[200,95120,35318],{"class":245},[200,95122,35321],{"class":2045},[200,95124,35324],{"class":245},[200,95126,35327],{"class":245},[200,95128,35330],{"class":245},[200,95130,35321],{"class":2045},[200,95132,35324],{"class":245},[200,95134,35337],{"class":245},[200,95136,34635],{"class":245},[200,95138,416],{"class":210},[200,95140,95141],{"class":202,"line":310},[200,95142,529],{"emptyLinePlaceholder":20},[200,95144,95145,95147,95149,95151,95153,95155,95157],{"class":202,"line":336},[200,95146,35350],{"class":1978},[200,95148,35353],{"class":245},[200,95150,35286],{"class":245},[200,95152,35358],{"class":245},[200,95154,35361],{"class":210},[200,95156,35364],{"class":245},[200,95158,35367],{"class":210},[35,95160,35370,95161,35373,95163,35376],{},[156,95162,11165],{},[156,95164,5139],{},[51,95166,35380],{"id":35379},[35,95168,35383,95169,35387,95171,35390,95173,35393,95175,26684],{},[156,95170,35386],{},[156,95172,11851],{},[156,95174,5139],{},[156,95176,35396],{},[192,95178,95179],{"className":1355,"code":35399,"language":1358,"meta":13,"style":13},[156,95180,95181],{"__ignoreMap":13},[200,95182,95183,95185,95187,95189],{"class":202,"line":203},[200,95184,35396],{"class":1978},[200,95186,35408],{"class":245},[200,95188,35411],{"class":245},[200,95190,35414],{"class":245},[35,95192,35417,95193,35420,95195,35423],{},[156,95194,34912],{},[156,95196,31600],{},[192,95198,95199],{"className":26986,"code":35426,"language":26988,"meta":13,"style":13},[156,95200,95201,95205,95223,95235,95247,95265,95281,95285,95297,95313,95317,95321,95339,95355],{"__ignoreMap":13},[200,95202,95203],{"class":202,"line":203},[200,95204,230],{"class":214},[200,95206,95207,95209,95211,95213,95215,95217,95219,95221],{"class":202,"line":14},[200,95208,26999],{"class":214},[200,95210,3771],{"class":379},[200,95212,611],{"class":214},[200,95214,239],{"class":214},[200,95216,27008],{"class":214},[200,95218,15093],{"class":245},[200,95220,611],{"class":214},[200,95222,252],{"class":214},[200,95224,95225,95227,95229,95231,95233],{"class":202,"line":255},[200,95226,26999],{"class":214},[200,95228,34912],{"class":379},[200,95230,611],{"class":214},[200,95232,239],{"class":214},[200,95234,280],{"class":214},[200,95236,95237,95239,95241,95243,95245],{"class":202,"line":272},[200,95238,27031],{"class":214},[200,95240,215],{"class":1978},[200,95242,611],{"class":214},[200,95244,239],{"class":214},[200,95246,280],{"class":214},[200,95248,95249,95251,95253,95255,95257,95259,95261,95263],{"class":202,"line":283},[200,95250,27056],{"class":214},[200,95252,34970],{"class":2045},[200,95254,611],{"class":214},[200,95256,239],{"class":214},[200,95258,27008],{"class":214},[200,95260,34979],{"class":245},[200,95262,611],{"class":214},[200,95264,252],{"class":214},[200,95266,95267,95269,95271,95273,95275,95277,95279],{"class":202,"line":300},[200,95268,27056],{"class":214},[200,95270,973],{"class":2045},[200,95272,611],{"class":214},[200,95274,239],{"class":214},[200,95276,27008],{"class":214},[200,95278,34998],{"class":245},[200,95280,617],{"class":214},[200,95282,95283],{"class":202,"line":310},[200,95284,339],{"class":214},[200,95286,95287,95289,95291,95293,95295],{"class":202,"line":336},[200,95288,27031],{"class":214},[200,95290,35519],{"class":1978},[200,95292,611],{"class":214},[200,95294,239],{"class":214},[200,95296,280],{"class":214},[200,95298,95299,95301,95303,95305,95307,95309,95311],{"class":202,"line":342},[200,95300,27056],{"class":214},[200,95302,973],{"class":2045},[200,95304,611],{"class":214},[200,95306,239],{"class":214},[200,95308,27008],{"class":214},[200,95310,35540],{"class":245},[200,95312,617],{"class":214},[200,95314,95315],{"class":202,"line":365},[200,95316,4962],{"class":214},[200,95318,95319],{"class":202,"line":371},[200,95320,368],{"class":214},[200,95322,95323,95325,95327,95329,95331,95333,95335,95337],{"class":202,"line":398},[200,95324,26999],{"class":214},[200,95326,32861],{"class":379},[200,95328,611],{"class":214},[200,95330,239],{"class":214},[200,95332,27008],{"class":214},[200,95334,35023],{"class":245},[200,95336,611],{"class":214},[200,95338,252],{"class":214},[200,95340,95341,95343,95345,95347,95349,95351,95353],{"class":202,"line":405},[200,95342,26999],{"class":214},[200,95344,34970],{"class":379},[200,95346,611],{"class":214},[200,95348,239],{"class":214},[200,95350,27008],{"class":214},[200,95352,35042],{"class":245},[200,95354,617],{"class":214},[200,95356,95357],{"class":202,"line":410},[200,95358,1130],{"class":214},[35,95360,35591,95361,35595],{},[1217,95362,35594],{},[51,95364,35599],{"id":35598},[35,95366,35602,95367,35605],{},[156,95368,35081],{},[192,95370,95371],{"className":21417,"code":35608,"language":12060,"meta":13,"style":13},[156,95372,95373,95393,95411],{"__ignoreMap":13},[200,95374,95375,95377,95379,95381,95383,95385,95387,95389,95391],{"class":202,"line":203},[200,95376,600],{"class":214},[200,95378,14892],{"class":235},[200,95380,14590],{"class":379},[200,95382,449],{"class":214},[200,95384,611],{"class":214},[200,95386,1373],{"class":245},[200,95388,611],{"class":214},[200,95390,21781],{"class":379},[200,95392,648],{"class":214},[200,95394,95395,95397,95399,95401,95403,95405,95407,95409],{"class":202,"line":14},[200,95396,973],{"class":206},[200,95398,318],{"class":214},[200,95400,34635],{"class":210},[200,95402,981],{"class":214},[200,95404,984],{"class":206},[200,95406,242],{"class":214},[200,95408,34644],{"class":245},[200,95410,992],{"class":214},[200,95412,95413,95415,95417],{"class":202,"line":255},[200,95414,681],{"class":214},[200,95416,14892],{"class":235},[200,95418,648],{"class":214},[35,95420,35659,95421,35662],{},[156,95422,5139],{},[192,95424,95426],{"className":95425,"code":35666,"language":698},[33336],[156,95427,35666],{"__ignoreMap":13},[35,95429,35671],{},[35,95431,35674,95432,35678,95434,35682],{},[156,95433,35677],{},[156,95435,35681],{},[51,95437,35686],{"id":35685},[35,95439,35689],{},[68,95441,95442,95449],{},[71,95443,95444,35697,95446,35702],{},[6850,95445,35696],{},[39,95447,24355],{"href":35700,"rel":95448},[77],[71,95450,95451,35708,95453,35714],{},[6850,95452,35707],{},[39,95454,35713],{"href":35711,"rel":95455},[77],[35,95457,35717],{},[35,95459,35720,95460,35723,95462,35726,95464,35730],{},[156,95461,11165],{},[156,95463,5139],{},[1217,95465,35729],{},[939,95467,95468],{},[35,95469,95470,35737,95472,35741,95474,35745,95476,35750,95479,215],{},[200,95471,945],{},[156,95473,35740],{},[156,95475,35744],{},[39,95477,11646],{"href":35748,"rel":95478},[77],[39,95480,35755],{"href":35753,"rel":95481},[77],[51,95483,35759],{"id":35758},[35,95485,35762],{},[35,95487,35765,95488,35769,95490,35772],{},[156,95489,35768],{},[156,95491,31600],{},[192,95493,95494],{"className":1370,"code":35775,"language":1373,"meta":13,"style":13},[156,95495,95496,95514,95518,95530,95548,95558],{"__ignoreMap":13},[200,95497,95498,95500,95502,95504,95506,95508,95510,95512],{"class":202,"line":203},[200,95499,973],{"class":206},[200,95501,318],{"class":214},[200,95503,35786],{"class":210},[200,95505,981],{"class":214},[200,95507,984],{"class":206},[200,95509,242],{"class":214},[200,95511,35148],{"class":245},[200,95513,992],{"class":214},[200,95515,95516],{"class":202,"line":14},[200,95517,529],{"emptyLinePlaceholder":20},[200,95519,95520,95522,95524,95526,95528],{"class":202,"line":255},[200,95521,1380],{"class":206},[200,95523,1383],{"class":206},[200,95525,35786],{"class":223},[200,95527,227],{"class":210},[200,95529,230],{"class":214},[200,95531,95532,95534,95536,95538,95540,95542,95544,95546],{"class":202,"line":272},[200,95533,35817],{"class":235},[200,95535,239],{"class":214},[200,95537,350],{"class":210},[200,95539,249],{"class":214},[200,95541,35826],{"class":245},[200,95543,249],{"class":214},[200,95545,360],{"class":210},[200,95547,252],{"class":214},[200,95549,95550,95552,95554,95556],{"class":202,"line":283},[200,95551,35837],{"class":235},[200,95553,239],{"class":214},[200,95555,2424],{"class":2423},[200,95557,252],{"class":214},[200,95559,95560,95562],{"class":202,"line":300},[200,95561,413],{"class":214},[200,95563,416],{"class":210},[35,95565,35852,95566,35856,95568,35859,95570,35862,95572,35865,95574,35869,95576,35872,95578,35876,95580,1552],{},[1217,95567,35855],{},[156,95569,6453],{},[156,95571,35826],{},[156,95573,2882],{},[156,95575,35868],{},[156,95577,35713],{},[156,95579,35875],{},[156,95581,35879],{},[35,95583,35882,95584,35420,95586,35887,95588,31299],{},[156,95585,34912],{},[156,95587,31600],{},[156,95589,34842],{},[35,95591,35892,95592,35895],{},[156,95593,35224],{},[192,95595,95596],{"className":1355,"code":35898,"language":1358,"meta":13,"style":13},[156,95597,95598,95606,95614,95626,95638,95652,95664,95676,95688,95700,95712],{"__ignoreMap":13},[200,95599,95600,95602,95604],{"class":202,"line":203},[200,95601,35235],{"class":1978},[200,95603,31480],{"class":245},[200,95605,35172],{"class":245},[200,95607,95608,95610,95612],{"class":202,"line":14},[200,95609,35244],{"class":1978},[200,95611,35273],{"class":245},[200,95613,35276],{"class":245},[200,95615,95616,95618,95620,95622,95624],{"class":202,"line":255},[200,95617,35244],{"class":1978},[200,95619,35283],{"class":245},[200,95621,35286],{"class":245},[200,95623,35289],{"class":245},[200,95625,35292],{"class":245},[200,95627,95628,95630,95632,95634,95636],{"class":202,"line":272},[200,95629,35297],{"class":1978},[200,95631,35300],{"class":245},[200,95633,35303],{"class":245},[200,95635,663],{"class":245},[200,95637,35276],{"class":245},[200,95639,95640,95642,95644,95646,95648,95650],{"class":202,"line":283},[200,95641,35945],{"class":1978},[200,95643,35315],{"class":210},[200,95645,35318],{"class":245},[200,95647,35952],{"class":2045},[200,95649,35955],{"class":245},[200,95651,416],{"class":210},[200,95653,95654,95656,95658,95660,95662],{"class":202,"line":300},[200,95655,35962],{"class":1978},[200,95657,35965],{"class":245},[200,95659,35968],{"class":210},[200,95661,35971],{"class":245},[200,95663,416],{"class":210},[200,95665,95666,95668,95670,95672,95674],{"class":202,"line":310},[200,95667,35962],{"class":1978},[200,95669,35980],{"class":245},[200,95671,35983],{"class":210},[200,95673,35971],{"class":245},[200,95675,416],{"class":210},[200,95677,95678,95680,95682,95684,95686],{"class":202,"line":336},[200,95679,35962],{"class":1978},[200,95681,35994],{"class":245},[200,95683,35997],{"class":210},[200,95685,35971],{"class":245},[200,95687,416],{"class":210},[200,95689,95690,95692,95694,95696,95698],{"class":202,"line":342},[200,95691,35962],{"class":1978},[200,95693,36008],{"class":245},[200,95695,36011],{"class":210},[200,95697,35971],{"class":245},[200,95699,416],{"class":210},[200,95701,95702,95704,95706,95708,95710],{"class":202,"line":365},[200,95703,35962],{"class":1978},[200,95705,36022],{"class":245},[200,95707,36025],{"class":210},[200,95709,35971],{"class":245},[200,95711,416],{"class":210},[200,95713,95714,95716,95718,95720,95722,95724,95726],{"class":202,"line":371},[200,95715,35350],{"class":1978},[200,95717,35353],{"class":245},[200,95719,35286],{"class":245},[200,95721,35358],{"class":245},[200,95723,35361],{"class":210},[200,95725,35364],{"class":245},[200,95727,36046],{"class":210},[35,95729,36049,95730,36052],{},[156,95731,5139],{},[192,95733,95735],{"className":95734,"code":36056,"language":698},[33336],[156,95736,36056],{"__ignoreMap":13},[35,95738,36061,95739,36064,95741,36068,95743,36071],{},[156,95740,11165],{},[156,95742,36067],{},[156,95744,5139],{},[51,95746,36075],{"id":36074},[35,95748,36078,95749,36082],{},[6850,95750,36081],{},[35,95752,36085],{},[35,95754,36088],{},[35,95756,36091,95757,215],{},[39,95758,36095],{"href":33800,"rel":95759},[77],[35,95761,36098,95762,36102],{},[156,95763,36101],{},[192,95765,95766],{"className":32575,"code":36105,"language":32577,"meta":13,"style":13},[156,95767,95768,95774,95780],{"__ignoreMap":13},[200,95769,95770,95772],{"class":202,"line":203},[200,95771,36112],{"class":235},[200,95773,30028],{"class":214},[200,95775,95776,95778],{"class":202,"line":14},[200,95777,36119],{"class":214},[200,95779,36122],{"class":2045},[200,95781,95782,95784],{"class":202,"line":255},[200,95783,36119],{"class":214},[200,95785,36129],{"class":245},[35,95787,36132,95788,36136],{},[156,95789,36135],{},[192,95791,95792],{"className":1355,"code":36139,"language":1358,"meta":13,"style":13},[156,95793,95794],{"__ignoreMap":13},[200,95795,95796,95798,95800,95802,95804],{"class":202,"line":203},[200,95797,31244],{"class":1978},[200,95799,36148],{"class":245},[200,95801,36151],{"class":245},[200,95803,36154],{"class":245},[200,95805,36157],{"class":245},[35,95807,36160],{},[192,95809,95810],{"className":1355,"code":36163,"language":1358,"meta":13,"style":13},[156,95811,95812],{"__ignoreMap":13},[200,95813,95814,95816],{"class":202,"line":203},[200,95815,2160],{"class":1978},[200,95817,36172],{"class":245},[939,95819,95820],{},[35,95821,95822,36179,95824,36183],{},[200,95823,945],{},[156,95825,36182],{},[35,95827,36186,95828,36189],{},[156,95829,36135],{},[35,95831,36192,95832,36195,95834,36199],{},[156,95833,21995],{},[156,95835,36198],{},[192,95837,95838],{"className":21417,"code":36202,"language":12060,"meta":13,"style":13},[156,95839,95840,95860,95878,95886,95890,95898,95906],{"__ignoreMap":13},[200,95841,95842,95844,95846,95848,95850,95852,95854,95856,95858],{"class":202,"line":203},[200,95843,600],{"class":214},[200,95845,14892],{"class":235},[200,95847,21781],{"class":379},[200,95849,14590],{"class":379},[200,95851,449],{"class":214},[200,95853,611],{"class":214},[200,95855,1373],{"class":245},[200,95857,611],{"class":214},[200,95859,648],{"class":214},[200,95861,95862,95864,95866,95868,95870,95872,95874,95876],{"class":202,"line":14},[200,95863,973],{"class":206},[200,95865,318],{"class":214},[200,95867,34877],{"class":210},[200,95869,981],{"class":214},[200,95871,984],{"class":206},[200,95873,242],{"class":214},[200,95875,36241],{"class":245},[200,95877,992],{"class":214},[200,95879,95880,95882,95884],{"class":202,"line":255},[200,95881,681],{"class":214},[200,95883,14892],{"class":235},[200,95885,648],{"class":214},[200,95887,95888],{"class":202,"line":272},[200,95889,529],{"emptyLinePlaceholder":20},[200,95891,95892,95894,95896],{"class":202,"line":283},[200,95893,600],{"class":214},[200,95895,21428],{"class":235},[200,95897,648],{"class":214},[200,95899,95900,95902,95904],{"class":202,"line":300},[200,95901,657],{"class":214},[200,95903,36198],{"class":235},[200,95905,21487],{"class":214},[200,95907,95908,95910,95912],{"class":202,"line":310},[200,95909,681],{"class":214},[200,95911,21428],{"class":235},[200,95913,648],{"class":214},[35,95915,36282],{},[192,95917,95918],{"className":1355,"code":36285,"language":1358,"meta":13,"style":13},[156,95919,95920],{"__ignoreMap":13},[200,95921,95922,95924,95926,95928,95930],{"class":202,"line":203},[200,95923,36292],{"class":223},[200,95925,36151],{"class":245},[200,95927,14928],{"class":214},[200,95929,36299],{"class":1978},[200,95931,13695],{"class":245},[35,95933,36304,95934,36307,95936],{},[156,95935,36198],{},[1217,95937,36310],{},[51,95939,36314],{"id":36313},[35,95941,36317],{},[35,95943,36320,95944,36324],{},[156,95945,36323],{},[35,95947,36327,95948,36333,95951,26684],{},[39,95949,36332],{"href":36330,"rel":95950},[77],[156,95952,36336],{},[35,95954,36339,95955,36345],{},[39,95956,36344],{"href":36342,"rel":95957},[77],[192,95959,95960],{"className":1355,"code":36348,"language":1358,"meta":13,"style":13},[156,95961,95962],{"__ignoreMap":13},[200,95963,95964,95966,95968,95970],{"class":202,"line":203},[200,95965,2160],{"class":1978},[200,95967,36357],{"class":245},[200,95969,12291],{"class":245},[200,95971,36362],{"class":245},[35,95973,36365,95974,239],{},[156,95975,31600],{},[192,95977,95978],{"className":26986,"code":36370,"language":26988,"meta":13,"style":13},[156,95979,95980,95984,95996,96014,96030,96034],{"__ignoreMap":13},[200,95981,95982],{"class":202,"line":203},[200,95983,230],{"class":214},[200,95985,95986,95988,95990,95992,95994],{"class":202,"line":14},[200,95987,26999],{"class":214},[200,95989,31616],{"class":379},[200,95991,611],{"class":214},[200,95993,239],{"class":214},[200,95995,280],{"class":214},[200,95997,95998,96000,96002,96004,96006,96008,96010,96012],{"class":202,"line":255},[200,95999,27031],{"class":214},[200,96001,36395],{"class":1978},[200,96003,611],{"class":214},[200,96005,239],{"class":214},[200,96007,27008],{"class":214},[200,96009,35148],{"class":245},[200,96011,611],{"class":214},[200,96013,252],{"class":214},[200,96015,96016,96018,96020,96022,96024,96026,96028],{"class":202,"line":272},[200,96017,27031],{"class":214},[200,96019,36414],{"class":1978},[200,96021,611],{"class":214},[200,96023,239],{"class":214},[200,96025,27008],{"class":214},[200,96027,36423],{"class":245},[200,96029,617],{"class":214},[200,96031,96032],{"class":202,"line":283},[200,96033,4490],{"class":214},[200,96035,96036],{"class":202,"line":300},[200,96037,1130],{"class":214},[35,96039,419,96040,36438,96042,36441,96044,36444],{},[156,96041,36395],{},[156,96043,35148],{},[156,96045,36414],{},[939,96047,96048],{},[35,96049,96050,36451,96052,36455],{},[200,96051,945],{},[156,96053,36454],{},[35,96055,36458],{},[192,96057,96058],{"className":1355,"code":36461,"language":1358,"meta":13,"style":13},[156,96059,96060],{"__ignoreMap":13},[200,96061,96062,96064,96066],{"class":202,"line":203},[200,96063,36332],{"class":1978},[200,96065,32709],{"class":245},[200,96067,36472],{"class":245},[35,96069,36475],{},[51,96071,36479],{"id":36478},[35,96073,36482],{},[1576,96075,96076,96078,96080,96082,96084,96086],{},[71,96077,36487],{},[71,96079,36490],{},[71,96081,36493],{},[71,96083,36496],{},[71,96085,36499],{},[71,96087,36502],{},[35,96089,36505,96090],{},[1582,96091,36508,96092,215],{"repo":34546},[6850,96093,36511],{},[35,96095,36514,96096,215],{},[39,96097,36519],{"href":36517,"rel":96098},[77],[35,96100,96101],{},[1217,96102,36524],{},[7905,96104],{"id":31061},[1171,96106,36529],{},{"title":13,"searchDepth":14,"depth":14,"links":96108},[96109,96110,96111,96112,96113,96114,96115,96116,96117],{"id":35051,"depth":14,"text":35052},{"id":35136,"depth":14,"text":35137},{"id":35379,"depth":14,"text":35380},{"id":35598,"depth":14,"text":35599},{"id":35685,"depth":14,"text":35686},{"id":35758,"depth":14,"text":35759},{"id":36074,"depth":14,"text":36075},{"id":36313,"depth":14,"text":36314},{"id":36478,"depth":14,"text":36479},{},{"title":34538,"description":36542},{"loc":36544},{"id":36551,"title":36552,"body":96122,"contentId":30929,"date":36913,"description":36914,"extension":18,"meta":96353,"navigation":20,"path":36916,"rawbody":36917,"robots":23,"schemaOrg":23,"seo":96354,"sitemap":96355,"stem":36939,"__hash__":36940},{"type":7,"value":96123,"toc":96346},[96124,96132,96136,96138,96140,96144,96148,96150,96152,96163,96167,96171,96176,96180,96184,96188,96190,96194,96196,96204,96208,96210,96217,96221,96223,96225,96234,96238,96242,96244,96248,96250,96252,96256,96260,96262,96282,96284,96294,96298,96302,96304,96308,96310,96320,96324,96328,96332,96334,96338,96340,96342],[35,96125,36557,96126,36563,96129,36567],{},[39,96127,36562],{"href":36560,"rel":96128},[77],[39,96130,17986],{"href":34562,"rel":96131},[77],[35,96133,36570,96134],{},[1217,96135,36573],{},[51,96137,36577],{"id":36576},[35,96139,36580],{},[35,96141,96142],{},[1690,96143],{"alt":36585,"src":36586},[35,96145,36589,96146],{},[1217,96147,36592],{},[51,96149,36596],{"id":36595},[35,96151,36599],{},[35,96153,36602,96154,36608,96157,36614,96160,36620],{},[39,96155,36607],{"href":36605,"rel":96156},[77],[39,96158,36613],{"href":36611,"rel":96159},[77],[39,96161,36619],{"href":36617,"rel":96162},[77],[35,96164,96165],{},[1690,96166],{"alt":36625,"src":36626},[35,96168,96169],{},[1690,96170],{"alt":36631,"src":36632},[35,96172,36635,96173,36641],{},[39,96174,36640],{"href":36638,"rel":96175},[77],[35,96177,96178],{},[1690,96179],{"alt":36646,"src":36647},[35,96181,96182],{},[1690,96183],{"alt":36652,"src":36653},[35,96185,96186],{},[1217,96187,36658],{},[35,96189,36661],{},[35,96191,96192],{},[1690,96193],{"alt":36666,"src":36667},[35,96195,36670],{},[35,96197,36673,96198,36679,96201,36685],{},[39,96199,36678],{"href":36676,"rel":96200},[77],[39,96202,36684],{"href":36682,"rel":96203},[77],[35,96205,96206],{},[1690,96207],{"alt":36690,"src":36691},[35,96209,36694],{},[35,96211,36697,96212,36703,96215],{},[39,96213,36702],{"href":36700,"rel":96214},[77],[1217,96216,36706],{},[35,96218,96219],{},[1690,96220],{"alt":36711,"src":36712},[35,96222,36715],{},[35,96224,36718],{},[35,96226,36721,96227,36727,96230,36731,96232],{},[39,96228,36726],{"href":36724,"rel":96229},[77],[1217,96231,36730],{},[1217,96233,36734],{},[35,96235,96236],{},[1690,96237],{"alt":36739,"src":36740},[35,96239,96240],{},[1690,96241],{"alt":36745,"src":36746},[35,96243,36749],{},[35,96245,96246],{},[1690,96247],{"alt":36754,"src":36755},[35,96249,36758],{},[51,96251,36762],{"id":36761},[939,96253,96254],{},[35,96255,36767],{},[35,96257,36770,96258],{},[1217,96259,36773],{},[35,96261,36776],{},[1576,96263,96264,96266,96268,96270,96272,96274,96276,96278,96280],{},[71,96265,36781],{},[71,96267,36784],{},[71,96269,36787],{},[71,96271,36790],{},[71,96273,36793],{},[71,96275,36796],{},[71,96277,36799],{},[71,96279,36802],{},[71,96281,36805],{},[35,96283,36808],{},[35,96285,36811,96286,36817,96289,36823,96292],{},[39,96287,36816],{"href":36814,"rel":96288},[77],[39,96290,36822],{"href":36820,"rel":96291},[77],[1217,96293,36826],{},[35,96295,96296],{},[1690,96297],{"alt":36831,"src":36832},[35,96299,96300],{},[1690,96301],{"alt":36837,"src":36838},[35,96303,36841],{},[35,96305,96306],{},[1690,96307],{"alt":36846,"src":36847},[51,96309,36851],{"id":36850},[35,96311,36854,96312,1638,96315,36862,96318],{},[39,96313,36562],{"href":36560,"rel":96314},[77],[39,96316,17423],{"href":36860,"rel":96317},[77],[1217,96319,36865],{},[35,96321,96322],{},[1690,96323],{"alt":36870,"src":36871},[35,96325,96326],{},[1690,96327],{"alt":36876,"src":36877},[35,96329,96330],{},[1690,96331],{"alt":36882,"src":36883},[51,96333,36887],{"id":36886},[35,96335,36890,96336],{},[1217,96337,36893],{},[35,96339,36896],{},[35,96341,36899],{},[35,96343,96344],{},[1690,96345],{"alt":36904,"src":36905},{"title":13,"searchDepth":14,"depth":14,"links":96347},[96348,96349,96350,96351,96352],{"id":36576,"depth":14,"text":36577},{"id":36595,"depth":14,"text":36596},{"id":36761,"depth":14,"text":36762},{"id":36850,"depth":14,"text":36851},{"id":36886,"depth":14,"text":36887},{},{"title":36552,"description":36914},{"loc":36916,"images":96356},[96357,96358,96359,96360,96361,96362,96363,96364,96365,96366,96367,96368,96369,96370,96371,96372,96373,96374],{"loc":36586},{"loc":36626},{"loc":36632},{"loc":36647},{"loc":36653},{"loc":36667},{"loc":36691},{"loc":36712},{"loc":36740},{"loc":36746},{"loc":36755},{"loc":36832},{"loc":36838},{"loc":36847},{"loc":36871},{"loc":36877},{"loc":36883},{"loc":36905},{"id":36942,"title":36943,"body":96376,"contentId":38085,"date":38086,"description":38087,"extension":18,"meta":97349,"navigation":20,"path":38089,"rawbody":38090,"robots":23,"schemaOrg":23,"seo":97350,"sitemap":97351,"stem":38097,"__hash__":38098},{"type":7,"value":96377,"toc":97346},[96378,96384,96386,96388,96442,96446,96448,96450,96452,96456,96468,96924,96928,97168,97172,97176,97181,97183,97190,97194,97196,97206,97210,97214,97218,97222,97224,97228,97234,97236,97238,97240,97344],[939,96379,96380],{},[35,96381,96382,36952],{},[200,96383,945],{},[35,96385,36955],{},[35,96387,36958],{},[192,96389,96390],{"className":25029,"code":36961,"language":25031,"meta":13,"style":13},[156,96391,96392,96412,96436],{"__ignoreMap":13},[200,96393,96394,96396,96398,96400,96402,96404,96406,96408,96410],{"class":202,"line":203},[200,96395,36968],{"class":223},[200,96397,227],{"class":210},[200,96399,249],{"class":214},[200,96401,36975],{"class":245},[200,96403,249],{"class":214},[200,96405,550],{"class":214},[200,96407,3714],{"class":214},[200,96409,393],{"class":379},[200,96411,280],{"class":214},[200,96413,96414,96416,96418,96420,96422,96424,96426,96428,96430,96432,96434],{"class":202,"line":14},[200,96415,36990],{"class":223},[200,96417,227],{"class":235},[200,96419,3188],{"class":2045},[200,96421,13205],{"class":214},[200,96423,3938],{"class":2045},[200,96425,2089],{"class":235},[200,96427,215],{"class":214},[200,96429,37005],{"class":223},[200,96431,227],{"class":235},[200,96433,4782],{"class":2045},[200,96435,416],{"class":235},[200,96437,96438,96440],{"class":202,"line":255},[200,96439,413],{"class":214},[200,96441,416],{"class":210},[35,96443,37020,96444,37024],{},[6850,96445,37023],{},[35,96447,37027],{},[51,96449,37031],{"id":37030},[35,96451,37034],{},[35,96453,37037,96454],{},[1217,96455,37040],{},[35,96457,37043,96458,37049,96461,37055,96464,37059,96466],{},[39,96459,37048],{"href":37046,"rel":96460},[77],[39,96462,37054],{"href":37052,"rel":96463},[77],[1217,96465,37058],{},[1217,96467,37062],{},[192,96469,96470],{"className":1370,"code":37065,"language":1373,"meta":13,"style":13},[156,96471,96472,96494,96508,96512,96528,96540,96560,96564,96580,96604,96620,96630,96644,96648,96672,96696,96702,96706,96728,96742,96746,96762,96774,96790,96794,96810,96834,96870,96894,96918],{"__ignoreMap":13},[200,96473,96474,96476,96478,96480,96482,96484,96486,96488,96490,96492],{"class":202,"line":203},[200,96475,36968],{"class":223},[200,96477,227],{"class":210},[200,96479,249],{"class":214},[200,96481,37078],{"class":245},[200,96483,249],{"class":214},[200,96485,550],{"class":214},[200,96487,380],{"class":379},[200,96489,3714],{"class":214},[200,96491,393],{"class":379},[200,96493,280],{"class":214},[200,96495,96496,96498,96500,96502,96504,96506],{"class":202,"line":14},[200,96497,36990],{"class":210},[200,96499,215],{"class":214},[200,96501,37099],{"class":223},[200,96503,227],{"class":235},[200,96505,37104],{"class":2045},[200,96507,416],{"class":235},[200,96509,96510],{"class":202,"line":255},[200,96511,529],{"emptyLinePlaceholder":20},[200,96513,96514,96516,96518,96520,96522,96524,96526],{"class":202,"line":272},[200,96515,3723],{"class":379},[200,96517,37117],{"class":210},[200,96519,239],{"class":214},[200,96521,37122],{"class":1978},[200,96523,510],{"class":214},[200,96525,452],{"class":206},[200,96527,31435],{"class":210},[200,96529,96530,96532,96534,96536,96538],{"class":202,"line":283},[200,96531,3755],{"class":214},[200,96533,37135],{"class":223},[200,96535,227],{"class":235},[200,96537,37140],{"class":210},[200,96539,416],{"class":235},[200,96541,96542,96544,96546,96548,96550,96552,96554,96556,96558],{"class":202,"line":300},[200,96543,3755],{"class":214},[200,96545,37149],{"class":223},[200,96547,227],{"class":235},[200,96549,3531],{"class":210},[200,96551,215],{"class":214},[200,96553,37158],{"class":210},[200,96555,550],{"class":214},[200,96557,37163],{"class":210},[200,96559,416],{"class":235},[200,96561,96562],{"class":202,"line":310},[200,96563,529],{"emptyLinePlaceholder":20},[200,96565,96566,96568,96570,96572,96574,96576,96578],{"class":202,"line":336},[200,96567,36990],{"class":223},[200,96569,227],{"class":235},[200,96571,37178],{"class":210},[200,96573,2089],{"class":235},[200,96575,215],{"class":214},[200,96577,37185],{"class":223},[200,96579,466],{"class":235},[200,96581,96582,96584,96586,96588,96590,96592,96594,96596,96598,96600,96602],{"class":202,"line":342},[200,96583,36990],{"class":223},[200,96585,227],{"class":235},[200,96587,37178],{"class":210},[200,96589,2089],{"class":235},[200,96591,215],{"class":214},[200,96593,37202],{"class":223},[200,96595,227],{"class":235},[200,96597,249],{"class":214},[200,96599,37158],{"class":245},[200,96601,249],{"class":214},[200,96603,416],{"class":235},[200,96605,96606,96608,96610,96612,96614,96616,96618],{"class":202,"line":365},[200,96607,36990],{"class":223},[200,96609,227],{"class":235},[200,96611,37178],{"class":210},[200,96613,2089],{"class":235},[200,96615,215],{"class":214},[200,96617,37202],{"class":223},[200,96619,3115],{"class":235},[200,96621,96622,96624,96626,96628],{"class":202,"line":371},[200,96623,1405],{"class":214},[200,96625,20089],{"class":245},[200,96627,249],{"class":214},[200,96629,252],{"class":214},[200,96631,96632,96634,96636,96638,96640,96642],{"class":202,"line":398},[200,96633,37243],{"class":210},[200,96635,215],{"class":214},[200,96637,20089],{"class":210},[200,96639,215],{"class":214},[200,96641,3832],{"class":223},[200,96643,466],{"class":235},[200,96645,96646],{"class":202,"line":405},[200,96647,30218],{"class":235},[200,96649,96650,96652,96654,96656,96658,96660,96662,96664,96666,96668,96670],{"class":202,"line":410},[200,96651,36990],{"class":223},[200,96653,227],{"class":235},[200,96655,37178],{"class":210},[200,96657,2089],{"class":235},[200,96659,215],{"class":214},[200,96661,37202],{"class":223},[200,96663,227],{"class":235},[200,96665,249],{"class":214},[200,96667,37278],{"class":245},[200,96669,249],{"class":214},[200,96671,416],{"class":235},[200,96673,96674,96676,96678,96680,96682,96684,96686,96688,96690,96692,96694],{"class":202,"line":861},[200,96675,36990],{"class":223},[200,96677,227],{"class":235},[200,96679,37178],{"class":210},[200,96681,2089],{"class":235},[200,96683,215],{"class":214},[200,96685,37202],{"class":223},[200,96687,227],{"class":235},[200,96689,249],{"class":214},[200,96691,37303],{"class":245},[200,96693,249],{"class":214},[200,96695,416],{"class":235},[200,96697,96698,96700],{"class":202,"line":890},[200,96699,413],{"class":214},[200,96701,416],{"class":210},[200,96703,96704],{"class":202,"line":900},[200,96705,529],{"emptyLinePlaceholder":20},[200,96707,96708,96710,96712,96714,96716,96718,96720,96722,96724,96726],{"class":202,"line":905},[200,96709,36968],{"class":223},[200,96711,227],{"class":210},[200,96713,249],{"class":214},[200,96715,37328],{"class":245},[200,96717,249],{"class":214},[200,96719,550],{"class":214},[200,96721,380],{"class":379},[200,96723,3714],{"class":214},[200,96725,393],{"class":379},[200,96727,280],{"class":214},[200,96729,96730,96732,96734,96736,96738,96740],{"class":202,"line":931},[200,96731,36990],{"class":210},[200,96733,215],{"class":214},[200,96735,37099],{"class":223},[200,96737,227],{"class":235},[200,96739,37104],{"class":2045},[200,96741,416],{"class":235},[200,96743,96744],{"class":202,"line":3640},[200,96745,529],{"emptyLinePlaceholder":20},[200,96747,96748,96750,96752,96754,96756,96758,96760],{"class":202,"line":3645},[200,96749,3723],{"class":379},[200,96751,37365],{"class":210},[200,96753,239],{"class":214},[200,96755,37122],{"class":1978},[200,96757,510],{"class":214},[200,96759,452],{"class":206},[200,96761,31435],{"class":210},[200,96763,96764,96766,96768,96770,96772],{"class":202,"line":4020},[200,96765,3755],{"class":214},[200,96767,37135],{"class":223},[200,96769,227],{"class":235},[200,96771,37140],{"class":210},[200,96773,416],{"class":235},[200,96775,96776,96778,96780,96782,96784,96786,96788],{"class":202,"line":4475},[200,96777,3755],{"class":214},[200,96779,37394],{"class":223},[200,96781,227],{"class":235},[200,96783,3531],{"class":210},[200,96785,215],{"class":214},[200,96787,37158],{"class":210},[200,96789,416],{"class":235},[200,96791,96792],{"class":202,"line":4481},[200,96793,529],{"emptyLinePlaceholder":20},[200,96795,96796,96798,96800,96802,96804,96806,96808],{"class":202,"line":4487},[200,96797,36990],{"class":223},[200,96799,227],{"class":235},[200,96801,37417],{"class":210},[200,96803,2089],{"class":235},[200,96805,215],{"class":214},[200,96807,37185],{"class":223},[200,96809,466],{"class":235},[200,96811,96812,96814,96816,96818,96820,96822,96824,96826,96828,96830,96832],{"class":202,"line":4493},[200,96813,36990],{"class":223},[200,96815,227],{"class":235},[200,96817,37417],{"class":210},[200,96819,2089],{"class":235},[200,96821,215],{"class":214},[200,96823,37202],{"class":223},[200,96825,227],{"class":235},[200,96827,249],{"class":214},[200,96829,37158],{"class":245},[200,96831,249],{"class":214},[200,96833,416],{"class":235},[200,96835,96836,96838,96840,96842,96844,96846,96848,96850,96852,96854,96856,96858,96860,96862,96864,96866,96868],{"class":202,"line":5454},[200,96837,36990],{"class":223},[200,96839,227],{"class":235},[200,96841,37417],{"class":210},[200,96843,2089],{"class":235},[200,96845,215],{"class":214},[200,96847,37202],{"class":223},[200,96849,227],{"class":235},[200,96851,249],{"class":214},[200,96853,20089],{"class":245},[200,96855,249],{"class":214},[200,96857,550],{"class":214},[200,96859,3546],{"class":210},[200,96861,215],{"class":214},[200,96863,20089],{"class":210},[200,96865,215],{"class":214},[200,96867,3832],{"class":223},[200,96869,10947],{"class":235},[200,96871,96872,96874,96876,96878,96880,96882,96884,96886,96888,96890,96892],{"class":202,"line":5459},[200,96873,36990],{"class":223},[200,96875,227],{"class":235},[200,96877,37417],{"class":210},[200,96879,2089],{"class":235},[200,96881,215],{"class":214},[200,96883,37202],{"class":223},[200,96885,227],{"class":235},[200,96887,249],{"class":214},[200,96889,37278],{"class":245},[200,96891,249],{"class":214},[200,96893,416],{"class":235},[200,96895,96896,96898,96900,96902,96904,96906,96908,96910,96912,96914,96916],{"class":202,"line":13227},[200,96897,36990],{"class":223},[200,96899,227],{"class":235},[200,96901,37417],{"class":210},[200,96903,2089],{"class":235},[200,96905,215],{"class":214},[200,96907,37202],{"class":223},[200,96909,227],{"class":235},[200,96911,249],{"class":214},[200,96913,37303],{"class":245},[200,96915,249],{"class":214},[200,96917,416],{"class":235},[200,96919,96920,96922],{"class":202,"line":13253},[200,96921,413],{"class":214},[200,96923,416],{"class":210},[35,96925,37542,96926,37546],{},[6850,96927,37545],{},[192,96929,96930],{"className":1370,"code":37549,"language":1373,"meta":13,"style":13},[156,96931,96932,96940,96948,96958,96968,96978,96992,97006,97020,97030,97034,97038,97046,97056,97088,97120,97130,97140,97150,97160,97164],{"__ignoreMap":13},[200,96933,96934,96936,96938],{"class":202,"line":203},[200,96935,1380],{"class":206},[200,96937,1383],{"class":206},[200,96939,280],{"class":214},[200,96941,96942,96944,96946],{"class":202,"line":14},[200,96943,37564],{"class":235},[200,96945,239],{"class":214},[200,96947,280],{"class":214},[200,96949,96950,96952,96954,96956],{"class":202,"line":255},[200,96951,37573],{"class":235},[200,96953,239],{"class":214},[200,96955,27960],{"class":210},[200,96957,252],{"class":214},[200,96959,96960,96962,96964,96966],{"class":202,"line":272},[200,96961,37584],{"class":235},[200,96963,239],{"class":214},[200,96965,27960],{"class":210},[200,96967,252],{"class":214},[200,96969,96970,96972,96974,96976],{"class":202,"line":283},[200,96971,37595],{"class":235},[200,96973,239],{"class":214},[200,96975,27960],{"class":210},[200,96977,252],{"class":214},[200,96979,96980,96982,96984,96986,96988,96990],{"class":202,"line":300},[200,96981,37606],{"class":235},[200,96983,239],{"class":214},[200,96985,350],{"class":210},[200,96987,37613],{"class":223},[200,96989,37616],{"class":210},[200,96991,252],{"class":214},[200,96993,96994,96996,96998,97000,97002,97004],{"class":202,"line":310},[200,96995,37623],{"class":235},[200,96997,239],{"class":214},[200,96999,350],{"class":210},[200,97001,37630],{"class":223},[200,97003,37633],{"class":210},[200,97005,252],{"class":214},[200,97007,97008,97010,97012,97014,97016,97018],{"class":202,"line":336},[200,97009,37640],{"class":235},[200,97011,239],{"class":214},[200,97013,350],{"class":210},[200,97015,37613],{"class":223},[200,97017,37616],{"class":210},[200,97019,252],{"class":214},[200,97021,97022,97024,97026,97028],{"class":202,"line":342},[200,97023,37655],{"class":235},[200,97025,239],{"class":214},[200,97027,27960],{"class":210},[200,97029,252],{"class":214},[200,97031,97032],{"class":202,"line":365},[200,97033,368],{"class":214},[200,97035,97036],{"class":202,"line":371},[200,97037,529],{"emptyLinePlaceholder":20},[200,97039,97040,97042,97044],{"class":202,"line":398},[200,97041,37674],{"class":235},[200,97043,239],{"class":214},[200,97045,280],{"class":214},[200,97047,97048,97050,97052,97054],{"class":202,"line":405},[200,97049,37573],{"class":235},[200,97051,239],{"class":214},[200,97053,27960],{"class":210},[200,97055,252],{"class":214},[200,97057,97058,97060,97062,97064,97066,97068,97070,97072,97074,97076,97078,97080,97082,97084,97086],{"class":202,"line":410},[200,97059,37584],{"class":235},[200,97061,239],{"class":214},[200,97063,350],{"class":210},[200,97065,37699],{"class":223},[200,97067,227],{"class":210},[200,97069,37704],{"class":223},[200,97071,227],{"class":210},[200,97073,249],{"class":214},[200,97075,37711],{"class":245},[200,97077,249],{"class":214},[200,97079,2089],{"class":210},[200,97081,550],{"class":214},[200,97083,37720],{"class":223},[200,97085,37723],{"class":210},[200,97087,252],{"class":214},[200,97089,97090,97092,97094,97096,97098,97100,97102,97104,97106,97108,97110,97112,97114,97116,97118],{"class":202,"line":861},[200,97091,37595],{"class":235},[200,97093,239],{"class":214},[200,97095,350],{"class":210},[200,97097,37699],{"class":223},[200,97099,227],{"class":210},[200,97101,37704],{"class":223},[200,97103,227],{"class":210},[200,97105,249],{"class":214},[200,97107,37711],{"class":245},[200,97109,249],{"class":214},[200,97111,2089],{"class":210},[200,97113,550],{"class":214},[200,97115,37720],{"class":223},[200,97117,37723],{"class":210},[200,97119,252],{"class":214},[200,97121,97122,97124,97126,97128],{"class":202,"line":890},[200,97123,37606],{"class":235},[200,97125,239],{"class":214},[200,97127,27960],{"class":210},[200,97129,252],{"class":214},[200,97131,97132,97134,97136,97138],{"class":202,"line":900},[200,97133,37623],{"class":235},[200,97135,239],{"class":214},[200,97137,27960],{"class":210},[200,97139,252],{"class":214},[200,97141,97142,97144,97146,97148],{"class":202,"line":905},[200,97143,37640],{"class":235},[200,97145,239],{"class":214},[200,97147,27960],{"class":210},[200,97149,252],{"class":214},[200,97151,97152,97154,97156,97158],{"class":202,"line":931},[200,97153,37655],{"class":235},[200,97155,239],{"class":214},[200,97157,27960],{"class":210},[200,97159,252],{"class":214},[200,97161,97162],{"class":202,"line":3640},[200,97163,368],{"class":214},[200,97165,97166],{"class":202,"line":3645},[200,97167,1130],{"class":214},[35,97169,37808,97170],{},[1217,97171,37811],{},[35,97173,37814,97174],{},[1217,97175,37817],{},[35,97177,37820,97178,37826],{},[39,97179,37825],{"href":37823,"rel":97180},[77],[35,97182,37829],{},[35,97184,37832,97185,37838,97188],{},[39,97186,37837],{"href":37835,"rel":97187},[77],[1217,97189,37841],{},[35,97191,97192],{},[1690,97193],{"alt":37846,"src":37847},[35,97195,37850],{},[1576,97197,97198,97200,97202,97204],{},[71,97199,37855],{},[71,97201,37858],{},[71,97203,37861],{},[71,97205,37864],{},[35,97207,37867,97208],{},[1217,97209,37870],{},[35,97211,37873,97212],{},[1217,97213,37876],{},[35,97215,37879,97216],{},[1217,97217,37882],{},[35,97219,97220],{},[1690,97221],{"alt":37887,"src":37888},[35,97223,37891],{},[35,97225,37894,97226,37898],{},[1217,97227,37897],{},[35,97229,37901,97230,37905,97232,37909],{},[156,97231,37904],{},[1217,97233,37908],{},[35,97235,37912],{},[35,97237,37915],{},[35,97239,37918],{},[7905,97241,97242,97244,97254,97261,97265,97269],{"id":20826},[35,97243,37923],{},[35,97245,37926,97246,37932,97249],{},[39,97247,37931],{"href":37929,"rel":97248},[77],[1217,97250,37935,97251,215],{},[39,97252,37940],{"href":37938,"rel":97253},[77],[35,97255,37943,97256,37949,97259,37953],{},[39,97257,37948],{"href":37946,"rel":97258},[77],[1217,97260,37952],{},[35,97262,97263],{},[1690,97264],{"alt":37958,"src":37959},[35,97266,37962,97267],{},[1217,97268,37965],{},[7139,97270,97271,97273,97275,97277,97331,97335,97337],{"icon":37968,"text":37969,"to":33689},[51,97272,37973],{"id":37972},[35,97274,37976],{},[35,97276,37979],{},[68,97278,97279,97281,97285,97289,97293,97297,97301,97305,97309,97319,97323,97327],{},[71,97280,37984],{},[71,97282,37987,97283,215],{},[156,97284,37990],{},[71,97286,37993,97287],{},[1217,97288,37996],{},[71,97290,37999,97291],{},[1217,97292,38002],{},[71,97294,38005,97295],{},[1217,97296,38008],{},[71,97298,38011,97299],{},[1217,97300,38014],{},[71,97302,38017,97303],{},[1217,97304,38020],{},[71,97306,38023,97307],{},[1217,97308,38026],{},[71,97310,38029,97311,3076,97313,3080,97315,38039,97317],{},[156,97312,38032],{},[156,97314,38035],{},[156,97316,38038],{},[1217,97318,38042],{},[71,97320,38045,97321,38048],{},[156,97322,38038],{},[71,97324,38051,97325],{},[1217,97326,38054],{},[71,97328,38057,97329],{},[1217,97330,38060],{},[35,97332,38063,97333],{},[1217,97334,38066],{},[35,97336,38069],{},[35,97338,97339],{},[1217,97340,38074,97341,215],{},[39,97342,38078],{"href":33689,"rel":97343},[77],[1171,97345,38081],{},{"title":13,"searchDepth":14,"depth":14,"links":97347},[97348],{"id":37030,"depth":14,"text":37031},{},{"title":36943,"description":38087},{"loc":38089,"images":97352},[97353,97354,97355],{"loc":37847},{"loc":37888},{"loc":37959},{"id":38100,"title":38101,"body":97357,"contentId":38335,"date":38336,"description":38337,"extension":18,"meta":97505,"navigation":20,"path":38339,"rawbody":38340,"robots":23,"schemaOrg":23,"seo":97506,"sitemap":97507,"stem":38346,"__hash__":38347},{"type":7,"value":97358,"toc":97501},[97359,97367,97372,97376,97378,97391,97398,97404,97408,97410,97412,97416,97420,97430,97440,97444,97446,97448,97453,97458,97463,97468,97475,97477],[939,97360,97361],{},[35,97362,97363,38110,97365,215],{},[200,97364,945],{},[39,97366,38114],{"href":38113},[35,97368,34543,97369],{},[39,97370,38121],{"href":38119,"rel":97371},[77],[35,97373,97374],{},[1690,97375],{"alt":38126,"src":38127},[35,97377,38130],{},[35,97379,38133,97380,38139,97383,38144,97386,38150,97389],{},[39,97381,38138],{"href":38136,"rel":97382},[77],[39,97384,36822],{"href":38142,"rel":97385},[77],[39,97387,38149],{"href":38147,"rel":97388},[77],[1217,97390,38153],{},[35,97392,38156,97393,38160,97395,38166],{},[6850,97394,38159],{},[39,97396,38165],{"href":38163,"rel":97397},[77],[35,97399,38169,97400,38173,97402],{},[1217,97401,38172],{},[1217,97403,38176],{},[939,97405,97406],{},[35,97407,38181],{},[35,97409,38184],{},[51,97411,38121],{"id":38187},[35,97413,97414],{},[1690,97415],{"alt":38192,"src":38193},[35,97417,38196,97418,38200],{},[1217,97419,38199],{},[35,97421,38203,97422,1638,97424,38210,97426,1638,97428,215],{},[6850,97423,38206],{},[6850,97425,38209],{},[6850,97427,38213],{},[6850,97429,38216],{},[35,97431,38219,97432,3076,97434,38226,97436,106,97438],{},[6850,97433,38222],{},[6850,97435,38225],{},[6850,97437,38229],{},[1217,97439,38232],{},[939,97441,97442],{},[35,97443,38237],{},[51,97445,38241],{"id":38240},[35,97447,38244],{},[35,97449,38247,97450,38251],{},[39,97451,38121],{"href":38119,"rel":97452},[77],[35,97454,38254,97455,38260],{},[39,97456,38259],{"href":38257,"rel":97457},[77],[35,97459,38263,97460,38268],{},[39,97461,38267],{"href":14203,"rel":97462},[77],[35,97464,38271,97465,38277],{},[39,97466,38276],{"href":38274,"rel":97467},[77],[35,97469,38280,97470,38286,97473,38290],{},[39,97471,38285],{"href":38283,"rel":97472},[77],[1217,97474,38289],{},[35,97476,38293],{},[7905,97478,97479],{"id":30855},[7905,97480,97481,97485,97487,97491,97495,97499],{"id":30850},[35,97482,38300,97483],{},[1582,97484,38304],{"repo":38303},[51,97486,38308],{"id":38307},[35,97488,38311,97489,38315],{},[6850,97490,38314],{},[35,97492,38318,97493],{},[1217,97494,38321],{},[35,97496,38324,97497],{},[1582,97498,38327],{"repo":38303},[35,97500,38330],{},{"title":13,"searchDepth":14,"depth":14,"links":97502},[97503,97504],{"id":38187,"depth":14,"text":38121},{"id":38240,"depth":14,"text":38241},{},{"title":38101,"description":38337},{"loc":38339,"images":97508},[97509,97510],{"loc":38127},{"loc":38193},{"id":38349,"title":38350,"body":97512,"contentId":30986,"date":38503,"description":38504,"extension":18,"meta":97620,"navigation":20,"path":38506,"rawbody":38507,"robots":23,"schemaOrg":23,"seo":97621,"sitemap":97622,"stem":38515,"__hash__":38516},{"type":7,"value":97513,"toc":97617},[97514,97528,97532,97537,97539,97541,97543,97545,97552,97556,97566,97570,97572,97579,97581,97585,97587,97589,97591,97595,97609,97613],[939,97515,97516],{},[35,97517,97518,38359,97520,3076,97523,13731,97526,215],{},[200,97519,33518],{},[39,97521,36519],{"href":36517,"rel":97522},[77],[39,97524,7243],{"href":7031,"rel":97525},[77],[39,97527,38368],{"href":20728},[35,97529,38371,97530],{},[1217,97531,38374],{},[35,97533,38377,97534,38382],{},[39,97535,38381],{"href":8204,"rel":97536},[77],[35,97538,38385],{},[35,97540,38388],{},[35,97542,38391],{},[35,97544,38394],{},[35,97546,38397,97547,38401,97550],{},[39,97548,18045],{"href":18043,"rel":97549},[77],[1217,97551,38404],{},[35,97553,97554],{},[1690,97555],{"alt":38409,"src":38410},[35,97557,38413,97558,1638,97561,38420,97564],{},[39,97559,2918],{"href":13818,"rel":97560},[77],[39,97562,30791],{"href":35152,"rel":97563},[77],[1217,97565,38423],{},[35,97567,97568],{},[1690,97569],{"alt":38428,"src":38429},[35,97571,38432],{},[35,97573,38435,97574,38439,97577,38443],{},[39,97575,21117],{"href":21115,"rel":97576},[77],[1217,97578,38442],{},[35,97580,38446],{},[35,97582,97583],{},[1690,97584],{"alt":38451,"src":38452},[35,97586,38455],{},[51,97588,38459],{"id":38458},[35,97590,38462],{},[35,97592,38465,97593],{},[1217,97594,38468],{},[35,97596,38471,97597,38475,97599,38479,97601,3076,97604,13731,97607,215],{},[6850,97598,38474],{},[1217,97600,38478],{},[39,97602,38483],{"href":36517,"rel":97603},[77],[39,97605,7243],{"href":7031,"rel":97606},[77],[39,97608,38368],{"href":20728},[35,97610,97611],{},[1217,97612,38493],{},[35,97614,97615],{},[1690,97616],{"alt":38498,"src":38499},{"title":13,"searchDepth":14,"depth":14,"links":97618},[97619],{"id":38458,"depth":14,"text":38459},{},{"title":38350,"description":38504},{"loc":38506,"images":97623},[97624,97625,97626,97627],{"loc":38410},{"loc":38429},{"loc":38452},{"loc":38499},{"id":38518,"title":38519,"body":97629,"contentId":38872,"date":38873,"description":38874,"extension":18,"meta":97860,"navigation":20,"path":38876,"rawbody":38877,"robots":23,"schemaOrg":23,"seo":97861,"sitemap":97862,"stem":38886,"__hash__":38887},{"type":7,"value":97630,"toc":97851},[97631,97638,97640,97645,97647,97651,97658,97660,97662,97669,97671,97675,97679,97681,97685,97692,97699,97701,97707,97709,97716,97721,97723,97727,97729,97731,97735,97737,97741,97743,97747,97749,97751,97753,97757,97759,97761,97763,97812,97814,97821,97827,97832,97837,97849],[939,97632,97633],{},[35,97634,38526,97635,215],{},[39,97636,38531],{"href":38529,"rel":97637},[77],[51,97639,17286],{"id":17285},[35,97641,38536,97642,38540],{},[39,97643,36822],{"href":38142,"rel":97644},[77],[35,97646,38543],{},[939,97648,97649],{},[35,97650,38548],{},[35,97652,38551,97653],{},[1217,97654,38554,97655,38560],{},[39,97656,38559],{"href":38557,"rel":97657},[77],[35,97659,38563],{},[51,97661,38567],{"id":38566},[35,97663,38570,97664,38579],{},[1217,97665,38573,97666,215],{},[39,97667,38578],{"href":38576,"rel":97668},[77],[35,97670,38582],{},[35,97672,38585,97673],{},[1217,97674,38588],{},[35,97676,38591,97677,38595],{},[1217,97678,38594],{},[51,97680,38599],{"id":38598},[35,97682,38602,97683,38606],{},[1217,97684,38605],{},[35,97686,38609,97687,38615,97690],{},[39,97688,38614],{"href":38612,"rel":97689},[77],[1217,97691,38618],{},[35,97693,38621,97694,38627,97697],{},[39,97695,38626],{"href":38624,"rel":97696},[77],[1217,97698,38630],{},[35,97700,38633],{},[35,97702,38636,97703,106,97705],{},[6850,97704,38639],{},[1217,97706,38642],{},[35,97708,38645],{},[35,97710,38648,97711,38652,97714],{},[39,97712,38531],{"href":38529,"rel":97713},[77],[1582,97715,38656],{"repo":38655},[35,97717,38659,97718,38665],{},[39,97719,38664],{"href":38662,"rel":97720},[77],[1913,97722,38241],{"id":38240},[35,97724,97725],{},[1690,97726],{"alt":38639,"src":38672},[35,97728,38675],{},[38677,97730,38680],{"id":38679},[35,97732,97733],{},[1690,97734],{"alt":38685,"src":38686},[35,97736,38689],{},[35,97738,97739],{},[1690,97740],{"alt":38694,"src":38695},[38677,97742,38699],{"id":38698},[35,97744,97745],{},[1690,97746],{"alt":38704,"src":38705},[35,97748,38708],{},[35,97750,38711],{},[38677,97752,38715],{"id":38714},[35,97754,97755],{},[1690,97756],{"alt":38720,"src":38721},[35,97758,38724],{},[1913,97760,38728],{"id":38727},[35,97762,38731],{},[1576,97764,97765,97772,97777,97782,97787,97792,97797,97802,97807],{},[71,97766,97767,34547,97770],{},[39,97768,38739],{"href":23044,"rel":97769},[77],[1217,97771,38742],{},[71,97773,97774,38748],{},[39,97775,9190],{"href":23049,"rel":97776},[77],[71,97778,97779,38754],{},[39,97780,36822],{"href":36820,"rel":97781},[77],[71,97783,97784,38762],{},[39,97785,38761],{"href":38759,"rel":97786},[77],[71,97788,97789,38768],{},[39,97790,9221],{"href":9219,"rel":97791},[77],[71,97793,97794,34462],{},[39,97795,38775],{"href":38773,"rel":97796},[77],[71,97798,97799,38783],{},[39,97800,38782],{"href":38780,"rel":97801},[77],[71,97803,97804,38791],{},[39,97805,38790],{"href":38788,"rel":97806},[77],[71,97808,97809,38799],{},[39,97810,38798],{"href":38796,"rel":97811},[77],[51,97813,38803],{"id":38802},[35,97815,38806,97816,38810,97819,38815],{},[39,97817,18569],{"href":18567,"rel":97818},[77],[39,97820,38814],{"href":38813},[35,97822,38818,97823,3080,97825,215],{},[39,97824,38822],{"href":38821},[39,97826,38826],{"href":38825},[35,97828,38829,97829,38833],{},[39,97830,9221],{"href":9219,"rel":97831},[77],[35,97833,38836,97834,11837],{},[39,97835,38841],{"href":38839,"rel":97836},[77],[35,97838,38844,97839,38851,97844],{},[39,97840,97842],{"href":38529,"rel":97841},[77],[6850,97843,38850],{},[1217,97845,38854,97846,11837],{},[39,97847,38859],{"href":38857,"rel":97848},[77],[35,97850,38862],{},{"title":13,"searchDepth":14,"depth":14,"links":97852},[97853,97854,97855,97859],{"id":17285,"depth":14,"text":17286},{"id":38566,"depth":14,"text":38567},{"id":38598,"depth":14,"text":38599,"children":97856},[97857,97858],{"id":38240,"depth":255,"text":38241},{"id":38727,"depth":255,"text":38728},{"id":38802,"depth":14,"text":38803},{},{"title":38519,"description":38874},{"loc":38876,"images":97863},[97864,97865,97866,97867,97868],{"loc":38672},{"loc":38686},{"loc":38695},{"loc":38705},{"loc":38721},{"id":38889,"title":38826,"body":97870,"contentId":30855,"date":40794,"description":40795,"extension":18,"meta":99537,"navigation":20,"path":40797,"rawbody":40798,"robots":23,"schemaOrg":23,"seo":99538,"sitemap":99539,"stem":40801,"__hash__":40802},{"type":7,"value":97871,"toc":99526},[97872,97880,97888,97894,97896,97898,99524],[35,97873,38894,97874,13731,97876,38901,97878,215],{},[6850,97875,38897],{},[6850,97877,38900],{},[6850,97879,38904],{},[35,97881,38907,97882,38911,97884,38915,97886,38919],{},[6850,97883,38910],{},[6850,97885,38914],{},[6850,97887,38918],{},[35,97889,38922,97890,38926,97892,38929],{},[6850,97891,38925],{},[6850,97893,38904],{},[35,97895,38932],{},[35,97897,38935],{},[7905,97899,97900,97902,97906,98074,98084,98090,98095,98097,98104,98112,98120,98190,98194,98196,98204,98402,98408,98478,98484,98486,98488,98492,98496,98834,98836,98846,98848,98862,98866,98870,98875,98877,98886,98890,98895,98897,98901,98903,98907,98919,98921,98923,98929,98933,98997,98999,99047,99049,99053,99057,99145,99155,99257,99259,99263,99269,99395,99403,99413,99421,99501,99505,99507,99512,99514,99516,99522],{"id":30840},[51,97901,29349],{"id":29348},[35,97903,38942,97904,38946],{},[6850,97905,38945],{},[192,97907,97908],{"className":1370,"code":38949,"language":1373,"meta":13,"style":13},[156,97909,97910,97932,97956,97960,97988,98000,98008,98018,98028,98038,98048,98052,98058,98062,98068],{"__ignoreMap":13},[200,97911,97912,97914,97916,97918,97920,97922,97924,97926,97928,97930],{"class":202,"line":203},[200,97913,1380],{"class":206},[200,97915,1383],{"class":206},[200,97917,38960],{"class":223},[200,97919,227],{"class":210},[200,97921,3711],{"class":379},[200,97923,1095],{"class":214},[200,97925,25386],{"class":386},[200,97927,2089],{"class":214},[200,97929,393],{"class":379},[200,97931,280],{"class":214},[200,97933,97934,97936,97938,97940,97942,97944,97946,97948,97950,97952,97954],{"class":202,"line":14},[200,97935,3723],{"class":379},[200,97937,3224],{"class":210},[200,97939,510],{"class":214},[200,97941,38985],{"class":223},[200,97943,227],{"class":235},[200,97945,25386],{"class":210},[200,97947,550],{"class":214},[200,97949,242],{"class":214},[200,97951,3280],{"class":245},[200,97953,249],{"class":214},[200,97955,416],{"class":235},[200,97957,97958],{"class":202,"line":255},[200,97959,529],{"emptyLinePlaceholder":20},[200,97961,97962,97964,97966,97968,97970,97972,97974,97976,97978,97980,97982,97984,97986],{"class":202,"line":272},[200,97963,3723],{"class":379},[200,97965,39010],{"class":210},[200,97967,510],{"class":214},[200,97969,452],{"class":206},[200,97971,39017],{"class":210},[200,97973,215],{"class":214},[200,97975,3509],{"class":210},[200,97977,215],{"class":214},[200,97979,39026],{"class":210},[200,97981,215],{"class":214},[200,97983,39031],{"class":223},[200,97985,227],{"class":235},[200,97987,230],{"class":214},[200,97989,97990,97992,97994,97996,97998],{"class":202,"line":283},[200,97991,39040],{"class":235},[200,97993,239],{"class":214},[200,97995,318],{"class":214},[200,97997,3224],{"class":210},[200,97999,333],{"class":214},[200,98001,98002,98004,98006],{"class":202,"line":300},[200,98003,39053],{"class":235},[200,98005,239],{"class":214},[200,98007,280],{"class":214},[200,98009,98010,98012,98014,98016],{"class":202,"line":310},[200,98011,3557],{"class":235},[200,98013,239],{"class":214},[200,98015,2424],{"class":2423},[200,98017,252],{"class":214},[200,98019,98020,98022,98024,98026],{"class":202,"line":336},[200,98021,3589],{"class":235},[200,98023,239],{"class":214},[200,98025,2424],{"class":2423},[200,98027,252],{"class":214},[200,98029,98030,98032,98034,98036],{"class":202,"line":342},[200,98031,39082],{"class":235},[200,98033,239],{"class":214},[200,98035,2424],{"class":2423},[200,98037,252],{"class":214},[200,98039,98040,98042,98044,98046],{"class":202,"line":365},[200,98041,39093],{"class":235},[200,98043,239],{"class":214},[200,98045,2424],{"class":2423},[200,98047,252],{"class":214},[200,98049,98050],{"class":202,"line":371},[200,98051,339],{"class":214},[200,98053,98054,98056],{"class":202,"line":398},[200,98055,5080],{"class":214},[200,98057,416],{"class":235},[200,98059,98060],{"class":202,"line":405},[200,98061,529],{"emptyLinePlaceholder":20},[200,98063,98064,98066],{"class":202,"line":410},[200,98065,3743],{"class":206},[200,98067,39120],{"class":210},[200,98069,98070,98072],{"class":202,"line":861},[200,98071,413],{"class":214},[200,98073,416],{"class":210},[35,98075,39129,98076,39133,98078,3076,98080,33624,98082,215],{},[156,98077,39132],{},[156,98079,39136],{},[156,98081,39139],{},[156,98083,39142],{},[35,98085,39145,98086,33991,98088,39152],{},[6850,98087,39148],{},[6850,98089,39151],{},[35,98091,39155,98092,39161],{},[39,98093,39160],{"href":39158,"rel":98094},[77],[51,98096,39165],{"id":39164},[35,98098,39168,98099,39174],{},[39,98100,98102],{"href":38759,"rel":98101},[77],[6850,98103,38276],{},[35,98105,39177,98106,39180,98108,39184,98110,39188],{},[6850,98107,38945],{},[6850,98109,39183],{},[156,98111,39187],{},[35,98113,39191,98114,39194,98116,39198,98118,39201],{},[6850,98115,38276],{},[156,98117,39197],{},[156,98119,39187],{},[192,98121,98122],{"className":1370,"code":39204,"language":1373,"meta":13,"style":13},[156,98123,98124,98146,98158,98162,98174,98178,98184],{"__ignoreMap":13},[200,98125,98126,98128,98130,98132,98134,98136,98138,98140,98142,98144],{"class":202,"line":203},[200,98127,1380],{"class":206},[200,98129,1383],{"class":206},[200,98131,38960],{"class":223},[200,98133,227],{"class":210},[200,98135,3711],{"class":379},[200,98137,1095],{"class":214},[200,98139,25386],{"class":386},[200,98141,2089],{"class":214},[200,98143,393],{"class":379},[200,98145,280],{"class":214},[200,98147,98148,98150,98152,98154,98156],{"class":202,"line":14},[200,98149,6165],{"class":206},[200,98151,39235],{"class":223},[200,98153,227],{"class":235},[200,98155,25386],{"class":210},[200,98157,416],{"class":235},[200,98159,98160],{"class":202,"line":255},[200,98161,529],{"emptyLinePlaceholder":20},[200,98163,98164,98166,98168,98170,98172],{"class":202,"line":272},[200,98165,3723],{"class":379},[200,98167,39010],{"class":210},[200,98169,510],{"class":214},[200,98171,39256],{"class":214},[200,98173,39259],{"class":401},[200,98175,98176],{"class":202,"line":283},[200,98177,529],{"emptyLinePlaceholder":20},[200,98179,98180,98182],{"class":202,"line":300},[200,98181,3743],{"class":206},[200,98183,39120],{"class":210},[200,98185,98186,98188],{"class":202,"line":310},[200,98187,413],{"class":214},[200,98189,416],{"class":210},[35,98191,39278,98192,39282],{},[6850,98193,39281],{},[51,98195,39286],{"id":39285},[35,98197,39289,98198,39292,98200,39296,98202,39300],{},[156,98199,39197],{},[156,98201,39295],{},[156,98203,39299],{},[192,98205,98206],{"className":1370,"code":39303,"language":1373,"meta":13,"style":13},[156,98207,98208,98264,98282,98296,98300,98324,98334,98352,98374,98380,98384,98388,98398],{"__ignoreMap":13},[200,98209,98210,98212,98214,98216,98218,98220,98222,98224,98226,98228,98230,98232,98234,98236,98238,98240,98242,98244,98246,98248,98250,98252,98254,98256,98258,98260,98262],{"class":202,"line":203},[200,98211,1380],{"class":206},[200,98213,380],{"class":379},[200,98215,4987],{"class":379},[200,98217,39316],{"class":223},[200,98219,227],{"class":214},[200,98221,25386],{"class":386},[200,98223,239],{"class":214},[200,98225,39325],{"class":1978},[200,98227,550],{"class":214},[200,98229,39330],{"class":386},[200,98231,239],{"class":214},[200,98233,318],{"class":214},[200,98235,39337],{"class":235},[200,98237,39340],{"class":214},[200,98239,20132],{"class":1978},[200,98241,550],{"class":214},[200,98243,39347],{"class":235},[200,98245,39340],{"class":214},[200,98247,22708],{"class":1978},[200,98249,981],{"class":214},[200,98251,510],{"class":214},[200,98253,39358],{"class":214},[200,98255,4996],{"class":1978},[200,98257,600],{"class":214},[200,98259,39365],{"class":1978},[200,98261,675],{"class":214},[200,98263,280],{"class":214},[200,98265,98266,98268,98270,98272,98274,98276,98278,98280],{"class":202,"line":14},[200,98267,3723],{"class":379},[200,98269,39376],{"class":210},[200,98271,510],{"class":214},[200,98273,452],{"class":206},[200,98275,39235],{"class":223},[200,98277,227],{"class":235},[200,98279,25386],{"class":210},[200,98281,416],{"class":235},[200,98283,98284,98286,98288,98290,98292,98294],{"class":202,"line":255},[200,98285,3723],{"class":379},[200,98287,318],{"class":214},[200,98289,34776],{"class":210},[200,98291,981],{"class":214},[200,98293,510],{"class":214},[200,98295,39403],{"class":210},[200,98297,98298],{"class":202,"line":272},[200,98299,529],{"emptyLinePlaceholder":20},[200,98301,98302,98304,98306,98308,98310,98312,98314,98316,98318,98320,98322],{"class":202,"line":283},[200,98303,20075],{"class":206},[200,98305,1095],{"class":235},[200,98307,26808],{"class":210},[200,98309,215],{"class":214},[200,98311,39420],{"class":210},[200,98313,13388],{"class":214},[200,98315,242],{"class":214},[200,98317,39427],{"class":245},[200,98319,249],{"class":214},[200,98321,5267],{"class":235},[200,98323,230],{"class":214},[200,98325,98326,98328,98330,98332],{"class":202,"line":300},[200,98327,39438],{"class":206},[200,98329,39441],{"class":223},[200,98331,227],{"class":235},[200,98333,230],{"class":214},[200,98335,98336,98338,98340,98342,98344,98346,98348,98350],{"class":202,"line":310},[200,98337,39450],{"class":235},[200,98339,239],{"class":214},[200,98341,39330],{"class":210},[200,98343,215],{"class":214},[200,98345,39459],{"class":210},[200,98347,15009],{"class":214},[200,98349,39464],{"class":2045},[200,98351,252],{"class":214},[200,98353,98354,98356,98358,98360,98362,98364,98366,98368,98370,98372],{"class":202,"line":336},[200,98355,39471],{"class":235},[200,98357,239],{"class":214},[200,98359,39330],{"class":210},[200,98361,215],{"class":214},[200,98363,29574],{"class":210},[200,98365,15009],{"class":214},[200,98367,242],{"class":214},[200,98369,39486],{"class":245},[200,98371,249],{"class":214},[200,98373,252],{"class":214},[200,98375,98376,98378],{"class":202,"line":342},[200,98377,2096],{"class":214},[200,98379,416],{"class":235},[200,98381,98382],{"class":202,"line":365},[200,98383,4490],{"class":214},[200,98385,98386],{"class":202,"line":371},[200,98387,529],{"emptyLinePlaceholder":20},[200,98389,98390,98392,98394,98396],{"class":202,"line":398},[200,98391,3743],{"class":206},[200,98393,39376],{"class":210},[200,98395,14674],{"class":206},[200,98397,39515],{"class":1978},[200,98399,98400],{"class":202,"line":405},[200,98401,1130],{"class":214},[35,98403,39522,98404,39525,98406,39528],{},[156,98405,39197],{},[156,98407,39295],{},[192,98409,98410],{"className":1370,"code":39531,"language":1373,"meta":13,"style":13},[156,98411,98412,98434,98446,98450,98462,98466,98472],{"__ignoreMap":13},[200,98413,98414,98416,98418,98420,98422,98424,98426,98428,98430,98432],{"class":202,"line":203},[200,98415,1380],{"class":206},[200,98417,1383],{"class":206},[200,98419,38960],{"class":223},[200,98421,227],{"class":210},[200,98423,3711],{"class":379},[200,98425,1095],{"class":214},[200,98427,25386],{"class":386},[200,98429,2089],{"class":214},[200,98431,393],{"class":379},[200,98433,280],{"class":214},[200,98435,98436,98438,98440,98442,98444],{"class":202,"line":14},[200,98437,6165],{"class":206},[200,98439,39316],{"class":223},[200,98441,227],{"class":235},[200,98443,25386],{"class":210},[200,98445,416],{"class":235},[200,98447,98448],{"class":202,"line":255},[200,98449,529],{"emptyLinePlaceholder":20},[200,98451,98452,98454,98456,98458,98460],{"class":202,"line":272},[200,98453,3723],{"class":379},[200,98455,39010],{"class":210},[200,98457,510],{"class":214},[200,98459,39256],{"class":214},[200,98461,39259],{"class":401},[200,98463,98464],{"class":202,"line":283},[200,98465,529],{"emptyLinePlaceholder":20},[200,98467,98468,98470],{"class":202,"line":300},[200,98469,3743],{"class":206},[200,98471,39120],{"class":210},[200,98473,98474,98476],{"class":202,"line":310},[200,98475,413],{"class":214},[200,98477,416],{"class":210},[35,98479,39602,98480,39606,98482,39609],{},[6850,98481,39605],{},[156,98483,39427],{},[35,98485,39612],{},[51,98487,39616],{"id":39615},[35,98489,39619,98490,39623],{},[6850,98491,39622],{},[35,98493,39626,98494,239],{},[156,98495,39629],{},[192,98497,98498],{"className":1370,"code":39632,"language":1373,"meta":13,"style":13},[156,98499,98500,98564,98582,98596,98600,98622,98628,98632,98646,98656,98674,98696,98702,98706,98710,98732,98738,98742,98764,98770,98774,98784,98802,98824,98830],{"__ignoreMap":13},[200,98501,98502,98504,98506,98508,98510,98512,98514,98516,98518,98520,98522,98524,98526,98528,98530,98532,98534,98536,98538,98540,98542,98544,98546,98548,98550,98552,98554,98556,98558,98560,98562],{"class":202,"line":203},[200,98503,1380],{"class":206},[200,98505,380],{"class":379},[200,98507,4987],{"class":379},[200,98509,39645],{"class":223},[200,98511,227],{"class":214},[200,98513,25386],{"class":386},[200,98515,239],{"class":214},[200,98517,39325],{"class":1978},[200,98519,550],{"class":214},[200,98521,39010],{"class":386},[200,98523,239],{"class":214},[200,98525,39662],{"class":1978},[200,98527,550],{"class":214},[200,98529,39330],{"class":386},[200,98531,239],{"class":214},[200,98533,318],{"class":214},[200,98535,39337],{"class":235},[200,98537,39340],{"class":214},[200,98539,20132],{"class":1978},[200,98541,550],{"class":214},[200,98543,39347],{"class":235},[200,98545,39340],{"class":214},[200,98547,22708],{"class":1978},[200,98549,981],{"class":214},[200,98551,510],{"class":214},[200,98553,39358],{"class":214},[200,98555,4996],{"class":1978},[200,98557,600],{"class":214},[200,98559,39365],{"class":1978},[200,98561,675],{"class":214},[200,98563,280],{"class":214},[200,98565,98566,98568,98570,98572,98574,98576,98578,98580],{"class":202,"line":14},[200,98567,3723],{"class":379},[200,98569,39376],{"class":210},[200,98571,510],{"class":214},[200,98573,452],{"class":206},[200,98575,39713],{"class":223},[200,98577,227],{"class":235},[200,98579,25386],{"class":210},[200,98581,416],{"class":235},[200,98583,98584,98586,98588,98590,98592,98594],{"class":202,"line":255},[200,98585,3723],{"class":379},[200,98587,318],{"class":214},[200,98589,34776],{"class":210},[200,98591,981],{"class":214},[200,98593,510],{"class":214},[200,98595,39403],{"class":210},[200,98597,98598],{"class":202,"line":272},[200,98599,529],{"emptyLinePlaceholder":20},[200,98601,98602,98604,98606,98608,98610,98612,98614,98616,98618,98620],{"class":202,"line":283},[200,98603,20075],{"class":206},[200,98605,1095],{"class":235},[200,98607,39746],{"class":210},[200,98609,215],{"class":214},[200,98611,39751],{"class":210},[200,98613,3283],{"class":214},[200,98615,242],{"class":214},[200,98617,39139],{"class":245},[200,98619,249],{"class":214},[200,98621,416],{"class":235},[200,98623,98624,98626],{"class":202,"line":300},[200,98625,3296],{"class":206},[200,98627,39403],{"class":210},[200,98629,98630],{"class":202,"line":310},[200,98631,529],{"emptyLinePlaceholder":20},[200,98633,98634,98636,98638,98640,98642,98644],{"class":202,"line":336},[200,98635,20075],{"class":206},[200,98637,1095],{"class":235},[200,98639,11837],{"class":214},[200,98641,39782],{"class":210},[200,98643,5267],{"class":235},[200,98645,230],{"class":214},[200,98647,98648,98650,98652,98654],{"class":202,"line":342},[200,98649,39438],{"class":206},[200,98651,39441],{"class":223},[200,98653,227],{"class":235},[200,98655,230],{"class":214},[200,98657,98658,98660,98662,98664,98666,98668,98670,98672],{"class":202,"line":365},[200,98659,39450],{"class":235},[200,98661,239],{"class":214},[200,98663,39330],{"class":210},[200,98665,215],{"class":214},[200,98667,39459],{"class":210},[200,98669,15009],{"class":214},[200,98671,39813],{"class":2045},[200,98673,252],{"class":214},[200,98675,98676,98678,98680,98682,98684,98686,98688,98690,98692,98694],{"class":202,"line":371},[200,98677,39471],{"class":235},[200,98679,239],{"class":214},[200,98681,39330],{"class":210},[200,98683,215],{"class":214},[200,98685,29574],{"class":210},[200,98687,15009],{"class":214},[200,98689,242],{"class":214},[200,98691,39486],{"class":245},[200,98693,249],{"class":214},[200,98695,252],{"class":214},[200,98697,98698,98700],{"class":202,"line":398},[200,98699,2096],{"class":214},[200,98701,416],{"class":235},[200,98703,98704],{"class":202,"line":405},[200,98705,4490],{"class":214},[200,98707,98708],{"class":202,"line":410},[200,98709,529],{"emptyLinePlaceholder":20},[200,98711,98712,98714,98716,98718,98720,98722,98724,98726,98728,98730],{"class":202,"line":861},[200,98713,20075],{"class":206},[200,98715,1095],{"class":235},[200,98717,39746],{"class":210},[200,98719,215],{"class":214},[200,98721,39864],{"class":210},[200,98723,3283],{"class":214},[200,98725,34776],{"class":210},[200,98727,215],{"class":214},[200,98729,3280],{"class":210},[200,98731,416],{"class":235},[200,98733,98734,98736],{"class":202,"line":890},[200,98735,3296],{"class":206},[200,98737,39403],{"class":210},[200,98739,98740],{"class":202,"line":900},[200,98741,529],{"emptyLinePlaceholder":20},[200,98743,98744,98746,98748,98750,98752,98754,98756,98758,98760,98762],{"class":202,"line":905},[200,98745,20075],{"class":206},[200,98747,1095],{"class":235},[200,98749,26808],{"class":210},[200,98751,215],{"class":214},[200,98753,39420],{"class":210},[200,98755,3283],{"class":214},[200,98757,242],{"class":214},[200,98759,39427],{"class":245},[200,98761,249],{"class":214},[200,98763,416],{"class":235},[200,98765,98766,98768],{"class":202,"line":931},[200,98767,3296],{"class":206},[200,98769,39403],{"class":210},[200,98771,98772],{"class":202,"line":3640},[200,98773,529],{"emptyLinePlaceholder":20},[200,98775,98776,98778,98780,98782],{"class":202,"line":3645},[200,98777,20264],{"class":206},[200,98779,39441],{"class":223},[200,98781,227],{"class":235},[200,98783,230],{"class":214},[200,98785,98786,98788,98790,98792,98794,98796,98798,98800],{"class":202,"line":4020},[200,98787,39931],{"class":235},[200,98789,239],{"class":214},[200,98791,39330],{"class":210},[200,98793,215],{"class":214},[200,98795,39459],{"class":210},[200,98797,15009],{"class":214},[200,98799,39464],{"class":2045},[200,98801,252],{"class":214},[200,98803,98804,98806,98808,98810,98812,98814,98816,98818,98820,98822],{"class":202,"line":4475},[200,98805,39950],{"class":235},[200,98807,239],{"class":214},[200,98809,39330],{"class":210},[200,98811,215],{"class":214},[200,98813,29574],{"class":210},[200,98815,15009],{"class":214},[200,98817,242],{"class":214},[200,98819,39965],{"class":245},[200,98821,249],{"class":214},[200,98823,252],{"class":214},[200,98825,98826,98828],{"class":202,"line":4481},[200,98827,5080],{"class":214},[200,98829,416],{"class":235},[200,98831,98832],{"class":202,"line":4487},[200,98833,1130],{"class":214},[35,98835,39982],{},[35,98837,39985,98838,39989,98840,39993,98842,1638,98844,215],{},[6850,98839,39988],{},[39,98841,39992],{"href":38821},[156,98843,39197],{},[156,98845,39998],{},[35,98847,40001],{},[1576,98849,98850,98854,98858],{},[71,98851,98852,40009],{},[6850,98853,40008],{},[71,98855,98856,40009],{},[6850,98857,40014],{},[71,98859,98860,40009],{},[6850,98861,40019],{},[35,98863,40022,98864,40025],{},[156,98865,40019],{},[35,98867,40028,98868,215],{},[6850,98869,40031],{},[35,98871,40034,98872,215],{},[39,98873,40038],{"href":38839,"rel":98874},[77],[51,98876,40042],{"id":40041},[35,98878,40045,98879,40048,98881,215],{},[6850,98880,38904],{},[39,98882,98884],{"href":38759,"rel":98883},[77],[6850,98885,38276],{},[35,98887,40056,98888],{},[1582,98889,40060],{"repo":40059},[35,98891,40063,98892,215],{},[39,98893,38483],{"href":40066,"rel":98894},[77],[1913,98896,40071],{"id":40070},[35,98898,40074,98899,40077],{},[6850,98900,40031],{},[35,98902,40080],{},[35,98904,40083,98905,40087],{},[6850,98906,40086],{},[35,98908,40090,98909,13731,98911,40095,98913,40098,98915,40101,98917,215],{},[156,98910,40008],{},[156,98912,40014],{},[156,98914,40019],{},[156,98916,40019],{},[156,98918,39299],{},[35,98920,40106],{},[1913,98922,40110],{"id":40109},[35,98924,40113,98925,36817,98927,215],{},[6850,98926,40116],{},[6850,98928,40119],{},[35,98930,40122,98931,40125],{},[6850,98932,40086],{},[192,98934,98935],{"className":1370,"code":40128,"language":1373,"meta":13,"style":13},[156,98936,98937,98973,98991],{"__ignoreMap":13},[200,98938,98939,98941,98943,98945,98947,98949,98951,98953,98955,98957,98959,98961,98963,98965,98967,98969,98971],{"class":202,"line":203},[200,98940,1380],{"class":206},[200,98942,14301],{"class":379},[200,98944,40139],{"class":210},[200,98946,449],{"class":214},[200,98948,40144],{"class":223},[200,98950,227],{"class":210},[200,98952,227],{"class":214},[200,98954,26808],{"class":386},[200,98956,239],{"class":214},[200,98958,34877],{"class":1978},[200,98960,550],{"class":214},[200,98962,40159],{"class":386},[200,98964,239],{"class":214},[200,98966,40164],{"class":1978},[200,98968,2089],{"class":214},[200,98970,393],{"class":379},[200,98972,280],{"class":214},[200,98974,98975,98977,98979,98981,98983,98985,98987,98989],{"class":202,"line":14},[200,98976,3743],{"class":206},[200,98978,34776],{"class":210},[200,98980,215],{"class":214},[200,98982,3280],{"class":210},[200,98984,3283],{"class":214},[200,98986,40159],{"class":210},[200,98988,215],{"class":214},[200,98990,40189],{"class":210},[200,98992,98993,98995],{"class":202,"line":255},[200,98994,413],{"class":214},[200,98996,416],{"class":210},[35,98998,40198],{},[192,99000,99001],{"className":1370,"code":40201,"language":1373,"meta":13,"style":13},[156,99002,99003,99035,99041],{"__ignoreMap":13},[200,99004,99005,99007,99009,99011,99013,99015,99017,99019,99021,99023,99025,99027,99029,99031,99033],{"class":202,"line":203},[200,99006,1380],{"class":206},[200,99008,14301],{"class":379},[200,99010,40212],{"class":210},[200,99012,449],{"class":214},[200,99014,40144],{"class":223},[200,99016,227],{"class":210},[200,99018,227],{"class":214},[200,99020,26808],{"class":386},[200,99022,239],{"class":214},[200,99024,34877],{"class":1978},[200,99026,40229],{"class":214},[200,99028,12422],{"class":1978},[200,99030,2089],{"class":214},[200,99032,393],{"class":379},[200,99034,280],{"class":214},[200,99036,99037,99039],{"class":202,"line":14},[200,99038,3743],{"class":206},[200,99040,22192],{"class":2423},[200,99042,99043,99045],{"class":202,"line":255},[200,99044,413],{"class":214},[200,99046,416],{"class":210},[35,99048,40252],{},[35,99050,419,99051,40258],{},[156,99052,40257],{},[35,99054,40261,99055,40264],{},[156,99056,40019],{},[192,99058,99059],{"className":1370,"code":40267,"language":1373,"meta":13,"style":13},[156,99060,99061,99083,99099,99103,99129,99133,99139],{"__ignoreMap":13},[200,99062,99063,99065,99067,99069,99071,99073,99075,99077,99079,99081],{"class":202,"line":203},[200,99064,1380],{"class":206},[200,99066,1383],{"class":206},[200,99068,38960],{"class":223},[200,99070,227],{"class":210},[200,99072,3711],{"class":379},[200,99074,1095],{"class":214},[200,99076,25386],{"class":386},[200,99078,2089],{"class":214},[200,99080,393],{"class":379},[200,99082,280],{"class":214},[200,99084,99085,99087,99089,99091,99093,99095,99097],{"class":202,"line":14},[200,99086,6165],{"class":206},[200,99088,40298],{"class":223},[200,99090,227],{"class":235},[200,99092,25386],{"class":210},[200,99094,550],{"class":214},[200,99096,40307],{"class":210},[200,99098,416],{"class":235},[200,99100,99101],{"class":202,"line":255},[200,99102,529],{"emptyLinePlaceholder":20},[200,99104,99105,99107,99109,99111,99113,99115,99117,99119,99121,99123,99125,99127],{"class":202,"line":272},[200,99106,3723],{"class":379},[200,99108,40320],{"class":210},[200,99110,510],{"class":214},[200,99112,452],{"class":206},[200,99114,39017],{"class":210},[200,99116,215],{"class":214},[200,99118,3509],{"class":210},[200,99120,215],{"class":214},[200,99122,40335],{"class":210},[200,99124,215],{"class":214},[200,99126,40340],{"class":223},[200,99128,466],{"class":235},[200,99130,99131],{"class":202,"line":283},[200,99132,529],{"emptyLinePlaceholder":20},[200,99134,99135,99137],{"class":202,"line":300},[200,99136,3743],{"class":206},[200,99138,40353],{"class":210},[200,99140,99141,99143],{"class":202,"line":310},[200,99142,413],{"class":214},[200,99144,416],{"class":210},[35,99146,419,99147,40364,99149,40367,99151,13731,99153,40374],{},[156,99148,40019],{},[156,99150,39299],{},[156,99152,40370],{},[156,99154,40373],{},[192,99156,99157],{"className":1370,"code":40377,"language":1373,"meta":13,"style":13},[156,99158,99159,99181,99205,99211,99215,99241,99245,99251],{"__ignoreMap":13},[200,99160,99161,99163,99165,99167,99169,99171,99173,99175,99177,99179],{"class":202,"line":203},[200,99162,1380],{"class":206},[200,99164,1383],{"class":206},[200,99166,38960],{"class":223},[200,99168,227],{"class":210},[200,99170,3711],{"class":379},[200,99172,1095],{"class":214},[200,99174,25386],{"class":386},[200,99176,2089],{"class":214},[200,99178,393],{"class":379},[200,99180,280],{"class":214},[200,99182,99183,99185,99187,99189,99191,99193,99195,99197,99199,99201,99203],{"class":202,"line":14},[200,99184,20075],{"class":206},[200,99186,1095],{"class":235},[200,99188,207],{"class":206},[200,99190,40412],{"class":223},[200,99192,227],{"class":235},[200,99194,25386],{"class":210},[200,99196,550],{"class":214},[200,99198,40307],{"class":210},[200,99200,550],{"class":214},[200,99202,40159],{"class":210},[200,99204,585],{"class":235},[200,99206,99207,99209],{"class":202,"line":255},[200,99208,3296],{"class":206},[200,99210,6421],{"class":235},[200,99212,99213],{"class":202,"line":272},[200,99214,529],{"emptyLinePlaceholder":20},[200,99216,99217,99219,99221,99223,99225,99227,99229,99231,99233,99235,99237,99239],{"class":202,"line":283},[200,99218,3723],{"class":379},[200,99220,40320],{"class":210},[200,99222,510],{"class":214},[200,99224,452],{"class":206},[200,99226,39017],{"class":210},[200,99228,215],{"class":214},[200,99230,3509],{"class":210},[200,99232,215],{"class":214},[200,99234,40335],{"class":210},[200,99236,215],{"class":214},[200,99238,40340],{"class":223},[200,99240,466],{"class":235},[200,99242,99243],{"class":202,"line":300},[200,99244,529],{"emptyLinePlaceholder":20},[200,99246,99247,99249],{"class":202,"line":310},[200,99248,3743],{"class":206},[200,99250,40353],{"class":210},[200,99252,99253,99255],{"class":202,"line":336},[200,99254,413],{"class":214},[200,99256,416],{"class":210},[35,99258,40481],{},[35,99260,40484,99261,40488],{},[156,99262,40487],{},[35,99264,40491,99265,1638,99267,40374],{},[156,99266,40008],{},[156,99268,40014],{},[192,99270,99271],{"className":1370,"code":40498,"language":1373,"meta":13,"style":13},[156,99272,99273,99309,99333,99343,99353,99367,99373,99377,99381,99389],{"__ignoreMap":13},[200,99274,99275,99277,99279,99281,99283,99285,99287,99289,99291,99293,99295,99297,99299,99301,99303,99305,99307],{"class":202,"line":203},[200,99276,1380],{"class":206},[200,99278,14301],{"class":379},[200,99280,40509],{"class":210},[200,99282,449],{"class":214},[200,99284,40144],{"class":223},[200,99286,227],{"class":210},[200,99288,227],{"class":214},[200,99290,26808],{"class":386},[200,99292,239],{"class":214},[200,99294,34877],{"class":1978},[200,99296,550],{"class":214},[200,99298,40159],{"class":386},[200,99300,239],{"class":214},[200,99302,40164],{"class":1978},[200,99304,2089],{"class":214},[200,99306,393],{"class":379},[200,99308,280],{"class":214},[200,99310,99311,99313,99315,99317,99319,99321,99323,99325,99327,99329,99331],{"class":202,"line":14},[200,99312,20075],{"class":206},[200,99314,1095],{"class":235},[200,99316,40546],{"class":210},[200,99318,215],{"class":214},[200,99320,39751],{"class":210},[200,99322,3283],{"class":214},[200,99324,242],{"class":214},[200,99326,39136],{"class":245},[200,99328,249],{"class":214},[200,99330,5267],{"class":235},[200,99332,230],{"class":214},[200,99334,99335,99337,99339,99341],{"class":202,"line":255},[200,99336,3296],{"class":206},[200,99338,40569],{"class":223},[200,99340,227],{"class":235},[200,99342,230],{"class":214},[200,99344,99345,99347,99349,99351],{"class":202,"line":272},[200,99346,39450],{"class":235},[200,99348,239],{"class":214},[200,99350,40582],{"class":2045},[200,99352,252],{"class":214},[200,99354,99355,99357,99359,99361,99363,99365],{"class":202,"line":283},[200,99356,39471],{"class":235},[200,99358,239],{"class":214},[200,99360,242],{"class":214},[200,99362,40595],{"class":245},[200,99364,249],{"class":214},[200,99366,252],{"class":214},[200,99368,99369,99371],{"class":202,"line":300},[200,99370,2096],{"class":214},[200,99372,416],{"class":235},[200,99374,99375],{"class":202,"line":310},[200,99376,4490],{"class":214},[200,99378,99379],{"class":202,"line":336},[200,99380,529],{"emptyLinePlaceholder":20},[200,99382,99383,99385,99387],{"class":202,"line":342},[200,99384,3743],{"class":206},[200,99386,40620],{"class":223},[200,99388,466],{"class":235},[200,99390,99391,99393],{"class":202,"line":365},[200,99392,413],{"class":214},[200,99394,416],{"class":210},[35,99396,36049,99397,40633,99399,40636,99401,215],{},[156,99398,40019],{},[156,99400,40487],{},[156,99402,39299],{},[35,99404,40641,99405,3076,99407,3080,99409,40648,99411,215],{},[156,99406,40370],{},[156,99408,40373],{},[156,99410,40019],{},[6850,99412,40086],{},[35,99414,40653,99415,1638,99417,40660,99419,215],{},[156,99416,40656],{},[156,99418,40659],{},[6850,99420,40086],{},[192,99422,99423],{"className":21417,"code":40665,"language":12060,"meta":13,"style":13},[156,99424,99425,99433,99469,99485,99493],{"__ignoreMap":13},[200,99426,99427,99429,99431],{"class":202,"line":203},[200,99428,600],{"class":214},[200,99430,21428],{"class":235},[200,99432,648],{"class":214},[200,99434,99435,99437,99439,99441,99443,99445,99447,99449,99451,99453,99455,99457,99459,99461,99463,99465,99467],{"class":202,"line":14},[200,99436,657],{"class":214},[200,99438,40656],{"class":235},[200,99440,28009],{"class":214},[200,99442,40116],{"class":379},[200,99444,449],{"class":214},[200,99446,611],{"class":214},[200,99448,40692],{"class":210},[200,99450,611],{"class":214},[200,99452,28009],{"class":214},[200,99454,28625],{"class":379},[200,99456,449],{"class":214},[200,99458,611],{"class":214},[200,99460,9858],{"class":214},[200,99462,40546],{"class":210},[200,99464,360],{"class":214},[200,99466,611],{"class":214},[200,99468,648],{"class":214},[200,99470,99471,99473,99475,99477,99479,99481,99483],{"class":202,"line":255},[200,99472,804],{"class":214},[200,99474,910],{"class":235},[200,99476,675],{"class":214},[200,99478,40723],{"class":210},[200,99480,681],{"class":214},[200,99482,910],{"class":235},[200,99484,648],{"class":214},[200,99486,99487,99489,99491],{"class":202,"line":272},[200,99488,893],{"class":214},[200,99490,40656],{"class":235},[200,99492,648],{"class":214},[200,99494,99495,99497,99499],{"class":202,"line":283},[200,99496,681],{"class":214},[200,99498,21428],{"class":235},[200,99500,648],{"class":214},[35,99502,40748,99503],{},[1582,99504,215],{"repo":40059},[1913,99506,24102],{"id":24101},[35,99508,40755,99509,40761],{},[39,99510,40760],{"href":40758,"rel":99511},[77],[35,99513,40764],{},[35,99515,40767],{},[35,99517,40770,99518,40773,99520,40777],{},[6850,99519,38904],{},[6850,99521,40776],{},[35,99523,38862],{},[1171,99525,40782],{},{"title":13,"searchDepth":14,"depth":14,"links":99527},[99528,99529,99530,99531,99532],{"id":29348,"depth":14,"text":29349},{"id":39164,"depth":14,"text":39165},{"id":39285,"depth":14,"text":39286},{"id":39615,"depth":14,"text":39616},{"id":40041,"depth":14,"text":40042,"children":99533},[99534,99535,99536],{"id":40070,"depth":255,"text":40071},{"id":40109,"depth":255,"text":40110},{"id":24101,"depth":255,"text":24102},{},{"title":38826,"description":40795},{"loc":40797},{"id":40804,"title":40805,"body":99541,"contentId":30850,"date":45981,"description":45982,"extension":18,"meta":104448,"navigation":20,"path":45984,"rawbody":45985,"robots":23,"schemaOrg":23,"seo":104449,"sitemap":104450,"stem":45988,"__hash__":45989},{"type":7,"value":99542,"toc":104442},[99543,99550,99554,104440],[35,99544,40810,99545,40814,99548,40818],{},[39,99546,36822],{"href":38142,"rel":99547},[77],[6850,99549,40817],{},[35,99551,99552],{},[1217,99553,40823],{},[7905,99555,99556,99563,99567,99569,99579,99585,99587,99589,99705,99707,99785,99789,99793,99795,99804,99942,99946],{"id":30840},[35,99557,99558],{},[1217,99559,40830,99560,40834],{},[39,99561,36822],{"href":38142,"rel":99562},[77],[35,99564,40837,99565],{},[1582,99566,215],{"repo":40840},[51,99568,18635],{"id":18634},[35,99570,40845,99571,40849,99573,40853,99575,1638,99577,40860],{},[6850,99572,40848],{},[6850,99574,40852],{},[6850,99576,40856],{},[6850,99578,40859],{},[35,99580,40863,99581,40867,99583,40871],{},[1217,99582,40866],{},[6850,99584,40870],{},[51,99586,40875],{"id":40874},[35,99588,40878],{},[192,99590,99591],{"className":21417,"code":40881,"language":12060,"meta":13,"style":13},[156,99592,99593,99601,99629,99659,99677,99681,99689,99697],{"__ignoreMap":13},[200,99594,99595,99597,99599],{"class":202,"line":203},[200,99596,600],{"class":214},[200,99598,21428],{"class":235},[200,99600,648],{"class":214},[200,99602,99603,99605,99607,99609,99611,99613,99615,99617,99619,99621,99623,99625,99627],{"class":202,"line":14},[200,99604,657],{"class":214},[200,99606,603],{"class":235},[200,99608,6577],{"class":379},[200,99610,449],{"class":214},[200,99612,611],{"class":214},[200,99614,3778],{"class":245},[200,99616,611],{"class":214},[200,99618,40910],{"class":379},[200,99620,449],{"class":214},[200,99622,611],{"class":214},[200,99624,40917],{"class":245},[200,99626,611],{"class":214},[200,99628,648],{"class":214},[200,99630,99631,99633,99635,99637,99639,99641,99643,99645,99647,99649,99651,99653,99655,99657],{"class":202,"line":255},[200,99632,804],{"class":214},[200,99634,355],{"class":235},[200,99636,321],{"class":379},[200,99638,449],{"class":214},[200,99640,611],{"class":214},[200,99642,698],{"class":245},[200,99644,611],{"class":214},[200,99646,40940],{"class":379},[200,99648,449],{"class":214},[200,99650,611],{"class":214},[200,99652,40947],{"class":245},[200,99654,611],{"class":214},[200,99656,40952],{"class":379},[200,99658,648],{"class":214},[200,99660,99661,99663,99665,99667,99669,99671,99673,99675],{"class":202,"line":272},[200,99662,804],{"class":214},[200,99664,910],{"class":235},[200,99666,321],{"class":379},[200,99668,449],{"class":214},[200,99670,611],{"class":214},[200,99672,917],{"class":245},[200,99674,611],{"class":214},[200,99676,648],{"class":214},[200,99678,99679],{"class":202,"line":283},[200,99680,40977],{"class":210},[200,99682,99683,99685,99687],{"class":202,"line":300},[200,99684,15074],{"class":214},[200,99686,910],{"class":235},[200,99688,648],{"class":214},[200,99690,99691,99693,99695],{"class":202,"line":310},[200,99692,893],{"class":214},[200,99694,603],{"class":235},[200,99696,648],{"class":214},[200,99698,99699,99701,99703],{"class":202,"line":336},[200,99700,681],{"class":214},[200,99702,21428],{"class":235},[200,99704,648],{"class":214},[35,99706,41004],{},[192,99708,99709],{"className":1370,"code":41007,"language":1373,"meta":13,"style":13},[156,99710,99711,99733,99751,99755,99759,99763,99779],{"__ignoreMap":13},[200,99712,99713,99715,99717,99719,99721,99723,99725,99727,99729,99731],{"class":202,"line":203},[200,99714,1380],{"class":206},[200,99716,1383],{"class":206},[200,99718,38960],{"class":223},[200,99720,227],{"class":210},[200,99722,3711],{"class":379},[200,99724,1095],{"class":214},[200,99726,25386],{"class":386},[200,99728,2089],{"class":214},[200,99730,393],{"class":379},[200,99732,280],{"class":214},[200,99734,99735,99737,99739,99741,99743,99745,99747,99749],{"class":202,"line":14},[200,99736,3723],{"class":379},[200,99738,41038],{"class":210},[200,99740,510],{"class":214},[200,99742,452],{"class":206},[200,99744,41045],{"class":223},[200,99746,227],{"class":235},[200,99748,25386],{"class":210},[200,99750,416],{"class":235},[200,99752,99753],{"class":202,"line":255},[200,99754,529],{"emptyLinePlaceholder":20},[200,99756,99757],{"class":202,"line":272},[200,99758,41060],{"class":401},[200,99760,99761],{"class":202,"line":283},[200,99762,529],{"emptyLinePlaceholder":20},[200,99764,99765,99767,99769,99771,99773,99775,99777],{"class":202,"line":300},[200,99766,3743],{"class":206},[200,99768,41071],{"class":223},[200,99770,227],{"class":235},[200,99772,25386],{"class":210},[200,99774,550],{"class":214},[200,99776,41080],{"class":2045},[200,99778,416],{"class":235},[200,99780,99781,99783],{"class":202,"line":310},[200,99782,413],{"class":214},[200,99784,416],{"class":210},[35,99786,41091,99787],{},[1217,99788,41094],{},[35,99790,99791],{},[1217,99792,41099],{},[1913,99794,41103],{"id":41102},[35,99796,41106,99797,106,99800,41115,99802,215],{},[39,99798,41111],{"href":41109,"rel":99799},[77],[1217,99801,41114],{},[156,99803,41118],{},[192,99805,99806],{"className":1370,"code":41121,"language":1373,"meta":13,"style":13},[156,99807,99808,99826,99830,99852,99880,99896,99908,99912,99916,99920,99936],{"__ignoreMap":13},[200,99809,99810,99812,99814,99816,99818,99820,99822,99824],{"class":202,"line":203},[200,99811,973],{"class":206},[200,99813,318],{"class":214},[200,99815,3165],{"class":210},[200,99817,981],{"class":214},[200,99819,984],{"class":206},[200,99821,242],{"class":214},[200,99823,19790],{"class":245},[200,99825,992],{"class":214},[200,99827,99828],{"class":202,"line":14},[200,99829,529],{"emptyLinePlaceholder":20},[200,99831,99832,99834,99836,99838,99840,99842,99844,99846,99848,99850],{"class":202,"line":255},[200,99833,1380],{"class":206},[200,99835,1383],{"class":206},[200,99837,38960],{"class":223},[200,99839,227],{"class":210},[200,99841,3711],{"class":379},[200,99843,1095],{"class":214},[200,99845,25386],{"class":386},[200,99847,2089],{"class":214},[200,99849,393],{"class":379},[200,99851,280],{"class":214},[200,99853,99854,99856,99858,99860,99862,99864,99866,99868,99870,99872,99874,99876,99878],{"class":202,"line":272},[200,99855,3723],{"class":379},[200,99857,41038],{"class":210},[200,99859,510],{"class":214},[200,99861,452],{"class":206},[200,99863,41180],{"class":223},[200,99865,227],{"class":235},[200,99867,25386],{"class":210},[200,99869,550],{"class":214},[200,99871,3165],{"class":210},[200,99873,215],{"class":214},[200,99875,293],{"class":223},[200,99877,227],{"class":235},[200,99879,230],{"class":214},[200,99881,99882,99884,99886,99888,99890,99892,99894],{"class":202,"line":283},[200,99883,11797],{"class":235},[200,99885,239],{"class":214},[200,99887,3165],{"class":210},[200,99889,215],{"class":214},[200,99891,328],{"class":223},[200,99893,1532],{"class":235},[200,99895,252],{"class":214},[200,99897,99898,99900,99902,99904,99906],{"class":202,"line":300},[200,99899,5080],{"class":214},[200,99901,2089],{"class":235},[200,99903,215],{"class":214},[200,99905,41223],{"class":210},[200,99907,416],{"class":235},[200,99909,99910],{"class":202,"line":310},[200,99911,529],{"emptyLinePlaceholder":20},[200,99913,99914],{"class":202,"line":336},[200,99915,41060],{"class":401},[200,99917,99918],{"class":202,"line":342},[200,99919,529],{"emptyLinePlaceholder":20},[200,99921,99922,99924,99926,99928,99930,99932,99934],{"class":202,"line":365},[200,99923,3743],{"class":206},[200,99925,41071],{"class":223},[200,99927,227],{"class":235},[200,99929,25386],{"class":210},[200,99931,550],{"class":214},[200,99933,41080],{"class":2045},[200,99935,416],{"class":235},[200,99937,99938,99940],{"class":202,"line":371},[200,99939,413],{"class":214},[200,99941,416],{"class":210},[35,99943,41262,99944,41265],{},[156,99945,41118],{},[7139,99947,99948,99950,99954,100204,100208,100480,100486,100488,100490,100960,100973,100975,100977,100979,100985,101069,101075,101077,101079,101169,101171,101279,101281,101283,101287,101291,101293,101295,101421,101427,101437,101893,101895,101899,102141,102143,102555,102559,102561,102651,102653,102659,102833,102835,102837,102841,102843,103019,103021,103311,103313,103317,103655,103659,103919,103925,104267,104271,104425,104427,104436,104438],{"icon":41268,"text":41269,"to":41270},[35,99949,41273],{},[35,99951,41276,99952,41280],{},[156,99953,41279],{},[192,99955,99956],{"className":1370,"code":41283,"language":1373,"meta":13,"style":13},[156,99957,99958,99976,99980,100002,100030,100046,100058,100062,100080,100090,100100,100114,100142,100148,100152,100156,100170,100174,100178,100182,100198],{"__ignoreMap":13},[200,99959,99960,99962,99964,99966,99968,99970,99972,99974],{"class":202,"line":203},[200,99961,973],{"class":206},[200,99963,318],{"class":214},[200,99965,3165],{"class":210},[200,99967,981],{"class":214},[200,99969,984],{"class":206},[200,99971,242],{"class":214},[200,99973,19790],{"class":245},[200,99975,992],{"class":214},[200,99977,99978],{"class":202,"line":14},[200,99979,529],{"emptyLinePlaceholder":20},[200,99981,99982,99984,99986,99988,99990,99992,99994,99996,99998,100000],{"class":202,"line":255},[200,99983,1380],{"class":206},[200,99985,1383],{"class":206},[200,99987,38960],{"class":223},[200,99989,227],{"class":210},[200,99991,3711],{"class":379},[200,99993,1095],{"class":214},[200,99995,25386],{"class":386},[200,99997,2089],{"class":214},[200,99999,393],{"class":379},[200,100001,280],{"class":214},[200,100003,100004,100006,100008,100010,100012,100014,100016,100018,100020,100022,100024,100026,100028],{"class":202,"line":272},[200,100005,3723],{"class":379},[200,100007,3546],{"class":210},[200,100009,510],{"class":214},[200,100011,452],{"class":206},[200,100013,41180],{"class":223},[200,100015,227],{"class":235},[200,100017,25386],{"class":210},[200,100019,550],{"class":214},[200,100021,3165],{"class":210},[200,100023,215],{"class":214},[200,100025,293],{"class":223},[200,100027,227],{"class":235},[200,100029,230],{"class":214},[200,100031,100032,100034,100036,100038,100040,100042,100044],{"class":202,"line":283},[200,100033,11797],{"class":235},[200,100035,239],{"class":214},[200,100037,3165],{"class":210},[200,100039,215],{"class":214},[200,100041,328],{"class":223},[200,100043,1532],{"class":235},[200,100045,252],{"class":214},[200,100047,100048,100050,100052,100054,100056],{"class":202,"line":300},[200,100049,5080],{"class":214},[200,100051,2089],{"class":235},[200,100053,215],{"class":214},[200,100055,41279],{"class":210},[200,100057,416],{"class":235},[200,100059,100060],{"class":202,"line":310},[200,100061,529],{"emptyLinePlaceholder":20},[200,100063,100064,100066,100068,100070,100072,100074,100076,100078],{"class":202,"line":336},[200,100065,20075],{"class":206},[200,100067,1095],{"class":235},[200,100069,11837],{"class":214},[200,100071,3531],{"class":210},[200,100073,215],{"class":214},[200,100075,41404],{"class":210},[200,100077,5267],{"class":235},[200,100079,230],{"class":214},[200,100081,100082,100084,100086,100088],{"class":202,"line":342},[200,100083,39438],{"class":206},[200,100085,39441],{"class":223},[200,100087,227],{"class":235},[200,100089,230],{"class":214},[200,100091,100092,100094,100096,100098],{"class":202,"line":365},[200,100093,39082],{"class":235},[200,100095,239],{"class":214},[200,100097,41427],{"class":2045},[200,100099,252],{"class":214},[200,100101,100102,100104,100106,100108,100110,100112],{"class":202,"line":371},[200,100103,39471],{"class":235},[200,100105,239],{"class":214},[200,100107,242],{"class":214},[200,100109,41440],{"class":245},[200,100111,249],{"class":214},[200,100113,252],{"class":214},[200,100115,100116,100118,100120,100122,100124,100126,100128,100130,100132,100134,100136,100138,100140],{"class":202,"line":398},[200,100117,41449],{"class":235},[200,100119,239],{"class":214},[200,100121,3546],{"class":210},[200,100123,215],{"class":214},[200,100125,19260],{"class":210},[200,100127,215],{"class":214},[200,100129,26789],{"class":210},[200,100131,9858],{"class":235},[200,100133,3933],{"class":2045},[200,100135,360],{"class":235},[200,100137,215],{"class":214},[200,100139,29574],{"class":210},[200,100141,252],{"class":214},[200,100143,100144,100146],{"class":202,"line":405},[200,100145,2096],{"class":214},[200,100147,416],{"class":235},[200,100149,100150],{"class":202,"line":410},[200,100151,4490],{"class":214},[200,100153,100154],{"class":202,"line":861},[200,100155,529],{"emptyLinePlaceholder":20},[200,100157,100158,100160,100162,100164,100166,100168],{"class":202,"line":890},[200,100159,3723],{"class":379},[200,100161,41038],{"class":210},[200,100163,510],{"class":214},[200,100165,3546],{"class":210},[200,100167,215],{"class":214},[200,100169,3750],{"class":210},[200,100171,100172],{"class":202,"line":900},[200,100173,529],{"emptyLinePlaceholder":20},[200,100175,100176],{"class":202,"line":905},[200,100177,41060],{"class":401},[200,100179,100180],{"class":202,"line":931},[200,100181,529],{"emptyLinePlaceholder":20},[200,100183,100184,100186,100188,100190,100192,100194,100196],{"class":202,"line":3640},[200,100185,3743],{"class":206},[200,100187,41071],{"class":223},[200,100189,227],{"class":235},[200,100191,25386],{"class":210},[200,100193,550],{"class":214},[200,100195,41080],{"class":2045},[200,100197,416],{"class":235},[200,100199,100200,100202],{"class":202,"line":3645},[200,100201,413],{"class":214},[200,100203,416],{"class":210},[35,100205,41538,100206,41542],{},[156,100207,41541],{},[192,100209,100210],{"className":1370,"code":41545,"language":1373,"meta":13,"style":13},[156,100211,100212,100232,100250,100254,100276,100308,100324,100356,100372,100382,100392,100406,100430,100436,100440,100446,100450,100454,100458,100474],{"__ignoreMap":13},[200,100213,100214,100216,100218,100220,100222,100224,100226,100228,100230],{"class":202,"line":203},[200,100215,973],{"class":206},[200,100217,321],{"class":206},[200,100219,318],{"class":214},[200,100221,41558],{"class":210},[200,100223,981],{"class":214},[200,100225,984],{"class":206},[200,100227,242],{"class":214},[200,100229,19790],{"class":245},[200,100231,992],{"class":214},[200,100233,100234,100236,100238,100240,100242,100244,100246,100248],{"class":202,"line":14},[200,100235,973],{"class":206},[200,100237,318],{"class":214},[200,100239,3165],{"class":210},[200,100241,981],{"class":214},[200,100243,984],{"class":206},[200,100245,242],{"class":214},[200,100247,19790],{"class":245},[200,100249,992],{"class":214},[200,100251,100252],{"class":202,"line":255},[200,100253,529],{"emptyLinePlaceholder":20},[200,100255,100256,100258,100260,100262,100264,100266,100268,100270,100272,100274],{"class":202,"line":272},[200,100257,1380],{"class":206},[200,100259,1383],{"class":206},[200,100261,38960],{"class":223},[200,100263,227],{"class":210},[200,100265,3711],{"class":379},[200,100267,1095],{"class":214},[200,100269,25386],{"class":386},[200,100271,2089],{"class":214},[200,100273,393],{"class":379},[200,100275,280],{"class":214},[200,100277,100278,100280,100282,100284,100286,100288,100290,100292,100294,100296,100298,100300,100302,100304,100306],{"class":202,"line":283},[200,100279,3723],{"class":379},[200,100281,41038],{"class":210},[200,100283,510],{"class":214},[200,100285,452],{"class":206},[200,100287,41180],{"class":223},[200,100289,227],{"class":235},[200,100291,25386],{"class":210},[200,100293,550],{"class":214},[200,100295,5041],{"class":386},[200,100297,393],{"class":379},[200,100299,3165],{"class":210},[200,100301,215],{"class":214},[200,100303,293],{"class":223},[200,100305,227],{"class":235},[200,100307,230],{"class":214},[200,100309,100310,100312,100314,100316,100318,100320,100322],{"class":202,"line":300},[200,100311,11797],{"class":235},[200,100313,239],{"class":214},[200,100315,3165],{"class":210},[200,100317,215],{"class":214},[200,100319,328],{"class":223},[200,100321,1532],{"class":235},[200,100323,252],{"class":214},[200,100325,100326,100328,100330,100332,100334,100336,100338,100340,100342,100344,100346,100348,100350,100352,100354],{"class":202,"line":310},[200,100327,5080],{"class":214},[200,100329,2089],{"class":235},[200,100331,215],{"class":214},[200,100333,41541],{"class":223},[200,100335,227],{"class":235},[200,100337,12726],{"class":210},[200,100339,2089],{"class":235},[200,100341,215],{"class":214},[200,100343,41681],{"class":223},[200,100345,227],{"class":235},[200,100347,227],{"class":214},[200,100349,19260],{"class":386},[200,100351,2089],{"class":214},[200,100353,393],{"class":379},[200,100355,280],{"class":214},[200,100357,100358,100360,100362,100364,100366,100368,100370],{"class":202,"line":336},[200,100359,5252],{"class":206},[200,100361,1095],{"class":235},[200,100363,19260],{"class":210},[200,100365,41704],{"class":214},[200,100367,41558],{"class":1978},[200,100369,5267],{"class":235},[200,100371,230],{"class":214},[200,100373,100374,100376,100378,100380],{"class":202,"line":342},[200,100375,20227],{"class":206},[200,100377,39441],{"class":223},[200,100379,227],{"class":235},[200,100381,230],{"class":214},[200,100383,100384,100386,100388,100390],{"class":202,"line":365},[200,100385,41725],{"class":235},[200,100387,239],{"class":214},[200,100389,41427],{"class":2045},[200,100391,252],{"class":214},[200,100393,100394,100396,100398,100400,100402,100404],{"class":202,"line":371},[200,100395,41736],{"class":235},[200,100397,239],{"class":214},[200,100399,242],{"class":214},[200,100401,41440],{"class":245},[200,100403,249],{"class":214},[200,100405,252],{"class":214},[200,100407,100408,100410,100412,100414,100416,100418,100420,100422,100424,100426,100428],{"class":202,"line":398},[200,100409,41751],{"class":235},[200,100411,239],{"class":214},[200,100413,19205],{"class":210},[200,100415,215],{"class":214},[200,100417,26789],{"class":210},[200,100419,9858],{"class":235},[200,100421,3933],{"class":2045},[200,100423,360],{"class":235},[200,100425,215],{"class":214},[200,100427,29574],{"class":210},[200,100429,252],{"class":214},[200,100431,100432,100434],{"class":202,"line":405},[200,100433,2632],{"class":214},[200,100435,416],{"class":235},[200,100437,100438],{"class":202,"line":410},[200,100439,4962],{"class":214},[200,100441,100442,100444],{"class":202,"line":861},[200,100443,5080],{"class":214},[200,100445,585],{"class":235},[200,100447,100448],{"class":202,"line":890},[200,100449,529],{"emptyLinePlaceholder":20},[200,100451,100452],{"class":202,"line":900},[200,100453,41060],{"class":401},[200,100455,100456],{"class":202,"line":905},[200,100457,529],{"emptyLinePlaceholder":20},[200,100459,100460,100462,100464,100466,100468,100470,100472],{"class":202,"line":931},[200,100461,3743],{"class":206},[200,100463,41071],{"class":223},[200,100465,227],{"class":235},[200,100467,25386],{"class":210},[200,100469,550],{"class":214},[200,100471,41080],{"class":2045},[200,100473,416],{"class":235},[200,100475,100476,100478],{"class":202,"line":3640},[200,100477,413],{"class":214},[200,100479,416],{"class":210},[35,100481,100482],{},[1217,100483,41826,100484,41829],{},[156,100485,26244],{},[1913,100487,41833],{"id":41832},[35,100489,41836],{},[192,100491,100492],{"className":21417,"code":41839,"language":12060,"meta":13,"style":13},[156,100493,100494,100514,100534,100552,100556,100574,100608,100614,100618,100632,100642,100648,100652,100664,100670,100690,100708,100722,100732,100738,100742,100754,100770,100780,100790,100804,100818,100824,100828,100832,100836,100844,100848,100856,100874,100914,100932,100936,100944,100952],{"__ignoreMap":13},[200,100495,100496,100498,100500,100502,100504,100506,100508,100510,100512],{"class":202,"line":203},[200,100497,600],{"class":214},[200,100499,14892],{"class":235},[200,100501,14590],{"class":379},[200,100503,449],{"class":214},[200,100505,611],{"class":214},[200,100507,1373],{"class":245},[200,100509,611],{"class":214},[200,100511,21781],{"class":379},[200,100513,648],{"class":214},[200,100515,100516,100518,100520,100522,100524,100526,100528,100530,100532],{"class":202,"line":14},[200,100517,973],{"class":206},[200,100519,321],{"class":206},[200,100521,318],{"class":214},[200,100523,41558],{"class":210},[200,100525,981],{"class":214},[200,100527,984],{"class":206},[200,100529,242],{"class":214},[200,100531,19790],{"class":245},[200,100533,992],{"class":214},[200,100535,100536,100538,100540,100542,100544,100546,100548,100550],{"class":202,"line":255},[200,100537,973],{"class":206},[200,100539,318],{"class":214},[200,100541,3165],{"class":210},[200,100543,981],{"class":214},[200,100545,984],{"class":206},[200,100547,242],{"class":214},[200,100549,19790],{"class":245},[200,100551,992],{"class":214},[200,100553,100554],{"class":202,"line":272},[200,100555,529],{"emptyLinePlaceholder":20},[200,100557,100558,100560,100562,100564,100566,100568,100570,100572],{"class":202,"line":283},[200,100559,443],{"class":379},[200,100561,41910],{"class":210},[200,100563,449],{"class":214},[200,100565,3165],{"class":210},[200,100567,215],{"class":214},[200,100569,293],{"class":223},[200,100571,227],{"class":210},[200,100573,230],{"class":214},[200,100575,100576,100578,100580,100582,100584,100586,100588,100590,100592,100594,100596,100598,100600,100602,100604,100606],{"class":202,"line":300},[200,100577,236],{"class":235},[200,100579,239],{"class":214},[200,100581,3165],{"class":210},[200,100583,215],{"class":214},[200,100585,328],{"class":223},[200,100587,227],{"class":210},[200,100589,2752],{"class":214},[200,100591,39347],{"class":235},[200,100593,239],{"class":214},[200,100595,242],{"class":214},[200,100597,41947],{"class":245},[200,100599,249],{"class":214},[200,100601,981],{"class":214},[200,100603,2089],{"class":210},[200,100605,550],{"class":214},[200,100607,41958],{"class":401},[200,100609,100610,100612],{"class":202,"line":310},[200,100611,413],{"class":214},[200,100613,416],{"class":210},[200,100615,100616],{"class":202,"line":336},[200,100617,529],{"emptyLinePlaceholder":20},[200,100619,100620,100622,100624,100626,100628,100630],{"class":202,"line":342},[200,100621,443],{"class":379},[200,100623,18946],{"class":210},[200,100625,449],{"class":214},[200,100627,41979],{"class":223},[200,100629,227],{"class":210},[200,100631,230],{"class":214},[200,100633,100634,100636,100638,100640],{"class":202,"line":365},[200,100635,236],{"class":235},[200,100637,239],{"class":214},[200,100639,3894],{"class":214},[200,100641,252],{"class":214},[200,100643,100644,100646],{"class":202,"line":371},[200,100645,413],{"class":214},[200,100647,416],{"class":210},[200,100649,100650],{"class":202,"line":398},[200,100651,529],{"emptyLinePlaceholder":20},[200,100653,100654,100656,100658,100660,100662],{"class":202,"line":405},[200,100655,3711],{"class":379},[200,100657,4987],{"class":379},[200,100659,42012],{"class":223},[200,100661,1532],{"class":214},[200,100663,280],{"class":214},[200,100665,100666,100668],{"class":202,"line":410},[200,100667,42021],{"class":206},[200,100669,280],{"class":214},[200,100671,100672,100674,100676,100678,100680,100682,100684,100686,100688],{"class":202,"line":861},[200,100673,1999],{"class":379},[200,100675,5041],{"class":210},[200,100677,510],{"class":214},[200,100679,42034],{"class":210},[200,100681,215],{"class":214},[200,100683,41223],{"class":223},[200,100685,227],{"class":235},[200,100687,26836],{"class":210},[200,100689,416],{"class":235},[200,100691,100692,100694,100696,100698,100700,100702,100704,100706],{"class":202,"line":890},[200,100693,19216],{"class":206},[200,100695,18741],{"class":223},[200,100697,227],{"class":235},[200,100699,249],{"class":214},[200,100701,40917],{"class":245},[200,100703,249],{"class":214},[200,100705,550],{"class":214},[200,100707,280],{"class":214},[200,100709,100710,100712,100714,100716,100718,100720],{"class":202,"line":900},[200,100711,42067],{"class":235},[200,100713,239],{"class":214},[200,100715,242],{"class":214},[200,100717,3778],{"class":245},[200,100719,249],{"class":214},[200,100721,252],{"class":214},[200,100723,100724,100726,100728,100730],{"class":202,"line":905},[200,100725,42082],{"class":235},[200,100727,239],{"class":214},[200,100729,5041],{"class":210},[200,100731,252],{"class":214},[200,100733,100734,100736],{"class":202,"line":931},[200,100735,2096],{"class":214},[200,100737,416],{"class":235},[200,100739,100740],{"class":202,"line":3640},[200,100741,4490],{"class":214},[200,100743,100744,100746,100748,100750,100752],{"class":202,"line":3645},[200,100745,42103],{"class":206},[200,100747,1095],{"class":235},[200,100749,19260],{"class":210},[200,100751,5267],{"class":235},[200,100753,230],{"class":214},[200,100755,100756,100758,100760,100762,100764,100766,100768],{"class":202,"line":4020},[200,100757,5252],{"class":206},[200,100759,1095],{"class":235},[200,100761,19260],{"class":210},[200,100763,41704],{"class":214},[200,100765,41558],{"class":1978},[200,100767,5267],{"class":235},[200,100769,230],{"class":214},[200,100771,100772,100774,100776,100778],{"class":202,"line":4475},[200,100773,20227],{"class":206},[200,100775,39441],{"class":223},[200,100777,227],{"class":235},[200,100779,230],{"class":214},[200,100781,100782,100784,100786,100788],{"class":202,"line":4481},[200,100783,41725],{"class":235},[200,100785,239],{"class":214},[200,100787,41427],{"class":2045},[200,100789,252],{"class":214},[200,100791,100792,100794,100796,100798,100800,100802],{"class":202,"line":4487},[200,100793,41736],{"class":235},[200,100795,239],{"class":214},[200,100797,242],{"class":214},[200,100799,41440],{"class":245},[200,100801,249],{"class":214},[200,100803,252],{"class":214},[200,100805,100806,100808,100810,100812,100814,100816],{"class":202,"line":4493},[200,100807,41751],{"class":235},[200,100809,239],{"class":214},[200,100811,19205],{"class":210},[200,100813,215],{"class":214},[200,100815,26789],{"class":210},[200,100817,252],{"class":214},[200,100819,100820,100822],{"class":202,"line":5454},[200,100821,2632],{"class":214},[200,100823,416],{"class":235},[200,100825,100826],{"class":202,"line":5459},[200,100827,4962],{"class":214},[200,100829,100830],{"class":202,"line":13227},[200,100831,4490],{"class":214},[200,100833,100834],{"class":202,"line":13253},[200,100835,1130],{"class":214},[200,100837,100838,100840,100842],{"class":202,"line":13269},[200,100839,681],{"class":214},[200,100841,14892],{"class":235},[200,100843,648],{"class":214},[200,100845,100846],{"class":202,"line":13313},[200,100847,529],{"emptyLinePlaceholder":20},[200,100849,100850,100852,100854],{"class":202,"line":13319},[200,100851,600],{"class":214},[200,100853,21428],{"class":235},[200,100855,648],{"class":214},[200,100857,100858,100860,100862,100864,100866,100868,100870,100872],{"class":202,"line":13324},[200,100859,657],{"class":214},[200,100861,603],{"class":235},[200,100863,42222],{"class":379},[200,100865,449],{"class":214},[200,100867,611],{"class":214},[200,100869,42229],{"class":245},[200,100871,611],{"class":214},[200,100873,648],{"class":214},[200,100875,100876,100878,100880,100882,100884,100886,100888,100890,100892,100894,100896,100898,100900,100902,100904,100906,100908,100910,100912],{"class":202,"line":13350},[200,100877,804],{"class":214},[200,100879,355],{"class":235},[200,100881,42242],{"class":379},[200,100883,449],{"class":214},[200,100885,611],{"class":214},[200,100887,42249],{"class":245},[200,100889,611],{"class":214},[200,100891,321],{"class":379},[200,100893,449],{"class":214},[200,100895,611],{"class":214},[200,100897,698],{"class":245},[200,100899,611],{"class":214},[200,100901,40940],{"class":379},[200,100903,449],{"class":214},[200,100905,611],{"class":214},[200,100907,40947],{"class":245},[200,100909,611],{"class":214},[200,100911,40952],{"class":379},[200,100913,648],{"class":214},[200,100915,100916,100918,100920,100922,100924,100926,100928,100930],{"class":202,"line":13355},[200,100917,804],{"class":214},[200,100919,910],{"class":235},[200,100921,321],{"class":379},[200,100923,449],{"class":214},[200,100925,611],{"class":214},[200,100927,917],{"class":245},[200,100929,611],{"class":214},[200,100931,648],{"class":214},[200,100933,100934],{"class":202,"line":13375},[200,100935,40977],{"class":210},[200,100937,100938,100940,100942],{"class":202,"line":13395},[200,100939,15074],{"class":214},[200,100941,910],{"class":235},[200,100943,648],{"class":214},[200,100945,100946,100948,100950],{"class":202,"line":13400},[200,100947,893],{"class":214},[200,100949,603],{"class":235},[200,100951,648],{"class":214},[200,100953,100954,100956,100958],{"class":202,"line":13408},[200,100955,681],{"class":214},[200,100957,21428],{"class":235},[200,100959,648],{"class":214},[35,100961,42324,100962,42328,100964,42332,100967,13731,100970,215],{},[156,100963,42327],{},[39,100965,9190],{"href":23049,"rel":100966},[77],[39,100968,42337],{"href":42335,"rel":100969},[77],[39,100971,42342],{"href":42340,"rel":100972},[77],[51,100974,42346],{"id":42345},[35,100976,42349],{},[35,100978,42352],{},[35,100980,42355,100981,2857,100983,42362],{},[156,100982,42358],{},[156,100984,42361],{},[192,100986,100987],{"className":1370,"code":42365,"language":1373,"meta":13,"style":13},[156,100988,100989,101007,101011,101031,101063],{"__ignoreMap":13},[200,100990,100991,100993,100995,100997,100999,101001,101003,101005],{"class":202,"line":203},[200,100992,973],{"class":206},[200,100994,318],{"class":214},[200,100996,3165],{"class":210},[200,100998,981],{"class":214},[200,101000,984],{"class":206},[200,101002,242],{"class":214},[200,101004,19790],{"class":245},[200,101006,992],{"class":214},[200,101008,101009],{"class":202,"line":14},[200,101010,529],{"emptyLinePlaceholder":20},[200,101012,101013,101015,101017,101019,101021,101023,101025,101027,101029],{"class":202,"line":255},[200,101014,1380],{"class":206},[200,101016,14301],{"class":379},[200,101018,42398],{"class":210},[200,101020,449],{"class":214},[200,101022,3165],{"class":210},[200,101024,215],{"class":214},[200,101026,293],{"class":223},[200,101028,227],{"class":210},[200,101030,230],{"class":214},[200,101032,101033,101035,101037,101039,101041,101043,101045,101047,101049,101051,101053,101055,101057,101059,101061],{"class":202,"line":272},[200,101034,236],{"class":235},[200,101036,239],{"class":214},[200,101038,3165],{"class":210},[200,101040,215],{"class":214},[200,101042,328],{"class":223},[200,101044,227],{"class":210},[200,101046,2752],{"class":214},[200,101048,39347],{"class":235},[200,101050,239],{"class":214},[200,101052,242],{"class":214},[200,101054,41947],{"class":245},[200,101056,249],{"class":214},[200,101058,981],{"class":214},[200,101060,2089],{"class":210},[200,101062,252],{"class":214},[200,101064,101065,101067],{"class":202,"line":283},[200,101066,413],{"class":214},[200,101068,416],{"class":210},[939,101070,101071],{},[35,101072,101073,42455],{},[200,101074,31267],{},[35,101076,42458],{},[35,101078,42461],{},[192,101080,101081],{"className":21417,"code":42464,"language":12060,"meta":13,"style":13},[156,101082,101083,101103,101113,101117,101121,101129,101133,101141,101149,101153,101161],{"__ignoreMap":13},[200,101084,101085,101087,101089,101091,101093,101095,101097,101099,101101],{"class":202,"line":203},[200,101086,600],{"class":214},[200,101088,14892],{"class":235},[200,101090,14590],{"class":379},[200,101092,449],{"class":214},[200,101094,611],{"class":214},[200,101096,1373],{"class":245},[200,101098,611],{"class":214},[200,101100,21781],{"class":379},[200,101102,648],{"class":214},[200,101104,101105,101107,101109,101111],{"class":202,"line":14},[200,101106,443],{"class":379},[200,101108,41910],{"class":210},[200,101110,449],{"class":214},[200,101112,42497],{"class":210},[200,101114,101115],{"class":202,"line":255},[200,101116,529],{"emptyLinePlaceholder":20},[200,101118,101119],{"class":202,"line":272},[200,101120,33144],{"class":401},[200,101122,101123,101125,101127],{"class":202,"line":283},[200,101124,681],{"class":214},[200,101126,14892],{"class":235},[200,101128,648],{"class":214},[200,101130,101131],{"class":202,"line":300},[200,101132,529],{"emptyLinePlaceholder":20},[200,101134,101135,101137,101139],{"class":202,"line":310},[200,101136,600],{"class":214},[200,101138,21428],{"class":235},[200,101140,648],{"class":214},[200,101142,101143,101145,101147],{"class":202,"line":336},[200,101144,657],{"class":214},[200,101146,603],{"class":235},[200,101148,648],{"class":214},[200,101150,101151],{"class":202,"line":342},[200,101152,42538],{"class":401},[200,101154,101155,101157,101159],{"class":202,"line":365},[200,101156,893],{"class":214},[200,101158,603],{"class":235},[200,101160,648],{"class":214},[200,101162,101163,101165,101167],{"class":202,"line":371},[200,101164,681],{"class":214},[200,101166,21428],{"class":235},[200,101168,648],{"class":214},[35,101170,42557],{},[192,101172,101173],{"className":1370,"code":42560,"language":1373,"meta":13,"style":13},[156,101174,101175,101193,101197,101219,101245,101249,101253,101257,101273],{"__ignoreMap":13},[200,101176,101177,101179,101181,101183,101185,101187,101189,101191],{"class":202,"line":203},[200,101178,973],{"class":206},[200,101180,318],{"class":214},[200,101182,42571],{"class":210},[200,101184,981],{"class":214},[200,101186,984],{"class":206},[200,101188,242],{"class":214},[200,101190,42580],{"class":245},[200,101192,992],{"class":214},[200,101194,101195],{"class":202,"line":14},[200,101196,529],{"emptyLinePlaceholder":20},[200,101198,101199,101201,101203,101205,101207,101209,101211,101213,101215,101217],{"class":202,"line":255},[200,101200,1380],{"class":206},[200,101202,1383],{"class":206},[200,101204,38960],{"class":223},[200,101206,227],{"class":210},[200,101208,3711],{"class":379},[200,101210,1095],{"class":214},[200,101212,25386],{"class":386},[200,101214,2089],{"class":214},[200,101216,393],{"class":379},[200,101218,280],{"class":214},[200,101220,101221,101223,101225,101227,101229,101231,101233,101235,101237,101239,101241,101243],{"class":202,"line":272},[200,101222,3723],{"class":379},[200,101224,41038],{"class":210},[200,101226,510],{"class":214},[200,101228,452],{"class":206},[200,101230,41180],{"class":223},[200,101232,227],{"class":235},[200,101234,25386],{"class":210},[200,101236,550],{"class":214},[200,101238,42571],{"class":210},[200,101240,215],{"class":214},[200,101242,41223],{"class":210},[200,101244,416],{"class":235},[200,101246,101247],{"class":202,"line":283},[200,101248,529],{"emptyLinePlaceholder":20},[200,101250,101251],{"class":202,"line":300},[200,101252,41060],{"class":401},[200,101254,101255],{"class":202,"line":310},[200,101256,529],{"emptyLinePlaceholder":20},[200,101258,101259,101261,101263,101265,101267,101269,101271],{"class":202,"line":336},[200,101260,3743],{"class":206},[200,101262,41071],{"class":223},[200,101264,227],{"class":235},[200,101266,25386],{"class":210},[200,101268,550],{"class":214},[200,101270,41080],{"class":2045},[200,101272,416],{"class":235},[200,101274,101275,101277],{"class":202,"line":342},[200,101276,413],{"class":214},[200,101278,416],{"class":210},[35,101280,42671],{},[51,101282,42675],{"id":42674},[35,101284,42678,101285,42682],{},[156,101286,42681],{},[35,101288,101289],{},[1217,101290,42687],{},[1913,101292,42691],{"id":42690},[35,101294,42694],{},[192,101296,101297],{"className":21417,"code":42697,"language":12060,"meta":13,"style":13},[156,101298,101299,101307,101345,101375,101393,101397,101405,101413],{"__ignoreMap":13},[200,101300,101301,101303,101305],{"class":202,"line":203},[200,101302,600],{"class":214},[200,101304,21428],{"class":235},[200,101306,648],{"class":214},[200,101308,101309,101311,101313,101315,101317,101319,101321,101323,101325,101327,101329,101331,101333,101335,101337,101339,101341,101343],{"class":202,"line":14},[200,101310,657],{"class":214},[200,101312,603],{"class":235},[200,101314,40910],{"class":379},[200,101316,449],{"class":214},[200,101318,611],{"class":214},[200,101320,42722],{"class":245},[200,101322,611],{"class":214},[200,101324,42727],{"class":379},[200,101326,449],{"class":214},[200,101328,611],{"class":214},[200,101330,42734],{"class":245},[200,101332,611],{"class":214},[200,101334,6577],{"class":379},[200,101336,449],{"class":214},[200,101338,611],{"class":214},[200,101340,3778],{"class":245},[200,101342,611],{"class":214},[200,101344,648],{"class":214},[200,101346,101347,101349,101351,101353,101355,101357,101359,101361,101363,101365,101367,101369,101371,101373],{"class":202,"line":255},[200,101348,804],{"class":214},[200,101350,355],{"class":235},[200,101352,321],{"class":379},[200,101354,449],{"class":214},[200,101356,611],{"class":214},[200,101358,42763],{"class":245},[200,101360,611],{"class":214},[200,101362,42768],{"class":379},[200,101364,449],{"class":214},[200,101366,611],{"class":214},[200,101368,42775],{"class":245},[200,101370,611],{"class":214},[200,101372,40952],{"class":379},[200,101374,648],{"class":214},[200,101376,101377,101379,101381,101383,101385,101387,101389,101391],{"class":202,"line":272},[200,101378,804],{"class":214},[200,101380,910],{"class":235},[200,101382,321],{"class":379},[200,101384,449],{"class":214},[200,101386,611],{"class":214},[200,101388,917],{"class":245},[200,101390,611],{"class":214},[200,101392,648],{"class":214},[200,101394,101395],{"class":202,"line":283},[200,101396,40977],{"class":210},[200,101398,101399,101401,101403],{"class":202,"line":300},[200,101400,15074],{"class":214},[200,101402,910],{"class":235},[200,101404,648],{"class":214},[200,101406,101407,101409,101411],{"class":202,"line":310},[200,101408,893],{"class":214},[200,101410,603],{"class":235},[200,101412,648],{"class":214},[200,101414,101415,101417,101419],{"class":202,"line":336},[200,101416,681],{"class":214},[200,101418,21428],{"class":235},[200,101420,648],{"class":214},[35,101422,42830,101423,42834,101425,42838],{},[156,101424,42833],{},[156,101426,42837],{},[35,101428,42841,101429,42844,101431,42848,101433,35149,101435,42853],{},[156,101430,42833],{},[156,101432,42847],{},[156,101434,9737],{},[156,101436,42833],{},[192,101438,101439],{"className":1370,"code":42856,"language":1373,"meta":13,"style":13},[156,101440,101441,101463,101467,101489,101507,101511,101535,101545,101555,101591,101635,101685,101755,101791,101801,101811,101825,101847,101853,101859,101863,101867,101871,101887],{"__ignoreMap":13},[200,101442,101443,101445,101447,101449,101451,101453,101455,101457,101459,101461],{"class":202,"line":203},[200,101444,973],{"class":206},[200,101446,318],{"class":214},[200,101448,42867],{"class":210},[200,101450,550],{"class":214},[200,101452,42872],{"class":210},[200,101454,981],{"class":214},[200,101456,984],{"class":206},[200,101458,242],{"class":214},[200,101460,19790],{"class":245},[200,101462,992],{"class":214},[200,101464,101465],{"class":202,"line":14},[200,101466,529],{"emptyLinePlaceholder":20},[200,101468,101469,101471,101473,101475,101477,101479,101481,101483,101485,101487],{"class":202,"line":255},[200,101470,1380],{"class":206},[200,101472,1383],{"class":206},[200,101474,38960],{"class":223},[200,101476,227],{"class":210},[200,101478,3711],{"class":379},[200,101480,1095],{"class":214},[200,101482,25386],{"class":386},[200,101484,2089],{"class":214},[200,101486,393],{"class":379},[200,101488,280],{"class":214},[200,101490,101491,101493,101495,101497,101499,101501,101503,101505],{"class":202,"line":272},[200,101492,3723],{"class":379},[200,101494,42915],{"class":210},[200,101496,510],{"class":214},[200,101498,452],{"class":206},[200,101500,42922],{"class":223},[200,101502,227],{"class":235},[200,101504,25386],{"class":210},[200,101506,416],{"class":235},[200,101508,101509],{"class":202,"line":283},[200,101510,529],{"emptyLinePlaceholder":20},[200,101512,101513,101515,101517,101519,101521,101523,101525,101527,101529,101531,101533],{"class":202,"line":300},[200,101514,3723],{"class":379},[200,101516,42939],{"class":210},[200,101518,510],{"class":214},[200,101520,42915],{"class":210},[200,101522,215],{"class":214},[200,101524,42948],{"class":223},[200,101526,227],{"class":235},[200,101528,249],{"class":214},[200,101530,9737],{"class":245},[200,101532,249],{"class":214},[200,101534,416],{"class":235},[200,101536,101537,101539,101541,101543],{"class":202,"line":310},[200,101538,6165],{"class":206},[200,101540,42872],{"class":223},[200,101542,227],{"class":235},[200,101544,230],{"class":214},[200,101546,101547,101549,101551,101553],{"class":202,"line":336},[200,101548,42973],{"class":235},[200,101550,239],{"class":214},[200,101552,42867],{"class":223},[200,101554,466],{"class":235},[200,101556,101557,101559,101561,101563,101565,101567,101569,101571,101573,101575,101577,101579,101581,101583,101585,101587,101589],{"class":202,"line":342},[200,101558,5051],{"class":214},[200,101560,42986],{"class":223},[200,101562,227],{"class":235},[200,101564,9737],{"class":386},[200,101566,393],{"class":379},[200,101568,42939],{"class":210},[200,101570,41704],{"class":214},[200,101572,42999],{"class":1978},[200,101574,550],{"class":214},[200,101576,318],{"class":214},[200,101578,39347],{"class":235},[200,101580,239],{"class":214},[200,101582,242],{"class":214},[200,101584,43012],{"class":245},[200,101586,249],{"class":214},[200,101588,981],{"class":214},[200,101590,416],{"class":235},[200,101592,101593,101595,101597,101599,101601,101603,101605,101607,101609,101611,101613,101615,101617,101619,101621,101623,101625,101627,101629,101631,101633],{"class":202,"line":365},[200,101594,5051],{"class":214},[200,101596,42986],{"class":223},[200,101598,227],{"class":235},[200,101600,9737],{"class":386},[200,101602,393],{"class":379},[200,101604,42939],{"class":210},[200,101606,215],{"class":214},[200,101608,35364],{"class":210},[200,101610,31253],{"class":214},[200,101612,43041],{"class":2045},[200,101614,29919],{"class":214},[200,101616,43041],{"class":2045},[200,101618,550],{"class":214},[200,101620,318],{"class":214},[200,101622,39347],{"class":235},[200,101624,239],{"class":214},[200,101626,242],{"class":214},[200,101628,43058],{"class":245},[200,101630,249],{"class":214},[200,101632,981],{"class":214},[200,101634,416],{"class":235},[200,101636,101637,101639,101641,101643,101645,101647,101649,101651,101653,101655,101657,101659,101661,101663,101665,101667,101669,101671,101673,101675,101677,101679,101681,101683],{"class":202,"line":371},[200,101638,5051],{"class":214},[200,101640,42986],{"class":223},[200,101642,227],{"class":235},[200,101644,9737],{"class":386},[200,101646,393],{"class":379},[200,101648,42939],{"class":210},[200,101650,215],{"class":214},[200,101652,3771],{"class":210},[200,101654,215],{"class":214},[200,101656,6108],{"class":223},[200,101658,227],{"class":235},[200,101660,249],{"class":214},[200,101662,43093],{"class":245},[200,101664,249],{"class":214},[200,101666,2089],{"class":235},[200,101668,550],{"class":214},[200,101670,318],{"class":214},[200,101672,39347],{"class":235},[200,101674,239],{"class":214},[200,101676,242],{"class":214},[200,101678,43110],{"class":245},[200,101680,249],{"class":214},[200,101682,981],{"class":214},[200,101684,416],{"class":235},[200,101686,101687,101689,101691,101693,101695,101697,101699,101701,101703,101705,101707,101709,101711,101713,101715,101717,101719,101721,101723,101725,101727,101729,101731,101733,101735,101737,101739,101741,101743,101745,101747,101749,101751,101753],{"class":202,"line":398},[200,101688,5051],{"class":214},[200,101690,42986],{"class":223},[200,101692,227],{"class":235},[200,101694,9737],{"class":386},[200,101696,393],{"class":379},[200,101698,350],{"class":235},[200,101700,249],{"class":214},[200,101702,43135],{"class":245},[200,101704,249],{"class":214},[200,101706,550],{"class":214},[200,101708,242],{"class":214},[200,101710,43144],{"class":245},[200,101712,249],{"class":214},[200,101714,550],{"class":214},[200,101716,242],{"class":214},[200,101718,43153],{"class":245},[200,101720,249],{"class":214},[200,101722,360],{"class":235},[200,101724,215],{"class":214},[200,101726,3841],{"class":223},[200,101728,227],{"class":235},[200,101730,9737],{"class":210},[200,101732,215],{"class":214},[200,101734,3771],{"class":210},[200,101736,2089],{"class":235},[200,101738,550],{"class":214},[200,101740,318],{"class":214},[200,101742,39347],{"class":235},[200,101744,239],{"class":214},[200,101746,242],{"class":214},[200,101748,43184],{"class":245},[200,101750,249],{"class":214},[200,101752,981],{"class":214},[200,101754,416],{"class":235},[200,101756,101757,101759,101761,101763,101765,101767,101769,101771,101773,101775,101777,101779,101781,101783,101785,101787,101789],{"class":202,"line":405},[200,101758,5080],{"class":214},[200,101760,2089],{"class":235},[200,101762,215],{"class":214},[200,101764,41541],{"class":223},[200,101766,227],{"class":235},[200,101768,2752],{"class":214},[200,101770,42939],{"class":210},[200,101772,981],{"class":214},[200,101774,2089],{"class":235},[200,101776,215],{"class":214},[200,101778,41681],{"class":223},[200,101780,227],{"class":235},[200,101782,227],{"class":214},[200,101784,19260],{"class":386},[200,101786,2089],{"class":214},[200,101788,393],{"class":379},[200,101790,280],{"class":214},[200,101792,101793,101795,101797,101799],{"class":202,"line":410},[200,101794,39438],{"class":206},[200,101796,39441],{"class":223},[200,101798,227],{"class":235},[200,101800,230],{"class":214},[200,101802,101803,101805,101807,101809],{"class":202,"line":861},[200,101804,39082],{"class":235},[200,101806,239],{"class":214},[200,101808,41427],{"class":2045},[200,101810,252],{"class":214},[200,101812,101813,101815,101817,101819,101821,101823],{"class":202,"line":890},[200,101814,39471],{"class":235},[200,101816,239],{"class":214},[200,101818,242],{"class":214},[200,101820,43257],{"class":245},[200,101822,249],{"class":214},[200,101824,252],{"class":214},[200,101826,101827,101829,101831,101833,101835,101837,101839,101841,101843,101845],{"class":202,"line":900},[200,101828,41449],{"class":235},[200,101830,239],{"class":214},[200,101832,19205],{"class":210},[200,101834,215],{"class":214},[200,101836,26789],{"class":210},[200,101838,9858],{"class":235},[200,101840,3933],{"class":2045},[200,101842,360],{"class":235},[200,101844,215],{"class":214},[200,101846,43284],{"class":210},[200,101848,101849,101851],{"class":202,"line":905},[200,101850,2096],{"class":214},[200,101852,416],{"class":235},[200,101854,101855,101857],{"class":202,"line":931},[200,101856,5080],{"class":214},[200,101858,416],{"class":235},[200,101860,101861],{"class":202,"line":3640},[200,101862,529],{"emptyLinePlaceholder":20},[200,101864,101865],{"class":202,"line":3645},[200,101866,43305],{"class":401},[200,101868,101869],{"class":202,"line":4020},[200,101870,529],{"emptyLinePlaceholder":20},[200,101872,101873,101875,101877,101879,101881,101883,101885],{"class":202,"line":4475},[200,101874,3743],{"class":206},[200,101876,41071],{"class":223},[200,101878,227],{"class":235},[200,101880,25386],{"class":210},[200,101882,550],{"class":214},[200,101884,41080],{"class":2045},[200,101886,416],{"class":235},[200,101888,101889,101891],{"class":202,"line":4481},[200,101890,413],{"class":214},[200,101892,416],{"class":210},[35,101894,43334],{},[35,101896,43337,101897,13591],{},[156,101898,43340],{},[192,101900,101901],{"className":1370,"code":43343,"language":1373,"meta":13,"style":13},[156,101902,101903,101925,101929,101945,102135],{"__ignoreMap":13},[200,101904,101905,101907,101909,101911,101913,101915,101917,101919,101921,101923],{"class":202,"line":203},[200,101906,973],{"class":206},[200,101908,318],{"class":214},[200,101910,42867],{"class":210},[200,101912,550],{"class":214},[200,101914,42872],{"class":210},[200,101916,981],{"class":214},[200,101918,984],{"class":206},[200,101920,242],{"class":214},[200,101922,19790],{"class":245},[200,101924,992],{"class":214},[200,101926,101927],{"class":202,"line":14},[200,101928,529],{"emptyLinePlaceholder":20},[200,101930,101931,101933,101935,101937,101939,101941,101943],{"class":202,"line":255},[200,101932,1380],{"class":206},[200,101934,14301],{"class":379},[200,101936,43380],{"class":210},[200,101938,449],{"class":214},[200,101940,42872],{"class":223},[200,101942,227],{"class":210},[200,101944,230],{"class":214},[200,101946,101947,101949,101951,101953,101955,101957,101959,101961,101963,101965,101967,101969,101971,101973,101975,101977,101979,101981,101983,101985,101987,101989,101991,101993,101995,101997,101999,102001,102003,102005,102007,102009,102011,102013,102015,102017,102019,102021,102023,102025,102027,102029,102031,102033,102035,102037,102039,102041,102043,102045,102047,102049,102051,102053,102055,102057,102059,102061,102063,102065,102067,102069,102071,102073,102075,102077,102079,102081,102083,102085,102087,102089,102091,102093,102095,102097,102099,102101,102103,102105,102107,102109,102111,102113,102115,102117,102119,102121,102123,102125,102127,102129,102131,102133],{"class":202,"line":272},[200,101948,43393],{"class":235},[200,101950,239],{"class":214},[200,101952,42867],{"class":223},[200,101954,1532],{"class":210},[200,101956,215],{"class":214},[200,101958,42986],{"class":223},[200,101960,227],{"class":210},[200,101962,9737],{"class":386},[200,101964,393],{"class":379},[200,101966,43412],{"class":210},[200,101968,43415],{"class":214},[200,101970,42999],{"class":1978},[200,101972,550],{"class":214},[200,101974,318],{"class":214},[200,101976,39347],{"class":235},[200,101978,239],{"class":214},[200,101980,242],{"class":214},[200,101982,43012],{"class":245},[200,101984,249],{"class":214},[200,101986,981],{"class":214},[200,101988,2089],{"class":210},[200,101990,215],{"class":214},[200,101992,42986],{"class":223},[200,101994,227],{"class":210},[200,101996,9737],{"class":386},[200,101998,393],{"class":379},[200,102000,42939],{"class":210},[200,102002,215],{"class":214},[200,102004,43452],{"class":210},[200,102006,600],{"class":214},[200,102008,43041],{"class":2045},[200,102010,29919],{"class":214},[200,102012,43041],{"class":2045},[200,102014,550],{"class":214},[200,102016,318],{"class":214},[200,102018,39347],{"class":235},[200,102020,239],{"class":214},[200,102022,242],{"class":214},[200,102024,43058],{"class":245},[200,102026,249],{"class":214},[200,102028,981],{"class":214},[200,102030,2089],{"class":210},[200,102032,215],{"class":214},[200,102034,42986],{"class":223},[200,102036,227],{"class":210},[200,102038,9737],{"class":386},[200,102040,393],{"class":379},[200,102042,42939],{"class":210},[200,102044,215],{"class":214},[200,102046,3771],{"class":210},[200,102048,215],{"class":214},[200,102050,6108],{"class":223},[200,102052,227],{"class":210},[200,102054,249],{"class":214},[200,102056,43093],{"class":245},[200,102058,249],{"class":214},[200,102060,2089],{"class":210},[200,102062,550],{"class":214},[200,102064,318],{"class":214},[200,102066,39347],{"class":235},[200,102068,239],{"class":214},[200,102070,242],{"class":214},[200,102072,43110],{"class":245},[200,102074,249],{"class":214},[200,102076,981],{"class":214},[200,102078,2089],{"class":210},[200,102080,215],{"class":214},[200,102082,42986],{"class":223},[200,102084,227],{"class":210},[200,102086,9737],{"class":386},[200,102088,393],{"class":379},[200,102090,350],{"class":210},[200,102092,249],{"class":214},[200,102094,43144],{"class":245},[200,102096,249],{"class":214},[200,102098,550],{"class":214},[200,102100,242],{"class":214},[200,102102,43135],{"class":245},[200,102104,249],{"class":214},[200,102106,360],{"class":210},[200,102108,215],{"class":214},[200,102110,3841],{"class":223},[200,102112,43561],{"class":210},[200,102114,215],{"class":214},[200,102116,43566],{"class":210},[200,102118,550],{"class":214},[200,102120,318],{"class":214},[200,102122,39347],{"class":235},[200,102124,239],{"class":214},[200,102126,242],{"class":214},[200,102128,43184],{"class":245},[200,102130,249],{"class":214},[200,102132,981],{"class":214},[200,102134,416],{"class":210},[200,102136,102137,102139],{"class":202,"line":283},[200,102138,413],{"class":214},[200,102140,416],{"class":210},[35,102142,43593],{},[192,102144,102145],{"className":21417,"code":43596,"language":12060,"meta":13,"style":13},[156,102146,102147,102167,102187,102205,102223,102227,102251,102257,102277,102281,102299,102313,102323,102329,102333,102345,102359,102363,102367,102375,102379,102387,102395,102411,102415,102423,102427,102435,102463,102501,102519,102523,102531,102539,102547],{"__ignoreMap":13},[200,102148,102149,102151,102153,102155,102157,102159,102161,102163,102165],{"class":202,"line":203},[200,102150,600],{"class":214},[200,102152,14892],{"class":235},[200,102154,14590],{"class":379},[200,102156,449],{"class":214},[200,102158,611],{"class":214},[200,102160,1373],{"class":245},[200,102162,611],{"class":214},[200,102164,21781],{"class":379},[200,102166,648],{"class":214},[200,102168,102169,102171,102173,102175,102177,102179,102181,102183,102185],{"class":202,"line":14},[200,102170,3711],{"class":379},[200,102172,4987],{"class":379},[200,102174,43627],{"class":223},[200,102176,227],{"class":214},[200,102178,25386],{"class":386},[200,102180,239],{"class":214},[200,102182,43636],{"class":1978},[200,102184,2089],{"class":214},[200,102186,280],{"class":214},[200,102188,102189,102191,102193,102195,102197,102199,102201,102203],{"class":202,"line":255},[200,102190,3723],{"class":379},[200,102192,43647],{"class":210},[200,102194,510],{"class":214},[200,102196,43652],{"class":210},[200,102198,215],{"class":214},[200,102200,43657],{"class":210},[200,102202,14674],{"class":206},[200,102204,43662],{"class":1978},[200,102206,102207,102209,102211,102213,102215,102217,102219,102221],{"class":202,"line":272},[200,102208,3723],{"class":379},[200,102210,42915],{"class":210},[200,102212,510],{"class":214},[200,102214,12997],{"class":214},[200,102216,43675],{"class":223},[200,102218,227],{"class":235},[200,102220,43657],{"class":210},[200,102222,416],{"class":235},[200,102224,102225],{"class":202,"line":283},[200,102226,529],{"emptyLinePlaceholder":20},[200,102228,102229,102231,102233,102235,102237,102239,102241,102243,102245,102247,102249],{"class":202,"line":300},[200,102230,3723],{"class":379},[200,102232,42939],{"class":210},[200,102234,510],{"class":214},[200,102236,42915],{"class":210},[200,102238,215],{"class":214},[200,102240,42948],{"class":223},[200,102242,227],{"class":235},[200,102244,249],{"class":214},[200,102246,9737],{"class":245},[200,102248,249],{"class":214},[200,102250,416],{"class":235},[200,102252,102253,102255],{"class":202,"line":310},[200,102254,42021],{"class":206},[200,102256,280],{"class":214},[200,102258,102259,102261,102263,102265,102267,102269,102271,102273,102275],{"class":202,"line":336},[200,102260,19216],{"class":206},[200,102262,43722],{"class":210},[200,102264,215],{"class":214},[200,102266,41541],{"class":223},[200,102268,227],{"class":235},[200,102270,2752],{"class":214},[200,102272,42939],{"class":210},[200,102274,981],{"class":214},[200,102276,416],{"class":235},[200,102278,102279],{"class":202,"line":342},[200,102280,529],{"emptyLinePlaceholder":20},[200,102282,102283,102285,102287,102289,102291,102293,102295,102297],{"class":202,"line":365},[200,102284,19216],{"class":206},[200,102286,18741],{"class":223},[200,102288,227],{"class":235},[200,102290,249],{"class":214},[200,102292,42722],{"class":245},[200,102294,249],{"class":214},[200,102296,550],{"class":214},[200,102298,280],{"class":214},[200,102300,102301,102303,102305,102307,102309,102311],{"class":202,"line":371},[200,102302,42067],{"class":235},[200,102304,239],{"class":214},[200,102306,242],{"class":214},[200,102308,18766],{"class":245},[200,102310,249],{"class":214},[200,102312,252],{"class":214},[200,102314,102315,102317,102319,102321],{"class":202,"line":398},[200,102316,42082],{"class":235},[200,102318,239],{"class":214},[200,102320,42915],{"class":210},[200,102322,252],{"class":214},[200,102324,102325,102327],{"class":202,"line":405},[200,102326,2096],{"class":214},[200,102328,416],{"class":235},[200,102330,102331],{"class":202,"line":410},[200,102332,4490],{"class":214},[200,102334,102335,102337,102339,102341,102343],{"class":202,"line":861},[200,102336,42103],{"class":206},[200,102338,1095],{"class":235},[200,102340,19260],{"class":210},[200,102342,5267],{"class":235},[200,102344,230],{"class":214},[200,102346,102347,102349,102351,102353,102355,102357],{"class":202,"line":890},[200,102348,43809],{"class":210},[200,102350,215],{"class":214},[200,102352,19260],{"class":223},[200,102354,227],{"class":235},[200,102356,19260],{"class":210},[200,102358,416],{"class":235},[200,102360,102361],{"class":202,"line":900},[200,102362,4490],{"class":214},[200,102364,102365],{"class":202,"line":905},[200,102366,1130],{"class":214},[200,102368,102369,102371,102373],{"class":202,"line":931},[200,102370,681],{"class":214},[200,102372,14892],{"class":235},[200,102374,648],{"class":214},[200,102376,102377],{"class":202,"line":3640},[200,102378,529],{"emptyLinePlaceholder":20},[200,102380,102381,102383,102385],{"class":202,"line":3645},[200,102382,600],{"class":214},[200,102384,21428],{"class":235},[200,102386,648],{"class":214},[200,102388,102389,102391,102393],{"class":202,"line":4020},[200,102390,657],{"class":214},[200,102392,11386],{"class":235},[200,102394,648],{"class":214},[200,102396,102397,102399,102401,102403,102405,102407,102409],{"class":202,"line":4475},[200,102398,804],{"class":214},[200,102400,10081],{"class":235},[200,102402,675],{"class":214},[200,102404,43866],{"class":210},[200,102406,681],{"class":214},[200,102408,10081],{"class":235},[200,102410,648],{"class":214},[200,102412,102413],{"class":202,"line":4481},[200,102414,529],{"emptyLinePlaceholder":20},[200,102416,102417,102419,102421],{"class":202,"line":4487},[200,102418,804],{"class":214},[200,102420,51],{"class":235},[200,102422,648],{"class":214},[200,102424,102425],{"class":202,"line":4493},[200,102426,43889],{"class":210},[200,102428,102429,102431,102433],{"class":202,"line":5454},[200,102430,15074],{"class":214},[200,102432,51],{"class":235},[200,102434,648],{"class":214},[200,102436,102437,102439,102441,102443,102445,102447,102449,102451,102453,102455,102457,102459,102461],{"class":202,"line":5459},[200,102438,804],{"class":214},[200,102440,603],{"class":235},[200,102442,42727],{"class":379},[200,102444,449],{"class":214},[200,102446,611],{"class":214},[200,102448,42734],{"class":245},[200,102450,611],{"class":214},[200,102452,42222],{"class":379},[200,102454,449],{"class":214},[200,102456,611],{"class":214},[200,102458,43922],{"class":245},[200,102460,611],{"class":214},[200,102462,648],{"class":214},[200,102464,102465,102467,102469,102471,102473,102475,102477,102479,102481,102483,102485,102487,102489,102491,102493,102495,102497,102499],{"class":202,"line":13227},[200,102466,22027],{"class":214},[200,102468,355],{"class":235},[200,102470,321],{"class":379},[200,102472,449],{"class":214},[200,102474,611],{"class":214},[200,102476,42763],{"class":245},[200,102478,611],{"class":214},[200,102480,701],{"class":379},[200,102482,449],{"class":214},[200,102484,611],{"class":214},[200,102486,9737],{"class":245},[200,102488,611],{"class":214},[200,102490,42768],{"class":379},[200,102492,449],{"class":214},[200,102494,611],{"class":214},[200,102496,42775],{"class":245},[200,102498,611],{"class":214},[200,102500,648],{"class":214},[200,102502,102503,102505,102507,102509,102511,102513,102515,102517],{"class":202,"line":13253},[200,102504,22027],{"class":214},[200,102506,910],{"class":235},[200,102508,321],{"class":379},[200,102510,449],{"class":214},[200,102512,611],{"class":214},[200,102514,917],{"class":245},[200,102516,611],{"class":214},[200,102518,648],{"class":214},[200,102520,102521],{"class":202,"line":13269},[200,102522,43987],{"class":210},[200,102524,102525,102527,102529],{"class":202,"line":13313},[200,102526,43992],{"class":214},[200,102528,910],{"class":235},[200,102530,648],{"class":214},[200,102532,102533,102535,102537],{"class":202,"line":13319},[200,102534,15074],{"class":214},[200,102536,603],{"class":235},[200,102538,648],{"class":214},[200,102540,102541,102543,102545],{"class":202,"line":13324},[200,102542,893],{"class":214},[200,102544,11386],{"class":235},[200,102546,648],{"class":214},[200,102548,102549,102551,102553],{"class":202,"line":13350},[200,102550,681],{"class":214},[200,102552,21428],{"class":235},[200,102554,648],{"class":214},[35,102556,44023,102557,44027],{},[156,102558,44026],{},[35,102560,44030],{},[192,102562,102563],{"className":1370,"code":44033,"language":1373,"meta":13,"style":13},[156,102564,102565,102583,102587,102609,102613,102617,102637,102641,102645],{"__ignoreMap":13},[200,102566,102567,102569,102571,102573,102575,102577,102579,102581],{"class":202,"line":203},[200,102568,973],{"class":206},[200,102570,318],{"class":214},[200,102572,44044],{"class":210},[200,102574,981],{"class":214},[200,102576,984],{"class":206},[200,102578,242],{"class":214},[200,102580,42580],{"class":245},[200,102582,992],{"class":214},[200,102584,102585],{"class":202,"line":14},[200,102586,529],{"emptyLinePlaceholder":20},[200,102588,102589,102591,102593,102595,102597,102599,102601,102603,102605,102607],{"class":202,"line":255},[200,102590,1380],{"class":206},[200,102592,1383],{"class":206},[200,102594,38960],{"class":223},[200,102596,227],{"class":210},[200,102598,3711],{"class":379},[200,102600,1095],{"class":214},[200,102602,25386],{"class":386},[200,102604,2089],{"class":214},[200,102606,393],{"class":379},[200,102608,280],{"class":214},[200,102610,102611],{"class":202,"line":272},[200,102612,10634],{"class":401},[200,102614,102615],{"class":202,"line":283},[200,102616,529],{"emptyLinePlaceholder":20},[200,102618,102619,102621,102623,102625,102627,102629,102631,102633,102635],{"class":202,"line":300},[200,102620,6165],{"class":206},[200,102622,44044],{"class":210},[200,102624,215],{"class":214},[200,102626,41541],{"class":223},[200,102628,227],{"class":235},[200,102630,2752],{"class":214},[200,102632,42939],{"class":210},[200,102634,981],{"class":214},[200,102636,416],{"class":235},[200,102638,102639],{"class":202,"line":310},[200,102640,529],{"emptyLinePlaceholder":20},[200,102642,102643],{"class":202,"line":336},[200,102644,10634],{"class":401},[200,102646,102647,102649],{"class":202,"line":342},[200,102648,413],{"class":214},[200,102650,416],{"class":210},[1913,102652,44126],{"id":44125},[35,102654,44129,102655,44132,102657],{},[156,102656,42681],{},[1217,102658,44135],{},[192,102660,102661],{"className":1370,"code":44138,"language":1373,"meta":13,"style":13},[156,102662,102663,102685,102703,102707,102735,102747,102761,102793,102799,102803,102807,102811,102827],{"__ignoreMap":13},[200,102664,102665,102667,102669,102671,102673,102675,102677,102679,102681,102683],{"class":202,"line":203},[200,102666,1380],{"class":206},[200,102668,1383],{"class":206},[200,102670,38960],{"class":223},[200,102672,227],{"class":210},[200,102674,3711],{"class":379},[200,102676,1095],{"class":214},[200,102678,25386],{"class":386},[200,102680,2089],{"class":214},[200,102682,393],{"class":379},[200,102684,280],{"class":214},[200,102686,102687,102689,102691,102693,102695,102697,102699,102701],{"class":202,"line":14},[200,102688,3723],{"class":379},[200,102690,42915],{"class":210},[200,102692,510],{"class":214},[200,102694,452],{"class":206},[200,102696,42922],{"class":223},[200,102698,227],{"class":235},[200,102700,25386],{"class":210},[200,102702,416],{"class":235},[200,102704,102705],{"class":202,"line":255},[200,102706,529],{"emptyLinePlaceholder":20},[200,102708,102709,102711,102713,102715,102717,102719,102721,102723,102725,102727,102729,102731,102733],{"class":202,"line":272},[200,102710,3723],{"class":379},[200,102712,42939],{"class":210},[200,102714,510],{"class":214},[200,102716,42915],{"class":210},[200,102718,215],{"class":214},[200,102720,42948],{"class":223},[200,102722,227],{"class":235},[200,102724,249],{"class":214},[200,102726,9737],{"class":245},[200,102728,249],{"class":214},[200,102730,5267],{"class":235},[200,102732,44211],{"class":206},[200,102734,44214],{"class":1978},[200,102736,102737,102739,102741,102743,102745],{"class":202,"line":283},[200,102738,44219],{"class":223},[200,102740,227],{"class":235},[200,102742,9737],{"class":210},[200,102744,550],{"class":214},[200,102746,280],{"class":214},[200,102748,102749,102751,102753,102755,102757,102759],{"class":202,"line":300},[200,102750,44232],{"class":235},[200,102752,239],{"class":214},[200,102754,242],{"class":214},[200,102756,44239],{"class":245},[200,102758,249],{"class":214},[200,102760,252],{"class":214},[200,102762,102763,102765,102767,102769,102771,102773,102775,102777,102779,102781,102783,102785,102787,102789,102791],{"class":202,"line":310},[200,102764,44248],{"class":235},[200,102766,239],{"class":214},[200,102768,350],{"class":235},[200,102770,249],{"class":214},[200,102772,43135],{"class":245},[200,102774,249],{"class":214},[200,102776,550],{"class":214},[200,102778,242],{"class":214},[200,102780,43144],{"class":245},[200,102782,249],{"class":214},[200,102784,550],{"class":214},[200,102786,242],{"class":214},[200,102788,43153],{"class":245},[200,102790,249],{"class":214},[200,102792,4496],{"class":235},[200,102794,102795,102797],{"class":202,"line":336},[200,102796,5080],{"class":214},[200,102798,416],{"class":235},[200,102800,102801],{"class":202,"line":342},[200,102802,529],{"emptyLinePlaceholder":20},[200,102804,102805],{"class":202,"line":365},[200,102806,43305],{"class":401},[200,102808,102809],{"class":202,"line":371},[200,102810,529],{"emptyLinePlaceholder":20},[200,102812,102813,102815,102817,102819,102821,102823,102825],{"class":202,"line":398},[200,102814,3743],{"class":206},[200,102816,41071],{"class":223},[200,102818,227],{"class":235},[200,102820,25386],{"class":210},[200,102822,550],{"class":214},[200,102824,41080],{"class":2045},[200,102826,416],{"class":235},[200,102828,102829,102831],{"class":202,"line":405},[200,102830,413],{"class":214},[200,102832,416],{"class":210},[51,102834,44320],{"id":44319},[35,102836,44323],{},[35,102838,44326,102839,44329],{},[156,102840,42833],{},[35,102842,44332],{},[192,102844,102845],{"className":21417,"code":44335,"language":12060,"meta":13,"style":13},[156,102846,102847,102855,102893,102933,102973,102991,102995,103003,103011],{"__ignoreMap":13},[200,102848,102849,102851,102853],{"class":202,"line":203},[200,102850,600],{"class":214},[200,102852,21428],{"class":235},[200,102854,648],{"class":214},[200,102856,102857,102859,102861,102863,102865,102867,102869,102871,102873,102875,102877,102879,102881,102883,102885,102887,102889,102891],{"class":202,"line":14},[200,102858,657],{"class":214},[200,102860,603],{"class":235},[200,102862,40910],{"class":379},[200,102864,449],{"class":214},[200,102866,611],{"class":214},[200,102868,44360],{"class":245},[200,102870,611],{"class":214},[200,102872,42727],{"class":379},[200,102874,449],{"class":214},[200,102876,611],{"class":214},[200,102878,42734],{"class":245},[200,102880,611],{"class":214},[200,102882,6577],{"class":379},[200,102884,449],{"class":214},[200,102886,611],{"class":214},[200,102888,3778],{"class":245},[200,102890,611],{"class":214},[200,102892,648],{"class":214},[200,102894,102895,102897,102899,102901,102903,102905,102907,102909,102911,102913,102915,102917,102919,102921,102923,102925,102927,102929,102931],{"class":202,"line":255},[200,102896,804],{"class":214},[200,102898,355],{"class":235},[200,102900,321],{"class":379},[200,102902,449],{"class":214},[200,102904,611],{"class":214},[200,102906,698],{"class":245},[200,102908,611],{"class":214},[200,102910,701],{"class":379},[200,102912,449],{"class":214},[200,102914,611],{"class":214},[200,102916,20089],{"class":245},[200,102918,611],{"class":214},[200,102920,40940],{"class":379},[200,102922,449],{"class":214},[200,102924,611],{"class":214},[200,102926,40947],{"class":245},[200,102928,611],{"class":214},[200,102930,40952],{"class":379},[200,102932,648],{"class":214},[200,102934,102935,102937,102939,102941,102943,102945,102947,102949,102951,102953,102955,102957,102959,102961,102963,102965,102967,102969,102971],{"class":202,"line":272},[200,102936,804],{"class":214},[200,102938,355],{"class":235},[200,102940,321],{"class":379},[200,102942,449],{"class":214},[200,102944,611],{"class":214},[200,102946,42763],{"class":245},[200,102948,611],{"class":214},[200,102950,701],{"class":379},[200,102952,449],{"class":214},[200,102954,611],{"class":214},[200,102956,9737],{"class":245},[200,102958,611],{"class":214},[200,102960,42768],{"class":379},[200,102962,449],{"class":214},[200,102964,611],{"class":214},[200,102966,42775],{"class":245},[200,102968,611],{"class":214},[200,102970,40952],{"class":379},[200,102972,648],{"class":214},[200,102974,102975,102977,102979,102981,102983,102985,102987,102989],{"class":202,"line":283},[200,102976,804],{"class":214},[200,102978,910],{"class":235},[200,102980,321],{"class":379},[200,102982,449],{"class":214},[200,102984,611],{"class":214},[200,102986,917],{"class":245},[200,102988,611],{"class":214},[200,102990,648],{"class":214},[200,102992,102993],{"class":202,"line":300},[200,102994,40977],{"class":210},[200,102996,102997,102999,103001],{"class":202,"line":310},[200,102998,15074],{"class":214},[200,103000,910],{"class":235},[200,103002,648],{"class":214},[200,103004,103005,103007,103009],{"class":202,"line":336},[200,103006,893],{"class":214},[200,103008,603],{"class":235},[200,103010,648],{"class":214},[200,103012,103013,103015,103017],{"class":202,"line":342},[200,103014,681],{"class":214},[200,103016,21428],{"class":235},[200,103018,648],{"class":214},[35,103020,44513],{},[192,103022,103023],{"className":1370,"code":44516,"language":1373,"meta":13,"style":13},[156,103024,103025,103051,103055,103071,103099,103109,103145,103189,103239,103305],{"__ignoreMap":13},[200,103026,103027,103029,103031,103033,103035,103037,103039,103041,103043,103045,103047,103049],{"class":202,"line":203},[200,103028,973],{"class":206},[200,103030,318],{"class":214},[200,103032,42867],{"class":210},[200,103034,550],{"class":214},[200,103036,42872],{"class":210},[200,103038,550],{"class":214},[200,103040,22708],{"class":210},[200,103042,981],{"class":214},[200,103044,984],{"class":206},[200,103046,242],{"class":214},[200,103048,19790],{"class":245},[200,103050,992],{"class":214},[200,103052,103053],{"class":202,"line":14},[200,103054,529],{"emptyLinePlaceholder":20},[200,103056,103057,103059,103061,103063,103065,103067,103069],{"class":202,"line":255},[200,103058,1380],{"class":206},[200,103060,14301],{"class":379},[200,103062,44557],{"class":210},[200,103064,449],{"class":214},[200,103066,42872],{"class":223},[200,103068,227],{"class":210},[200,103070,230],{"class":214},[200,103072,103073,103075,103077,103079,103081,103083,103085,103087,103089,103091,103093,103095,103097],{"class":202,"line":272},[200,103074,236],{"class":235},[200,103076,239],{"class":214},[200,103078,22708],{"class":223},[200,103080,227],{"class":210},[200,103082,2752],{"class":214},[200,103084,39347],{"class":235},[200,103086,239],{"class":214},[200,103088,242],{"class":214},[200,103090,41947],{"class":245},[200,103092,249],{"class":214},[200,103094,981],{"class":214},[200,103096,2089],{"class":210},[200,103098,252],{"class":214},[200,103100,103101,103103,103105,103107],{"class":202,"line":283},[200,103102,43393],{"class":235},[200,103104,239],{"class":214},[200,103106,42867],{"class":223},[200,103108,466],{"class":210},[200,103110,103111,103113,103115,103117,103119,103121,103123,103125,103127,103129,103131,103133,103135,103137,103139,103141,103143],{"class":202,"line":300},[200,103112,3755],{"class":214},[200,103114,42986],{"class":223},[200,103116,227],{"class":210},[200,103118,9737],{"class":386},[200,103120,393],{"class":379},[200,103122,43412],{"class":210},[200,103124,43415],{"class":214},[200,103126,42999],{"class":1978},[200,103128,550],{"class":214},[200,103130,318],{"class":214},[200,103132,39347],{"class":235},[200,103134,239],{"class":214},[200,103136,242],{"class":214},[200,103138,43012],{"class":245},[200,103140,249],{"class":214},[200,103142,981],{"class":214},[200,103144,416],{"class":210},[200,103146,103147,103149,103151,103153,103155,103157,103159,103161,103163,103165,103167,103169,103171,103173,103175,103177,103179,103181,103183,103185,103187],{"class":202,"line":310},[200,103148,3755],{"class":214},[200,103150,42986],{"class":223},[200,103152,227],{"class":210},[200,103154,9737],{"class":386},[200,103156,393],{"class":379},[200,103158,42939],{"class":210},[200,103160,215],{"class":214},[200,103162,43452],{"class":210},[200,103164,600],{"class":214},[200,103166,43041],{"class":2045},[200,103168,29919],{"class":214},[200,103170,43041],{"class":2045},[200,103172,550],{"class":214},[200,103174,318],{"class":214},[200,103176,39347],{"class":235},[200,103178,239],{"class":214},[200,103180,242],{"class":214},[200,103182,43058],{"class":245},[200,103184,249],{"class":214},[200,103186,981],{"class":214},[200,103188,416],{"class":210},[200,103190,103191,103193,103195,103197,103199,103201,103203,103205,103207,103209,103211,103213,103215,103217,103219,103221,103223,103225,103227,103229,103231,103233,103235,103237],{"class":202,"line":336},[200,103192,3755],{"class":214},[200,103194,42986],{"class":223},[200,103196,227],{"class":210},[200,103198,9737],{"class":386},[200,103200,393],{"class":379},[200,103202,42939],{"class":210},[200,103204,215],{"class":214},[200,103206,3771],{"class":210},[200,103208,215],{"class":214},[200,103210,6108],{"class":223},[200,103212,227],{"class":210},[200,103214,249],{"class":214},[200,103216,43093],{"class":245},[200,103218,249],{"class":214},[200,103220,2089],{"class":210},[200,103222,550],{"class":214},[200,103224,318],{"class":214},[200,103226,39347],{"class":235},[200,103228,239],{"class":214},[200,103230,242],{"class":214},[200,103232,43110],{"class":245},[200,103234,249],{"class":214},[200,103236,981],{"class":214},[200,103238,416],{"class":210},[200,103240,103241,103243,103245,103247,103249,103251,103253,103255,103257,103259,103261,103263,103265,103267,103269,103271,103273,103275,103277,103279,103281,103283,103285,103287,103289,103291,103293,103295,103297,103299,103301,103303],{"class":202,"line":342},[200,103242,3755],{"class":214},[200,103244,42986],{"class":223},[200,103246,227],{"class":210},[200,103248,9737],{"class":386},[200,103250,393],{"class":379},[200,103252,350],{"class":210},[200,103254,249],{"class":214},[200,103256,43135],{"class":245},[200,103258,249],{"class":214},[200,103260,550],{"class":214},[200,103262,242],{"class":214},[200,103264,43144],{"class":245},[200,103266,249],{"class":214},[200,103268,550],{"class":214},[200,103270,242],{"class":214},[200,103272,43153],{"class":245},[200,103274,249],{"class":214},[200,103276,360],{"class":210},[200,103278,215],{"class":214},[200,103280,3841],{"class":223},[200,103282,43561],{"class":210},[200,103284,215],{"class":214},[200,103286,43566],{"class":210},[200,103288,550],{"class":214},[200,103290,318],{"class":214},[200,103292,39347],{"class":235},[200,103294,239],{"class":214},[200,103296,242],{"class":214},[200,103298,43184],{"class":245},[200,103300,249],{"class":214},[200,103302,981],{"class":214},[200,103304,416],{"class":210},[200,103306,103307,103309],{"class":202,"line":365},[200,103308,413],{"class":214},[200,103310,416],{"class":210},[35,103312,44808],{},[35,103314,44811,103315,44814],{},[156,103316,42681],{},[192,103318,103319],{"className":1370,"code":44817,"language":1373,"meta":13,"style":13},[156,103320,103321,103347,103351,103367,103395,103401,103405,103421,103431,103467,103511,103561,103627,103633,103637],{"__ignoreMap":13},[200,103322,103323,103325,103327,103329,103331,103333,103335,103337,103339,103341,103343,103345],{"class":202,"line":203},[200,103324,973],{"class":206},[200,103326,318],{"class":214},[200,103328,42867],{"class":210},[200,103330,550],{"class":214},[200,103332,42872],{"class":210},[200,103334,550],{"class":214},[200,103336,22708],{"class":210},[200,103338,981],{"class":214},[200,103340,984],{"class":206},[200,103342,242],{"class":214},[200,103344,19790],{"class":245},[200,103346,992],{"class":214},[200,103348,103349],{"class":202,"line":14},[200,103350,529],{"emptyLinePlaceholder":20},[200,103352,103353,103355,103357,103359,103361,103363,103365],{"class":202,"line":255},[200,103354,1380],{"class":206},[200,103356,14301],{"class":379},[200,103358,44858],{"class":210},[200,103360,449],{"class":214},[200,103362,42872],{"class":223},[200,103364,227],{"class":210},[200,103366,230],{"class":214},[200,103368,103369,103371,103373,103375,103377,103379,103381,103383,103385,103387,103389,103391,103393],{"class":202,"line":272},[200,103370,236],{"class":235},[200,103372,239],{"class":214},[200,103374,22708],{"class":223},[200,103376,227],{"class":210},[200,103378,2752],{"class":214},[200,103380,39347],{"class":235},[200,103382,239],{"class":214},[200,103384,242],{"class":214},[200,103386,41947],{"class":245},[200,103388,249],{"class":214},[200,103390,981],{"class":214},[200,103392,2089],{"class":210},[200,103394,252],{"class":214},[200,103396,103397,103399],{"class":202,"line":283},[200,103398,413],{"class":214},[200,103400,416],{"class":210},[200,103402,103403],{"class":202,"line":300},[200,103404,529],{"emptyLinePlaceholder":20},[200,103406,103407,103409,103411,103413,103415,103417,103419],{"class":202,"line":310},[200,103408,1380],{"class":206},[200,103410,14301],{"class":379},[200,103412,43380],{"class":210},[200,103414,449],{"class":214},[200,103416,42872],{"class":223},[200,103418,227],{"class":210},[200,103420,230],{"class":214},[200,103422,103423,103425,103427,103429],{"class":202,"line":336},[200,103424,43393],{"class":235},[200,103426,239],{"class":214},[200,103428,42867],{"class":223},[200,103430,466],{"class":210},[200,103432,103433,103435,103437,103439,103441,103443,103445,103447,103449,103451,103453,103455,103457,103459,103461,103463,103465],{"class":202,"line":342},[200,103434,3755],{"class":214},[200,103436,42986],{"class":223},[200,103438,227],{"class":210},[200,103440,9737],{"class":386},[200,103442,393],{"class":379},[200,103444,43412],{"class":210},[200,103446,43415],{"class":214},[200,103448,42999],{"class":1978},[200,103450,550],{"class":214},[200,103452,318],{"class":214},[200,103454,39347],{"class":235},[200,103456,239],{"class":214},[200,103458,242],{"class":214},[200,103460,43012],{"class":245},[200,103462,249],{"class":214},[200,103464,981],{"class":214},[200,103466,416],{"class":210},[200,103468,103469,103471,103473,103475,103477,103479,103481,103483,103485,103487,103489,103491,103493,103495,103497,103499,103501,103503,103505,103507,103509],{"class":202,"line":365},[200,103470,3755],{"class":214},[200,103472,42986],{"class":223},[200,103474,227],{"class":210},[200,103476,9737],{"class":386},[200,103478,393],{"class":379},[200,103480,42939],{"class":210},[200,103482,215],{"class":214},[200,103484,43452],{"class":210},[200,103486,600],{"class":214},[200,103488,43041],{"class":2045},[200,103490,29919],{"class":214},[200,103492,43041],{"class":2045},[200,103494,550],{"class":214},[200,103496,318],{"class":214},[200,103498,39347],{"class":235},[200,103500,239],{"class":214},[200,103502,242],{"class":214},[200,103504,43058],{"class":245},[200,103506,249],{"class":214},[200,103508,981],{"class":214},[200,103510,416],{"class":210},[200,103512,103513,103515,103517,103519,103521,103523,103525,103527,103529,103531,103533,103535,103537,103539,103541,103543,103545,103547,103549,103551,103553,103555,103557,103559],{"class":202,"line":371},[200,103514,3755],{"class":214},[200,103516,42986],{"class":223},[200,103518,227],{"class":210},[200,103520,9737],{"class":386},[200,103522,393],{"class":379},[200,103524,42939],{"class":210},[200,103526,215],{"class":214},[200,103528,3771],{"class":210},[200,103530,215],{"class":214},[200,103532,6108],{"class":223},[200,103534,227],{"class":210},[200,103536,249],{"class":214},[200,103538,43093],{"class":245},[200,103540,249],{"class":214},[200,103542,2089],{"class":210},[200,103544,550],{"class":214},[200,103546,318],{"class":214},[200,103548,39347],{"class":235},[200,103550,239],{"class":214},[200,103552,242],{"class":214},[200,103554,43110],{"class":245},[200,103556,249],{"class":214},[200,103558,981],{"class":214},[200,103560,416],{"class":210},[200,103562,103563,103565,103567,103569,103571,103573,103575,103577,103579,103581,103583,103585,103587,103589,103591,103593,103595,103597,103599,103601,103603,103605,103607,103609,103611,103613,103615,103617,103619,103621,103623,103625],{"class":202,"line":398},[200,103564,3755],{"class":214},[200,103566,42986],{"class":223},[200,103568,227],{"class":210},[200,103570,9737],{"class":386},[200,103572,393],{"class":379},[200,103574,350],{"class":210},[200,103576,249],{"class":214},[200,103578,43135],{"class":245},[200,103580,249],{"class":214},[200,103582,550],{"class":214},[200,103584,242],{"class":214},[200,103586,43144],{"class":245},[200,103588,249],{"class":214},[200,103590,550],{"class":214},[200,103592,242],{"class":214},[200,103594,43153],{"class":245},[200,103596,249],{"class":214},[200,103598,360],{"class":210},[200,103600,215],{"class":214},[200,103602,3841],{"class":223},[200,103604,43561],{"class":210},[200,103606,215],{"class":214},[200,103608,43566],{"class":210},[200,103610,550],{"class":214},[200,103612,318],{"class":214},[200,103614,39347],{"class":235},[200,103616,239],{"class":214},[200,103618,242],{"class":214},[200,103620,43184],{"class":245},[200,103622,249],{"class":214},[200,103624,981],{"class":214},[200,103626,416],{"class":210},[200,103628,103629,103631],{"class":202,"line":405},[200,103630,413],{"class":214},[200,103632,416],{"class":210},[200,103634,103635],{"class":202,"line":410},[200,103636,529],{"emptyLinePlaceholder":20},[200,103638,103639,103641,103643,103645,103647,103649,103651,103653],{"class":202,"line":861},[200,103640,1380],{"class":206},[200,103642,14301],{"class":379},[200,103644,44557],{"class":210},[200,103646,449],{"class":214},[200,103648,45149],{"class":210},[200,103650,215],{"class":214},[200,103652,45154],{"class":223},[200,103654,45157],{"class":210},[35,103656,45160,103657,45164],{},[156,103658,45163],{},[192,103660,103661],{"className":21417,"code":45167,"language":12060,"meta":13,"style":13},[156,103662,103663,103683,103703,103721,103739,103743,103767,103791,103797,103821,103825,103843,103857,103867,103873,103877,103889,103903,103907,103911],{"__ignoreMap":13},[200,103664,103665,103667,103669,103671,103673,103675,103677,103679,103681],{"class":202,"line":203},[200,103666,600],{"class":214},[200,103668,14892],{"class":235},[200,103670,14590],{"class":379},[200,103672,449],{"class":214},[200,103674,611],{"class":214},[200,103676,1373],{"class":245},[200,103678,611],{"class":214},[200,103680,21781],{"class":379},[200,103682,648],{"class":214},[200,103684,103685,103687,103689,103691,103693,103695,103697,103699,103701],{"class":202,"line":14},[200,103686,3711],{"class":379},[200,103688,4987],{"class":379},[200,103690,42012],{"class":223},[200,103692,227],{"class":214},[200,103694,25386],{"class":386},[200,103696,239],{"class":214},[200,103698,43636],{"class":1978},[200,103700,2089],{"class":214},[200,103702,280],{"class":214},[200,103704,103705,103707,103709,103711,103713,103715,103717,103719],{"class":202,"line":255},[200,103706,3723],{"class":379},[200,103708,43647],{"class":210},[200,103710,510],{"class":214},[200,103712,43652],{"class":210},[200,103714,215],{"class":214},[200,103716,43657],{"class":210},[200,103718,14674],{"class":206},[200,103720,43662],{"class":1978},[200,103722,103723,103725,103727,103729,103731,103733,103735,103737],{"class":202,"line":272},[200,103724,3723],{"class":379},[200,103726,42915],{"class":210},[200,103728,510],{"class":214},[200,103730,12997],{"class":214},[200,103732,43675],{"class":223},[200,103734,227],{"class":235},[200,103736,43657],{"class":210},[200,103738,416],{"class":235},[200,103740,103741],{"class":202,"line":283},[200,103742,529],{"emptyLinePlaceholder":20},[200,103744,103745,103747,103749,103751,103753,103755,103757,103759,103761,103763,103765],{"class":202,"line":300},[200,103746,3723],{"class":379},[200,103748,701],{"class":210},[200,103750,510],{"class":214},[200,103752,42915],{"class":210},[200,103754,215],{"class":214},[200,103756,42948],{"class":223},[200,103758,227],{"class":235},[200,103760,249],{"class":214},[200,103762,20089],{"class":245},[200,103764,249],{"class":214},[200,103766,416],{"class":235},[200,103768,103769,103771,103773,103775,103777,103779,103781,103783,103785,103787,103789],{"class":202,"line":310},[200,103770,3723],{"class":379},[200,103772,42939],{"class":210},[200,103774,510],{"class":214},[200,103776,42915],{"class":210},[200,103778,215],{"class":214},[200,103780,42948],{"class":223},[200,103782,227],{"class":235},[200,103784,249],{"class":214},[200,103786,9737],{"class":245},[200,103788,249],{"class":214},[200,103790,416],{"class":235},[200,103792,103793,103795],{"class":202,"line":336},[200,103794,42021],{"class":206},[200,103796,280],{"class":214},[200,103798,103799,103801,103803,103805,103807,103809,103811,103813,103815,103817,103819],{"class":202,"line":342},[200,103800,19216],{"class":206},[200,103802,45310],{"class":210},[200,103804,215],{"class":214},[200,103806,41541],{"class":223},[200,103808,227],{"class":235},[200,103810,2752],{"class":214},[200,103812,701],{"class":210},[200,103814,550],{"class":214},[200,103816,42939],{"class":210},[200,103818,981],{"class":214},[200,103820,416],{"class":235},[200,103822,103823],{"class":202,"line":365},[200,103824,529],{"emptyLinePlaceholder":20},[200,103826,103827,103829,103831,103833,103835,103837,103839,103841],{"class":202,"line":371},[200,103828,19216],{"class":206},[200,103830,18741],{"class":223},[200,103832,227],{"class":235},[200,103834,249],{"class":214},[200,103836,44360],{"class":245},[200,103838,249],{"class":214},[200,103840,550],{"class":214},[200,103842,280],{"class":214},[200,103844,103845,103847,103849,103851,103853,103855],{"class":202,"line":398},[200,103846,42067],{"class":235},[200,103848,239],{"class":214},[200,103850,242],{"class":214},[200,103852,18766],{"class":245},[200,103854,249],{"class":214},[200,103856,252],{"class":214},[200,103858,103859,103861,103863,103865],{"class":202,"line":405},[200,103860,42082],{"class":235},[200,103862,239],{"class":214},[200,103864,42915],{"class":210},[200,103866,252],{"class":214},[200,103868,103869,103871],{"class":202,"line":410},[200,103870,2096],{"class":214},[200,103872,416],{"class":235},[200,103874,103875],{"class":202,"line":861},[200,103876,4490],{"class":214},[200,103878,103879,103881,103883,103885,103887],{"class":202,"line":890},[200,103880,42103],{"class":206},[200,103882,1095],{"class":235},[200,103884,19260],{"class":210},[200,103886,5267],{"class":235},[200,103888,230],{"class":214},[200,103890,103891,103893,103895,103897,103899,103901],{"class":202,"line":900},[200,103892,43809],{"class":210},[200,103894,215],{"class":214},[200,103896,19260],{"class":223},[200,103898,227],{"class":235},[200,103900,19260],{"class":210},[200,103902,416],{"class":235},[200,103904,103905],{"class":202,"line":905},[200,103906,4490],{"class":214},[200,103908,103909],{"class":202,"line":931},[200,103910,1130],{"class":214},[200,103912,103913,103915,103917],{"class":202,"line":3640},[200,103914,681],{"class":214},[200,103916,14892],{"class":235},[200,103918,648],{"class":214},[35,103920,45429,103921,45433,103923,45436],{},[156,103922,45432],{},[156,103924,42681],{},[192,103926,103927],{"className":1370,"code":45439,"language":1373,"meta":13,"style":13},[156,103928,103929,103947,103951,103973,103991,103995,104019,104061,104071,104081,104095,104117,104123,104129,104133,104137,104141,104169,104181,104195,104227,104233,104237,104241,104245,104261],{"__ignoreMap":13},[200,103930,103931,103933,103935,103937,103939,103941,103943,103945],{"class":202,"line":203},[200,103932,973],{"class":206},[200,103934,318],{"class":214},[200,103936,45149],{"class":210},[200,103938,981],{"class":214},[200,103940,984],{"class":206},[200,103942,242],{"class":214},[200,103944,42580],{"class":245},[200,103946,992],{"class":214},[200,103948,103949],{"class":202,"line":14},[200,103950,529],{"emptyLinePlaceholder":20},[200,103952,103953,103955,103957,103959,103961,103963,103965,103967,103969,103971],{"class":202,"line":255},[200,103954,1380],{"class":206},[200,103956,1383],{"class":206},[200,103958,38960],{"class":223},[200,103960,227],{"class":210},[200,103962,3711],{"class":379},[200,103964,1095],{"class":214},[200,103966,25386],{"class":386},[200,103968,2089],{"class":214},[200,103970,393],{"class":379},[200,103972,280],{"class":214},[200,103974,103975,103977,103979,103981,103983,103985,103987,103989],{"class":202,"line":272},[200,103976,3723],{"class":379},[200,103978,42915],{"class":210},[200,103980,510],{"class":214},[200,103982,452],{"class":206},[200,103984,42922],{"class":223},[200,103986,227],{"class":235},[200,103988,25386],{"class":210},[200,103990,416],{"class":235},[200,103992,103993],{"class":202,"line":283},[200,103994,529],{"emptyLinePlaceholder":20},[200,103996,103997,103999,104001,104003,104005,104007,104009,104011,104013,104015,104017],{"class":202,"line":300},[200,103998,3723],{"class":379},[200,104000,701],{"class":210},[200,104002,510],{"class":214},[200,104004,42915],{"class":210},[200,104006,215],{"class":214},[200,104008,42948],{"class":223},[200,104010,227],{"class":235},[200,104012,249],{"class":214},[200,104014,20089],{"class":245},[200,104016,249],{"class":214},[200,104018,416],{"class":235},[200,104020,104021,104023,104025,104027,104029,104031,104033,104035,104037,104039,104041,104043,104045,104047,104049,104051,104053,104055,104057,104059],{"class":202,"line":310},[200,104022,3723],{"class":379},[200,104024,5041],{"class":210},[200,104026,510],{"class":214},[200,104028,452],{"class":206},[200,104030,45149],{"class":210},[200,104032,215],{"class":214},[200,104034,41541],{"class":223},[200,104036,227],{"class":235},[200,104038,2752],{"class":214},[200,104040,701],{"class":210},[200,104042,981],{"class":214},[200,104044,2089],{"class":235},[200,104046,215],{"class":214},[200,104048,41681],{"class":223},[200,104050,227],{"class":235},[200,104052,227],{"class":214},[200,104054,19260],{"class":386},[200,104056,2089],{"class":214},[200,104058,393],{"class":379},[200,104060,280],{"class":214},[200,104062,104063,104065,104067,104069],{"class":202,"line":336},[200,104064,39438],{"class":206},[200,104066,39441],{"class":223},[200,104068,227],{"class":235},[200,104070,230],{"class":214},[200,104072,104073,104075,104077,104079],{"class":202,"line":342},[200,104074,39082],{"class":235},[200,104076,239],{"class":214},[200,104078,41427],{"class":2045},[200,104080,252],{"class":214},[200,104082,104083,104085,104087,104089,104091,104093],{"class":202,"line":365},[200,104084,39471],{"class":235},[200,104086,239],{"class":214},[200,104088,242],{"class":214},[200,104090,41440],{"class":245},[200,104092,249],{"class":214},[200,104094,252],{"class":214},[200,104096,104097,104099,104101,104103,104105,104107,104109,104111,104113,104115],{"class":202,"line":371},[200,104098,41449],{"class":235},[200,104100,239],{"class":214},[200,104102,19205],{"class":210},[200,104104,215],{"class":214},[200,104106,26789],{"class":210},[200,104108,9858],{"class":235},[200,104110,3933],{"class":2045},[200,104112,360],{"class":235},[200,104114,215],{"class":214},[200,104116,43284],{"class":210},[200,104118,104119,104121],{"class":202,"line":398},[200,104120,2096],{"class":214},[200,104122,416],{"class":235},[200,104124,104125,104127],{"class":202,"line":405},[200,104126,5080],{"class":214},[200,104128,416],{"class":235},[200,104130,104131],{"class":202,"line":410},[200,104132,529],{"emptyLinePlaceholder":20},[200,104134,104135],{"class":202,"line":861},[200,104136,45650],{"class":401},[200,104138,104139],{"class":202,"line":890},[200,104140,529],{"emptyLinePlaceholder":20},[200,104142,104143,104145,104147,104149,104151,104153,104155,104157,104159,104161,104163,104165,104167],{"class":202,"line":900},[200,104144,3723],{"class":379},[200,104146,42939],{"class":210},[200,104148,510],{"class":214},[200,104150,42915],{"class":210},[200,104152,215],{"class":214},[200,104154,42948],{"class":223},[200,104156,227],{"class":235},[200,104158,249],{"class":214},[200,104160,9737],{"class":245},[200,104162,249],{"class":214},[200,104164,5267],{"class":235},[200,104166,44211],{"class":206},[200,104168,44214],{"class":1978},[200,104170,104171,104173,104175,104177,104179],{"class":202,"line":905},[200,104172,44219],{"class":223},[200,104174,227],{"class":235},[200,104176,9737],{"class":210},[200,104178,550],{"class":214},[200,104180,280],{"class":214},[200,104182,104183,104185,104187,104189,104191,104193],{"class":202,"line":931},[200,104184,44232],{"class":235},[200,104186,239],{"class":214},[200,104188,242],{"class":214},[200,104190,44239],{"class":245},[200,104192,249],{"class":214},[200,104194,252],{"class":214},[200,104196,104197,104199,104201,104203,104205,104207,104209,104211,104213,104215,104217,104219,104221,104223,104225],{"class":202,"line":3640},[200,104198,44248],{"class":235},[200,104200,239],{"class":214},[200,104202,350],{"class":235},[200,104204,249],{"class":214},[200,104206,43135],{"class":245},[200,104208,249],{"class":214},[200,104210,550],{"class":214},[200,104212,242],{"class":214},[200,104214,43144],{"class":245},[200,104216,249],{"class":214},[200,104218,550],{"class":214},[200,104220,242],{"class":214},[200,104222,43153],{"class":245},[200,104224,249],{"class":214},[200,104226,4496],{"class":235},[200,104228,104229,104231],{"class":202,"line":3645},[200,104230,5080],{"class":214},[200,104232,416],{"class":235},[200,104234,104235],{"class":202,"line":4020},[200,104236,529],{"emptyLinePlaceholder":20},[200,104238,104239],{"class":202,"line":4475},[200,104240,43305],{"class":401},[200,104242,104243],{"class":202,"line":4481},[200,104244,529],{"emptyLinePlaceholder":20},[200,104246,104247,104249,104251,104253,104255,104257,104259],{"class":202,"line":4487},[200,104248,3743],{"class":206},[200,104250,41071],{"class":223},[200,104252,227],{"class":235},[200,104254,25386],{"class":210},[200,104256,550],{"class":214},[200,104258,41080],{"class":2045},[200,104260,416],{"class":235},[200,104262,104263,104265],{"class":202,"line":4493},[200,104264,413],{"class":214},[200,104266,416],{"class":210},[35,104268,45783,104269,1552],{},[156,104270,42681],{},[192,104272,104273],{"className":1370,"code":45788,"language":1373,"meta":13,"style":13},[156,104274,104275,104285,104315,104335,104359,104363,104367,104389,104393,104397,104403,104417,104421],{"__ignoreMap":13},[200,104276,104277,104279,104281,104283],{"class":202,"line":203},[200,104278,443],{"class":379},[200,104280,10954],{"class":210},[200,104282,449],{"class":214},[200,104284,19891],{"class":214},[200,104286,104287,104289,104291,104293,104295,104297,104299,104301,104303,104305,104307,104309,104311,104313],{"class":202,"line":14},[200,104288,1090],{"class":206},[200,104290,1095],{"class":210},[200,104292,443],{"class":379},[200,104294,350],{"class":214},[200,104296,24584],{"class":210},[200,104298,550],{"class":214},[200,104300,810],{"class":210},[200,104302,360],{"class":214},[200,104304,6445],{"class":214},[200,104306,42915],{"class":210},[200,104308,215],{"class":214},[200,104310,6453],{"class":223},[200,104312,45829],{"class":210},[200,104314,230],{"class":214},[200,104316,104317,104319,104321,104323,104325,104327,104329,104331,104333],{"class":202,"line":255},[200,104318,20075],{"class":206},[200,104320,1095],{"class":235},[200,104322,24584],{"class":210},[200,104324,3283],{"class":214},[200,104326,242],{"class":214},[200,104328,45846],{"class":245},[200,104330,249],{"class":214},[200,104332,5267],{"class":235},[200,104334,230],{"class":214},[200,104336,104337,104339,104341,104343,104345,104347,104349,104351,104353,104355,104357],{"class":202,"line":272},[200,104338,45857],{"class":210},[200,104340,9858],{"class":235},[200,104342,24584],{"class":210},[200,104344,18836],{"class":235},[200,104346,449],{"class":214},[200,104348,42915],{"class":210},[200,104350,215],{"class":214},[200,104352,45872],{"class":223},[200,104354,227],{"class":235},[200,104356,24584],{"class":210},[200,104358,416],{"class":235},[200,104360,104361],{"class":202,"line":283},[200,104362,45883],{"class":206},[200,104364,104365],{"class":202,"line":300},[200,104366,4490],{"class":214},[200,104368,104369,104371,104373,104375,104377,104379,104381,104383,104385,104387],{"class":202,"line":310},[200,104370,45892],{"class":206},[200,104372,45895],{"class":206},[200,104374,1095],{"class":235},[200,104376,24584],{"class":210},[200,104378,3283],{"class":214},[200,104380,242],{"class":214},[200,104382,9737],{"class":245},[200,104384,249],{"class":214},[200,104386,5267],{"class":235},[200,104388,230],{"class":214},[200,104390,104391],{"class":202,"line":336},[200,104392,45883],{"class":206},[200,104394,104395],{"class":202,"line":342},[200,104396,4490],{"class":214},[200,104398,104399,104401],{"class":202,"line":365},[200,104400,45892],{"class":206},[200,104402,280],{"class":214},[200,104404,104405,104407,104409,104411,104413,104415],{"class":202,"line":371},[200,104406,45857],{"class":210},[200,104408,9858],{"class":235},[200,104410,24584],{"class":210},[200,104412,18836],{"class":235},[200,104414,449],{"class":214},[200,104416,45940],{"class":210},[200,104418,104419],{"class":202,"line":398},[200,104420,4490],{"class":214},[200,104422,104423],{"class":202,"line":405},[200,104424,1130],{"class":214},[51,104426,30681],{"id":30680},[35,104428,29256,104429,45956,104431],{},[1217,104430,45955],{},[1217,104432,45959,104433,45965],{},[39,104434,45964],{"href":45962,"rel":104435},[77],[35,104437,45968],{},[35,104439,45971],{},[1171,104441,45974],{},{"title":13,"searchDepth":14,"depth":14,"links":104443},[104444,104445],{"id":18634,"depth":14,"text":18635},{"id":40874,"depth":14,"text":40875,"children":104446},[104447],{"id":41102,"depth":255,"text":41103},{},{"title":40805,"description":45982},{"loc":45984},{"id":45991,"title":45992,"body":104452,"contentId":30840,"date":60380,"description":60381,"extension":18,"meta":117559,"navigation":20,"path":60383,"rawbody":60384,"robots":23,"schemaOrg":23,"seo":117560,"sitemap":117561,"stem":60412,"__hash__":60413},{"type":7,"value":104453,"toc":117555},[104454,104458,104460,104471,104473,104477,104479,104481,104483,104487,104503,104507,104511,104516,104523,104533,104679,104681,104688,104702,104704,104708,104712,104714,104718,104734,104736,104738,117553],[35,104455,104456],{},[6850,104457,45999],{},[35,104459,46002],{},[11386,104461,132,104463],{"className":104462},[46006,46007,46008,46009],[46011,104464,132,104465],{},[7139,104466,132,104467],{"icon":7141,"text":46015,"href":46016},[7139,104468,132,104469],{"icon":46019,"text":46020,"href":36517},[7139,104470],{"icon":46023,"text":46024,"href":46025},[35,104472,46028],{},[35,104474,104475],{},[6850,104476,46033],{},[35,104478,46036],{},[35,104480,46039],{},[51,104482,46043],{"id":46042},[35,104484,104485],{},[1690,104486],{"alt":46048,"src":46049},[35,104488,104489],{},[1217,104490,104491,46057,104494,46062,104497,46066,104500,215],{},[39,104492,38639],{"href":46025,"rel":104493},[77],[39,104495,46061],{"href":18567,"rel":104496},[77],[39,104498,19412],{"href":46016,"rel":104499},[77],[39,104501,11622],{"href":46025,"rel":104502},[77],[35,104504,46072,104505],{},[1217,104506,46075],{},[35,104508,104509],{},[1690,104510],{"alt":46080,"src":46081},[35,104512,46084,104513,46088],{},[39,104514,36822],{"href":38142,"rel":104515},[77],[35,104517,46091,104518,46101],{},[1217,104519,46094,104520,46100],{},[39,104521,46099],{"href":46097,"rel":104522},[77],[35,104524,46104,104525,17879,104530,46114],{},[39,104526,104528],{"href":38274,"rel":104527},[77],[156,104529,38276],{},[39,104531,38138],{"href":46112,"rel":104532},[77],[192,104534,104535],{"className":1370,"code":46117,"language":1373,"meta":13,"style":13},[156,104536,104537,104553,104561,104571,104575,104599,104613,104621,104635,104639,104645,104649,104669,104673],{"__ignoreMap":13},[200,104538,104539,104541,104543,104545,104547,104549,104551],{"class":202,"line":203},[200,104540,1380],{"class":206},[200,104542,1383],{"class":206},[200,104544,46128],{"class":210},[200,104546,215],{"class":214},[200,104548,46133],{"class":223},[200,104550,227],{"class":210},[200,104552,230],{"class":214},[200,104554,104555,104557,104559],{"class":202,"line":14},[200,104556,46142],{"class":235},[200,104558,239],{"class":214},[200,104560,280],{"class":214},[200,104562,104563,104565,104567,104569],{"class":202,"line":255},[200,104564,46151],{"class":235},[200,104566,239],{"class":214},[200,104568,2424],{"class":2423},[200,104570,252],{"class":214},[200,104572,104573],{"class":202,"line":272},[200,104574,368],{"class":214},[200,104576,104577,104579,104581,104583,104585,104587,104589,104591,104593,104595,104597],{"class":202,"line":283},[200,104578,3219],{"class":379},[200,104580,46168],{"class":235},[200,104582,227],{"class":214},[200,104584,25386],{"class":386},[200,104586,550],{"class":214},[200,104588,318],{"class":214},[200,104590,34776],{"class":386},[200,104592,550],{"class":214},[200,104594,9855],{"class":386},[200,104596,390],{"class":214},[200,104598,280],{"class":214},[200,104600,104601,104603,104605,104607,104609,104611],{"class":202,"line":300},[200,104602,19216],{"class":206},[200,104604,46193],{"class":223},[200,104606,227],{"class":235},[200,104608,25386],{"class":210},[200,104610,550],{"class":214},[200,104612,280],{"class":214},[200,104614,104615,104617,104619],{"class":202,"line":310},[200,104616,46206],{"class":235},[200,104618,239],{"class":214},[200,104620,280],{"class":214},[200,104622,104623,104625,104627,104629,104631,104633],{"class":202,"line":336},[200,104624,46215],{"class":235},[200,104626,239],{"class":214},[200,104628,34776],{"class":210},[200,104630,215],{"class":214},[200,104632,46224],{"class":210},[200,104634,252],{"class":214},[200,104636,104637],{"class":202,"line":342},[200,104638,12083],{"class":214},[200,104640,104641,104643],{"class":202,"line":365},[200,104642,2096],{"class":214},[200,104644,416],{"class":235},[200,104646,104647],{"class":202,"line":371},[200,104648,529],{"emptyLinePlaceholder":20},[200,104650,104651,104653,104655,104657,104659,104661,104663,104665,104667],{"class":202,"line":398},[200,104652,3296],{"class":206},[200,104654,46247],{"class":223},[200,104656,227],{"class":235},[200,104658,25386],{"class":210},[200,104660,550],{"class":214},[200,104662,242],{"class":214},[200,104664,2882],{"class":245},[200,104666,249],{"class":214},[200,104668,416],{"class":235},[200,104670,104671],{"class":202,"line":405},[200,104672,368],{"class":214},[200,104674,104675,104677],{"class":202,"line":410},[200,104676,413],{"class":214},[200,104678,416],{"class":210},[35,104680,46274],{},[35,104682,46277,104683,46284],{},[1217,104684,104685],{},[39,104686,46099],{"href":46282,"rel":104687},[77],[35,104689,46287,104690,46294,104695,46298,104697,46304,104700],{},[1217,104691,104692],{},[39,104693,46099],{"href":46292,"rel":104694},[77],[1217,104696,46297],{},[39,104698,46303],{"href":46301,"rel":104699},[77],[1217,104701,46307],{},[35,104703,46310],{},[35,104705,104706],{},[1217,104707,46315],{},[35,104709,46318,104710],{},[1582,104711,46321],{"repo":38655},[51,104713,46325],{"id":46324},[35,104715,104716],{},[1690,104717],{"alt":46330,"src":46331},[35,104719,104720],{},[1217,104721,104722,46057,104725,46062,104728,46066,104731,215],{},[39,104723,38639],{"href":46025,"rel":104724},[77],[39,104726,46061],{"href":18567,"rel":104727},[77],[39,104729,19412],{"href":46016,"rel":104730},[77],[39,104732,11622],{"href":46025,"rel":104733},[77],[35,104735,46350],{},[35,104737,46353],{},[7905,104739,104740],{"id":30890},[7905,104741,104742,104744,104746,104748,104750,104754,104756,104761,104769,104771,104773,104800,104802,104804,104808,104810,104814,104818,104825,104829,104831,104833,104849,104851,104855,104857,104861,104881,104883,104885,104889,104894,105488,105490,105492,105494,105498,105500,105504,105520,105522,105524,105526,105530,105532,105536,105541,105543,105565,105567,105569,105579,105583,105585,105587,105593,105637,105639,105641,105645,105647,105653,105785,105797,105801,105941,105945,105947,105949,106065,106069,106073,106345,106349,106351,106467,106469,106915,106921,106925,106929,106931],{"id":20826},[35,104743,46360],{},[1913,104745,46364],{"id":46363},[35,104747,46367],{},[35,104749,46370],{},[35,104751,104752],{},[1690,104753],{"alt":46375,"src":46376},[35,104755,46379],{},[35,104757,46382,104758,46388],{},[39,104759,46387],{"href":46385,"rel":104760},[77],[939,104762,104763,104765],{},[35,104764,46393],{},[35,104766,104767],{},[6850,104768,46398],{},[35,104770,46401],{},[35,104772,46404],{},[1576,104774,104775,104780,104785,104790,104795],{},[71,104776,104777],{},[39,104778,46413],{"href":46411,"rel":104779},[77],[71,104781,104782],{},[39,104783,46420],{"href":46418,"rel":104784},[77],[71,104786,104787],{},[39,104788,46427],{"href":46425,"rel":104789},[77],[71,104791,104792],{},[39,104793,46434],{"href":46432,"rel":104794},[77],[71,104796,104797],{},[39,104798,46441],{"href":46439,"rel":104799},[77],[35,104801,46444],{},[1913,104803,36822],{"id":46447},[35,104805,104806],{},[1690,104807],{"alt":46452,"src":46453},[35,104809,46456],{},[35,104811,46459,104812,46463],{},[1217,104813,46462],{},[35,104815,104816],{},[1690,104817],{"alt":46468,"src":46469},[35,104819,46472,104820,215],{},[39,104821,104823],{"href":46475,"rel":104822},[77],[156,104824,46479],{},[35,104826,104827],{},[1690,104828],{"alt":46484,"src":46485},[35,104830,46488],{},[35,104832,46491],{},[939,104834,104835,104837],{},[35,104836,46496],{},[1576,104838,104839,104841,104843,104845,104847],{},[71,104840,46501],{},[71,104842,46504],{},[71,104844,46507],{},[71,104846,46510],{},[71,104848,46513],{},[1913,104850,46517],{"id":46516},[35,104852,46520,104853,46523],{},[156,104854,38276],{},[35,104856,46526],{},[35,104858,104859],{},[1690,104860],{"alt":46531,"src":46532},[68,104862,104863,104867,104869,104871,104873,104875,104877,104879],{},[71,104864,46537,104865,215],{},[156,104866,46540],{},[71,104868,46543],{},[71,104870,46546],{},[71,104872,46549],{},[71,104874,46552],{},[71,104876,46555],{},[71,104878,46558],{},[71,104880,46561],{},[35,104882,46564],{},[38677,104884,46568],{"id":46567},[35,104886,46571,104887,46574],{},[156,104888,38276],{},[35,104890,46577,104891,215],{},[39,104892,2145],{"href":38759,"rel":104893},[77],[192,104895,104896],{"className":1370,"code":46583,"language":1373,"meta":13,"style":13},[156,104897,104898,104902,104906,104910,104914,104918,104946,104970,104982,104986,104990,104994,104998,105014,105018,105034,105038,105042,105046,105050,105068,105072,105076,105080,105084,105088,105108,105116,105122,105142,105156,105166,105192,105204,105212,105216,105220,105224,105228,105232,105236,105254,105268,105272,105286,105294,105308,105312,105316,105320,105324,105338,105342,105346,105350,105354,105382,105390,105408,105412,105418,105422,105426,105430,105434,105438,105442,105446,105460,105466,105472,105478,105484],{"__ignoreMap":13},[200,104899,104900],{"class":202,"line":203},[200,104901,26078],{"class":401},[200,104903,104904],{"class":202,"line":14},[200,104905,46594],{"class":401},[200,104907,104908],{"class":202,"line":255},[200,104909,46599],{"class":401},[200,104911,104912],{"class":202,"line":272},[200,104913,46604],{"class":401},[200,104915,104916],{"class":202,"line":283},[200,104917,26093],{"class":401},[200,104919,104920,104922,104924,104926,104928,104930,104932,104934,104936,104938,104940,104942,104944],{"class":202,"line":300},[200,104921,1380],{"class":206},[200,104923,4987],{"class":379},[200,104925,46617],{"class":223},[200,104927,3951],{"class":214},[200,104929,46622],{"class":386},[200,104931,550],{"class":214},[200,104933,46168],{"class":386},[200,104935,46629],{"class":214},[200,104937,46632],{"class":1978},[200,104939,600],{"class":214},[200,104941,46637],{"class":1978},[200,104943,46640],{"class":214},[200,104945,280],{"class":214},[200,104947,104948,104950,104952,104954,104956,104958,104960,104962,104964,104966,104968],{"class":202,"line":310},[200,104949,3743],{"class":206},[200,104951,46649],{"class":223},[200,104953,227],{"class":235},[200,104955,3711],{"class":379},[200,104957,1095],{"class":214},[200,104959,25386],{"class":386},[200,104961,239],{"class":214},[200,104963,39325],{"class":1978},[200,104965,2089],{"class":214},[200,104967,393],{"class":379},[200,104969,280],{"class":214},[200,104971,104972,104974,104976,104978,104980],{"class":202,"line":336},[200,104973,24931],{"class":210},[200,104975,510],{"class":214},[200,104977,318],{"class":214},[200,104979,46678],{"class":401},[200,104981,1130],{"class":214},[200,104983,104984],{"class":202,"line":342},[200,104985,529],{"emptyLinePlaceholder":20},[200,104987,104988],{"class":202,"line":365},[200,104989,19618],{"class":401},[200,104991,104992],{"class":202,"line":371},[200,104993,46693],{"class":401},[200,104995,104996],{"class":202,"line":398},[200,104997,19628],{"class":401},[200,104999,105000,105002,105004,105006,105008,105010,105012],{"class":202,"line":405},[200,105001,1999],{"class":379},[200,105003,3484],{"class":210},[200,105005,510],{"class":214},[200,105007,46708],{"class":223},[200,105009,227],{"class":235},[200,105011,25386],{"class":210},[200,105013,416],{"class":235},[200,105015,105016],{"class":202,"line":410},[200,105017,529],{"emptyLinePlaceholder":20},[200,105019,105020,105022,105024,105026,105028,105030,105032],{"class":202,"line":861},[200,105021,5252],{"class":206},[200,105023,1095],{"class":235},[200,105025,3509],{"class":210},[200,105027,215],{"class":214},[200,105029,19260],{"class":210},[200,105031,5267],{"class":235},[200,105033,230],{"class":214},[200,105035,105036],{"class":202,"line":890},[200,105037,46739],{"class":401},[200,105039,105040],{"class":202,"line":900},[200,105041,46744],{"class":206},[200,105043,105044],{"class":202,"line":905},[200,105045,4962],{"class":214},[200,105047,105048],{"class":202,"line":931},[200,105049,529],{"emptyLinePlaceholder":20},[200,105051,105052,105054,105056,105058,105060,105062,105064,105066],{"class":202,"line":3640},[200,105053,5252],{"class":206},[200,105055,1095],{"class":235},[200,105057,11837],{"class":214},[200,105059,3509],{"class":210},[200,105061,215],{"class":214},[200,105063,156],{"class":210},[200,105065,5267],{"class":235},[200,105067,230],{"class":214},[200,105069,105070],{"class":202,"line":3645},[200,105071,46775],{"class":401},[200,105073,105074],{"class":202,"line":4020},[200,105075,46780],{"class":401},[200,105077,105078],{"class":202,"line":4475},[200,105079,46785],{"class":401},[200,105081,105082],{"class":202,"line":4481},[200,105083,46790],{"class":401},[200,105085,105086],{"class":202,"line":4487},[200,105087,46795],{"class":401},[200,105089,105090,105092,105094,105096,105098,105100,105102,105104,105106],{"class":202,"line":4493},[200,105091,20113],{"class":379},[200,105093,46802],{"class":210},[200,105095,510],{"class":214},[200,105097,46807],{"class":223},[200,105099,227],{"class":235},[200,105101,25386],{"class":210},[200,105103,2089],{"class":235},[200,105105,215],{"class":214},[200,105107,46818],{"class":210},[200,105109,105110,105112,105114],{"class":202,"line":5454},[200,105111,11715],{"class":206},[200,105113,46247],{"class":223},[200,105115,3115],{"class":235},[200,105117,105118,105120],{"class":202,"line":5459},[200,105119,46831],{"class":210},[200,105121,252],{"class":214},[200,105123,105124,105126,105128,105130,105132,105134,105136,105138,105140],{"class":202,"line":13227},[200,105125,46838],{"class":223},[200,105127,227],{"class":235},[200,105129,13480],{"class":210},[200,105131,215],{"class":214},[200,105133,46847],{"class":210},[200,105135,14674],{"class":206},[200,105137,22708],{"class":1978},[200,105139,550],{"class":214},[200,105141,280],{"class":214},[200,105143,105144,105146,105148,105150,105152,105154],{"class":202,"line":13253},[200,105145,46860],{"class":235},[200,105147,239],{"class":214},[200,105149,46622],{"class":210},[200,105151,215],{"class":214},[200,105153,46869],{"class":210},[200,105155,252],{"class":214},[200,105157,105158,105160,105162,105164],{"class":202,"line":13269},[200,105159,46876],{"class":235},[200,105161,239],{"class":214},[200,105163,46802],{"class":210},[200,105165,252],{"class":214},[200,105167,105168,105170,105172,105174,105176,105178,105180,105182,105184,105186,105188,105190],{"class":202,"line":13313},[200,105169,46887],{"class":235},[200,105171,239],{"class":214},[200,105173,46622],{"class":210},[200,105175,215],{"class":214},[200,105177,46896],{"class":210},[200,105179,215],{"class":214},[200,105181,6190],{"class":223},[200,105183,227],{"class":235},[200,105185,249],{"class":214},[200,105187,242],{"class":214},[200,105189,2089],{"class":235},[200,105191,252],{"class":214},[200,105193,105194,105196,105198,105200,105202],{"class":202,"line":13319},[200,105195,46915],{"class":214},[200,105197,13480],{"class":210},[200,105199,215],{"class":214},[200,105201,46922],{"class":210},[200,105203,252],{"class":214},[200,105205,105206,105208,105210],{"class":202,"line":13324},[200,105207,13378],{"class":214},[200,105209,2089],{"class":235},[200,105211,252],{"class":214},[200,105213,105214],{"class":202,"line":13350},[200,105215,46937],{"class":235},[200,105217,105218],{"class":202,"line":13355},[200,105219,4962],{"class":214},[200,105221,105222],{"class":202,"line":13375},[200,105223,529],{"emptyLinePlaceholder":20},[200,105225,105226],{"class":202,"line":13395},[200,105227,19618],{"class":401},[200,105229,105230],{"class":202,"line":13400},[200,105231,46954],{"class":401},[200,105233,105234],{"class":202,"line":13408},[200,105235,19628],{"class":401},[200,105237,105238,105240,105242,105244,105246,105248,105250,105252],{"class":202,"line":13426},[200,105239,1999],{"class":379},[200,105241,9855],{"class":210},[200,105243,239],{"class":214},[200,105245,42867],{"class":1978},[200,105247,510],{"class":214},[200,105249,452],{"class":206},[200,105251,18741],{"class":223},[200,105253,3115],{"class":235},[200,105255,105256,105258,105260,105262,105264,105266],{"class":202,"line":13456},[200,105257,46981],{"class":210},[200,105259,215],{"class":214},[200,105261,46986],{"class":210},[200,105263,14674],{"class":206},[200,105265,22708],{"class":1978},[200,105267,252],{"class":214},[200,105269,105270],{"class":202,"line":13462},[200,105271,4442],{"class":214},[200,105273,105274,105276,105278,105280,105282,105284],{"class":202,"line":13467},[200,105275,47001],{"class":235},[200,105277,239],{"class":214},[200,105279,242],{"class":214},[200,105281,18766],{"class":245},[200,105283,249],{"class":214},[200,105285,252],{"class":214},[200,105287,105288,105290,105292],{"class":202,"line":13472},[200,105289,47016],{"class":235},[200,105291,239],{"class":214},[200,105293,280],{"class":214},[200,105295,105296,105298,105300,105302,105304,105306],{"class":202,"line":13487},[200,105297,13411],{"class":235},[200,105299,239],{"class":214},[200,105301,3484],{"class":210},[200,105303,215],{"class":214},[200,105305,156],{"class":210},[200,105307,252],{"class":214},[200,105309,105310],{"class":202,"line":13498},[200,105311,4950],{"class":214},[200,105313,105314],{"class":202,"line":13503},[200,105315,12083],{"class":214},[200,105317,105318],{"class":202,"line":13508},[200,105319,3854],{"class":235},[200,105321,105322],{"class":202,"line":20224},[200,105323,529],{"emptyLinePlaceholder":20},[200,105325,105326,105328,105330,105332,105334,105336],{"class":202,"line":20246},[200,105327,1999],{"class":379},[200,105329,47057],{"class":210},[200,105331,510],{"class":214},[200,105333,9855],{"class":210},[200,105335,215],{"class":214},[200,105337,47066],{"class":210},[200,105339,105340],{"class":202,"line":20251},[200,105341,529],{"emptyLinePlaceholder":20},[200,105343,105344],{"class":202,"line":20256},[200,105345,19618],{"class":401},[200,105347,105348],{"class":202,"line":20261},[200,105349,47079],{"class":401},[200,105351,105352],{"class":202,"line":20282},[200,105353,19628],{"class":401},[200,105355,105356,105358,105360,105362,105364,105366,105368,105370,105372,105374,105376,105378,105380],{"class":202,"line":20289},[200,105357,1999],{"class":379},[200,105359,34776],{"class":210},[200,105361,239],{"class":214},[200,105363,42867],{"class":1978},[200,105365,510],{"class":214},[200,105367,452],{"class":206},[200,105369,4843],{"class":223},[200,105371,227],{"class":235},[200,105373,249],{"class":214},[200,105375,47106],{"class":245},[200,105377,249],{"class":214},[200,105379,550],{"class":214},[200,105381,280],{"class":214},[200,105383,105384,105386,105388],{"class":202,"line":20294},[200,105385,47117],{"class":235},[200,105387,239],{"class":214},[200,105389,280],{"class":214},[200,105391,105392,105394,105396,105398,105400,105402,105404,105406],{"class":202,"line":20310},[200,105393,47126],{"class":235},[200,105395,239],{"class":214},[200,105397,11139],{"class":214},[200,105399,47133],{"class":245},[200,105401,4853],{"class":214},[200,105403,47138],{"class":210},[200,105405,23001],{"class":214},[200,105407,252],{"class":214},[200,105409,105410],{"class":202,"line":47145},[200,105411,12083],{"class":214},[200,105413,105414,105416],{"class":202,"line":47150},[200,105415,2096],{"class":214},[200,105417,416],{"class":235},[200,105419,105420],{"class":202,"line":47157},[200,105421,529],{"emptyLinePlaceholder":20},[200,105423,105424],{"class":202,"line":47162},[200,105425,19618],{"class":401},[200,105427,105428],{"class":202,"line":47167},[200,105429,47170],{"class":401},[200,105431,105432],{"class":202,"line":47173},[200,105433,47176],{"class":401},[200,105435,105436],{"class":202,"line":47179},[200,105437,47182],{"class":401},[200,105439,105440],{"class":202,"line":47185},[200,105441,47188],{"class":401},[200,105443,105444],{"class":202,"line":47191},[200,105445,19628],{"class":401},[200,105447,105448,105450,105452,105454,105456,105458],{"class":202,"line":47196},[200,105449,3296],{"class":206},[200,105451,46168],{"class":223},[200,105453,227],{"class":235},[200,105455,25386],{"class":210},[200,105457,550],{"class":214},[200,105459,280],{"class":214},[200,105461,105462,105464],{"class":202,"line":47211},[200,105463,46206],{"class":210},[200,105465,252],{"class":214},[200,105467,105468,105470],{"class":202,"line":47218},[200,105469,47221],{"class":210},[200,105471,252],{"class":214},[200,105473,105474,105476],{"class":202,"line":47226},[200,105475,2096],{"class":214},[200,105477,416],{"class":235},[200,105479,105480,105482],{"class":202,"line":47233},[200,105481,5080],{"class":214},[200,105483,416],{"class":235},[200,105485,105486],{"class":202,"line":47240},[200,105487,1130],{"class":214},[35,105489,47245],{},[1913,105491,30681],{"id":30680},[35,105493,47250],{},[35,105495,46318,105496],{},[1582,105497,46321],{"repo":38655},[51,105499,47258],{"id":47257},[35,105501,105502],{},[1690,105503],{"alt":47263,"src":47264},[35,105505,105506],{},[1217,105507,105508,46057,105511,46062,105514,46066,105517,215],{},[39,105509,38639],{"href":46025,"rel":105510},[77],[39,105512,46061],{"href":18567,"rel":105513},[77],[39,105515,19412],{"href":46016,"rel":105516},[77],[39,105518,11622],{"href":46025,"rel":105519},[77],[35,105521,47283],{},[1913,105523,47287],{"id":47286},[35,105525,47290],{},[35,105527,47293,105528],{},[1217,105529,47296],{},[35,105531,47299],{},[35,105533,105534],{},[1690,105535],{"alt":47304,"src":47305},[35,105537,47308,105538,47314],{},[39,105539,47313],{"href":47311,"rel":105540},[77],[35,105542,47317],{},[1576,105544,105545,105549,105553,105557,105561],{},[71,105546,47322,105547,47326],{},[156,105548,47325],{},[71,105550,47322,105551,47332],{},[156,105552,47331],{},[71,105554,47322,105555,47337],{},[156,105556,2461],{},[71,105558,47322,105559,47343],{},[156,105560,47342],{},[71,105562,47322,105563,47349],{},[156,105564,47348],{},[35,105566,47352],{},[1913,105568,31704],{"id":31703},[35,105570,47357,105571,47363,105574,47369,105577],{},[39,105572,47362],{"href":47360,"rel":105573},[77],[39,105575,47368],{"href":47366,"rel":105576},[77],[1217,105578,47372],{},[35,105580,47375,105581,47379],{},[1217,105582,47378],{},[35,105584,47382],{},[1913,105586,47386],{"id":47385},[35,105588,47389,105589,47393,105591,215],{},[1217,105590,47392],{},[156,105592,47396],{},[192,105594,105595],{"className":21417,"code":47399,"language":12060,"meta":13,"style":13},[156,105596,105597,105605,105613,105621,105629],{"__ignoreMap":13},[200,105598,105599,105601,105603],{"class":202,"line":203},[200,105600,600],{"class":214},[200,105602,21428],{"class":235},[200,105604,648],{"class":214},[200,105606,105607,105609,105611],{"class":202,"line":14},[200,105608,657],{"class":214},[200,105610,47416],{"class":235},[200,105612,648],{"class":214},[200,105614,105615,105617,105619],{"class":202,"line":255},[200,105616,804],{"class":214},[200,105618,47425],{"class":235},[200,105620,21487],{"class":214},[200,105622,105623,105625,105627],{"class":202,"line":272},[200,105624,893],{"class":214},[200,105626,47416],{"class":235},[200,105628,648],{"class":214},[200,105630,105631,105633,105635],{"class":202,"line":283},[200,105632,681],{"class":214},[200,105634,21428],{"class":235},[200,105636,648],{"class":214},[1913,105638,47447],{"id":47446},[35,105640,47450],{},[35,105642,105643],{},[1690,105644],{"alt":47455,"src":47456},[38677,105646,47460],{"id":47459},[35,105648,47463,105649,47467,105651,47471],{},[156,105650,47466],{},[156,105652,47470],{},[192,105654,105655],{"className":1370,"code":47474,"language":1373,"meta":13,"style":13},[156,105656,105657,105675,105697,105715,105733,105749,105769,105773,105779],{"__ignoreMap":13},[200,105658,105659,105661,105663,105665,105667,105669,105671,105673],{"class":202,"line":203},[200,105660,1380],{"class":206},[200,105662,1383],{"class":206},[200,105664,38960],{"class":223},[200,105666,227],{"class":210},[200,105668,3711],{"class":379},[200,105670,3714],{"class":214},[200,105672,393],{"class":379},[200,105674,280],{"class":214},[200,105676,105677,105679,105681,105683,105685,105687,105689,105691,105693,105695],{"class":202,"line":14},[200,105678,3723],{"class":379},[200,105680,47501],{"class":210},[200,105682,510],{"class":214},[200,105684,452],{"class":206},[200,105686,47508],{"class":223},[200,105688,1532],{"class":235},[200,105690,215],{"class":214},[200,105692,770],{"class":223},[200,105694,227],{"class":235},[200,105696,230],{"class":214},[200,105698,105699,105701,105703,105705,105707,105709,105711,105713],{"class":202,"line":255},[200,105700,15858],{"class":235},[200,105702,239],{"class":214},[200,105704,47527],{"class":210},[200,105706,215],{"class":214},[200,105708,47331],{"class":210},[200,105710,215],{"class":214},[200,105712,3280],{"class":210},[200,105714,252],{"class":214},[200,105716,105717,105719,105721,105723,105725,105727,105729,105731],{"class":202,"line":272},[200,105718,47542],{"class":235},[200,105720,239],{"class":214},[200,105722,47527],{"class":210},[200,105724,215],{"class":214},[200,105726,47331],{"class":210},[200,105728,215],{"class":214},[200,105730,47555],{"class":210},[200,105732,252],{"class":214},[200,105734,105735,105737,105739,105741,105743,105745,105747],{"class":202,"line":283},[200,105736,11797],{"class":235},[200,105738,239],{"class":214},[200,105740,47527],{"class":210},[200,105742,215],{"class":214},[200,105744,47331],{"class":210},[200,105746,215],{"class":214},[200,105748,30209],{"class":210},[200,105750,105751,105753,105755,105757,105759,105761,105763,105765,105767],{"class":202,"line":300},[200,105752,5080],{"class":214},[200,105754,2089],{"class":235},[200,105756,215],{"class":214},[200,105758,1472],{"class":223},[200,105760,227],{"class":235},[200,105762,47588],{"class":210},[200,105764,215],{"class":214},[200,105766,47331],{"class":210},[200,105768,416],{"class":235},[200,105770,105771],{"class":202,"line":310},[200,105772,529],{"emptyLinePlaceholder":20},[200,105774,105775,105777],{"class":202,"line":336},[200,105776,3743],{"class":206},[200,105778,47605],{"class":210},[200,105780,105781,105783],{"class":202,"line":342},[200,105782,413],{"class":214},[200,105784,416],{"class":210},[35,105786,419,105787,47617,105789,3076,105791,3080,105793,47624,105795],{},[156,105788,47616],{},[156,105790,3280],{},[156,105792,47555],{},[156,105794,20089],{},[1217,105796,47627],{},[35,105798,47630,105799,47634],{},[156,105800,47633],{},[192,105802,105803],{"className":1370,"code":47637,"language":1373,"meta":13,"style":13},[156,105804,105805,105827,105845,105849,105879,105899,105913,105925,105929,105935],{"__ignoreMap":13},[200,105806,105807,105809,105811,105813,105815,105817,105819,105821,105823,105825],{"class":202,"line":203},[200,105808,1380],{"class":206},[200,105810,1383],{"class":206},[200,105812,38960],{"class":223},[200,105814,227],{"class":210},[200,105816,3711],{"class":379},[200,105818,1095],{"class":214},[200,105820,25386],{"class":386},[200,105822,2089],{"class":214},[200,105824,393],{"class":379},[200,105826,280],{"class":214},[200,105828,105829,105831,105833,105835,105837,105839,105841,105843],{"class":202,"line":14},[200,105830,3723],{"class":379},[200,105832,41038],{"class":210},[200,105834,510],{"class":214},[200,105836,452],{"class":206},[200,105838,41045],{"class":223},[200,105840,227],{"class":235},[200,105842,25386],{"class":210},[200,105844,416],{"class":235},[200,105846,105847],{"class":202,"line":255},[200,105848,529],{"emptyLinePlaceholder":20},[200,105850,105851,105853,105855,105857,105859,105861,105863,105865,105867,105869,105871,105873,105875,105877],{"class":202,"line":272},[200,105852,6165],{"class":206},[200,105854,47508],{"class":223},[200,105856,1532],{"class":235},[200,105858,215],{"class":214},[200,105860,47696],{"class":223},[200,105862,227],{"class":235},[200,105864,47588],{"class":210},[200,105866,215],{"class":214},[200,105868,47331],{"class":210},[200,105870,2089],{"class":235},[200,105872,215],{"class":214},[200,105874,47711],{"class":223},[200,105876,227],{"class":235},[200,105878,230],{"class":214},[200,105880,105881,105883,105885,105887,105889,105891,105893,105895,105897],{"class":202,"line":283},[200,105882,47542],{"class":235},[200,105884,239],{"class":214},[200,105886,47724],{"class":223},[200,105888,227],{"class":235},[200,105890,23870],{"class":210},[200,105892,215],{"class":214},[200,105894,20089],{"class":210},[200,105896,2089],{"class":235},[200,105898,252],{"class":214},[200,105900,105901,105903,105905,105907,105909,105911],{"class":202,"line":300},[200,105902,11797],{"class":235},[200,105904,239],{"class":214},[200,105906,41038],{"class":210},[200,105908,215],{"class":214},[200,105910,20089],{"class":210},[200,105912,252],{"class":214},[200,105914,105915,105917,105919,105921,105923],{"class":202,"line":310},[200,105916,5080],{"class":214},[200,105918,2089],{"class":235},[200,105920,215],{"class":214},[200,105922,47761],{"class":223},[200,105924,466],{"class":235},[200,105926,105927],{"class":202,"line":336},[200,105928,529],{"emptyLinePlaceholder":20},[200,105930,105931,105933],{"class":202,"line":342},[200,105932,3743],{"class":206},[200,105934,47774],{"class":210},[200,105936,105937,105939],{"class":202,"line":365},[200,105938,413],{"class":214},[200,105940,416],{"class":210},[35,105942,47783,105943,215],{},[6850,105944,47786],{},[38677,105946,47790],{"id":47789},[35,105948,47793],{},[192,105950,105951],{"className":21417,"code":47796,"language":12060,"meta":13,"style":13},[156,105952,105953,105973,105981,105993,105999,106007,106011,106019,106027,106037,106041,106049,106057],{"__ignoreMap":13},[200,105954,105955,105957,105959,105961,105963,105965,105967,105969,105971],{"class":202,"line":203},[200,105956,600],{"class":214},[200,105958,14892],{"class":235},[200,105960,21781],{"class":379},[200,105962,14590],{"class":379},[200,105964,449],{"class":214},[200,105966,611],{"class":214},[200,105968,1373],{"class":245},[200,105970,611],{"class":214},[200,105972,648],{"class":214},[200,105974,105975,105977,105979],{"class":202,"line":14},[200,105976,47823],{"class":223},[200,105978,227],{"class":210},[200,105980,230],{"class":214},[200,105982,105983,105985,105987,105989,105991],{"class":202,"line":255},[200,105984,47832],{"class":235},[200,105986,239],{"class":214},[200,105988,242],{"class":214},[200,105990,39427],{"class":245},[200,105992,992],{"class":214},[200,105994,105995,105997],{"class":202,"line":272},[200,105996,413],{"class":214},[200,105998,416],{"class":210},[200,106000,106001,106003,106005],{"class":202,"line":283},[200,106002,681],{"class":214},[200,106004,14892],{"class":235},[200,106006,648],{"class":214},[200,106008,106009],{"class":202,"line":300},[200,106010,529],{"emptyLinePlaceholder":20},[200,106012,106013,106015,106017],{"class":202,"line":310},[200,106014,600],{"class":214},[200,106016,21428],{"class":235},[200,106018,648],{"class":214},[200,106020,106021,106023,106025],{"class":202,"line":336},[200,106022,657],{"class":214},[200,106024,47873],{"class":235},[200,106026,648],{"class":214},[200,106028,106029,106031,106033,106035],{"class":202,"line":342},[200,106030,804],{"class":214},[200,106032,47882],{"class":235},[200,106034,47885],{"class":379},[200,106036,648],{"class":214},[200,106038,106039],{"class":202,"line":365},[200,106040,47892],{"class":401},[200,106042,106043,106045,106047],{"class":202,"line":371},[200,106044,15074],{"class":214},[200,106046,47882],{"class":235},[200,106048,648],{"class":214},[200,106050,106051,106053,106055],{"class":202,"line":398},[200,106052,893],{"class":214},[200,106054,47873],{"class":235},[200,106056,648],{"class":214},[200,106058,106059,106061,106063],{"class":202,"line":405},[200,106060,681],{"class":214},[200,106062,21428],{"class":235},[200,106064,648],{"class":214},[35,106066,12236,106067,47921],{},[156,106068,47882],{},[35,106070,47924,106071,239],{},[156,106072,47882],{},[192,106074,106075],{"className":21417,"code":47929,"language":12060,"meta":13,"style":13},[156,106076,106077,106097,106109,106123,106135,106141,106155,106169,106177,106183,106197,106211,106217,106221,106257,106267,106273,106281,106285,106293,106337],{"__ignoreMap":13},[200,106078,106079,106081,106083,106085,106087,106089,106091,106093,106095],{"class":202,"line":203},[200,106080,600],{"class":214},[200,106082,14892],{"class":235},[200,106084,21781],{"class":379},[200,106086,14590],{"class":379},[200,106088,449],{"class":214},[200,106090,611],{"class":214},[200,106092,1373],{"class":245},[200,106094,611],{"class":214},[200,106096,648],{"class":214},[200,106098,106099,106101,106103,106105,106107],{"class":202,"line":14},[200,106100,443],{"class":379},[200,106102,47958],{"class":210},[200,106104,449],{"class":214},[200,106106,350],{"class":210},[200,106108,230],{"class":214},[200,106110,106111,106113,106115,106117,106119,106121],{"class":202,"line":255},[200,106112,18962],{"class":235},[200,106114,239],{"class":214},[200,106116,242],{"class":214},[200,106118,3280],{"class":245},[200,106120,249],{"class":214},[200,106122,252],{"class":214},[200,106124,106125,106127,106129,106131,106133],{"class":202,"line":272},[200,106126,47983],{"class":235},[200,106128,239],{"class":214},[200,106130,242],{"class":214},[200,106132,2886],{"class":245},[200,106134,992],{"class":214},[200,106136,106137,106139],{"class":202,"line":283},[200,106138,26858],{"class":214},[200,106140,280],{"class":214},[200,106142,106143,106145,106147,106149,106151,106153],{"class":202,"line":300},[200,106144,18962],{"class":235},[200,106146,239],{"class":214},[200,106148,242],{"class":214},[200,106150,20089],{"class":245},[200,106152,249],{"class":214},[200,106154,252],{"class":214},[200,106156,106157,106159,106161,106163,106165,106167],{"class":202,"line":310},[200,106158,47983],{"class":235},[200,106160,239],{"class":214},[200,106162,242],{"class":214},[200,106164,40947],{"class":245},[200,106166,249],{"class":214},[200,106168,252],{"class":214},[200,106170,106171,106173,106175],{"class":202,"line":336},[200,106172,48030],{"class":235},[200,106174,239],{"class":214},[200,106176,22192],{"class":2423},[200,106178,106179,106181],{"class":202,"line":342},[200,106180,26858],{"class":214},[200,106182,280],{"class":214},[200,106184,106185,106187,106189,106191,106193,106195],{"class":202,"line":365},[200,106186,18962],{"class":235},[200,106188,239],{"class":214},[200,106190,242],{"class":214},[200,106192,47555],{"class":245},[200,106194,249],{"class":214},[200,106196,252],{"class":214},[200,106198,106199,106201,106203,106205,106207,106209],{"class":202,"line":371},[200,106200,47983],{"class":235},[200,106202,239],{"class":214},[200,106204,242],{"class":214},[200,106206,48065],{"class":245},[200,106208,249],{"class":214},[200,106210,252],{"class":214},[200,106212,106213,106215],{"class":202,"line":398},[200,106214,413],{"class":214},[200,106216,4496],{"class":210},[200,106218,106219],{"class":202,"line":405},[200,106220,529],{"emptyLinePlaceholder":20},[200,106222,106223,106225,106227,106229,106231,106233,106235,106237,106239,106241,106243,106245,106247,106249,106251,106253,106255],{"class":202,"line":410},[200,106224,443],{"class":379},[200,106226,318],{"class":214},[200,106228,5041],{"class":235},[200,106230,239],{"class":214},[200,106232,47501],{"class":210},[200,106234,550],{"class":214},[200,106236,48096],{"class":210},[200,106238,413],{"class":214},[200,106240,510],{"class":214},[200,106242,452],{"class":206},[200,106244,48105],{"class":223},[200,106246,227],{"class":210},[200,106248,249],{"class":214},[200,106250,48112],{"class":245},[200,106252,249],{"class":214},[200,106254,550],{"class":214},[200,106256,280],{"class":214},[200,106258,106259,106261,106263,106265],{"class":202,"line":861},[200,106260,48123],{"class":235},[200,106262,239],{"class":214},[200,106264,16166],{"class":2423},[200,106266,252],{"class":214},[200,106268,106269,106271],{"class":202,"line":890},[200,106270,413],{"class":214},[200,106272,416],{"class":210},[200,106274,106275,106277,106279],{"class":202,"line":900},[200,106276,681],{"class":214},[200,106278,14892],{"class":235},[200,106280,648],{"class":214},[200,106282,106283],{"class":202,"line":905},[200,106284,529],{"emptyLinePlaceholder":20},[200,106286,106287,106289,106291],{"class":202,"line":931},[200,106288,600],{"class":214},[200,106290,21428],{"class":235},[200,106292,648],{"class":214},[200,106294,106295,106297,106299,106301,106303,106305,106307,106309,106311,106313,106315,106317,106319,106321,106323,106325,106327,106329,106331,106333,106335],{"class":202,"line":3640},[200,106296,657],{"class":214},[200,106298,48162],{"class":235},[200,106300,28009],{"class":214},[200,106302,48167],{"class":379},[200,106304,449],{"class":214},[200,106306,611],{"class":214},[200,106308,48167],{"class":210},[200,106310,611],{"class":214},[200,106312,28009],{"class":214},[200,106314,48180],{"class":379},[200,106316,449],{"class":214},[200,106318,611],{"class":214},[200,106320,47331],{"class":210},[200,106322,611],{"class":214},[200,106324,28009],{"class":214},[200,106326,9886],{"class":379},[200,106328,449],{"class":214},[200,106330,611],{"class":214},[200,106332,48199],{"class":210},[200,106334,611],{"class":214},[200,106336,21487],{"class":214},[200,106338,106339,106341,106343],{"class":202,"line":3645},[200,106340,681],{"class":214},[200,106342,21428],{"class":235},[200,106344,648],{"class":214},[35,106346,48214,106347],{},[1217,106348,48217],{},[35,106350,48220],{},[192,106352,106353],{"className":21417,"code":48223,"language":12060,"meta":13,"style":13},[156,106354,106355,106363,106369,106381,106385,106397,106403,106415,106427,106439,106443,106451,106459],{"__ignoreMap":13},[200,106356,106357,106359,106361],{"class":202,"line":203},[200,106358,600],{"class":214},[200,106360,21428],{"class":235},[200,106362,648],{"class":214},[200,106364,106365,106367],{"class":202,"line":14},[200,106366,657],{"class":214},[200,106368,48240],{"class":235},[200,106370,106371,106373,106375,106377,106379],{"class":202,"line":255},[200,106372,21442],{"class":379},[200,106374,449],{"class":214},[200,106376,611],{"class":214},[200,106378,48251],{"class":245},[200,106380,617],{"class":214},[200,106382,106383],{"class":202,"line":272},[200,106384,11536],{"class":214},[200,106386,106387,106389,106391,106393,106395],{"class":202,"line":283},[200,106388,804],{"class":214},[200,106390,21428],{"class":235},[200,106392,23867],{"class":214},[200,106394,48268],{"class":379},[200,106396,648],{"class":214},[200,106398,106399,106401],{"class":202,"line":300},[200,106400,22027],{"class":214},[200,106402,48277],{"class":235},[200,106404,106405,106407,106409,106411,106413],{"class":202,"line":310},[200,106406,23884],{"class":379},[200,106408,449],{"class":214},[200,106410,611],{"class":214},[200,106412,48288],{"class":245},[200,106414,617],{"class":214},[200,106416,106417,106419,106421,106423,106425],{"class":202,"line":336},[200,106418,48295],{"class":379},[200,106420,449],{"class":214},[200,106422,611],{"class":214},[200,106424,48302],{"class":245},[200,106426,617],{"class":214},[200,106428,106429,106431,106433,106435,106437],{"class":202,"line":342},[200,106430,48309],{"class":379},[200,106432,449],{"class":214},[200,106434,611],{"class":214},[200,106436,48316],{"class":245},[200,106438,617],{"class":214},[200,106440,106441],{"class":202,"line":365},[200,106442,23913],{"class":214},[200,106444,106445,106447,106449],{"class":202,"line":371},[200,106446,15074],{"class":214},[200,106448,21428],{"class":235},[200,106450,648],{"class":214},[200,106452,106453,106455,106457],{"class":202,"line":398},[200,106454,893],{"class":214},[200,106456,48337],{"class":235},[200,106458,648],{"class":214},[200,106460,106461,106463,106465],{"class":202,"line":405},[200,106462,681],{"class":214},[200,106464,21428],{"class":235},[200,106466,648],{"class":214},[35,106468,48350],{},[192,106470,106471],{"className":21417,"code":48353,"language":12060,"meta":13,"style":13},[156,106472,106473,106493,106513,106533,106555,106559,106573,106601,106607,106611,106627,106631,106645,106653,106659,106663,106687,106705,106719,106733,106739,106743,106751,106755,106763,106807,106835,106857,106865,106869,106887,106891,106899,106907],{"__ignoreMap":13},[200,106474,106475,106477,106479,106481,106483,106485,106487,106489,106491],{"class":202,"line":203},[200,106476,600],{"class":214},[200,106478,14892],{"class":235},[200,106480,14590],{"class":379},[200,106482,449],{"class":214},[200,106484,611],{"class":214},[200,106486,1373],{"class":245},[200,106488,611],{"class":214},[200,106490,21781],{"class":379},[200,106492,648],{"class":214},[200,106494,106495,106497,106499,106501,106503,106505,106507,106509,106511],{"class":202,"line":14},[200,106496,973],{"class":206},[200,106498,321],{"class":206},[200,106500,318],{"class":214},[200,106502,48386],{"class":210},[200,106504,981],{"class":214},[200,106506,984],{"class":206},[200,106508,242],{"class":214},[200,106510,48395],{"class":245},[200,106512,992],{"class":214},[200,106514,106515,106517,106519,106521,106523,106525,106527,106529,106531],{"class":202,"line":255},[200,106516,973],{"class":206},[200,106518,321],{"class":206},[200,106520,318],{"class":214},[200,106522,48408],{"class":210},[200,106524,981],{"class":214},[200,106526,984],{"class":206},[200,106528,242],{"class":214},[200,106530,19790],{"class":245},[200,106532,992],{"class":214},[200,106534,106535,106537,106539,106541,106543,106545,106547,106549,106551,106553],{"class":202,"line":272},[200,106536,973],{"class":206},[200,106538,318],{"class":214},[200,106540,42872],{"class":210},[200,106542,550],{"class":214},[200,106544,22708],{"class":210},[200,106546,981],{"class":214},[200,106548,984],{"class":206},[200,106550,242],{"class":214},[200,106552,19790],{"class":245},[200,106554,992],{"class":214},[200,106556,106557],{"class":202,"line":283},[200,106558,529],{"emptyLinePlaceholder":20},[200,106560,106561,106563,106565,106567,106569,106571],{"class":202,"line":300},[200,106562,443],{"class":379},[200,106564,41910],{"class":210},[200,106566,449],{"class":214},[200,106568,42872],{"class":223},[200,106570,227],{"class":210},[200,106572,230],{"class":214},[200,106574,106575,106577,106579,106581,106583,106585,106587,106589,106591,106593,106595,106597,106599],{"class":202,"line":310},[200,106576,236],{"class":235},[200,106578,239],{"class":214},[200,106580,22708],{"class":223},[200,106582,227],{"class":210},[200,106584,2752],{"class":214},[200,106586,39347],{"class":235},[200,106588,239],{"class":214},[200,106590,242],{"class":214},[200,106592,41947],{"class":245},[200,106594,249],{"class":214},[200,106596,981],{"class":214},[200,106598,2089],{"class":210},[200,106600,252],{"class":214},[200,106602,106603,106605],{"class":202,"line":336},[200,106604,413],{"class":214},[200,106606,416],{"class":210},[200,106608,106609],{"class":202,"line":342},[200,106610,529],{"emptyLinePlaceholder":20},[200,106612,106613,106615,106617,106619,106621,106623,106625],{"class":202,"line":365},[200,106614,3771],{"class":379},[200,106616,48503],{"class":1978},[200,106618,510],{"class":214},[200,106620,48408],{"class":1978},[200,106622,48510],{"class":214},[200,106624,42034],{"class":210},[200,106626,648],{"class":214},[200,106628,106629],{"class":202,"line":371},[200,106630,529],{"emptyLinePlaceholder":20},[200,106632,106633,106635,106637,106639,106641,106643],{"class":202,"line":398},[200,106634,443],{"class":379},[200,106636,18946],{"class":210},[200,106638,449],{"class":214},[200,106640,41979],{"class":223},[200,106642,227],{"class":210},[200,106644,230],{"class":214},[200,106646,106647,106649,106651],{"class":202,"line":405},[200,106648,236],{"class":235},[200,106650,239],{"class":214},[200,106652,48541],{"class":214},[200,106654,106655,106657],{"class":202,"line":410},[200,106656,413],{"class":214},[200,106658,416],{"class":210},[200,106660,106661],{"class":202,"line":861},[200,106662,529],{"emptyLinePlaceholder":20},[200,106664,106665,106667,106669,106671,106673,106675,106677,106679,106681,106683,106685],{"class":202,"line":890},[200,106666,3711],{"class":379},[200,106668,4987],{"class":379},[200,106670,42012],{"class":223},[200,106672,227],{"class":214},[200,106674,25386],{"class":386},[200,106676,239],{"class":214},[200,106678,48386],{"class":1978},[200,106680,600],{"class":214},[200,106682,48572],{"class":1978},[200,106684,46640],{"class":214},[200,106686,280],{"class":214},[200,106688,106689,106691,106693,106695,106697,106699,106701,106703],{"class":202,"line":900},[200,106690,6165],{"class":206},[200,106692,18741],{"class":223},[200,106694,227],{"class":235},[200,106696,249],{"class":214},[200,106698,48112],{"class":245},[200,106700,249],{"class":214},[200,106702,550],{"class":214},[200,106704,280],{"class":214},[200,106706,106707,106709,106711,106713,106715,106717],{"class":202,"line":905},[200,106708,18759],{"class":235},[200,106710,239],{"class":214},[200,106712,242],{"class":214},[200,106714,18766],{"class":245},[200,106716,249],{"class":214},[200,106718,252],{"class":214},[200,106720,106721,106723,106725,106727,106729,106731],{"class":202,"line":931},[200,106722,18775],{"class":235},[200,106724,239],{"class":214},[200,106726,43652],{"class":210},[200,106728,215],{"class":214},[200,106730,12726],{"class":210},[200,106732,252],{"class":214},[200,106734,106735,106737],{"class":202,"line":3640},[200,106736,5080],{"class":214},[200,106738,416],{"class":235},[200,106740,106741],{"class":202,"line":3645},[200,106742,1130],{"class":214},[200,106744,106745,106747,106749],{"class":202,"line":4020},[200,106746,681],{"class":214},[200,106748,14892],{"class":235},[200,106750,648],{"class":214},[200,106752,106753],{"class":202,"line":4475},[200,106754,529],{"emptyLinePlaceholder":20},[200,106756,106757,106759,106761],{"class":202,"line":4481},[200,106758,600],{"class":214},[200,106760,21428],{"class":235},[200,106762,648],{"class":214},[200,106764,106765,106767,106769,106771,106773,106775,106777,106779,106781,106783,106785,106787,106789,106791,106793,106795,106797,106799,106801,106803,106805],{"class":202,"line":4487},[200,106766,657],{"class":214},[200,106768,48659],{"class":235},[200,106770,28009],{"class":214},[200,106772,48664],{"class":379},[200,106774,449],{"class":214},[200,106776,611],{"class":214},[200,106778,48664],{"class":210},[200,106780,611],{"class":214},[200,106782,28009],{"class":214},[200,106784,26836],{"class":379},[200,106786,449],{"class":214},[200,106788,611],{"class":214},[200,106790,26836],{"class":210},[200,106792,611],{"class":214},[200,106794,22373],{"class":214},[200,106796,917],{"class":379},[200,106798,449],{"class":214},[200,106800,611],{"class":214},[200,106802,48695],{"class":210},[200,106804,611],{"class":214},[200,106806,648],{"class":214},[200,106808,106809,106811,106813,106815,106817,106819,106821,106823,106825,106827,106829,106831,106833],{"class":202,"line":4493},[200,106810,804],{"class":214},[200,106812,48706],{"class":235},[200,106814,21476],{"class":379},[200,106816,449],{"class":214},[200,106818,611],{"class":214},[200,106820,40947],{"class":245},[200,106822,611],{"class":214},[200,106824,701],{"class":379},[200,106826,449],{"class":214},[200,106828,611],{"class":214},[200,106830,20089],{"class":245},[200,106832,611],{"class":214},[200,106834,648],{"class":214},[200,106836,106837,106839,106841,106843,106845,106847,106849,106851,106853,106855],{"class":202,"line":5454},[200,106838,22027],{"class":214},[200,106840,48735],{"class":235},[200,106842,42242],{"class":379},[200,106844,449],{"class":214},[200,106846,611],{"class":214},[200,106848,26836],{"class":210},[200,106850,215],{"class":214},[200,106852,20089],{"class":210},[200,106854,611],{"class":214},[200,106856,21487],{"class":214},[200,106858,106859,106861,106863],{"class":202,"line":5459},[200,106860,15074],{"class":214},[200,106862,48706],{"class":235},[200,106864,648],{"class":214},[200,106866,106867],{"class":202,"line":13227},[200,106868,529],{"emptyLinePlaceholder":20},[200,106870,106871,106873,106875,106877,106879,106881,106883,106885],{"class":202,"line":13253},[200,106872,804],{"class":214},[200,106874,48770],{"class":235},[200,106876,321],{"class":379},[200,106878,449],{"class":214},[200,106880,611],{"class":214},[200,106882,917],{"class":245},[200,106884,611],{"class":214},[200,106886,648],{"class":214},[200,106888,106889],{"class":202,"line":13269},[200,106890,48787],{"class":210},[200,106892,106893,106895,106897],{"class":202,"line":13313},[200,106894,15074],{"class":214},[200,106896,48770],{"class":235},[200,106898,648],{"class":214},[200,106900,106901,106903,106905],{"class":202,"line":13319},[200,106902,893],{"class":214},[200,106904,48659],{"class":235},[200,106906,648],{"class":214},[200,106908,106909,106911,106913],{"class":202,"line":13324},[200,106910,681],{"class":214},[200,106912,21428],{"class":235},[200,106914,648],{"class":214},[35,106916,48814,106917,48818,106919,48821],{},[1217,106918,48817],{},[156,106920,48695],{},[35,106922,48824,106923,48828],{},[156,106924,48827],{},[35,106926,106927],{},[1217,106928,48833],{},[35,106930,48836],{},[7139,106932,106933,106935,106937,106941,106943,106947,106963,106965,106967,106969,106979,107159,107165,107172,107174,107178,107182,107336,107340,107766,107768,107772,108092,108094,108096,108100,108204,108210,108214,108220,108420,108422,108474,108476,108478,108480,108484,108486,108490,108506,108516,108592,108602,108616],{"icon":46023,"text":48839,"to":46025,"target":7144},[1913,106934,48843],{"id":48842},[35,106936,48846],{},[35,106938,46318,106939],{},[1582,106940,46321],{"repo":38655},[51,106942,48854],{"id":48853},[35,106944,106945],{},[1690,106946],{"alt":48859,"src":48860},[35,106948,106949],{},[1217,106950,106951,46057,106954,46062,106957,46066,106960,215],{},[39,106952,38639],{"href":46025,"rel":106953},[77],[39,106955,46061],{"href":18567,"rel":106956},[77],[39,106958,19412],{"href":46016,"rel":106959},[77],[39,106961,11622],{"href":46025,"rel":106962},[77],[35,106964,48879],{},[1913,106966,48883],{"id":48882},[35,106968,48886],{},[35,106970,48889,106971,48893,106973,48897,106975,48901,106977,48904],{},[156,106972,48892],{},[156,106974,48896],{},[156,106976,48900],{},[156,106978,3280],{},[192,106980,106981],{"className":1370,"code":48907,"language":1373,"meta":13,"style":13},[156,106982,106983,107005,107023,107027,107057,107077,107091,107133,107137,107153],{"__ignoreMap":13},[200,106984,106985,106987,106989,106991,106993,106995,106997,106999,107001,107003],{"class":202,"line":203},[200,106986,1380],{"class":206},[200,106988,1383],{"class":206},[200,106990,38960],{"class":223},[200,106992,227],{"class":210},[200,106994,3711],{"class":379},[200,106996,1095],{"class":214},[200,106998,25386],{"class":386},[200,107000,2089],{"class":214},[200,107002,393],{"class":379},[200,107004,280],{"class":214},[200,107006,107007,107009,107011,107013,107015,107017,107019,107021],{"class":202,"line":14},[200,107008,3723],{"class":379},[200,107010,41038],{"class":210},[200,107012,510],{"class":214},[200,107014,452],{"class":206},[200,107016,41045],{"class":223},[200,107018,227],{"class":235},[200,107020,25386],{"class":210},[200,107022,416],{"class":235},[200,107024,107025],{"class":202,"line":255},[200,107026,529],{"emptyLinePlaceholder":20},[200,107028,107029,107031,107033,107035,107037,107039,107041,107043,107045,107047,107049,107051,107053,107055],{"class":202,"line":272},[200,107030,6165],{"class":206},[200,107032,47508],{"class":223},[200,107034,1532],{"class":235},[200,107036,215],{"class":214},[200,107038,48966],{"class":223},[200,107040,227],{"class":235},[200,107042,47588],{"class":210},[200,107044,215],{"class":214},[200,107046,47331],{"class":210},[200,107048,2089],{"class":235},[200,107050,215],{"class":214},[200,107052,48981],{"class":223},[200,107054,227],{"class":235},[200,107056,230],{"class":214},[200,107058,107059,107061,107063,107065,107067,107069,107071,107073,107075],{"class":202,"line":283},[200,107060,47542],{"class":235},[200,107062,239],{"class":214},[200,107064,47724],{"class":223},[200,107066,227],{"class":235},[200,107068,23870],{"class":210},[200,107070,215],{"class":214},[200,107072,20089],{"class":210},[200,107074,2089],{"class":235},[200,107076,252],{"class":214},[200,107078,107079,107081,107083,107085,107087,107089],{"class":202,"line":300},[200,107080,11797],{"class":235},[200,107082,239],{"class":214},[200,107084,41038],{"class":210},[200,107086,215],{"class":214},[200,107088,20089],{"class":210},[200,107090,252],{"class":214},[200,107092,107093,107095,107097,107099,107101,107103,107105,107107,107109,107111,107113,107115,107117,107119,107121,107123,107125,107127,107129,107131],{"class":202,"line":310},[200,107094,5080],{"class":214},[200,107096,2089],{"class":235},[200,107098,215],{"class":214},[200,107100,49030],{"class":223},[200,107102,227],{"class":235},[200,107104,49035],{"class":223},[200,107106,227],{"class":235},[200,107108,47588],{"class":210},[200,107110,215],{"class":214},[200,107112,47331],{"class":210},[200,107114,215],{"class":214},[200,107116,3280],{"class":210},[200,107118,550],{"class":214},[200,107120,49052],{"class":210},[200,107122,215],{"class":214},[200,107124,3280],{"class":210},[200,107126,6119],{"class":235},[200,107128,215],{"class":214},[200,107130,47761],{"class":223},[200,107132,466],{"class":235},[200,107134,107135],{"class":202,"line":336},[200,107136,529],{"emptyLinePlaceholder":20},[200,107138,107139,107141,107143,107145,107147,107149,107151],{"class":202,"line":342},[200,107140,3743],{"class":206},[200,107142,41071],{"class":223},[200,107144,227],{"class":235},[200,107146,25386],{"class":210},[200,107148,550],{"class":214},[200,107150,49083],{"class":2045},[200,107152,416],{"class":235},[200,107154,107155,107157],{"class":202,"line":365},[200,107156,413],{"class":214},[200,107158,416],{"class":210},[35,107160,107161],{},[1217,107162,49096,107163,49100],{},[156,107164,49099],{},[35,107166,49103,107167,49109,107170],{},[39,107168,49108],{"href":49106,"rel":107169},[77],[1217,107171,49112],{},[1913,107173,49116],{"id":49115},[35,107175,49119,107176,49123],{},[1217,107177,49122],{},[35,107179,49126,107180,239],{},[156,107181,49129],{},[192,107183,107184],{"className":1370,"code":49132,"language":1373,"meta":13,"style":13},[156,107185,107186,107208,107230,107248,107266,107284,107300,107320,107324,107330],{"__ignoreMap":13},[200,107187,107188,107190,107192,107194,107196,107198,107200,107202,107204,107206],{"class":202,"line":203},[200,107189,1380],{"class":206},[200,107191,1383],{"class":206},[200,107193,38960],{"class":223},[200,107195,227],{"class":210},[200,107197,3711],{"class":379},[200,107199,1095],{"class":214},[200,107201,25386],{"class":386},[200,107203,2089],{"class":214},[200,107205,393],{"class":379},[200,107207,280],{"class":214},[200,107209,107210,107212,107214,107216,107218,107220,107222,107224,107226,107228],{"class":202,"line":14},[200,107211,3723],{"class":379},[200,107213,49163],{"class":210},[200,107215,510],{"class":214},[200,107217,452],{"class":206},[200,107219,47508],{"class":223},[200,107221,1532],{"class":235},[200,107223,215],{"class":214},[200,107225,770],{"class":223},[200,107227,227],{"class":235},[200,107229,230],{"class":214},[200,107231,107232,107234,107236,107238,107240,107242,107244,107246],{"class":202,"line":255},[200,107233,15858],{"class":235},[200,107235,239],{"class":214},[200,107237,47527],{"class":210},[200,107239,215],{"class":214},[200,107241,47325],{"class":210},[200,107243,215],{"class":214},[200,107245,3280],{"class":210},[200,107247,252],{"class":214},[200,107249,107250,107252,107254,107256,107258,107260,107262,107264],{"class":202,"line":272},[200,107251,49202],{"class":235},[200,107253,239],{"class":214},[200,107255,47527],{"class":210},[200,107257,215],{"class":214},[200,107259,47325],{"class":210},[200,107261,215],{"class":214},[200,107263,46224],{"class":210},[200,107265,252],{"class":214},[200,107267,107268,107270,107272,107274,107276,107278,107280,107282],{"class":202,"line":283},[200,107269,49221],{"class":235},[200,107271,239],{"class":214},[200,107273,47527],{"class":210},[200,107275,215],{"class":214},[200,107277,47325],{"class":210},[200,107279,215],{"class":214},[200,107281,49234],{"class":210},[200,107283,252],{"class":214},[200,107285,107286,107288,107290,107292,107294,107296,107298],{"class":202,"line":300},[200,107287,49241],{"class":235},[200,107289,239],{"class":214},[200,107291,47527],{"class":210},[200,107293,215],{"class":214},[200,107295,47325],{"class":210},[200,107297,215],{"class":214},[200,107299,49254],{"class":210},[200,107301,107302,107304,107306,107308,107310,107312,107314,107316,107318],{"class":202,"line":310},[200,107303,5080],{"class":214},[200,107305,2089],{"class":235},[200,107307,215],{"class":214},[200,107309,1472],{"class":223},[200,107311,227],{"class":235},[200,107313,47588],{"class":210},[200,107315,215],{"class":214},[200,107317,47325],{"class":210},[200,107319,416],{"class":235},[200,107321,107322],{"class":202,"line":336},[200,107323,529],{"emptyLinePlaceholder":20},[200,107325,107326,107328],{"class":202,"line":342},[200,107327,3743],{"class":206},[200,107329,49285],{"class":210},[200,107331,107332,107334],{"class":202,"line":365},[200,107333,413],{"class":214},[200,107335,416],{"class":210},[35,107337,49294,107338,49297],{},[156,107339,48162],{},[192,107341,107342],{"className":21417,"code":49300,"language":12060,"meta":13,"style":13},[156,107343,107344,107364,107376,107390,107402,107408,107422,107436,107442,107456,107470,107478,107484,107498,107510,107516,107548,107558,107564,107572,107576,107584,107592,107602,107634,107658,107714,107718,107726,107734,107742,107750,107758],{"__ignoreMap":13},[200,107345,107346,107348,107350,107352,107354,107356,107358,107360,107362],{"class":202,"line":203},[200,107347,600],{"class":214},[200,107349,14892],{"class":235},[200,107351,14590],{"class":379},[200,107353,449],{"class":214},[200,107355,611],{"class":214},[200,107357,1373],{"class":245},[200,107359,611],{"class":214},[200,107361,21781],{"class":379},[200,107363,648],{"class":214},[200,107365,107366,107368,107370,107372,107374],{"class":202,"line":14},[200,107367,443],{"class":379},[200,107369,47958],{"class":210},[200,107371,449],{"class":214},[200,107373,350],{"class":210},[200,107375,230],{"class":214},[200,107377,107378,107380,107382,107384,107386,107388],{"class":202,"line":255},[200,107379,18962],{"class":235},[200,107381,239],{"class":214},[200,107383,242],{"class":214},[200,107385,3280],{"class":245},[200,107387,249],{"class":214},[200,107389,252],{"class":214},[200,107391,107392,107394,107396,107398,107400],{"class":202,"line":272},[200,107393,47983],{"class":235},[200,107395,239],{"class":214},[200,107397,242],{"class":214},[200,107399,2886],{"class":245},[200,107401,992],{"class":214},[200,107403,107404,107406],{"class":202,"line":283},[200,107405,26858],{"class":214},[200,107407,280],{"class":214},[200,107409,107410,107412,107414,107416,107418,107420],{"class":202,"line":300},[200,107411,18962],{"class":235},[200,107413,239],{"class":214},[200,107415,242],{"class":214},[200,107417,46224],{"class":245},[200,107419,249],{"class":214},[200,107421,252],{"class":214},[200,107423,107424,107426,107428,107430,107432,107434],{"class":202,"line":310},[200,107425,47983],{"class":235},[200,107427,239],{"class":214},[200,107429,242],{"class":214},[200,107431,49391],{"class":245},[200,107433,249],{"class":214},[200,107435,252],{"class":214},[200,107437,107438,107440],{"class":202,"line":336},[200,107439,26858],{"class":214},[200,107441,280],{"class":214},[200,107443,107444,107446,107448,107450,107452,107454],{"class":202,"line":342},[200,107445,18962],{"class":235},[200,107447,239],{"class":214},[200,107449,242],{"class":214},[200,107451,49234],{"class":245},[200,107453,249],{"class":214},[200,107455,252],{"class":214},[200,107457,107458,107460,107462,107464,107466,107468],{"class":202,"line":365},[200,107459,47983],{"class":235},[200,107461,239],{"class":214},[200,107463,242],{"class":214},[200,107465,49426],{"class":245},[200,107467,249],{"class":214},[200,107469,252],{"class":214},[200,107471,107472,107474,107476],{"class":202,"line":371},[200,107473,48030],{"class":235},[200,107475,239],{"class":214},[200,107477,22192],{"class":2423},[200,107479,107480,107482],{"class":202,"line":398},[200,107481,26858],{"class":214},[200,107483,280],{"class":214},[200,107485,107486,107488,107490,107492,107494,107496],{"class":202,"line":405},[200,107487,18962],{"class":235},[200,107489,239],{"class":214},[200,107491,242],{"class":214},[200,107493,39420],{"class":245},[200,107495,249],{"class":214},[200,107497,252],{"class":214},[200,107499,107500,107502,107504,107506,107508],{"class":202,"line":410},[200,107501,47983],{"class":235},[200,107503,239],{"class":214},[200,107505,242],{"class":214},[200,107507,49469],{"class":245},[200,107509,992],{"class":214},[200,107511,107512,107514],{"class":202,"line":861},[200,107513,413],{"class":214},[200,107515,4496],{"class":210},[200,107517,107518,107520,107522,107524,107526,107528,107530,107532,107534,107536,107538,107540,107542,107544,107546],{"class":202,"line":890},[200,107519,443],{"class":379},[200,107521,318],{"class":214},[200,107523,5041],{"class":235},[200,107525,239],{"class":214},[200,107527,49490],{"class":210},[200,107529,413],{"class":214},[200,107531,510],{"class":214},[200,107533,452],{"class":206},[200,107535,48105],{"class":223},[200,107537,227],{"class":210},[200,107539,249],{"class":214},[200,107541,49505],{"class":245},[200,107543,249],{"class":214},[200,107545,550],{"class":214},[200,107547,280],{"class":214},[200,107549,107550,107552,107554,107556],{"class":202,"line":900},[200,107551,48123],{"class":235},[200,107553,239],{"class":214},[200,107555,16166],{"class":2423},[200,107557,252],{"class":214},[200,107559,107560,107562],{"class":202,"line":905},[200,107561,413],{"class":214},[200,107563,416],{"class":210},[200,107565,107566,107568,107570],{"class":202,"line":931},[200,107567,681],{"class":214},[200,107569,14892],{"class":235},[200,107571,648],{"class":214},[200,107573,107574],{"class":202,"line":3640},[200,107575,529],{"emptyLinePlaceholder":20},[200,107577,107578,107580,107582],{"class":202,"line":3645},[200,107579,600],{"class":214},[200,107581,21428],{"class":235},[200,107583,648],{"class":214},[200,107585,107586,107588,107590],{"class":202,"line":4020},[200,107587,657],{"class":214},[200,107589,47873],{"class":235},[200,107591,648],{"class":214},[200,107593,107594,107596,107598,107600],{"class":202,"line":4475},[200,107595,804],{"class":214},[200,107597,47882],{"class":235},[200,107599,47885],{"class":379},[200,107601,648],{"class":214},[200,107603,107604,107606,107608,107610,107612,107614,107616,107618,107620,107622,107624,107626,107628,107630,107632],{"class":202,"line":4481},[200,107605,22027],{"class":214},[200,107607,48162],{"class":235},[200,107609,28009],{"class":214},[200,107611,48167],{"class":379},[200,107613,449],{"class":214},[200,107615,611],{"class":214},[200,107617,48167],{"class":210},[200,107619,611],{"class":214},[200,107621,28009],{"class":214},[200,107623,48180],{"class":379},[200,107625,449],{"class":214},[200,107627,611],{"class":214},[200,107629,47325],{"class":210},[200,107631,611],{"class":214},[200,107633,648],{"class":214},[200,107635,107636,107638,107640,107642,107644,107646,107648,107650,107652,107654,107656],{"class":202,"line":4487},[200,107637,49602],{"class":214},[200,107639,21428],{"class":235},[200,107641,23867],{"class":214},[200,107643,49609],{"class":379},[200,107645,449],{"class":214},[200,107647,611],{"class":214},[200,107649,2752],{"class":214},[200,107651,49618],{"class":210},[200,107653,413],{"class":214},[200,107655,611],{"class":214},[200,107657,648],{"class":214},[200,107659,107660,107662,107664,107666,107668,107670,107672,107674,107676,107678,107680,107682,107684,107686,107688,107690,107692,107694,107696,107698,107700,107702,107704,107706,107708,107710,107712],{"class":202,"line":4493},[200,107661,49629],{"class":214},[200,107663,49632],{"class":235},[200,107665,49635],{"class":379},[200,107667,449],{"class":214},[200,107669,611],{"class":214},[200,107671,49642],{"class":245},[200,107673,611],{"class":214},[200,107675,28009],{"class":214},[200,107677,49649],{"class":379},[200,107679,449],{"class":214},[200,107681,611],{"class":214},[200,107683,49656],{"class":210},[200,107685,215],{"class":214},[200,107687,39420],{"class":210},[200,107689,49663],{"class":214},[200,107691,249],{"class":214},[200,107693,39427],{"class":245},[200,107695,249],{"class":214},[200,107697,49672],{"class":214},[200,107699,249],{"class":214},[200,107701,49677],{"class":245},[200,107703,249],{"class":214},[200,107705,49682],{"class":214},[200,107707,249],{"class":214},[200,107709,49687],{"class":245},[200,107711,14702],{"class":214},[200,107713,648],{"class":214},[200,107715,107716],{"class":202,"line":5454},[200,107717,49696],{"class":210},[200,107719,107720,107722,107724],{"class":202,"line":5459},[200,107721,49701],{"class":214},[200,107723,49632],{"class":235},[200,107725,648],{"class":214},[200,107727,107728,107730,107732],{"class":202,"line":13227},[200,107729,49710],{"class":214},[200,107731,21428],{"class":235},[200,107733,648],{"class":214},[200,107735,107736,107738,107740],{"class":202,"line":13253},[200,107737,43992],{"class":214},[200,107739,48162],{"class":235},[200,107741,648],{"class":214},[200,107743,107744,107746,107748],{"class":202,"line":13269},[200,107745,15074],{"class":214},[200,107747,47882],{"class":235},[200,107749,648],{"class":214},[200,107751,107752,107754,107756],{"class":202,"line":13313},[200,107753,893],{"class":214},[200,107755,47873],{"class":235},[200,107757,648],{"class":214},[200,107759,107760,107762,107764],{"class":202,"line":13319},[200,107761,681],{"class":214},[200,107763,21428],{"class":235},[200,107765,648],{"class":214},[35,107767,49749],{},[35,107769,49752,107770,49756],{},[156,107771,49755],{},[192,107773,107774],{"className":21417,"code":49759,"language":12060,"meta":13,"style":13},[156,107775,107776,107796,107808,107822,107836,107850,107864,107872,107886,107890,107896,107910,107924,107938,107952,107960,107974,107978,107984,107992,107996,108004,108012,108020,108028,108048,108056,108064,108068,108076,108084],{"__ignoreMap":13},[200,107777,107778,107780,107782,107784,107786,107788,107790,107792,107794],{"class":202,"line":203},[200,107779,600],{"class":214},[200,107781,14892],{"class":235},[200,107783,14590],{"class":379},[200,107785,449],{"class":214},[200,107787,611],{"class":214},[200,107789,1373],{"class":245},[200,107791,611],{"class":214},[200,107793,21781],{"class":379},[200,107795,648],{"class":214},[200,107797,107798,107800,107802,107804,107806],{"class":202,"line":14},[200,107799,443],{"class":379},[200,107801,49788],{"class":210},[200,107803,449],{"class":214},[200,107805,350],{"class":210},[200,107807,230],{"class":214},[200,107809,107810,107812,107814,107816,107818,107820],{"class":202,"line":255},[200,107811,49799],{"class":235},[200,107813,239],{"class":214},[200,107815,242],{"class":214},[200,107817,47331],{"class":245},[200,107819,249],{"class":214},[200,107821,252],{"class":214},[200,107823,107824,107826,107828,107830,107832,107834],{"class":202,"line":272},[200,107825,47983],{"class":235},[200,107827,239],{"class":214},[200,107829,242],{"class":214},[200,107831,48251],{"class":245},[200,107833,249],{"class":214},[200,107835,252],{"class":214},[200,107837,107838,107840,107842,107844,107846,107848],{"class":202,"line":283},[200,107839,49828],{"class":235},[200,107841,239],{"class":214},[200,107843,242],{"class":214},[200,107845,49835],{"class":245},[200,107847,249],{"class":214},[200,107849,252],{"class":214},[200,107851,107852,107854,107856,107858,107860,107862],{"class":202,"line":300},[200,107853,49844],{"class":235},[200,107855,239],{"class":214},[200,107857,242],{"class":214},[200,107859,49851],{"class":245},[200,107861,249],{"class":214},[200,107863,252],{"class":214},[200,107865,107866,107868,107870],{"class":202,"line":310},[200,107867,49860],{"class":235},[200,107869,239],{"class":214},[200,107871,280],{"class":214},[200,107873,107874,107876,107878,107880,107882,107884],{"class":202,"line":336},[200,107875,49869],{"class":235},[200,107877,239],{"class":214},[200,107879,242],{"class":214},[200,107881,48251],{"class":245},[200,107883,249],{"class":214},[200,107885,252],{"class":214},[200,107887,107888],{"class":202,"line":342},[200,107889,4490],{"class":214},[200,107891,107892,107894],{"class":202,"line":365},[200,107893,26858],{"class":214},[200,107895,280],{"class":214},[200,107897,107898,107900,107902,107904,107906,107908],{"class":202,"line":371},[200,107899,49799],{"class":235},[200,107901,239],{"class":214},[200,107903,242],{"class":214},[200,107905,47325],{"class":245},[200,107907,249],{"class":214},[200,107909,252],{"class":214},[200,107911,107912,107914,107916,107918,107920,107922],{"class":202,"line":398},[200,107913,47983],{"class":235},[200,107915,239],{"class":214},[200,107917,242],{"class":214},[200,107919,49914],{"class":245},[200,107921,249],{"class":214},[200,107923,252],{"class":214},[200,107925,107926,107928,107930,107932,107934,107936],{"class":202,"line":405},[200,107927,49828],{"class":235},[200,107929,239],{"class":214},[200,107931,242],{"class":214},[200,107933,49929],{"class":245},[200,107935,249],{"class":214},[200,107937,252],{"class":214},[200,107939,107940,107942,107944,107946,107948,107950],{"class":202,"line":410},[200,107941,49844],{"class":235},[200,107943,239],{"class":214},[200,107945,242],{"class":214},[200,107947,49944],{"class":245},[200,107949,249],{"class":214},[200,107951,252],{"class":214},[200,107953,107954,107956,107958],{"class":202,"line":861},[200,107955,49860],{"class":235},[200,107957,239],{"class":214},[200,107959,280],{"class":214},[200,107961,107962,107964,107966,107968,107970,107972],{"class":202,"line":890},[200,107963,49869],{"class":235},[200,107965,239],{"class":214},[200,107967,242],{"class":214},[200,107969,49914],{"class":245},[200,107971,249],{"class":214},[200,107973,252],{"class":214},[200,107975,107976],{"class":202,"line":900},[200,107977,4490],{"class":214},[200,107979,107980,107982],{"class":202,"line":905},[200,107981,413],{"class":214},[200,107983,4496],{"class":210},[200,107985,107986,107988,107990],{"class":202,"line":931},[200,107987,681],{"class":214},[200,107989,14892],{"class":235},[200,107991,648],{"class":214},[200,107993,107994],{"class":202,"line":3640},[200,107995,529],{"emptyLinePlaceholder":20},[200,107997,107998,108000,108002],{"class":202,"line":3645},[200,107999,600],{"class":214},[200,108001,21428],{"class":235},[200,108003,648],{"class":214},[200,108005,108006,108008,108010],{"class":202,"line":4020},[200,108007,657],{"class":214},[200,108009,47416],{"class":235},[200,108011,648],{"class":214},[200,108013,108014,108016,108018],{"class":202,"line":4475},[200,108015,804],{"class":214},[200,108017,47882],{"class":235},[200,108019,648],{"class":214},[200,108021,108022,108024,108026],{"class":202,"line":4481},[200,108023,22027],{"class":214},[200,108025,49755],{"class":235},[200,108027,648],{"class":214},[200,108029,108030,108032,108034,108036,108038,108040,108042,108044,108046],{"class":202,"line":4487},[200,108031,49602],{"class":214},[200,108033,50031],{"class":235},[200,108035,28009],{"class":214},[200,108037,50036],{"class":379},[200,108039,449],{"class":214},[200,108041,611],{"class":214},[200,108043,50036],{"class":210},[200,108045,611],{"class":214},[200,108047,21487],{"class":214},[200,108049,108050,108052,108054],{"class":202,"line":4493},[200,108051,43992],{"class":214},[200,108053,49755],{"class":235},[200,108055,648],{"class":214},[200,108057,108058,108060,108062],{"class":202,"line":5454},[200,108059,15074],{"class":214},[200,108061,47882],{"class":235},[200,108063,648],{"class":214},[200,108065,108066],{"class":202,"line":5459},[200,108067,529],{"emptyLinePlaceholder":20},[200,108069,108070,108072,108074],{"class":202,"line":13227},[200,108071,804],{"class":214},[200,108073,47425],{"class":235},[200,108075,21487],{"class":214},[200,108077,108078,108080,108082],{"class":202,"line":13253},[200,108079,893],{"class":214},[200,108081,47416],{"class":235},[200,108083,648],{"class":214},[200,108085,108086,108088,108090],{"class":202,"line":13269},[200,108087,681],{"class":214},[200,108089,21428],{"class":235},[200,108091,648],{"class":214},[1913,108093,50094],{"id":50093},[35,108095,50097],{},[35,108097,50100,108098,50104],{},[156,108099,50103],{},[192,108101,108102],{"className":1370,"code":50107,"language":1373,"meta":13,"style":13},[156,108103,108104,108120,108136,108140,108166,108170,108182,108198],{"__ignoreMap":13},[200,108105,108106,108108,108110,108112,108114,108116,108118],{"class":202,"line":203},[200,108107,1380],{"class":206},[200,108109,1383],{"class":206},[200,108111,50118],{"class":223},[200,108113,227],{"class":210},[200,108115,1532],{"class":214},[200,108117,393],{"class":379},[200,108119,280],{"class":214},[200,108121,108122,108124,108126,108128,108130,108132,108134],{"class":202,"line":14},[200,108123,3723],{"class":379},[200,108125,318],{"class":214},[200,108127,34776],{"class":210},[200,108129,981],{"class":214},[200,108131,510],{"class":214},[200,108133,50141],{"class":223},[200,108135,466],{"class":235},[200,108137,108138],{"class":202,"line":255},[200,108139,529],{"emptyLinePlaceholder":20},[200,108141,108142,108144,108146,108148,108150,108152,108154,108156,108158,108160,108162,108164],{"class":202,"line":272},[200,108143,3723],{"class":379},[200,108145,50154],{"class":210},[200,108147,510],{"class":214},[200,108149,34776],{"class":210},[200,108151,215],{"class":214},[200,108153,15837],{"class":210},[200,108155,6524],{"class":214},[200,108157,39420],{"class":210},[200,108159,3283],{"class":214},[200,108161,242],{"class":214},[200,108163,39427],{"class":245},[200,108165,992],{"class":214},[200,108167,108168],{"class":202,"line":283},[200,108169,529],{"emptyLinePlaceholder":20},[200,108171,108172,108174,108176,108178,108180],{"class":202,"line":300},[200,108173,20075],{"class":206},[200,108175,1095],{"class":235},[200,108177,11837],{"class":214},[200,108179,50189],{"class":210},[200,108181,416],{"class":235},[200,108183,108184,108186,108188,108190,108192,108194,108196],{"class":202,"line":310},[200,108185,3296],{"class":206},[200,108187,50198],{"class":223},[200,108189,227],{"class":235},[200,108191,249],{"class":214},[200,108193,2882],{"class":245},[200,108195,249],{"class":214},[200,108197,416],{"class":235},[200,108199,108200,108202],{"class":202,"line":336},[200,108201,413],{"class":214},[200,108203,416],{"class":210},[35,108205,108206],{},[1217,108207,50219,108208,215],{},[156,108209,47823],{},[35,108211,50224,108212,50228],{},[156,108213,50227],{},[35,108215,50231,108216,50235,108218,239],{},[156,108217,50234],{},[156,108219,50238],{},[192,108221,108222],{"className":1370,"code":50241,"language":1373,"meta":13,"style":13},[156,108223,108224,108244,108264,108268,108298,108316,108320,108358,108368,108378,108392,108398,108402,108406,108416],{"__ignoreMap":13},[200,108225,108226,108228,108230,108232,108234,108236,108238,108240,108242],{"class":202,"line":203},[200,108227,973],{"class":206},[200,108229,321],{"class":206},[200,108231,318],{"class":214},[200,108233,50254],{"class":210},[200,108235,981],{"class":214},[200,108237,984],{"class":206},[200,108239,242],{"class":214},[200,108241,50263],{"class":245},[200,108243,992],{"class":214},[200,108245,108246,108248,108250,108252,108254,108256,108258,108260,108262],{"class":202,"line":14},[200,108247,973],{"class":206},[200,108249,321],{"class":206},[200,108251,318],{"class":214},[200,108253,39325],{"class":210},[200,108255,981],{"class":214},[200,108257,984],{"class":206},[200,108259,242],{"class":214},[200,108261,1913],{"class":245},[200,108263,992],{"class":214},[200,108265,108266],{"class":202,"line":255},[200,108267,529],{"emptyLinePlaceholder":20},[200,108269,108270,108272,108274,108276,108278,108280,108282,108284,108286,108288,108290,108292,108294,108296],{"class":202,"line":272},[200,108271,1380],{"class":206},[200,108273,380],{"class":379},[200,108275,4987],{"class":379},[200,108277,50300],{"class":223},[200,108279,227],{"class":214},[200,108281,25386],{"class":386},[200,108283,239],{"class":214},[200,108285,39325],{"class":1978},[200,108287,50311],{"class":214},[200,108289,4996],{"class":1978},[200,108291,600],{"class":214},[200,108293,39365],{"class":1978},[200,108295,675],{"class":214},[200,108297,280],{"class":214},[200,108299,108300,108302,108304,108306,108308,108310,108312,108314],{"class":202,"line":283},[200,108301,3723],{"class":379},[200,108303,39376],{"class":210},[200,108305,510],{"class":214},[200,108307,452],{"class":206},[200,108309,39713],{"class":223},[200,108311,227],{"class":235},[200,108313,25386],{"class":210},[200,108315,416],{"class":235},[200,108317,108318],{"class":202,"line":300},[200,108319,529],{"emptyLinePlaceholder":20},[200,108321,108322,108324,108326,108328,108330,108332,108334,108336,108338,108340,108342,108344,108346,108348,108350,108352,108354,108356],{"class":202,"line":310},[200,108323,20075],{"class":206},[200,108325,1095],{"class":235},[200,108327,11837],{"class":214},[200,108329,39782],{"class":210},[200,108331,215],{"class":214},[200,108333,26808],{"class":210},[200,108335,15009],{"class":214},[200,108337,39376],{"class":210},[200,108339,215],{"class":214},[200,108341,26808],{"class":210},[200,108343,215],{"class":214},[200,108345,39420],{"class":210},[200,108347,13388],{"class":214},[200,108349,242],{"class":214},[200,108351,39427],{"class":245},[200,108353,249],{"class":214},[200,108355,5267],{"class":235},[200,108357,230],{"class":214},[200,108359,108360,108362,108364,108366],{"class":202,"line":336},[200,108361,39438],{"class":206},[200,108363,39441],{"class":223},[200,108365,227],{"class":235},[200,108367,230],{"class":214},[200,108369,108370,108372,108374,108376],{"class":202,"line":342},[200,108371,39450],{"class":235},[200,108373,239],{"class":214},[200,108375,39813],{"class":2045},[200,108377,252],{"class":214},[200,108379,108380,108382,108384,108386,108388,108390],{"class":202,"line":365},[200,108381,39471],{"class":235},[200,108383,239],{"class":214},[200,108385,242],{"class":214},[200,108387,39486],{"class":245},[200,108389,249],{"class":214},[200,108391,252],{"class":214},[200,108393,108394,108396],{"class":202,"line":371},[200,108395,2096],{"class":214},[200,108397,416],{"class":235},[200,108399,108400],{"class":202,"line":398},[200,108401,4490],{"class":214},[200,108403,108404],{"class":202,"line":405},[200,108405,529],{"emptyLinePlaceholder":20},[200,108407,108408,108410,108412,108414],{"class":202,"line":410},[200,108409,3743],{"class":206},[200,108411,39376],{"class":210},[200,108413,14674],{"class":206},[200,108415,39515],{"class":1978},[200,108417,108418],{"class":202,"line":861},[200,108419,1130],{"class":214},[35,108421,50446],{},[192,108423,108424],{"className":1370,"code":50449,"language":1373,"meta":13,"style":13},[156,108425,108426,108448,108460,108464,108468],{"__ignoreMap":13},[200,108427,108428,108430,108432,108434,108436,108438,108440,108442,108444,108446],{"class":202,"line":203},[200,108429,1380],{"class":206},[200,108431,1383],{"class":206},[200,108433,38960],{"class":223},[200,108435,227],{"class":210},[200,108437,3711],{"class":379},[200,108439,1095],{"class":214},[200,108441,25386],{"class":386},[200,108443,2089],{"class":214},[200,108445,393],{"class":379},[200,108447,280],{"class":214},[200,108449,108450,108452,108454,108456,108458],{"class":202,"line":14},[200,108451,6165],{"class":206},[200,108453,50300],{"class":223},[200,108455,227],{"class":235},[200,108457,25386],{"class":210},[200,108459,416],{"class":235},[200,108461,108462],{"class":202,"line":255},[200,108463,529],{"emptyLinePlaceholder":20},[200,108465,108466],{"class":202,"line":272},[200,108467,50494],{"class":401},[200,108469,108470,108472],{"class":202,"line":283},[200,108471,413],{"class":214},[200,108473,416],{"class":210},[35,108475,50503],{},[1913,108477,50507],{"id":50506},[35,108479,50510],{},[35,108481,46318,108482],{},[1582,108483,46321],{"repo":38655},[51,108485,50518],{"id":50517},[35,108487,108488],{},[1690,108489],{"alt":50523,"src":50524},[35,108491,108492],{},[1217,108493,108494,46057,108497,46062,108500,46066,108503,215],{},[39,108495,38639],{"href":46025,"rel":108496},[77],[39,108498,46061],{"href":18567,"rel":108499},[77],[39,108501,19412],{"href":46016,"rel":108502},[77],[39,108504,11622],{"href":46025,"rel":108505},[77],[35,108507,50543,108508,50546,108510,50549,108512,50552,108514,50555],{},[156,108509,33403],{},[156,108511,33403],{},[156,108513,9895],{},[156,108515,48199],{},[192,108517,108518],{"className":1370,"code":50558,"language":1373,"meta":13,"style":13},[156,108519,108520,108552,108562,108574,108586],{"__ignoreMap":13},[200,108521,108522,108524,108526,108528,108530,108532,108534,108536,108538,108540,108542,108544,108546,108548,108550],{"class":202,"line":203},[200,108523,443],{"class":379},[200,108525,318],{"class":214},[200,108527,5041],{"class":210},[200,108529,550],{"class":214},[200,108531,48096],{"class":210},[200,108533,413],{"class":214},[200,108535,510],{"class":214},[200,108537,452],{"class":206},[200,108539,48105],{"class":223},[200,108541,227],{"class":210},[200,108543,249],{"class":214},[200,108545,48112],{"class":245},[200,108547,249],{"class":214},[200,108549,550],{"class":214},[200,108551,280],{"class":214},[200,108553,108554,108556,108558,108560],{"class":202,"line":14},[200,108555,48123],{"class":235},[200,108557,239],{"class":214},[200,108559,16166],{"class":2423},[200,108561,252],{"class":214},[200,108563,108564,108566,108568,108570,108572],{"class":202,"line":255},[200,108565,50607],{"class":235},[200,108567,239],{"class":214},[200,108569,2424],{"class":2423},[200,108571,550],{"class":214},[200,108573,50616],{"class":401},[200,108575,108576,108578,108580,108582,108584],{"class":202,"line":272},[200,108577,50621],{"class":223},[200,108579,239],{"class":214},[200,108581,3714],{"class":214},[200,108583,393],{"class":379},[200,108585,6421],{"class":210},[200,108587,108588,108590],{"class":202,"line":283},[200,108589,413],{"class":214},[200,108591,416],{"class":210},[35,108593,419,108594,50640,108596,50643,108598,50646,108600,50649],{},[156,108595,48199],{},[156,108597,9886],{},[156,108599,48162],{},[1217,108601,45955],{},[11386,108603,132,108604,132,108610],{"flex":50652,"md":50653,"gap-x-2":13},[130,108605,136,108606,136,108608,132],{},[1690,108607],{"src":50658,"alt":50659,"loading":9895},[143,108609,50662],{},[130,108611,136,108612,136,108614,132],{},[1690,108613],{"src":50667,"alt":50668,"loading":9895},[143,108615,50671],{},[7139,108617,108618,108620,108625,108629,108631,108638,108640,108642,108650,108656,108726,108736,108844,108850,108856,109066,109072,109074,109108,109115,109125,109127,109345,109349,109353,109355,109357,109361,109513,109515,109517,109519,109523,109525,109527,109529,109533,109535,109542,109544,109552,109554,109558,109568,109570,109590,109592,109980,109990,109994,110614,110616,110618,110622,110638,110640,110642,110646,110648,111104,111110,111122,111130,111132,111232,111234,111236,111240,111672,111682,111688,111690,111694,111700,111704,111868,111870,111888,111892,111896,113110,113114,113304,113306,113432,113436,113548,113552,113562,113924,113936,113938,113940,113942,113944,113948,113964,113966,113974,113978,113980,113996,113998,114034,114048,114068,114070,114282,114286,114494,114496,114498,114500,114502,114552,114556,114562,114570,114754,114760,114762,114766,114770,114774,114792,114794,114798,114806,114884,114886,114890,114892,114934,114938,114940,114942,115122,115124,115126,115128,115130,115132,115136,115138,115142,115148,115150,115152,115154,115204,115210,115214,115234,115236,115298,115304,115306,115312,115387,115389,115391,115655,115657,115659,115945,115947,115951,115953,115955,115957,115961,115977,115979,115981,115983,115985,115989,115993,115999,116319,116321,116323,116325,116329,116495,116497,116501,116637,116639,116641,116643,116959,116963,116965,116969,116979,116987,116989,117113,117117,117126,117132,117398,117403,117405,117543,117547,117549,117551],{"icon":7141,"text":50674,"to":50675,"target":7144},[1913,108619,50679],{"id":50678},[35,108621,50682,108622,50686],{},[39,108623,2454],{"href":2452,"rel":108624},[77],[35,108626,50689,108627,50693],{},[156,108628,50692],{},[38677,108630,50697],{"id":50696},[35,108632,50700,108633,50708],{},[39,108634,108636],{"href":50703,"rel":108635},[77],[156,108637,50707],{},[35,108639,50711],{},[38677,108641,50715],{"id":50714},[35,108643,50718,108644,1095,108646,50728],{},[156,108645,50721],{},[1217,108647,50724,108648,50727],{},[156,108649,24434],{},[35,108651,50731,108652,50734,108654,50737],{},[156,108653,50234],{},[156,108655,33406],{},[192,108657,108658],{"className":1370,"code":50740,"language":1373,"meta":13,"style":13},[156,108659,108660,108682,108694,108698,108720],{"__ignoreMap":13},[200,108661,108662,108664,108666,108668,108670,108672,108674,108676,108678,108680],{"class":202,"line":203},[200,108663,1380],{"class":206},[200,108665,1383],{"class":206},[200,108667,38960],{"class":223},[200,108669,227],{"class":210},[200,108671,3711],{"class":379},[200,108673,1095],{"class":214},[200,108675,25386],{"class":386},[200,108677,2089],{"class":214},[200,108679,393],{"class":379},[200,108681,280],{"class":214},[200,108683,108684,108686,108688,108690,108692],{"class":202,"line":14},[200,108685,6165],{"class":206},[200,108687,50300],{"class":223},[200,108689,227],{"class":235},[200,108691,25386],{"class":210},[200,108693,416],{"class":235},[200,108695,108696],{"class":202,"line":255},[200,108697,529],{"emptyLinePlaceholder":20},[200,108699,108700,108702,108704,108706,108708,108710,108712,108714,108716,108718],{"class":202,"line":272},[200,108701,3723],{"class":379},[200,108703,5041],{"class":210},[200,108705,510],{"class":214},[200,108707,452],{"class":206},[200,108709,18741],{"class":223},[200,108711,227],{"class":235},[200,108713,249],{"class":214},[200,108715,50703],{"class":245},[200,108717,249],{"class":214},[200,108719,416],{"class":235},[200,108721,108722,108724],{"class":202,"line":283},[200,108723,413],{"class":214},[200,108725,416],{"class":210},[35,108727,50811,108728,3076,108730,3076,108732,3080,108734,50822],{},[156,108729,20089],{},[156,108731,50816],{},[156,108733,3771],{},[156,108735,50821],{},[192,108737,108738],{"className":1370,"code":50825,"language":1373,"meta":13,"style":13},[156,108739,108740,108754,108804],{"__ignoreMap":13},[200,108741,108742,108744,108746,108748,108750,108752],{"class":202,"line":203},[200,108743,443],{"class":379},[200,108745,50834],{"class":210},[200,108747,449],{"class":214},[200,108749,5041],{"class":210},[200,108751,215],{"class":214},[200,108753,50843],{"class":210},[200,108755,108756,108758,108760,108762,108764,108766,108768,108770,108772,108774,108776,108778,108780,108782,108784,108786,108788,108790,108792,108794,108796,108798,108800,108802],{"class":202,"line":14},[200,108757,50848],{"class":214},[200,108759,3526],{"class":223},[200,108761,227],{"class":210},[200,108763,3951],{"class":214},[200,108765,701],{"class":386},[200,108767,550],{"class":214},[200,108769,321],{"class":386},[200,108771,550],{"class":214},[200,108773,50865],{"class":386},[200,108775,550],{"class":214},[200,108777,50870],{"class":386},[200,108779,390],{"class":214},[200,108781,393],{"class":379},[200,108783,1095],{"class":210},[200,108785,2752],{"class":214},[200,108787,701],{"class":210},[200,108789,550],{"class":214},[200,108791,321],{"class":210},[200,108793,550],{"class":214},[200,108795,50865],{"class":210},[200,108797,550],{"class":214},[200,108799,50893],{"class":210},[200,108801,413],{"class":214},[200,108803,585],{"class":210},[200,108805,108806,108808,108810,108812,108814,108816,108818,108820,108822,108824,108826,108828,108830,108832,108834,108836,108838,108840,108842],{"class":202,"line":255},[200,108807,50848],{"class":214},[200,108809,3758],{"class":223},[200,108811,227],{"class":210},[200,108813,3951],{"class":214},[200,108815,321],{"class":386},[200,108817,390],{"class":214},[200,108819,393],{"class":379},[200,108821,50916],{"class":210},[200,108823,6546],{"class":214},[200,108825,242],{"class":214},[200,108827,50923],{"class":245},[200,108829,249],{"class":214},[200,108831,15009],{"class":214},[200,108833,50916],{"class":210},[200,108835,6546],{"class":214},[200,108837,242],{"class":214},[200,108839,50936],{"class":245},[200,108841,249],{"class":214},[200,108843,416],{"class":210},[35,108845,50943,108846,50947,108848,50950],{},[156,108847,50946],{},[156,108849,50816],{},[35,108851,50953,108852,50957,108854,239],{},[156,108853,50956],{},[156,108855,2461],{},[192,108857,108858],{"className":1370,"code":50962,"language":1373,"meta":13,"style":13},[156,108859,108860,108884,108922,108954,108986,109040,109060],{"__ignoreMap":13},[200,108861,108862,108864,108866,108868,108870,108872,108874,108876,108878,108880,108882],{"class":202,"line":203},[200,108863,1380],{"class":206},[200,108865,14301],{"class":379},[200,108867,50834],{"class":210},[200,108869,449],{"class":214},[200,108871,50977],{"class":223},[200,108873,227],{"class":210},[200,108875,249],{"class":214},[200,108877,2461],{"class":245},[200,108879,249],{"class":214},[200,108881,550],{"class":214},[200,108883,280],{"class":214},[200,108885,108886,108888,108890,108892,108894,108896,108898,108900,108902,108904,108906,108908,108910,108912,108914,108916,108918,108920],{"class":202,"line":14},[200,108887,49799],{"class":235},[200,108889,239],{"class":214},[200,108891,50998],{"class":223},[200,108893,227],{"class":210},[200,108895,249],{"class":214},[200,108897,3280],{"class":245},[200,108899,249],{"class":214},[200,108901,2089],{"class":210},[200,108903,215],{"class":214},[200,108905,51013],{"class":223},[200,108907,227],{"class":210},[200,108909,2752],{"class":214},[200,108911,51020],{"class":235},[200,108913,239],{"class":214},[200,108915,2424],{"class":2423},[200,108917,981],{"class":214},[200,108919,2089],{"class":210},[200,108921,252],{"class":214},[200,108923,108924,108926,108928,108930,108932,108934,108936,108938,108940,108942,108944,108946,108948,108950,108952],{"class":202,"line":255},[200,108925,236],{"class":235},[200,108927,239],{"class":214},[200,108929,51039],{"class":223},[200,108931,227],{"class":210},[200,108933,249],{"class":214},[200,108935,20089],{"class":245},[200,108937,249],{"class":214},[200,108939,2089],{"class":210},[200,108941,215],{"class":214},[200,108943,51054],{"class":223},[200,108945,1532],{"class":210},[200,108947,215],{"class":214},[200,108949,51061],{"class":223},[200,108951,1532],{"class":210},[200,108953,252],{"class":214},[200,108955,108956,108958,108960,108962,108964,108966,108968,108970,108972,108974,108976,108978,108980,108982,108984],{"class":202,"line":272},[200,108957,51070],{"class":235},[200,108959,239],{"class":214},[200,108961,51039],{"class":223},[200,108963,227],{"class":210},[200,108965,249],{"class":214},[200,108967,50816],{"class":245},[200,108969,249],{"class":214},[200,108971,2089],{"class":210},[200,108973,215],{"class":214},[200,108975,51054],{"class":223},[200,108977,1532],{"class":210},[200,108979,215],{"class":214},[200,108981,51061],{"class":223},[200,108983,1532],{"class":210},[200,108985,252],{"class":214},[200,108987,108988,108990,108992,108994,108996,108998,109000,109002,109004,109006,109008,109010,109012,109014,109016,109018,109020,109022,109024,109026,109028,109030,109032,109034,109036,109038],{"class":202,"line":283},[200,108989,51103],{"class":235},[200,108991,239],{"class":214},[200,108993,51039],{"class":223},[200,108995,227],{"class":210},[200,108997,249],{"class":214},[200,108999,3771],{"class":245},[200,109001,249],{"class":214},[200,109003,550],{"class":214},[200,109005,318],{"class":214},[200,109007,51122],{"class":235},[200,109009,239],{"class":214},[200,109011,350],{"class":210},[200,109013,249],{"class":214},[200,109015,50923],{"class":245},[200,109017,249],{"class":214},[200,109019,550],{"class":214},[200,109021,242],{"class":214},[200,109023,50936],{"class":245},[200,109025,249],{"class":214},[200,109027,18836],{"class":210},[200,109029,413],{"class":214},[200,109031,2089],{"class":210},[200,109033,215],{"class":214},[200,109035,51054],{"class":223},[200,109037,1532],{"class":210},[200,109039,252],{"class":214},[200,109041,109042,109044,109046,109048,109050,109052,109054,109056,109058],{"class":202,"line":300},[200,109043,49828],{"class":235},[200,109045,239],{"class":214},[200,109047,51039],{"class":223},[200,109049,227],{"class":210},[200,109051,249],{"class":214},[200,109053,50821],{"class":245},[200,109055,249],{"class":214},[200,109057,2089],{"class":210},[200,109059,252],{"class":214},[200,109061,109062,109064],{"class":202,"line":310},[200,109063,413],{"class":214},[200,109065,416],{"class":210},[939,109067,109068],{},[35,109069,109070,51187],{},[200,109071,945],{},[35,109073,51190],{},[192,109075,109076],{"className":1370,"code":51193,"language":1373,"meta":13,"style":13},[156,109077,109078],{"__ignoreMap":13},[200,109079,109080,109082,109084,109086,109088,109090,109092,109094,109096,109098,109100,109102,109104,109106],{"class":202,"line":203},[200,109081,207],{"class":206},[200,109083,47508],{"class":223},[200,109085,1532],{"class":210},[200,109087,215],{"class":214},[200,109089,47696],{"class":223},[200,109091,51210],{"class":210},[200,109093,215],{"class":214},[200,109095,51215],{"class":210},[200,109097,215],{"class":214},[200,109099,47711],{"class":223},[200,109101,51222],{"class":210},[200,109103,215],{"class":214},[200,109105,47761],{"class":223},[200,109107,466],{"class":210},[35,109109,51231,109110,51235,109112,51241],{},[6850,109111,51234],{},[39,109113,51240],{"href":51238,"rel":109114},[77],[35,109116,51244,109117,17885,109119,51250,109121,51254,109123,51258],{},[156,109118,51247],{},[156,109120,28004],{},[156,109122,51253],{},[6850,109124,51257],{},[35,109126,51261],{},[192,109128,109129],{"className":1370,"code":51264,"language":1373,"meta":13,"style":13},[156,109130,109131,109155,109177,109201,109205,109237,109277,109341],{"__ignoreMap":13},[200,109132,109133,109135,109137,109139,109141,109143,109145,109147,109149,109151,109153],{"class":202,"line":203},[200,109134,443],{"class":379},[200,109136,51273],{"class":210},[200,109138,449],{"class":214},[200,109140,6448],{"class":210},[200,109142,215],{"class":214},[200,109144,27398],{"class":223},[200,109146,51284],{"class":210},[200,109148,3933],{"class":2045},[200,109150,32457],{"class":210},[200,109152,215],{"class":214},[200,109154,13220],{"class":210},[200,109156,109157,109159,109161,109163,109165,109167,109169,109171,109173,109175],{"class":202,"line":14},[200,109158,443],{"class":379},[200,109160,51299],{"class":210},[200,109162,449],{"class":214},[200,109164,51304],{"class":210},[200,109166,215],{"class":214},[200,109168,51309],{"class":223},[200,109170,227],{"class":210},[200,109172,51314],{"class":2045},[200,109174,11945],{"class":214},[200,109176,51319],{"class":210},[200,109178,109179,109181,109183,109185,109187,109189,109191,109193,109195,109197,109199],{"class":202,"line":255},[200,109180,443],{"class":379},[200,109182,51326],{"class":210},[200,109184,449],{"class":214},[200,109186,51304],{"class":210},[200,109188,215],{"class":214},[200,109190,51335],{"class":223},[200,109192,51338],{"class":210},[200,109194,215],{"class":214},[200,109196,51343],{"class":210},[200,109198,2882],{"class":214},[200,109200,51348],{"class":210},[200,109202,109203],{"class":202,"line":272},[200,109204,529],{"emptyLinePlaceholder":20},[200,109206,109207,109209,109211,109213,109215,109217,109219,109221,109223,109225,109227,109229,109231,109233,109235],{"class":202,"line":283},[200,109208,1090],{"class":206},[200,109210,1095],{"class":210},[200,109212,12327],{"class":379},[200,109214,51363],{"class":210},[200,109216,449],{"class":214},[200,109218,51368],{"class":2045},[200,109220,5496],{"class":214},[200,109222,51363],{"class":210},[200,109224,600],{"class":214},[200,109226,51377],{"class":210},[200,109228,5496],{"class":214},[200,109230,51382],{"class":210},[200,109232,51385],{"class":214},[200,109234,5267],{"class":210},[200,109236,230],{"class":214},[200,109238,109239,109241,109243,109245,109247,109249,109251,109253,109255,109257,109259,109261,109263,109265,109267,109269,109271,109273,109275],{"class":202,"line":300},[200,109240,3723],{"class":379},[200,109242,51396],{"class":210},[200,109244,510],{"class":214},[200,109246,51401],{"class":210},[200,109248,215],{"class":214},[200,109250,3928],{"class":223},[200,109252,227],{"class":235},[200,109254,51410],{"class":210},[200,109256,29919],{"class":214},[200,109258,51415],{"class":210},[200,109260,550],{"class":214},[200,109262,1095],{"class":235},[200,109264,51410],{"class":210},[200,109266,13205],{"class":214},[200,109268,3938],{"class":2045},[200,109270,5267],{"class":235},[200,109272,29471],{"class":214},[200,109274,51415],{"class":210},[200,109276,416],{"class":235},[200,109278,109279,109281,109283,109285,109287,109289,109291,109293,109295,109297,109299,109301,109303,109305,109307,109309,109311,109313,109315,109317,109319,109321,109323,109325,109327,109329,109331,109333,109335,109337,109339],{"class":202,"line":310},[200,109280,6165],{"class":206},[200,109282,47508],{"class":223},[200,109284,1532],{"class":235},[200,109286,215],{"class":214},[200,109288,47696],{"class":223},[200,109290,227],{"class":235},[200,109292,47588],{"class":210},[200,109294,215],{"class":214},[200,109296,2461],{"class":210},[200,109298,2089],{"class":235},[200,109300,215],{"class":214},[200,109302,47711],{"class":223},[200,109304,227],{"class":235},[200,109306,47711],{"class":210},[200,109308,2089],{"class":235},[200,109310,215],{"class":214},[200,109312,51470],{"class":223},[200,109314,227],{"class":235},[200,109316,2752],{"class":214},[200,109318,43647],{"class":235},[200,109320,239],{"class":214},[200,109322,47527],{"class":210},[200,109324,215],{"class":214},[200,109326,2461],{"class":210},[200,109328,215],{"class":214},[200,109330,50816],{"class":210},[200,109332,981],{"class":214},[200,109334,2089],{"class":235},[200,109336,215],{"class":214},[200,109338,47761],{"class":223},[200,109340,466],{"class":235},[200,109342,109343],{"class":202,"line":336},[200,109344,1130],{"class":214},[35,109346,51505,109347,51508],{},[156,109348,51470],{},[35,109350,109351],{},[1217,109352,51513],{},[35,109354,51516],{},[38677,109356,51520],{"id":51519},[35,109358,51523,109359,51527],{},[156,109360,51526],{},[192,109362,109363],{"className":1370,"code":51530,"language":1373,"meta":13,"style":13},[156,109364,109365,109383,109405,109423,109441,109459,109477,109497,109501,109507],{"__ignoreMap":13},[200,109366,109367,109369,109371,109373,109375,109377,109379,109381],{"class":202,"line":203},[200,109368,1380],{"class":206},[200,109370,1383],{"class":206},[200,109372,38960],{"class":223},[200,109374,227],{"class":210},[200,109376,3711],{"class":379},[200,109378,3714],{"class":214},[200,109380,393],{"class":379},[200,109382,280],{"class":214},[200,109384,109385,109387,109389,109391,109393,109395,109397,109399,109401,109403],{"class":202,"line":14},[200,109386,3723],{"class":379},[200,109388,51401],{"class":210},[200,109390,510],{"class":214},[200,109392,452],{"class":206},[200,109394,47508],{"class":223},[200,109396,1532],{"class":235},[200,109398,215],{"class":214},[200,109400,770],{"class":223},[200,109402,227],{"class":235},[200,109404,230],{"class":214},[200,109406,109407,109409,109411,109413,109415,109417,109419,109421],{"class":202,"line":255},[200,109408,15858],{"class":235},[200,109410,239],{"class":214},[200,109412,47527],{"class":210},[200,109414,215],{"class":214},[200,109416,2461],{"class":210},[200,109418,215],{"class":214},[200,109420,3280],{"class":210},[200,109422,252],{"class":214},[200,109424,109425,109427,109429,109431,109433,109435,109437,109439],{"class":202,"line":272},[200,109426,11797],{"class":235},[200,109428,239],{"class":214},[200,109430,47527],{"class":210},[200,109432,215],{"class":214},[200,109434,2461],{"class":210},[200,109436,215],{"class":214},[200,109438,20089],{"class":210},[200,109440,252],{"class":214},[200,109442,109443,109445,109447,109449,109451,109453,109455,109457],{"class":202,"line":283},[200,109444,286],{"class":235},[200,109446,239],{"class":214},[200,109448,47527],{"class":210},[200,109450,215],{"class":214},[200,109452,2461],{"class":210},[200,109454,215],{"class":214},[200,109456,3771],{"class":210},[200,109458,252],{"class":214},[200,109460,109461,109463,109465,109467,109469,109471,109473,109475],{"class":202,"line":300},[200,109462,51631],{"class":235},[200,109464,239],{"class":214},[200,109466,47527],{"class":210},[200,109468,215],{"class":214},[200,109470,2461],{"class":210},[200,109472,215],{"class":214},[200,109474,50821],{"class":210},[200,109476,252],{"class":214},[200,109478,109479,109481,109483,109485,109487,109489,109491,109493,109495],{"class":202,"line":310},[200,109480,5080],{"class":214},[200,109482,2089],{"class":235},[200,109484,215],{"class":214},[200,109486,1472],{"class":223},[200,109488,227],{"class":235},[200,109490,47588],{"class":210},[200,109492,215],{"class":214},[200,109494,2461],{"class":210},[200,109496,416],{"class":235},[200,109498,109499],{"class":202,"line":336},[200,109500,529],{"emptyLinePlaceholder":20},[200,109502,109503,109505],{"class":202,"line":342},[200,109504,3743],{"class":206},[200,109506,51676],{"class":210},[200,109508,109509,109511],{"class":202,"line":365},[200,109510,413],{"class":214},[200,109512,416],{"class":210},[35,109514,51685],{},[1913,109516,30681],{"id":51688},[35,109518,51691],{},[35,109520,109521],{},[1690,109522],{"alt":51696,"src":51697},[35,109524,51700],{},[35,109526,51703],{},[51,109528,51707],{"id":51706},[35,109530,109531],{},[1217,109532,51712],{},[35,109534,51715],{},[35,109536,51718,109537,51724,109540],{},[39,109538,51723],{"href":51721,"rel":109539},[77],[1217,109541,51727],{},[35,109543,51730],{},[68,109545,109546,109548,109550],{},[71,109547,51735],{},[71,109549,51738],{},[71,109551,51741],{},[35,109553,51744],{},[35,109555,51747,109556],{},[1217,109557,51750],{},[35,109559,51753,109560,2857,109562,51760,109564,51763,109566],{},[156,109561,51756],{},[156,109563,51759],{},[156,109565,51756],{},[1217,109567,51766],{},[35,109569,51769],{},[1576,109571,109572,109574,109576,109582,109588],{},[71,109573,51774],{},[71,109575,51777],{},[71,109577,51780,109578,51783,109580,215],{},[156,109579,49234],{},[156,109581,51786],{},[71,109583,51789,109584,51793,109586,51797],{},[156,109585,51792],{},[156,109587,51796],{},[71,109589,51800],{},[35,109591,51803],{},[192,109593,109594],{"className":1370,"code":51806,"language":1373,"meta":13,"style":13},[156,109595,109596,109620,109658,109690,109722,109754,109774,109800,109862,109908,109974],{"__ignoreMap":13},[200,109597,109598,109600,109602,109604,109606,109608,109610,109612,109614,109616,109618],{"class":202,"line":203},[200,109599,1380],{"class":206},[200,109601,14301],{"class":379},[200,109603,49490],{"class":210},[200,109605,449],{"class":214},[200,109607,50977],{"class":223},[200,109609,227],{"class":210},[200,109611,249],{"class":214},[200,109613,47325],{"class":245},[200,109615,249],{"class":214},[200,109617,550],{"class":214},[200,109619,280],{"class":214},[200,109621,109622,109624,109626,109628,109630,109632,109634,109636,109638,109640,109642,109644,109646,109648,109650,109652,109654,109656],{"class":202,"line":14},[200,109623,49799],{"class":235},[200,109625,239],{"class":214},[200,109627,50998],{"class":223},[200,109629,227],{"class":210},[200,109631,249],{"class":214},[200,109633,3280],{"class":245},[200,109635,249],{"class":214},[200,109637,2089],{"class":210},[200,109639,215],{"class":214},[200,109641,51013],{"class":223},[200,109643,227],{"class":210},[200,109645,2752],{"class":214},[200,109647,51020],{"class":235},[200,109649,239],{"class":214},[200,109651,2424],{"class":2423},[200,109653,981],{"class":214},[200,109655,2089],{"class":210},[200,109657,252],{"class":214},[200,109659,109660,109662,109664,109666,109668,109670,109672,109674,109676,109678,109680,109682,109684,109686,109688],{"class":202,"line":255},[200,109661,51875],{"class":235},[200,109663,239],{"class":214},[200,109665,50998],{"class":223},[200,109667,227],{"class":210},[200,109669,249],{"class":214},[200,109671,51886],{"class":245},[200,109673,249],{"class":214},[200,109675,2089],{"class":210},[200,109677,215],{"class":214},[200,109679,51054],{"class":223},[200,109681,1532],{"class":210},[200,109683,215],{"class":214},[200,109685,51061],{"class":223},[200,109687,1532],{"class":210},[200,109689,252],{"class":214},[200,109691,109692,109694,109696,109698,109700,109702,109704,109706,109708,109710,109712,109714,109716,109718,109720],{"class":202,"line":272},[200,109693,51909],{"class":235},[200,109695,239],{"class":214},[200,109697,51039],{"class":223},[200,109699,227],{"class":210},[200,109701,249],{"class":214},[200,109703,51920],{"class":245},[200,109705,249],{"class":214},[200,109707,2089],{"class":210},[200,109709,215],{"class":214},[200,109711,51054],{"class":223},[200,109713,1532],{"class":210},[200,109715,215],{"class":214},[200,109717,51061],{"class":223},[200,109719,1532],{"class":210},[200,109721,252],{"class":214},[200,109723,109724,109726,109728,109730,109732,109734,109736,109738,109740,109742,109744,109746,109748,109750,109752],{"class":202,"line":283},[200,109725,51943],{"class":235},[200,109727,239],{"class":214},[200,109729,51039],{"class":223},[200,109731,227],{"class":210},[200,109733,249],{"class":214},[200,109735,51786],{"class":245},[200,109737,249],{"class":214},[200,109739,2089],{"class":210},[200,109741,215],{"class":214},[200,109743,51054],{"class":223},[200,109745,1532],{"class":210},[200,109747,215],{"class":214},[200,109749,51061],{"class":223},[200,109751,1532],{"class":210},[200,109753,252],{"class":214},[200,109755,109756,109758,109760,109762,109764,109766,109768,109770,109772],{"class":202,"line":300},[200,109757,236],{"class":235},[200,109759,239],{"class":214},[200,109761,51039],{"class":223},[200,109763,227],{"class":210},[200,109765,249],{"class":214},[200,109767,20089],{"class":245},[200,109769,249],{"class":214},[200,109771,2089],{"class":210},[200,109773,252],{"class":214},[200,109775,109776,109778,109780,109782,109784,109786,109788,109790,109792,109794,109796,109798],{"class":202,"line":310},[200,109777,51996],{"class":235},[200,109779,239],{"class":214},[200,109781,51039],{"class":223},[200,109783,227],{"class":210},[200,109785,249],{"class":214},[200,109787,52007],{"class":245},[200,109789,249],{"class":214},[200,109791,2089],{"class":210},[200,109793,215],{"class":214},[200,109795,51054],{"class":223},[200,109797,1532],{"class":210},[200,109799,252],{"class":214},[200,109801,109802,109804,109806,109808,109810,109812,109814,109816,109818,109820,109822,109824,109826,109828,109830,109832,109834,109836,109838,109840,109842,109844,109846,109848,109850,109852,109854,109856,109858,109860],{"class":202,"line":336},[200,109803,52024],{"class":235},[200,109805,239],{"class":214},[200,109807,51039],{"class":223},[200,109809,227],{"class":210},[200,109811,249],{"class":214},[200,109813,52035],{"class":245},[200,109815,249],{"class":214},[200,109817,550],{"class":214},[200,109819,318],{"class":214},[200,109821,51122],{"class":235},[200,109823,239],{"class":214},[200,109825,350],{"class":210},[200,109827,249],{"class":214},[200,109829,39427],{"class":245},[200,109831,249],{"class":214},[200,109833,550],{"class":214},[200,109835,242],{"class":214},[200,109837,52060],{"class":245},[200,109839,249],{"class":214},[200,109841,18836],{"class":210},[200,109843,413],{"class":214},[200,109845,2089],{"class":210},[200,109847,215],{"class":214},[200,109849,52073],{"class":223},[200,109851,227],{"class":210},[200,109853,249],{"class":214},[200,109855,52060],{"class":245},[200,109857,249],{"class":214},[200,109859,2089],{"class":210},[200,109861,252],{"class":214},[200,109863,109864,109866,109868,109870,109872,109874,109876,109878,109880,109882,109884,109886,109888,109890,109892,109894,109896,109898,109900,109902,109904,109906],{"class":202,"line":342},[200,109865,52090],{"class":235},[200,109867,239],{"class":214},[200,109869,51039],{"class":223},[200,109871,227],{"class":210},[200,109873,249],{"class":214},[200,109875,51792],{"class":245},[200,109877,249],{"class":214},[200,109879,2089],{"class":210},[200,109881,215],{"class":214},[200,109883,51054],{"class":223},[200,109885,1532],{"class":210},[200,109887,215],{"class":214},[200,109889,52115],{"class":223},[200,109891,227],{"class":210},[200,109893,1532],{"class":214},[200,109895,393],{"class":379},[200,109897,52124],{"class":223},[200,109899,3318],{"class":214},[200,109901,52129],{"class":245},[200,109903,3318],{"class":214},[200,109905,2089],{"class":210},[200,109907,252],{"class":214},[200,109909,109910,109912,109914,109916,109918,109920,109922,109924,109926,109928,109930,109932,109934,109936,109938,109940,109942,109944,109946,109948,109950,109952,109954,109956,109958,109960,109962,109964,109966,109968,109970,109972],{"class":202,"line":365},[200,109911,52140],{"class":235},[200,109913,239],{"class":214},[200,109915,51039],{"class":223},[200,109917,227],{"class":210},[200,109919,249],{"class":214},[200,109921,51796],{"class":245},[200,109923,249],{"class":214},[200,109925,2089],{"class":210},[200,109927,215],{"class":214},[200,109929,51054],{"class":223},[200,109931,1532],{"class":210},[200,109933,215],{"class":214},[200,109935,52115],{"class":223},[200,109937,227],{"class":210},[200,109939,1532],{"class":214},[200,109941,393],{"class":379},[200,109943,52124],{"class":223},[200,109945,3318],{"class":214},[200,109947,52129],{"class":245},[200,109949,3318],{"class":214},[200,109951,2089],{"class":210},[200,109953,215],{"class":214},[200,109955,52185],{"class":223},[200,109957,227],{"class":210},[200,109959,1532],{"class":214},[200,109961,393],{"class":379},[200,109963,52124],{"class":223},[200,109965,3318],{"class":214},[200,109967,52129],{"class":245},[200,109969,3318],{"class":214},[200,109971,2089],{"class":210},[200,109973,252],{"class":214},[200,109975,109976,109978],{"class":202,"line":371},[200,109977,413],{"class":214},[200,109979,416],{"class":210},[35,109981,52212,109982,1638,109984,52217,109986,52220,109988,52223],{},[156,109983,52115],{},[156,109985,52185],{},[156,109987,37278],{},[156,109989,37303],{},[35,109991,52226,109992,52230],{},[156,109993,52229],{},[192,109995,109996],{"className":1370,"code":52233,"language":1373,"meta":13,"style":13},[156,109997,109998,110014,110022,110032,110036,110054,110072,110076,110090,110094,110108,110112,110176,110180,110184,110188,110192,110196,110222,110256,110262,110276,110290,110304,110318,110332,110350,110358,110372,110386,110400,110414,110418,110436,110440,110444,110448,110452,110456,110470,110478,110492,110506,110520,110534,110548,110570,110574,110580,110584,110604,110608],{"__ignoreMap":13},[200,109999,110000,110002,110004,110006,110008,110010,110012],{"class":202,"line":203},[200,110001,1380],{"class":206},[200,110003,1383],{"class":206},[200,110005,46128],{"class":210},[200,110007,215],{"class":214},[200,110009,46133],{"class":223},[200,110011,227],{"class":210},[200,110013,230],{"class":214},[200,110015,110016,110018,110020],{"class":202,"line":14},[200,110017,46142],{"class":235},[200,110019,239],{"class":214},[200,110021,280],{"class":214},[200,110023,110024,110026,110028,110030],{"class":202,"line":255},[200,110025,46151],{"class":235},[200,110027,239],{"class":214},[200,110029,2424],{"class":2423},[200,110031,252],{"class":214},[200,110033,110034],{"class":202,"line":272},[200,110035,368],{"class":214},[200,110037,110038,110040,110042,110044,110046,110048,110050,110052],{"class":202,"line":283},[200,110039,3219],{"class":379},[200,110041,46168],{"class":235},[200,110043,227],{"class":214},[200,110045,25386],{"class":386},[200,110047,550],{"class":214},[200,110049,3546],{"class":386},[200,110051,2089],{"class":214},[200,110053,280],{"class":214},[200,110055,110056,110058,110060,110062,110064,110066,110068,110070],{"class":202,"line":300},[200,110057,1999],{"class":379},[200,110059,318],{"class":214},[200,110061,34776],{"class":235},[200,110063,239],{"class":214},[200,110065,52304],{"class":210},[200,110067,981],{"class":214},[200,110069,510],{"class":214},[200,110071,4200],{"class":210},[200,110073,110074],{"class":202,"line":310},[200,110075,529],{"emptyLinePlaceholder":20},[200,110077,110078,110080,110082,110084,110086,110088],{"class":202,"line":336},[200,110079,1999],{"class":379},[200,110081,52321],{"class":210},[200,110083,510],{"class":214},[200,110085,52304],{"class":210},[200,110087,215],{"class":214},[200,110089,52330],{"class":210},[200,110091,110092],{"class":202,"line":342},[200,110093,529],{"emptyLinePlaceholder":20},[200,110095,110096,110098,110100,110102,110104,110106],{"class":202,"line":365},[200,110097,52339],{"class":379},[200,110099,34776],{"class":210},[200,110101,239],{"class":214},[200,110103,34877],{"class":1978},[200,110105,40229],{"class":214},[200,110107,52350],{"class":1978},[200,110109,110110],{"class":202,"line":371},[200,110111,529],{"emptyLinePlaceholder":20},[200,110113,110114,110116,110118,110120,110122,110124,110126,110128,110130,110132,110134,110136,110138,110140,110142,110144,110146,110148,110150,110152,110154,110156,110158,110160,110162,110164,110166,110168,110170,110172,110174],{"class":202,"line":398},[200,110115,52359],{"class":210},[200,110117,510],{"class":214},[200,110119,452],{"class":206},[200,110121,47508],{"class":223},[200,110123,1532],{"class":235},[200,110125,215],{"class":214},[200,110127,770],{"class":223},[200,110129,1532],{"class":235},[200,110131,215],{"class":214},[200,110133,1472],{"class":223},[200,110135,227],{"class":235},[200,110137,47588],{"class":210},[200,110139,215],{"class":214},[200,110141,47325],{"class":210},[200,110143,2089],{"class":235},[200,110145,215],{"class":214},[200,110147,49030],{"class":223},[200,110149,227],{"class":235},[200,110151,49035],{"class":223},[200,110153,227],{"class":235},[200,110155,47588],{"class":210},[200,110157,215],{"class":214},[200,110159,47325],{"class":210},[200,110161,215],{"class":214},[200,110163,46224],{"class":210},[200,110165,550],{"class":214},[200,110167,52321],{"class":210},[200,110169,6119],{"class":235},[200,110171,215],{"class":214},[200,110173,42948],{"class":223},[200,110175,466],{"class":235},[200,110177,110178],{"class":202,"line":405},[200,110179,529],{"emptyLinePlaceholder":20},[200,110181,110182],{"class":202,"line":410},[200,110183,19618],{"class":401},[200,110185,110186],{"class":202,"line":861},[200,110187,52432],{"class":401},[200,110189,110190],{"class":202,"line":890},[200,110191,52437],{"class":401},[200,110193,110194],{"class":202,"line":900},[200,110195,19628],{"class":401},[200,110197,110198,110200,110202,110204,110206,110208,110210,110212,110214,110216,110218,110220],{"class":202,"line":905},[200,110199,5252],{"class":206},[200,110201,1095],{"class":235},[200,110203,11837],{"class":214},[200,110205,26808],{"class":210},[200,110207,15009],{"class":214},[200,110209,52456],{"class":223},[200,110211,227],{"class":235},[200,110213,26808],{"class":210},[200,110215,550],{"class":214},[200,110217,52304],{"class":210},[200,110219,6466],{"class":235},[200,110221,230],{"class":214},[200,110223,110224,110226,110228,110230,110232,110234,110236,110238,110240,110242,110244,110246,110248,110250,110252,110254],{"class":202,"line":931},[200,110225,46206],{"class":210},[200,110227,510],{"class":214},[200,110229,452],{"class":206},[200,110231,47508],{"class":223},[200,110233,1532],{"class":235},[200,110235,215],{"class":214},[200,110237,47696],{"class":223},[200,110239,227],{"class":235},[200,110241,47588],{"class":210},[200,110243,215],{"class":214},[200,110245,47325],{"class":210},[200,110247,2089],{"class":235},[200,110249,215],{"class":214},[200,110251,47711],{"class":223},[200,110253,227],{"class":235},[200,110255,230],{"class":214},[200,110257,110258,110260],{"class":202,"line":3640},[200,110259,46215],{"class":210},[200,110261,252],{"class":214},[200,110263,110264,110266,110268,110270,110272,110274],{"class":202,"line":3645},[200,110265,52513],{"class":235},[200,110267,239],{"class":214},[200,110269,52304],{"class":210},[200,110271,215],{"class":214},[200,110273,51786],{"class":210},[200,110275,252],{"class":214},[200,110277,110278,110280,110282,110284,110286,110288],{"class":202,"line":4020},[200,110279,52528],{"class":235},[200,110281,239],{"class":214},[200,110283,52304],{"class":210},[200,110285,215],{"class":214},[200,110287,51920],{"class":210},[200,110289,252],{"class":214},[200,110291,110292,110294,110296,110298,110300,110302],{"class":202,"line":4475},[200,110293,11722],{"class":235},[200,110295,239],{"class":214},[200,110297,52304],{"class":210},[200,110299,215],{"class":214},[200,110301,20089],{"class":210},[200,110303,252],{"class":214},[200,110305,110306,110308,110310,110312,110314,110316],{"class":202,"line":4481},[200,110307,52557],{"class":235},[200,110309,239],{"class":214},[200,110311,52304],{"class":210},[200,110313,215],{"class":214},[200,110315,52007],{"class":210},[200,110317,252],{"class":214},[200,110319,110320,110322,110324,110326,110328,110330],{"class":202,"line":4487},[200,110321,2632],{"class":214},[200,110323,2089],{"class":235},[200,110325,215],{"class":214},[200,110327,52578],{"class":223},[200,110329,227],{"class":235},[200,110331,230],{"class":214},[200,110333,110334,110336,110338,110340,110342,110344,110346,110348],{"class":202,"line":4493},[200,110335,52587],{"class":235},[200,110337,239],{"class":214},[200,110339,47527],{"class":210},[200,110341,215],{"class":214},[200,110343,47325],{"class":210},[200,110345,215],{"class":214},[200,110347,46224],{"class":210},[200,110349,252],{"class":214},[200,110351,110352,110354,110356],{"class":202,"line":5454},[200,110353,52606],{"class":235},[200,110355,239],{"class":214},[200,110357,280],{"class":214},[200,110359,110360,110362,110364,110366,110368,110370],{"class":202,"line":5459},[200,110361,52615],{"class":235},[200,110363,239],{"class":214},[200,110365,52304],{"class":210},[200,110367,215],{"class":214},[200,110369,51786],{"class":210},[200,110371,252],{"class":214},[200,110373,110374,110376,110378,110380,110382,110384],{"class":202,"line":13227},[200,110375,52630],{"class":235},[200,110377,239],{"class":214},[200,110379,52304],{"class":210},[200,110381,215],{"class":214},[200,110383,51920],{"class":210},[200,110385,252],{"class":214},[200,110387,110388,110390,110392,110394,110396,110398],{"class":202,"line":13253},[200,110389,52645],{"class":235},[200,110391,239],{"class":214},[200,110393,52304],{"class":210},[200,110395,215],{"class":214},[200,110397,20089],{"class":210},[200,110399,252],{"class":214},[200,110401,110402,110404,110406,110408,110410,110412],{"class":202,"line":13269},[200,110403,52660],{"class":235},[200,110405,239],{"class":214},[200,110407,52304],{"class":210},[200,110409,215],{"class":214},[200,110411,52007],{"class":210},[200,110413,252],{"class":214},[200,110415,110416],{"class":202,"line":13313},[200,110417,4950],{"class":214},[200,110419,110420,110422,110424,110426,110428,110430,110432,110434],{"class":202,"line":13319},[200,110421,2632],{"class":214},[200,110423,2089],{"class":235},[200,110425,215],{"class":214},[200,110427,52685],{"class":223},[200,110429,1532],{"class":235},[200,110431,215],{"class":214},[200,110433,42948],{"class":223},[200,110435,466],{"class":235},[200,110437,110438],{"class":202,"line":13324},[200,110439,4962],{"class":214},[200,110441,110442],{"class":202,"line":13350},[200,110443,529],{"emptyLinePlaceholder":20},[200,110445,110446],{"class":202,"line":13355},[200,110447,19618],{"class":401},[200,110449,110450],{"class":202,"line":13375},[200,110451,52710],{"class":401},[200,110453,110454],{"class":202,"line":13395},[200,110455,19628],{"class":401},[200,110457,110458,110460,110462,110464,110466,110468],{"class":202,"line":13400},[200,110459,19216],{"class":206},[200,110461,46193],{"class":223},[200,110463,227],{"class":235},[200,110465,25386],{"class":210},[200,110467,550],{"class":214},[200,110469,280],{"class":214},[200,110471,110472,110474,110476],{"class":202,"line":13408},[200,110473,46206],{"class":235},[200,110475,239],{"class":214},[200,110477,280],{"class":214},[200,110479,110480,110482,110484,110486,110488,110490],{"class":202,"line":13426},[200,110481,12050],{"class":235},[200,110483,239],{"class":214},[200,110485,34776],{"class":210},[200,110487,215],{"class":214},[200,110489,3280],{"class":210},[200,110491,52751],{"class":214},[200,110493,110494,110496,110498,110500,110502,110504],{"class":202,"line":13456},[200,110495,52513],{"class":235},[200,110497,239],{"class":214},[200,110499,52304],{"class":210},[200,110501,215],{"class":214},[200,110503,51786],{"class":210},[200,110505,252],{"class":214},[200,110507,110508,110510,110512,110514,110516,110518],{"class":202,"line":13462},[200,110509,52528],{"class":235},[200,110511,239],{"class":214},[200,110513,52304],{"class":210},[200,110515,215],{"class":214},[200,110517,51920],{"class":210},[200,110519,252],{"class":214},[200,110521,110522,110524,110526,110528,110530,110532],{"class":202,"line":13467},[200,110523,11722],{"class":235},[200,110525,239],{"class":214},[200,110527,52304],{"class":210},[200,110529,215],{"class":214},[200,110531,20089],{"class":210},[200,110533,252],{"class":214},[200,110535,110536,110538,110540,110542,110544,110546],{"class":202,"line":13472},[200,110537,52557],{"class":235},[200,110539,239],{"class":214},[200,110541,52304],{"class":210},[200,110543,215],{"class":214},[200,110545,52007],{"class":210},[200,110547,252],{"class":214},[200,110549,110550,110552,110554,110556,110558,110560,110562,110564,110566,110568],{"class":202,"line":13487},[200,110551,52812],{"class":235},[200,110553,239],{"class":214},[200,110555,34776],{"class":210},[200,110557,215],{"class":214},[200,110559,39420],{"class":210},[200,110561,15009],{"class":214},[200,110563,242],{"class":214},[200,110565,52060],{"class":245},[200,110567,249],{"class":214},[200,110569,252],{"class":214},[200,110571,110572],{"class":202,"line":13498},[200,110573,12083],{"class":214},[200,110575,110576,110578],{"class":202,"line":13503},[200,110577,2096],{"class":214},[200,110579,416],{"class":235},[200,110581,110582],{"class":202,"line":13508},[200,110583,529],{"emptyLinePlaceholder":20},[200,110585,110586,110588,110590,110592,110594,110596,110598,110600,110602],{"class":202,"line":20224},[200,110587,3296],{"class":206},[200,110589,46247],{"class":223},[200,110591,227],{"class":235},[200,110593,25386],{"class":210},[200,110595,550],{"class":214},[200,110597,242],{"class":214},[200,110599,52861],{"class":245},[200,110601,249],{"class":214},[200,110603,416],{"class":235},[200,110605,110606],{"class":202,"line":20246},[200,110607,368],{"class":214},[200,110609,110610,110612],{"class":202,"line":20251},[200,110611,413],{"class":214},[200,110613,416],{"class":210},[35,110615,52878],{},[51,110617,52882],{"id":52881},[35,110619,110620],{},[1690,110621],{"alt":52887,"src":52888},[35,110623,110624],{},[1217,110625,110626,46057,110629,46062,110632,46066,110635,215],{},[39,110627,38639],{"href":46025,"rel":110628},[77],[39,110630,46061],{"href":18567,"rel":110631},[77],[39,110633,19412],{"href":46016,"rel":110634},[77],[39,110636,11622],{"href":46025,"rel":110637},[77],[35,110639,52907],{},[1913,110641,52911],{"id":52910},[35,110643,110644],{},[1217,110645,52916],{},[35,110647,52919],{},[192,110649,110650],{"className":1370,"code":52922,"language":1373,"meta":13,"style":13},[156,110651,110652,110676,110714,110746,110772,110798,110874,110942,110962,110988,111014,111056,111098],{"__ignoreMap":13},[200,110653,110654,110656,110658,110660,110662,110664,110666,110668,110670,110672,110674],{"class":202,"line":203},[200,110655,1380],{"class":206},[200,110657,14301],{"class":379},[200,110659,52933],{"class":210},[200,110661,449],{"class":214},[200,110663,50977],{"class":223},[200,110665,227],{"class":210},[200,110667,249],{"class":214},[200,110669,51759],{"class":245},[200,110671,249],{"class":214},[200,110673,550],{"class":214},[200,110675,280],{"class":214},[200,110677,110678,110680,110682,110684,110686,110688,110690,110692,110694,110696,110698,110700,110702,110704,110706,110708,110710,110712],{"class":202,"line":14},[200,110679,49799],{"class":235},[200,110681,239],{"class":214},[200,110683,50998],{"class":223},[200,110685,227],{"class":210},[200,110687,249],{"class":214},[200,110689,3280],{"class":245},[200,110691,249],{"class":214},[200,110693,2089],{"class":210},[200,110695,215],{"class":214},[200,110697,51013],{"class":223},[200,110699,227],{"class":210},[200,110701,2752],{"class":214},[200,110703,51020],{"class":235},[200,110705,239],{"class":214},[200,110707,2424],{"class":2423},[200,110709,981],{"class":214},[200,110711,2089],{"class":210},[200,110713,252],{"class":214},[200,110715,110716,110718,110720,110722,110724,110726,110728,110730,110732,110734,110736,110738,110740,110742,110744],{"class":202,"line":255},[200,110717,52992],{"class":235},[200,110719,239],{"class":214},[200,110721,51039],{"class":223},[200,110723,227],{"class":210},[200,110725,249],{"class":214},[200,110727,53003],{"class":245},[200,110729,249],{"class":214},[200,110731,2089],{"class":210},[200,110733,215],{"class":214},[200,110735,51054],{"class":223},[200,110737,1532],{"class":210},[200,110739,215],{"class":214},[200,110741,51061],{"class":223},[200,110743,1532],{"class":210},[200,110745,252],{"class":214},[200,110747,110748,110750,110752,110754,110756,110758,110760,110762,110764,110766,110768,110770],{"class":202,"line":272},[200,110749,53026],{"class":235},[200,110751,239],{"class":214},[200,110753,51039],{"class":223},[200,110755,227],{"class":210},[200,110757,249],{"class":214},[200,110759,47555],{"class":245},[200,110761,249],{"class":214},[200,110763,2089],{"class":210},[200,110765,215],{"class":214},[200,110767,51054],{"class":223},[200,110769,1532],{"class":210},[200,110771,252],{"class":214},[200,110773,110774,110776,110778,110780,110782,110784,110786,110788,110790,110792,110794,110796],{"class":202,"line":283},[200,110775,23659],{"class":235},[200,110777,239],{"class":214},[200,110779,51039],{"class":223},[200,110781,227],{"class":210},[200,110783,249],{"class":214},[200,110785,3566],{"class":245},[200,110787,249],{"class":214},[200,110789,2089],{"class":210},[200,110791,215],{"class":214},[200,110793,51054],{"class":223},[200,110795,1532],{"class":210},[200,110797,252],{"class":214},[200,110799,110800,110802,110804,110806,110808,110810,110812,110814,110816,110818,110820,110822,110824,110826,110828,110830,110832,110834,110836,110838,110840,110842,110844,110846,110848,110850,110852,110854,110856,110858,110860,110862,110864,110866,110868,110870,110872],{"class":202,"line":300},[200,110801,53079],{"class":235},[200,110803,239],{"class":214},[200,110805,51039],{"class":223},[200,110807,227],{"class":210},[200,110809,249],{"class":214},[200,110811,39751],{"class":245},[200,110813,249],{"class":214},[200,110815,550],{"class":214},[200,110817,318],{"class":214},[200,110819,51122],{"class":235},[200,110821,239],{"class":214},[200,110823,350],{"class":210},[200,110825,249],{"class":214},[200,110827,53106],{"class":245},[200,110829,249],{"class":214},[200,110831,550],{"class":214},[200,110833,242],{"class":214},[200,110835,53115],{"class":245},[200,110837,249],{"class":214},[200,110839,550],{"class":214},[200,110841,242],{"class":214},[200,110843,53124],{"class":245},[200,110845,249],{"class":214},[200,110847,18836],{"class":210},[200,110849,413],{"class":214},[200,110851,2089],{"class":210},[200,110853,215],{"class":214},[200,110855,51054],{"class":223},[200,110857,1532],{"class":210},[200,110859,215],{"class":214},[200,110861,52073],{"class":223},[200,110863,227],{"class":210},[200,110865,249],{"class":214},[200,110867,53106],{"class":245},[200,110869,249],{"class":214},[200,110871,2089],{"class":210},[200,110873,252],{"class":214},[200,110875,110876,110878,110880,110882,110884,110886,110888,110890,110892,110894,110896,110898,110900,110902,110904,110906,110908,110910,110912,110914,110916,110918,110920,110922,110924,110926,110928,110930,110932,110934,110936,110938,110940],{"class":202,"line":310},[200,110877,53159],{"class":235},[200,110879,239],{"class":214},[200,110881,51039],{"class":223},[200,110883,227],{"class":210},[200,110885,249],{"class":214},[200,110887,53170],{"class":245},[200,110889,249],{"class":214},[200,110891,550],{"class":214},[200,110893,318],{"class":214},[200,110895,51122],{"class":235},[200,110897,239],{"class":214},[200,110899,350],{"class":210},[200,110901,249],{"class":214},[200,110903,38206],{"class":245},[200,110905,249],{"class":214},[200,110907,550],{"class":214},[200,110909,242],{"class":214},[200,110911,53195],{"class":245},[200,110913,249],{"class":214},[200,110915,18836],{"class":210},[200,110917,413],{"class":214},[200,110919,2089],{"class":210},[200,110921,215],{"class":214},[200,110923,51054],{"class":223},[200,110925,1532],{"class":210},[200,110927,215],{"class":214},[200,110929,52073],{"class":223},[200,110931,227],{"class":210},[200,110933,249],{"class":214},[200,110935,38206],{"class":245},[200,110937,249],{"class":214},[200,110939,2089],{"class":210},[200,110941,252],{"class":214},[200,110943,110944,110946,110948,110950,110952,110954,110956,110958,110960],{"class":202,"line":336},[200,110945,53230],{"class":235},[200,110947,239],{"class":214},[200,110949,51039],{"class":223},[200,110951,227],{"class":210},[200,110953,249],{"class":214},[200,110955,53241],{"class":245},[200,110957,249],{"class":214},[200,110959,2089],{"class":210},[200,110961,252],{"class":214},[200,110963,110964,110966,110968,110970,110972,110974,110976,110978,110980,110982,110984,110986],{"class":202,"line":342},[200,110965,53252],{"class":235},[200,110967,239],{"class":214},[200,110969,51039],{"class":223},[200,110971,227],{"class":210},[200,110973,249],{"class":214},[200,110975,53263],{"class":245},[200,110977,249],{"class":214},[200,110979,2089],{"class":210},[200,110981,215],{"class":214},[200,110983,51054],{"class":223},[200,110985,1532],{"class":210},[200,110987,252],{"class":214},[200,110989,110990,110992,110994,110996,110998,111000,111002,111004,111006,111008,111010,111012],{"class":202,"line":365},[200,110991,258],{"class":235},[200,110993,239],{"class":214},[200,110995,51039],{"class":223},[200,110997,227],{"class":210},[200,110999,249],{"class":214},[200,111001,3582],{"class":245},[200,111003,249],{"class":214},[200,111005,2089],{"class":210},[200,111007,215],{"class":214},[200,111009,51054],{"class":223},[200,111011,1532],{"class":210},[200,111013,252],{"class":214},[200,111015,111016,111018,111020,111022,111024,111026,111028,111030,111032,111034,111036,111038,111040,111042,111044,111046,111048,111050,111052,111054],{"class":202,"line":371},[200,111017,53306],{"class":235},[200,111019,239],{"class":214},[200,111021,50998],{"class":223},[200,111023,227],{"class":210},[200,111025,249],{"class":214},[200,111027,51756],{"class":245},[200,111029,249],{"class":214},[200,111031,2089],{"class":210},[200,111033,215],{"class":214},[200,111035,51054],{"class":223},[200,111037,1532],{"class":210},[200,111039,215],{"class":214},[200,111041,53331],{"class":223},[200,111043,227],{"class":210},[200,111045,1532],{"class":214},[200,111047,393],{"class":379},[200,111049,49163],{"class":210},[200,111051,215],{"class":214},[200,111053,53344],{"class":210},[200,111055,252],{"class":214},[200,111057,111058,111060,111062,111064,111066,111068,111070,111072,111074,111076,111078,111080,111082,111084,111086,111088,111090,111092,111094,111096],{"class":202,"line":398},[200,111059,53351],{"class":235},[200,111061,239],{"class":214},[200,111063,50998],{"class":223},[200,111065,227],{"class":210},[200,111067,249],{"class":214},[200,111069,53362],{"class":245},[200,111071,249],{"class":214},[200,111073,2089],{"class":210},[200,111075,215],{"class":214},[200,111077,51054],{"class":223},[200,111079,1532],{"class":210},[200,111081,215],{"class":214},[200,111083,53331],{"class":223},[200,111085,227],{"class":210},[200,111087,1532],{"class":214},[200,111089,393],{"class":379},[200,111091,47501],{"class":210},[200,111093,215],{"class":214},[200,111095,53344],{"class":210},[200,111097,252],{"class":214},[200,111099,111100,111102],{"class":202,"line":405},[200,111101,413],{"class":214},[200,111103,416],{"class":210},[35,111105,419,111106,53401,111108,53405],{},[156,111107,53003],{},[156,111109,53404],{},[35,111111,53408,111112,53411,111114,53414,111116,53418,111118,53421,111120,19487],{},[156,111113,47325],{},[156,111115,51759],{},[156,111117,53417],{},[156,111119,3280],{},[156,111121,47325],{},[35,111123,419,111124,53429,111126,53421,111128,53434],{},[156,111125,53428],{},[156,111127,3280],{},[156,111129,47331],{},[35,111131,53437],{},[192,111133,111134],{"className":1370,"code":53440,"language":1373,"meta":13,"style":13},[156,111135,111136,111164,111176,111182,111186,111214,111226],{"__ignoreMap":13},[200,111137,111138,111140,111142,111144,111146,111148,111150,111152,111154,111156,111158,111160,111162],{"class":202,"line":203},[200,111139,1380],{"class":206},[200,111141,14301],{"class":379},[200,111143,53451],{"class":210},[200,111145,449],{"class":214},[200,111147,53456],{"class":223},[200,111149,53459],{"class":210},[200,111151,550],{"class":214},[200,111153,383],{"class":214},[200,111155,53466],{"class":386},[200,111157,390],{"class":214},[200,111159,393],{"class":379},[200,111161,1095],{"class":210},[200,111163,230],{"class":214},[200,111165,111166,111168,111170,111172,111174],{"class":202,"line":14},[200,111167,53479],{"class":235},[200,111169,239],{"class":214},[200,111171,53466],{"class":223},[200,111173,53486],{"class":210},[200,111175,252],{"class":214},[200,111177,111178,111180],{"class":202,"line":255},[200,111179,413],{"class":214},[200,111181,585],{"class":210},[200,111183,111184],{"class":202,"line":272},[200,111185,529],{"emptyLinePlaceholder":20},[200,111187,111188,111190,111192,111194,111196,111198,111200,111202,111204,111206,111208,111210,111212],{"class":202,"line":283},[200,111189,1380],{"class":206},[200,111191,14301],{"class":379},[200,111193,53507],{"class":210},[200,111195,449],{"class":214},[200,111197,53456],{"class":223},[200,111199,53514],{"class":210},[200,111201,550],{"class":214},[200,111203,383],{"class":214},[200,111205,53466],{"class":386},[200,111207,390],{"class":214},[200,111209,393],{"class":379},[200,111211,1095],{"class":210},[200,111213,230],{"class":214},[200,111215,111216,111218,111220,111222,111224],{"class":202,"line":300},[200,111217,53479],{"class":235},[200,111219,239],{"class":214},[200,111221,53466],{"class":223},[200,111223,53486],{"class":210},[200,111225,252],{"class":214},[200,111227,111228,111230],{"class":202,"line":310},[200,111229,413],{"class":214},[200,111231,585],{"class":210},[1913,111233,53550],{"id":53549},[35,111235,53553],{},[35,111237,53556,111238,53560],{},[156,111239,53559],{},[192,111241,111242],{"className":1370,"code":53563,"language":1373,"meta":13,"style":13},[156,111243,111244,111278,111282,111304,111326,111330,111354,111366,111394,111406,111424,111436,111458,111470,111474,111504,111516,111536,111550,111564,111578,111592,111606,111620,111634,111646,111650,111666],{"__ignoreMap":13},[200,111245,111246,111248,111250,111252,111254,111256,111258,111260,111262,111264,111266,111268,111270,111272,111274,111276],{"class":202,"line":203},[200,111247,973],{"class":206},[200,111249,318],{"class":214},[200,111251,20132],{"class":210},[200,111253,550],{"class":214},[200,111255,42872],{"class":210},[200,111257,550],{"class":214},[200,111259,22708],{"class":210},[200,111261,550],{"class":214},[200,111263,51122],{"class":210},[200,111265,14674],{"class":206},[200,111267,53590],{"class":210},[200,111269,981],{"class":214},[200,111271,984],{"class":206},[200,111273,242],{"class":214},[200,111275,19790],{"class":245},[200,111277,992],{"class":214},[200,111279,111280],{"class":202,"line":14},[200,111281,529],{"emptyLinePlaceholder":20},[200,111283,111284,111286,111288,111290,111292,111294,111296,111298,111300,111302],{"class":202,"line":255},[200,111285,1380],{"class":206},[200,111287,1383],{"class":206},[200,111289,38960],{"class":223},[200,111291,227],{"class":210},[200,111293,3711],{"class":379},[200,111295,1095],{"class":214},[200,111297,25386],{"class":386},[200,111299,2089],{"class":214},[200,111301,393],{"class":379},[200,111303,280],{"class":214},[200,111305,111306,111308,111310,111312,111314,111316,111318,111320,111322,111324],{"class":202,"line":272},[200,111307,3723],{"class":379},[200,111309,318],{"class":214},[200,111311,34776],{"class":210},[200,111313,981],{"class":214},[200,111315,510],{"class":214},[200,111317,452],{"class":206},[200,111319,39235],{"class":223},[200,111321,227],{"class":235},[200,111323,25386],{"class":210},[200,111325,416],{"class":235},[200,111327,111328],{"class":202,"line":283},[200,111329,529],{"emptyLinePlaceholder":20},[200,111331,111332,111334,111336,111338,111340,111342,111344,111346,111348,111350,111352],{"class":202,"line":300},[200,111333,3723],{"class":379},[200,111335,41038],{"class":210},[200,111337,510],{"class":214},[200,111339,452],{"class":206},[200,111341,41180],{"class":223},[200,111343,227],{"class":235},[200,111345,25386],{"class":210},[200,111347,550],{"class":214},[200,111349,42872],{"class":223},[200,111351,227],{"class":235},[200,111353,230],{"class":214},[200,111355,111356,111358,111360,111362,111364],{"class":202,"line":310},[200,111357,21442],{"class":235},[200,111359,239],{"class":214},[200,111361,22708],{"class":223},[200,111363,1532],{"class":235},[200,111365,252],{"class":214},[200,111367,111368,111370,111372,111374,111376,111378,111380,111382,111384,111386,111388,111390,111392],{"class":202,"line":336},[200,111369,53693],{"class":235},[200,111371,239],{"class":214},[200,111373,53590],{"class":223},[200,111375,25183],{"class":235},[200,111377,249],{"class":214},[200,111379,38206],{"class":245},[200,111381,249],{"class":214},[200,111383,550],{"class":214},[200,111385,242],{"class":214},[200,111387,53195],{"class":245},[200,111389,249],{"class":214},[200,111391,32457],{"class":235},[200,111393,252],{"class":214},[200,111395,111396,111398,111400,111402,111404],{"class":202,"line":342},[200,111397,53722],{"class":235},[200,111399,239],{"class":214},[200,111401,20132],{"class":223},[200,111403,1532],{"class":235},[200,111405,252],{"class":214},[200,111407,111408,111410,111412,111414,111416,111418,111420,111422],{"class":202,"line":365},[200,111409,53735],{"class":235},[200,111411,239],{"class":214},[200,111413,22708],{"class":223},[200,111415,1532],{"class":235},[200,111417,215],{"class":214},[200,111419,53746],{"class":223},[200,111421,1532],{"class":235},[200,111423,252],{"class":214},[200,111425,111426,111428,111430,111432,111434],{"class":202,"line":371},[200,111427,53755],{"class":235},[200,111429,239],{"class":214},[200,111431,22708],{"class":223},[200,111433,1532],{"class":235},[200,111435,252],{"class":214},[200,111437,111438,111440,111442,111444,111446,111448,111450,111452,111454,111456],{"class":202,"line":398},[200,111439,3136],{"class":235},[200,111441,239],{"class":214},[200,111443,22708],{"class":223},[200,111445,1532],{"class":235},[200,111447,215],{"class":214},[200,111449,4123],{"class":223},[200,111451,227],{"class":235},[200,111453,53782],{"class":2045},[200,111455,2089],{"class":235},[200,111457,252],{"class":214},[200,111459,111460,111462,111464,111466,111468],{"class":202,"line":405},[200,111461,5080],{"class":214},[200,111463,2089],{"class":235},[200,111465,215],{"class":214},[200,111467,41223],{"class":210},[200,111469,416],{"class":235},[200,111471,111472],{"class":202,"line":410},[200,111473,529],{"emptyLinePlaceholder":20},[200,111475,111476,111478,111480,111482,111484,111486,111488,111490,111492,111494,111496,111498,111500,111502],{"class":202,"line":861},[200,111477,6165],{"class":206},[200,111479,47508],{"class":223},[200,111481,1532],{"class":235},[200,111483,215],{"class":214},[200,111485,47696],{"class":223},[200,111487,227],{"class":235},[200,111489,47588],{"class":210},[200,111491,215],{"class":214},[200,111493,51759],{"class":210},[200,111495,2089],{"class":235},[200,111497,215],{"class":214},[200,111499,47711],{"class":223},[200,111501,227],{"class":235},[200,111503,230],{"class":214},[200,111505,111506,111508,111510,111512,111514],{"class":202,"line":890},[200,111507,53837],{"class":235},[200,111509,239],{"class":214},[200,111511,53842],{"class":223},[200,111513,1532],{"class":235},[200,111515,252],{"class":214},[200,111517,111518,111520,111522,111524,111526,111528,111530,111532,111534],{"class":202,"line":900},[200,111519,47542],{"class":235},[200,111521,239],{"class":214},[200,111523,47724],{"class":223},[200,111525,227],{"class":235},[200,111527,23870],{"class":210},[200,111529,215],{"class":214},[200,111531,3566],{"class":210},[200,111533,2089],{"class":235},[200,111535,252],{"class":214},[200,111537,111538,111540,111542,111544,111546,111548],{"class":202,"line":905},[200,111539,21442],{"class":235},[200,111541,239],{"class":214},[200,111543,41038],{"class":210},[200,111545,215],{"class":214},[200,111547,3566],{"class":210},[200,111549,252],{"class":214},[200,111551,111552,111554,111556,111558,111560,111562],{"class":202,"line":931},[200,111553,53722],{"class":235},[200,111555,239],{"class":214},[200,111557,41038],{"class":210},[200,111559,215],{"class":214},[200,111561,53428],{"class":210},[200,111563,252],{"class":214},[200,111565,111566,111568,111570,111572,111574,111576],{"class":202,"line":3640},[200,111567,53693],{"class":235},[200,111569,239],{"class":214},[200,111571,41038],{"class":210},[200,111573,215],{"class":214},[200,111575,53907],{"class":210},[200,111577,252],{"class":214},[200,111579,111580,111582,111584,111586,111588,111590],{"class":202,"line":3645},[200,111581,53735],{"class":235},[200,111583,239],{"class":214},[200,111585,41038],{"class":210},[200,111587,215],{"class":214},[200,111589,53922],{"class":210},[200,111591,252],{"class":214},[200,111593,111594,111596,111598,111600,111602,111604],{"class":202,"line":4020},[200,111595,53755],{"class":235},[200,111597,239],{"class":214},[200,111599,41038],{"class":210},[200,111601,215],{"class":214},[200,111603,53937],{"class":210},[200,111605,252],{"class":214},[200,111607,111608,111610,111612,111614,111616,111618],{"class":202,"line":4475},[200,111609,3136],{"class":235},[200,111611,239],{"class":214},[200,111613,41038],{"class":210},[200,111615,215],{"class":214},[200,111617,3582],{"class":210},[200,111619,252],{"class":214},[200,111621,111622,111624,111626,111628,111630,111632],{"class":202,"line":4481},[200,111623,53958],{"class":235},[200,111625,239],{"class":214},[200,111627,34776],{"class":210},[200,111629,215],{"class":214},[200,111631,3280],{"class":210},[200,111633,252],{"class":214},[200,111635,111636,111638,111640,111642,111644],{"class":202,"line":4487},[200,111637,5080],{"class":214},[200,111639,2089],{"class":235},[200,111641,215],{"class":214},[200,111643,47761],{"class":223},[200,111645,466],{"class":235},[200,111647,111648],{"class":202,"line":4493},[200,111649,529],{"emptyLinePlaceholder":20},[200,111651,111652,111654,111656,111658,111660,111662,111664],{"class":202,"line":5454},[200,111653,3743],{"class":206},[200,111655,41071],{"class":223},[200,111657,227],{"class":235},[200,111659,25386],{"class":210},[200,111661,550],{"class":214},[200,111663,41080],{"class":2045},[200,111665,416],{"class":235},[200,111667,111668,111670],{"class":202,"line":5459},[200,111669,413],{"class":214},[200,111671,416],{"class":210},[35,111673,54009,111674,54013,111676,54016,111678,54019,111680,54022],{},[6850,111675,54012],{},[156,111677,39197],{},[156,111679,53417],{},[156,111681,51759],{},[35,111683,419,111684,54028,111686,215],{},[156,111685,54027],{},[156,111687,54031],{},[1913,111689,54035],{"id":54034},[35,111691,111692],{},[1217,111693,54040],{},[35,111695,54043,111696,54047,111698],{},[1217,111697,54046],{},[1217,111699,54050],{},[35,111701,54053,111702,239],{},[156,111703,54056],{},[192,111705,111706],{"className":1370,"code":54059,"language":1373,"meta":13,"style":13},[156,111707,111708,111726,111740,111748,111764,111808,111852,111856,111862],{"__ignoreMap":13},[200,111709,111710,111712,111714,111716,111718,111720,111722,111724],{"class":202,"line":203},[200,111711,1380],{"class":206},[200,111713,1383],{"class":206},[200,111715,38960],{"class":223},[200,111717,227],{"class":210},[200,111719,3711],{"class":379},[200,111721,3714],{"class":214},[200,111723,393],{"class":379},[200,111725,280],{"class":214},[200,111727,111728,111730,111732,111734,111736,111738],{"class":202,"line":14},[200,111729,3723],{"class":379},[200,111731,54086],{"class":210},[200,111733,510],{"class":214},[200,111735,452],{"class":206},[200,111737,47508],{"class":223},[200,111739,466],{"class":235},[200,111741,111742,111744,111746],{"class":202,"line":255},[200,111743,3755],{"class":214},[200,111745,770],{"class":223},[200,111747,466],{"class":235},[200,111749,111750,111752,111754,111756,111758,111760,111762],{"class":202,"line":272},[200,111751,3755],{"class":214},[200,111753,1472],{"class":223},[200,111755,227],{"class":235},[200,111757,47588],{"class":210},[200,111759,215],{"class":214},[200,111761,51759],{"class":210},[200,111763,416],{"class":235},[200,111765,111766,111768,111770,111772,111774,111776,111778,111780,111782,111784,111786,111788,111790,111792,111794,111796,111798,111800,111802,111804,111806],{"class":202,"line":283},[200,111767,3755],{"class":214},[200,111769,54125],{"class":223},[200,111771,227],{"class":235},[200,111773,47588],{"class":210},[200,111775,215],{"class":214},[200,111777,47325],{"class":210},[200,111779,550],{"class":214},[200,111781,54138],{"class":223},[200,111783,227],{"class":235},[200,111785,47588],{"class":210},[200,111787,215],{"class":214},[200,111789,51759],{"class":210},[200,111791,215],{"class":214},[200,111793,53417],{"class":210},[200,111795,550],{"class":214},[200,111797,47527],{"class":210},[200,111799,215],{"class":214},[200,111801,47325],{"class":210},[200,111803,215],{"class":214},[200,111805,3280],{"class":210},[200,111807,585],{"class":235},[200,111809,111810,111812,111814,111816,111818,111820,111822,111824,111826,111828,111830,111832,111834,111836,111838,111840,111842,111844,111846,111848,111850],{"class":202,"line":300},[200,111811,3755],{"class":214},[200,111813,54125],{"class":223},[200,111815,227],{"class":235},[200,111817,47588],{"class":210},[200,111819,215],{"class":214},[200,111821,47331],{"class":210},[200,111823,550],{"class":214},[200,111825,54138],{"class":223},[200,111827,227],{"class":235},[200,111829,47588],{"class":210},[200,111831,215],{"class":214},[200,111833,51759],{"class":210},[200,111835,215],{"class":214},[200,111837,53428],{"class":210},[200,111839,550],{"class":214},[200,111841,47527],{"class":210},[200,111843,215],{"class":214},[200,111845,47331],{"class":210},[200,111847,215],{"class":214},[200,111849,3280],{"class":210},[200,111851,585],{"class":235},[200,111853,111854],{"class":202,"line":310},[200,111855,529],{"emptyLinePlaceholder":20},[200,111857,111858,111860],{"class":202,"line":336},[200,111859,3743],{"class":206},[200,111861,54219],{"class":210},[200,111863,111864,111866],{"class":202,"line":342},[200,111865,413],{"class":214},[200,111867,416],{"class":210},[35,111869,54228],{},[35,111871,47322,111872,54233,111874,54236,111876,53411,111878,54241,111880,54244,111882,54247,111884,13731,111886,19487],{},[156,111873,6190],{},[156,111875,51759],{},[156,111877,47325],{},[156,111879,47331],{},[156,111881,54125],{},[156,111883,51759],{},[156,111885,47325],{},[156,111887,47331],{},[35,111889,111890],{},[1690,111891],{"alt":54256,"src":54257},[35,111893,54260,111894,54263],{},[156,111895,48162],{},[192,111897,111898],{"className":21417,"code":54266,"language":12060,"meta":13,"style":13},[156,111899,111900,111920,111932,111946,111960,111966,111980,111994,112004,112010,112024,112038,112048,112054,112068,112082,112092,112098,112112,112126,112132,112146,112160,112166,112180,112194,112200,112214,112228,112234,112248,112262,112268,112272,112312,112322,112332,112346,112352,112360,112364,112372,112378,112392,112406,112420,112424,112448,112454,112470,112482,112494,112512,112524,112528,112544,112548,112566,112574,112592,112600,112624,112630,112642,112654,112672,112684,112688,112704,112708,112726,112734,112742,112766,112794,112800,112812,112824,112828,112832,112840,112848,112876,112882,112894,112906,112910,112914,112922,112930,112938,112962,112980,112986,112998,113010,113022,113026,113042,113050,113058,113082,113086,113094,113102],{"__ignoreMap":13},[200,111901,111902,111904,111906,111908,111910,111912,111914,111916,111918],{"class":202,"line":203},[200,111903,600],{"class":214},[200,111905,14892],{"class":235},[200,111907,14590],{"class":379},[200,111909,449],{"class":214},[200,111911,611],{"class":214},[200,111913,1373],{"class":245},[200,111915,611],{"class":214},[200,111917,21781],{"class":379},[200,111919,648],{"class":214},[200,111921,111922,111924,111926,111928,111930],{"class":202,"line":14},[200,111923,443],{"class":379},[200,111925,47958],{"class":210},[200,111927,449],{"class":214},[200,111929,350],{"class":210},[200,111931,230],{"class":214},[200,111933,111934,111936,111938,111940,111942,111944],{"class":202,"line":255},[200,111935,18962],{"class":235},[200,111937,239],{"class":214},[200,111939,242],{"class":214},[200,111941,3280],{"class":245},[200,111943,249],{"class":214},[200,111945,252],{"class":214},[200,111947,111948,111950,111952,111954,111956,111958],{"class":202,"line":272},[200,111949,47983],{"class":235},[200,111951,239],{"class":214},[200,111953,242],{"class":214},[200,111955,2886],{"class":245},[200,111957,249],{"class":214},[200,111959,252],{"class":214},[200,111961,111962,111964],{"class":202,"line":283},[200,111963,26858],{"class":214},[200,111965,280],{"class":214},[200,111967,111968,111970,111972,111974,111976,111978],{"class":202,"line":300},[200,111969,18962],{"class":235},[200,111971,239],{"class":214},[200,111973,242],{"class":214},[200,111975,3566],{"class":245},[200,111977,249],{"class":214},[200,111979,252],{"class":214},[200,111981,111982,111984,111986,111988,111990,111992],{"class":202,"line":310},[200,111983,47983],{"class":235},[200,111985,239],{"class":214},[200,111987,242],{"class":214},[200,111989,54359],{"class":245},[200,111991,249],{"class":214},[200,111993,252],{"class":214},[200,111995,111996,111998,112000,112002],{"class":202,"line":336},[200,111997,48030],{"class":235},[200,111999,239],{"class":214},[200,112001,2424],{"class":2423},[200,112003,252],{"class":214},[200,112005,112006,112008],{"class":202,"line":342},[200,112007,26858],{"class":214},[200,112009,280],{"class":214},[200,112011,112012,112014,112016,112018,112020,112022],{"class":202,"line":365},[200,112013,18962],{"class":235},[200,112015,239],{"class":214},[200,112017,242],{"class":214},[200,112019,39751],{"class":245},[200,112021,249],{"class":214},[200,112023,252],{"class":214},[200,112025,112026,112028,112030,112032,112034,112036],{"class":202,"line":371},[200,112027,47983],{"class":235},[200,112029,239],{"class":214},[200,112031,242],{"class":214},[200,112033,54404],{"class":245},[200,112035,249],{"class":214},[200,112037,252],{"class":214},[200,112039,112040,112042,112044,112046],{"class":202,"line":398},[200,112041,48030],{"class":235},[200,112043,239],{"class":214},[200,112045,2424],{"class":2423},[200,112047,252],{"class":214},[200,112049,112050,112052],{"class":202,"line":405},[200,112051,26858],{"class":214},[200,112053,280],{"class":214},[200,112055,112056,112058,112060,112062,112064,112066],{"class":202,"line":410},[200,112057,18962],{"class":235},[200,112059,239],{"class":214},[200,112061,242],{"class":214},[200,112063,53907],{"class":245},[200,112065,249],{"class":214},[200,112067,252],{"class":214},[200,112069,112070,112072,112074,112076,112078,112080],{"class":202,"line":861},[200,112071,47983],{"class":235},[200,112073,239],{"class":214},[200,112075,242],{"class":214},[200,112077,54449],{"class":245},[200,112079,249],{"class":214},[200,112081,252],{"class":214},[200,112083,112084,112086,112088,112090],{"class":202,"line":890},[200,112085,48030],{"class":235},[200,112087,239],{"class":214},[200,112089,2424],{"class":2423},[200,112091,252],{"class":214},[200,112093,112094,112096],{"class":202,"line":900},[200,112095,26858],{"class":214},[200,112097,280],{"class":214},[200,112099,112100,112102,112104,112106,112108,112110],{"class":202,"line":905},[200,112101,18962],{"class":235},[200,112103,239],{"class":214},[200,112105,242],{"class":214},[200,112107,53922],{"class":245},[200,112109,249],{"class":214},[200,112111,252],{"class":214},[200,112113,112114,112116,112118,112120,112122,112124],{"class":202,"line":931},[200,112115,47983],{"class":235},[200,112117,239],{"class":214},[200,112119,242],{"class":214},[200,112121,54494],{"class":245},[200,112123,249],{"class":214},[200,112125,252],{"class":214},[200,112127,112128,112130],{"class":202,"line":3640},[200,112129,26858],{"class":214},[200,112131,280],{"class":214},[200,112133,112134,112136,112138,112140,112142,112144],{"class":202,"line":3645},[200,112135,18962],{"class":235},[200,112137,239],{"class":214},[200,112139,242],{"class":214},[200,112141,53937],{"class":245},[200,112143,249],{"class":214},[200,112145,252],{"class":214},[200,112147,112148,112150,112152,112154,112156,112158],{"class":202,"line":4020},[200,112149,47983],{"class":235},[200,112151,239],{"class":214},[200,112153,242],{"class":214},[200,112155,54529],{"class":245},[200,112157,249],{"class":214},[200,112159,252],{"class":214},[200,112161,112162,112164],{"class":202,"line":4475},[200,112163,26858],{"class":214},[200,112165,280],{"class":214},[200,112167,112168,112170,112172,112174,112176,112178],{"class":202,"line":4481},[200,112169,18962],{"class":235},[200,112171,239],{"class":214},[200,112173,242],{"class":214},[200,112175,3582],{"class":245},[200,112177,249],{"class":214},[200,112179,252],{"class":214},[200,112181,112182,112184,112186,112188,112190,112192],{"class":202,"line":4487},[200,112183,47983],{"class":235},[200,112185,239],{"class":214},[200,112187,242],{"class":214},[200,112189,54564],{"class":245},[200,112191,249],{"class":214},[200,112193,252],{"class":214},[200,112195,112196,112198],{"class":202,"line":4493},[200,112197,26858],{"class":214},[200,112199,280],{"class":214},[200,112201,112202,112204,112206,112208,112210,112212],{"class":202,"line":5454},[200,112203,18962],{"class":235},[200,112205,239],{"class":214},[200,112207,242],{"class":214},[200,112209,54585],{"class":245},[200,112211,249],{"class":214},[200,112213,252],{"class":214},[200,112215,112216,112218,112220,112222,112224,112226],{"class":202,"line":5459},[200,112217,47983],{"class":235},[200,112219,239],{"class":214},[200,112221,242],{"class":214},[200,112223,54600],{"class":245},[200,112225,249],{"class":214},[200,112227,252],{"class":214},[200,112229,112230,112232],{"class":202,"line":13227},[200,112231,26858],{"class":214},[200,112233,280],{"class":214},[200,112235,112236,112238,112240,112242,112244,112246],{"class":202,"line":13253},[200,112237,18962],{"class":235},[200,112239,239],{"class":214},[200,112241,242],{"class":214},[200,112243,54621],{"class":245},[200,112245,249],{"class":214},[200,112247,252],{"class":214},[200,112249,112250,112252,112254,112256,112258,112260],{"class":202,"line":13269},[200,112251,47983],{"class":235},[200,112253,239],{"class":214},[200,112255,242],{"class":214},[200,112257,54636],{"class":245},[200,112259,249],{"class":214},[200,112261,252],{"class":214},[200,112263,112264,112266],{"class":202,"line":13313},[200,112265,413],{"class":214},[200,112267,4496],{"class":210},[200,112269,112270],{"class":202,"line":13319},[200,112271,529],{"emptyLinePlaceholder":20},[200,112273,112274,112276,112278,112280,112282,112284,112286,112288,112290,112292,112294,112296,112298,112300,112302,112304,112306,112308,112310],{"class":202,"line":13324},[200,112275,443],{"class":379},[200,112277,318],{"class":214},[200,112279,5041],{"class":235},[200,112281,239],{"class":214},[200,112283,54086],{"class":210},[200,112285,550],{"class":214},[200,112287,54667],{"class":210},[200,112289,550],{"class":214},[200,112291,48096],{"class":210},[200,112293,413],{"class":214},[200,112295,510],{"class":214},[200,112297,452],{"class":206},[200,112299,48105],{"class":223},[200,112301,227],{"class":210},[200,112303,249],{"class":214},[200,112305,54686],{"class":245},[200,112307,249],{"class":214},[200,112309,550],{"class":214},[200,112311,280],{"class":214},[200,112313,112314,112316,112318,112320],{"class":202,"line":13350},[200,112315,48123],{"class":235},[200,112317,239],{"class":214},[200,112319,16166],{"class":2423},[200,112321,252],{"class":214},[200,112323,112324,112326,112328,112330],{"class":202,"line":13355},[200,112325,50607],{"class":235},[200,112327,239],{"class":214},[200,112329,2424],{"class":2423},[200,112331,252],{"class":214},[200,112333,112334,112336,112338,112340,112342,112344],{"class":202,"line":13375},[200,112335,50621],{"class":223},[200,112337,239],{"class":214},[200,112339,3714],{"class":214},[200,112341,393],{"class":379},[200,112343,27960],{"class":210},[200,112345,252],{"class":214},[200,112347,112348,112350],{"class":202,"line":13395},[200,112349,413],{"class":214},[200,112351,416],{"class":210},[200,112353,112354,112356,112358],{"class":202,"line":13400},[200,112355,681],{"class":214},[200,112357,14892],{"class":235},[200,112359,648],{"class":214},[200,112361,112362],{"class":202,"line":13408},[200,112363,529],{"emptyLinePlaceholder":20},[200,112365,112366,112368,112370],{"class":202,"line":13426},[200,112367,600],{"class":214},[200,112369,21428],{"class":235},[200,112371,648],{"class":214},[200,112373,112374,112376],{"class":202,"line":13456},[200,112375,657],{"class":214},[200,112377,54759],{"class":235},[200,112379,112380,112382,112384,112386,112388,112390],{"class":202,"line":13462},[200,112381,23821],{"class":214},[200,112383,48167],{"class":379},[200,112385,449],{"class":214},[200,112387,611],{"class":214},[200,112389,48167],{"class":210},[200,112391,617],{"class":214},[200,112393,112394,112396,112398,112400,112402,112404],{"class":202,"line":13467},[200,112395,23821],{"class":214},[200,112397,48180],{"class":379},[200,112399,449],{"class":214},[200,112401,611],{"class":214},[200,112403,51759],{"class":210},[200,112405,617],{"class":214},[200,112407,112408,112410,112412,112414,112416,112418],{"class":202,"line":13472},[200,112409,23821],{"class":214},[200,112411,9886],{"class":379},[200,112413,449],{"class":214},[200,112415,611],{"class":214},[200,112417,48199],{"class":210},[200,112419,617],{"class":214},[200,112421,112422],{"class":202,"line":13487},[200,112423,11536],{"class":214},[200,112425,112426,112428,112430,112432,112434,112436,112438,112440,112442,112444,112446],{"class":202,"line":13498},[200,112427,804],{"class":214},[200,112429,21428],{"class":235},[200,112431,23867],{"class":214},[200,112433,54816],{"class":379},[200,112435,449],{"class":214},[200,112437,611],{"class":214},[200,112439,2752],{"class":214},[200,112441,49618],{"class":210},[200,112443,413],{"class":214},[200,112445,611],{"class":214},[200,112447,648],{"class":214},[200,112449,112450,112452],{"class":202,"line":13503},[200,112451,22027],{"class":214},[200,112453,48277],{"class":235},[200,112455,112456,112458,112460,112462,112464,112466,112468],{"class":202,"line":13508},[200,112457,54841],{"class":206},[200,112459,449],{"class":214},[200,112461,611],{"class":214},[200,112463,49656],{"class":210},[200,112465,215],{"class":214},[200,112467,53922],{"class":210},[200,112469,617],{"class":214},[200,112471,112472,112474,112476,112478,112480],{"class":202,"line":20224},[200,112473,54858],{"class":379},[200,112475,449],{"class":214},[200,112477,611],{"class":214},[200,112479,14616],{"class":245},[200,112481,617],{"class":214},[200,112483,112484,112486,112488,112490,112492],{"class":202,"line":20246},[200,112485,52587],{"class":379},[200,112487,449],{"class":214},[200,112489,611],{"class":214},[200,112491,7144],{"class":245},[200,112493,617],{"class":214},[200,112495,112496,112498,112500,112502,112504,112506,112508,112510],{"class":202,"line":20251},[200,112497,54883],{"class":214},[200,112499,54886],{"class":379},[200,112501,449],{"class":214},[200,112503,611],{"class":214},[200,112505,49656],{"class":210},[200,112507,215],{"class":214},[200,112509,53922],{"class":210},[200,112511,617],{"class":214},[200,112513,112514,112516,112518,112520,112522],{"class":202,"line":20256},[200,112515,54903],{"class":379},[200,112517,449],{"class":214},[200,112519,611],{"class":214},[200,112521,54910],{"class":245},[200,112523,617],{"class":214},[200,112525,112526],{"class":202,"line":20261},[200,112527,54917],{"class":214},[200,112529,112530,112532,112534,112536,112538,112540,112542],{"class":202,"line":20282},[200,112531,49602],{"class":214},[200,112533,200],{"class":235},[200,112535,675],{"class":214},[200,112537,54928],{"class":210},[200,112539,681],{"class":214},[200,112541,200],{"class":235},[200,112543,648],{"class":214},[200,112545,112546],{"class":202,"line":20289},[200,112547,529],{"emptyLinePlaceholder":20},[200,112549,112550,112552,112554,112556,112558,112560,112562,112564],{"class":202,"line":20294},[200,112551,49602],{"class":214},[200,112553,200],{"class":235},[200,112555,26173],{"class":379},[200,112557,449],{"class":214},[200,112559,611],{"class":214},[200,112561,54953],{"class":245},[200,112563,611],{"class":214},[200,112565,21487],{"class":214},[200,112567,112568,112570,112572],{"class":202,"line":20310},[200,112569,43992],{"class":214},[200,112571,48770],{"class":235},[200,112573,648],{"class":214},[200,112575,112576,112578,112580,112582,112584,112586,112588,112590],{"class":202,"line":47145},[200,112577,22027],{"class":214},[200,112579,200],{"class":235},[200,112581,54974],{"class":379},[200,112583,675],{"class":214},[200,112585,54979],{"class":210},[200,112587,681],{"class":214},[200,112589,200],{"class":235},[200,112591,648],{"class":214},[200,112593,112594,112596,112598],{"class":202,"line":47150},[200,112595,15074],{"class":214},[200,112597,21428],{"class":235},[200,112599,648],{"class":214},[200,112601,112602,112604,112606,112608,112610,112612,112614,112616,112618,112620,112622],{"class":202,"line":47157},[200,112603,804],{"class":214},[200,112605,21428],{"class":235},[200,112607,23867],{"class":214},[200,112609,55004],{"class":379},[200,112611,449],{"class":214},[200,112613,611],{"class":214},[200,112615,2752],{"class":214},[200,112617,49618],{"class":210},[200,112619,413],{"class":214},[200,112621,611],{"class":214},[200,112623,648],{"class":214},[200,112625,112626,112628],{"class":202,"line":47162},[200,112627,22027],{"class":214},[200,112629,48277],{"class":235},[200,112631,112632,112634,112636,112638,112640],{"class":202,"line":47167},[200,112633,54858],{"class":379},[200,112635,449],{"class":214},[200,112637,611],{"class":214},[200,112639,14616],{"class":245},[200,112641,617],{"class":214},[200,112643,112644,112646,112648,112650,112652],{"class":202,"line":47173},[200,112645,52587],{"class":379},[200,112647,449],{"class":214},[200,112649,611],{"class":214},[200,112651,7144],{"class":245},[200,112653,617],{"class":214},[200,112655,112656,112658,112660,112662,112664,112666,112668,112670],{"class":202,"line":47179},[200,112657,54883],{"class":214},[200,112659,54886],{"class":379},[200,112661,449],{"class":214},[200,112663,611],{"class":214},[200,112665,49656],{"class":210},[200,112667,215],{"class":214},[200,112669,53937],{"class":210},[200,112671,617],{"class":214},[200,112673,112674,112676,112678,112680,112682],{"class":202,"line":47185},[200,112675,54903],{"class":379},[200,112677,449],{"class":214},[200,112679,611],{"class":214},[200,112681,54910],{"class":245},[200,112683,617],{"class":214},[200,112685,112686],{"class":202,"line":47191},[200,112687,54917],{"class":214},[200,112689,112690,112692,112694,112696,112698,112700,112702],{"class":202,"line":47196},[200,112691,49602],{"class":214},[200,112693,200],{"class":235},[200,112695,675],{"class":214},[200,112697,55093],{"class":210},[200,112699,681],{"class":214},[200,112701,200],{"class":235},[200,112703,648],{"class":214},[200,112705,112706],{"class":202,"line":47211},[200,112707,529],{"emptyLinePlaceholder":20},[200,112709,112710,112712,112714,112716,112718,112720,112722,112724],{"class":202,"line":47218},[200,112711,49602],{"class":214},[200,112713,200],{"class":235},[200,112715,26173],{"class":379},[200,112717,449],{"class":214},[200,112719,611],{"class":214},[200,112721,54953],{"class":245},[200,112723,611],{"class":214},[200,112725,21487],{"class":214},[200,112727,112728,112730,112732],{"class":202,"line":47226},[200,112729,43992],{"class":214},[200,112731,48770],{"class":235},[200,112733,648],{"class":214},[200,112735,112736,112738,112740],{"class":202,"line":47233},[200,112737,15074],{"class":214},[200,112739,21428],{"class":235},[200,112741,648],{"class":214},[200,112743,112744,112746,112748,112750,112752,112754,112756,112758,112760,112762,112764],{"class":202,"line":47240},[200,112745,804],{"class":214},[200,112747,21428],{"class":235},[200,112749,23867],{"class":214},[200,112751,55148],{"class":379},[200,112753,449],{"class":214},[200,112755,611],{"class":214},[200,112757,2752],{"class":214},[200,112759,49618],{"class":210},[200,112761,413],{"class":214},[200,112763,611],{"class":214},[200,112765,648],{"class":214},[200,112767,112768,112770,112772,112774,112776,112778,112780,112782,112784,112786,112788,112790,112792],{"class":202,"line":55165},[200,112769,22027],{"class":214},[200,112771,21428],{"class":235},[200,112773,34696],{"class":206},[200,112775,449],{"class":214},[200,112777,611],{"class":214},[200,112779,49656],{"class":210},[200,112781,215],{"class":214},[200,112783,55182],{"class":210},[200,112785,6546],{"class":214},[200,112787,242],{"class":214},[200,112789,38206],{"class":245},[200,112791,14702],{"class":214},[200,112793,648],{"class":214},[200,112795,112796,112798],{"class":202,"line":55195},[200,112797,49602],{"class":214},[200,112799,55200],{"class":235},[200,112801,112802,112804,112806,112808,112810],{"class":202,"line":55203},[200,112803,55206],{"class":379},[200,112805,449],{"class":214},[200,112807,611],{"class":214},[200,112809,55213],{"class":245},[200,112811,617],{"class":214},[200,112813,112814,112816,112818,112820,112822],{"class":202,"line":55218},[200,112815,55221],{"class":379},[200,112817,449],{"class":214},[200,112819,611],{"class":214},[200,112821,49642],{"class":245},[200,112823,617],{"class":214},[200,112825,112826],{"class":202,"line":55232},[200,112827,55235],{"class":214},[200,112829,112830],{"class":202,"line":55238},[200,112831,55241],{"class":210},[200,112833,112834,112836,112838],{"class":202,"line":55244},[200,112835,49710],{"class":214},[200,112837,49632],{"class":235},[200,112839,648],{"class":214},[200,112841,112842,112844,112846],{"class":202,"line":55253},[200,112843,43992],{"class":214},[200,112845,21428],{"class":235},[200,112847,648],{"class":214},[200,112849,112850,112852,112854,112856,112858,112860,112862,112864,112866,112868,112870,112872,112874],{"class":202,"line":55262},[200,112851,22027],{"class":214},[200,112853,21428],{"class":235},[200,112855,55269],{"class":206},[200,112857,449],{"class":214},[200,112859,611],{"class":214},[200,112861,49656],{"class":210},[200,112863,215],{"class":214},[200,112865,55182],{"class":210},[200,112867,6546],{"class":214},[200,112869,242],{"class":214},[200,112871,53195],{"class":245},[200,112873,14702],{"class":214},[200,112875,648],{"class":214},[200,112877,112878,112880],{"class":202,"line":55292},[200,112879,49602],{"class":214},[200,112881,55200],{"class":235},[200,112883,112884,112886,112888,112890,112892],{"class":202,"line":55299},[200,112885,55206],{"class":379},[200,112887,449],{"class":214},[200,112889,611],{"class":214},[200,112891,55308],{"class":245},[200,112893,617],{"class":214},[200,112895,112896,112898,112900,112902,112904],{"class":202,"line":55313},[200,112897,55221],{"class":379},[200,112899,449],{"class":214},[200,112901,611],{"class":214},[200,112903,49642],{"class":245},[200,112905,617],{"class":214},[200,112907,112908],{"class":202,"line":55326},[200,112909,55235],{"class":214},[200,112911,112912],{"class":202,"line":55331},[200,112913,55334],{"class":210},[200,112915,112916,112918,112920],{"class":202,"line":55337},[200,112917,49710],{"class":214},[200,112919,49632],{"class":235},[200,112921,648],{"class":214},[200,112923,112924,112926,112928],{"class":202,"line":55346},[200,112925,43992],{"class":214},[200,112927,21428],{"class":235},[200,112929,648],{"class":214},[200,112931,112932,112934,112936],{"class":202,"line":55355},[200,112933,15074],{"class":214},[200,112935,21428],{"class":235},[200,112937,648],{"class":214},[200,112939,112940,112942,112944,112946,112948,112950,112952,112954,112956,112958,112960],{"class":202,"line":55364},[200,112941,804],{"class":214},[200,112943,21428],{"class":235},[200,112945,23867],{"class":214},[200,112947,55373],{"class":379},[200,112949,449],{"class":214},[200,112951,611],{"class":214},[200,112953,2752],{"class":214},[200,112955,49618],{"class":210},[200,112957,413],{"class":214},[200,112959,611],{"class":214},[200,112961,648],{"class":214},[200,112963,112964,112966,112968,112970,112972,112974,112976,112978],{"class":202,"line":55390},[200,112965,22027],{"class":214},[200,112967,11386],{"class":235},[200,112969,26173],{"class":379},[200,112971,449],{"class":214},[200,112973,611],{"class":214},[200,112975,55403],{"class":245},[200,112977,611],{"class":214},[200,112979,648],{"class":214},[200,112981,112982,112984],{"class":202,"line":55410},[200,112983,49602],{"class":214},[200,112985,11449],{"class":235},[200,112987,112988,112990,112992,112994,112996],{"class":202,"line":55417},[200,112989,55420],{"class":379},[200,112991,449],{"class":214},[200,112993,611],{"class":214},[200,112995,55427],{"class":245},[200,112997,617],{"class":214},[200,112999,113000,113002,113004,113006,113008],{"class":202,"line":55432},[200,113001,55435],{"class":379},[200,113003,449],{"class":214},[200,113005,611],{"class":214},[200,113007,55442],{"class":245},[200,113009,617],{"class":214},[200,113011,113012,113014,113016,113018,113020],{"class":202,"line":55447},[200,113013,55450],{"class":379},[200,113015,449],{"class":214},[200,113017,611],{"class":214},[200,113019,55457],{"class":245},[200,113021,617],{"class":214},[200,113023,113024],{"class":202,"line":55462},[200,113025,55235],{"class":214},[200,113027,113028,113030,113032,113034,113036,113038,113040],{"class":202,"line":55467},[200,113029,49602],{"class":214},[200,113031,200],{"class":235},[200,113033,675],{"class":214},[200,113035,55476],{"class":210},[200,113037,681],{"class":214},[200,113039,200],{"class":235},[200,113041,648],{"class":214},[200,113043,113044,113046,113048],{"class":202,"line":55485},[200,113045,43992],{"class":214},[200,113047,11386],{"class":235},[200,113049,648],{"class":214},[200,113051,113052,113054,113056],{"class":202,"line":55494},[200,113053,15074],{"class":214},[200,113055,21428],{"class":235},[200,113057,648],{"class":214},[200,113059,113060,113062,113064,113066,113068,113070,113072,113074,113076,113078,113080],{"class":202,"line":55503},[200,113061,804],{"class":214},[200,113063,21428],{"class":235},[200,113065,23867],{"class":214},[200,113067,55512],{"class":379},[200,113069,449],{"class":214},[200,113071,611],{"class":214},[200,113073,2752],{"class":214},[200,113075,49618],{"class":210},[200,113077,413],{"class":214},[200,113079,611],{"class":214},[200,113081,648],{"class":214},[200,113083,113084],{"class":202,"line":55529},[200,113085,55532],{"class":210},[200,113087,113088,113090,113092],{"class":202,"line":55535},[200,113089,15074],{"class":214},[200,113091,21428],{"class":235},[200,113093,648],{"class":214},[200,113095,113096,113098,113100],{"class":202,"line":55544},[200,113097,893],{"class":214},[200,113099,48162],{"class":235},[200,113101,648],{"class":214},[200,113103,113104,113106,113108],{"class":202,"line":55553},[200,113105,681],{"class":214},[200,113107,21428],{"class":235},[200,113109,648],{"class":214},[35,113111,113112,55565],{},[6850,113113,55564],{},[192,113115,113116],{"className":26986,"code":55568,"language":26988,"meta":13,"style":13},[156,113117,113118,113122,113136,113154,113172,113190,113202,113216,113234,113250,113254,113266,113280,113296,113300],{"__ignoreMap":13},[200,113119,113120],{"class":202,"line":203},[200,113121,230],{"class":214},[200,113123,113124,113126,113128,113130,113132,113134],{"class":202,"line":14},[200,113125,26999],{"class":214},[200,113127,3280],{"class":379},[200,113129,611],{"class":214},[200,113131,239],{"class":214},[200,113133,3938],{"class":2045},[200,113135,252],{"class":214},[200,113137,113138,113140,113142,113144,113146,113148,113150,113152],{"class":202,"line":255},[200,113139,26999],{"class":214},[200,113141,3566],{"class":379},[200,113143,611],{"class":214},[200,113145,239],{"class":214},[200,113147,27008],{"class":214},[200,113149,55603],{"class":245},[200,113151,611],{"class":214},[200,113153,252],{"class":214},[200,113155,113156,113158,113160,113162,113164,113166,113168,113170],{"class":202,"line":272},[200,113157,26999],{"class":214},[200,113159,39751],{"class":379},[200,113161,611],{"class":214},[200,113163,239],{"class":214},[200,113165,27008],{"class":214},[200,113167,53106],{"class":245},[200,113169,611],{"class":214},[200,113171,252],{"class":214},[200,113173,113174,113176,113178,113180,113182,113184,113186,113188],{"class":202,"line":283},[200,113175,26999],{"class":214},[200,113177,53922],{"class":379},[200,113179,611],{"class":214},[200,113181,239],{"class":214},[200,113183,27008],{"class":214},[200,113185,29707],{"class":245},[200,113187,611],{"class":214},[200,113189,252],{"class":214},[200,113191,113192,113194,113196,113198,113200],{"class":202,"line":300},[200,113193,26999],{"class":214},[200,113195,26808],{"class":379},[200,113197,611],{"class":214},[200,113199,239],{"class":214},[200,113201,280],{"class":214},[200,113203,113204,113206,113208,113210,113212,113214],{"class":202,"line":310},[200,113205,27031],{"class":214},[200,113207,3280],{"class":1978},[200,113209,611],{"class":214},[200,113211,239],{"class":214},[200,113213,3938],{"class":2045},[200,113215,252],{"class":214},[200,113217,113218,113220,113222,113224,113226,113228,113230,113232],{"class":202,"line":336},[200,113219,27031],{"class":214},[200,113221,20089],{"class":1978},[200,113223,611],{"class":214},[200,113225,239],{"class":214},[200,113227,27008],{"class":214},[200,113229,34793],{"class":245},[200,113231,611],{"class":214},[200,113233,252],{"class":214},[200,113235,113236,113238,113240,113242,113244,113246,113248],{"class":202,"line":342},[200,113237,27031],{"class":214},[200,113239,55694],{"class":1978},[200,113241,611],{"class":214},[200,113243,239],{"class":214},[200,113245,27008],{"class":214},[200,113247,55703],{"class":245},[200,113249,617],{"class":214},[200,113251,113252],{"class":202,"line":365},[200,113253,368],{"class":214},[200,113255,113256,113258,113260,113262,113264],{"class":202,"line":371},[200,113257,26999],{"class":214},[200,113259,54585],{"class":379},[200,113261,611],{"class":214},[200,113263,239],{"class":214},[200,113265,280],{"class":214},[200,113267,113268,113270,113272,113274,113276,113278],{"class":202,"line":398},[200,113269,27031],{"class":214},[200,113271,3280],{"class":1978},[200,113273,611],{"class":214},[200,113275,239],{"class":214},[200,113277,3938],{"class":2045},[200,113279,252],{"class":214},[200,113281,113282,113284,113286,113288,113290,113292,113294],{"class":202,"line":405},[200,113283,27031],{"class":214},[200,113285,20089],{"class":1978},[200,113287,611],{"class":214},[200,113289,239],{"class":214},[200,113291,27008],{"class":214},[200,113293,55750],{"class":245},[200,113295,617],{"class":214},[200,113297,113298],{"class":202,"line":410},[200,113299,4490],{"class":214},[200,113301,113302],{"class":202,"line":861},[200,113303,1130],{"class":214},[35,113305,55763],{},[192,113307,113308],{"className":26986,"code":55766,"language":26988,"meta":13,"style":13},[156,113309,113310,113314,113326,113340,113358,113376,113392,113396,113408,113412,113424,113428],{"__ignoreMap":13},[200,113311,113312],{"class":202,"line":203},[200,113313,230],{"class":214},[200,113315,113316,113318,113320,113322,113324],{"class":202,"line":14},[200,113317,26999],{"class":214},[200,113319,21428],{"class":379},[200,113321,611],{"class":214},[200,113323,239],{"class":214},[200,113325,280],{"class":214},[200,113327,113328,113330,113332,113334,113336,113338],{"class":202,"line":255},[200,113329,27031],{"class":214},[200,113331,3280],{"class":1978},[200,113333,611],{"class":214},[200,113335,239],{"class":214},[200,113337,3938],{"class":2045},[200,113339,252],{"class":214},[200,113341,113342,113344,113346,113348,113350,113352,113354,113356],{"class":202,"line":272},[200,113343,27031],{"class":214},[200,113345,3566],{"class":1978},[200,113347,611],{"class":214},[200,113349,239],{"class":214},[200,113351,27008],{"class":214},[200,113353,55603],{"class":245},[200,113355,611],{"class":214},[200,113357,252],{"class":214},[200,113359,113360,113362,113364,113366,113368,113370,113372,113374],{"class":202,"line":283},[200,113361,27031],{"class":214},[200,113363,39751],{"class":1978},[200,113365,611],{"class":214},[200,113367,239],{"class":214},[200,113369,27008],{"class":214},[200,113371,53106],{"class":245},[200,113373,611],{"class":214},[200,113375,252],{"class":214},[200,113377,113378,113380,113382,113384,113386,113388,113390],{"class":202,"line":300},[200,113379,27031],{"class":214},[200,113381,53922],{"class":1978},[200,113383,611],{"class":214},[200,113385,239],{"class":214},[200,113387,27008],{"class":214},[200,113389,29707],{"class":245},[200,113391,617],{"class":214},[200,113393,113394],{"class":202,"line":310},[200,113395,368],{"class":214},[200,113397,113398,113400,113402,113404,113406],{"class":202,"line":336},[200,113399,26999],{"class":214},[200,113401,26808],{"class":379},[200,113403,611],{"class":214},[200,113405,239],{"class":214},[200,113407,280],{"class":214},[200,113409,113410],{"class":202,"line":342},[200,113411,368],{"class":214},[200,113413,113414,113416,113418,113420,113422],{"class":202,"line":365},[200,113415,26999],{"class":214},[200,113417,54585],{"class":379},[200,113419,611],{"class":214},[200,113421,239],{"class":214},[200,113423,280],{"class":214},[200,113425,113426],{"class":202,"line":371},[200,113427,4490],{"class":214},[200,113429,113430],{"class":202,"line":398},[200,113431,1130],{"class":214},[35,113433,55893,113434],{},[1217,113435,55896],{},[192,113437,113438],{"className":26986,"code":55899,"language":26988,"meta":13,"style":13},[156,113439,113440,113444,113456,113474,113490,113494,113506,113524,113540,113544],{"__ignoreMap":13},[200,113441,113442],{"class":202,"line":203},[200,113443,230],{"class":214},[200,113445,113446,113448,113450,113452,113454],{"class":202,"line":14},[200,113447,26999],{"class":214},[200,113449,21428],{"class":379},[200,113451,611],{"class":214},[200,113453,239],{"class":214},[200,113455,280],{"class":214},[200,113457,113458,113460,113462,113464,113466,113468,113470,113472],{"class":202,"line":255},[200,113459,27031],{"class":214},[200,113461,3280],{"class":1978},[200,113463,611],{"class":214},[200,113465,239],{"class":214},[200,113467,27008],{"class":214},[200,113469,55603],{"class":245},[200,113471,611],{"class":214},[200,113473,252],{"class":214},[200,113475,113476,113478,113480,113482,113484,113486,113488],{"class":202,"line":272},[200,113477,27031],{"class":214},[200,113479,39751],{"class":1978},[200,113481,611],{"class":214},[200,113483,239],{"class":214},[200,113485,27008],{"class":214},[200,113487,29707],{"class":245},[200,113489,617],{"class":214},[200,113491,113492],{"class":202,"line":283},[200,113493,368],{"class":214},[200,113495,113496,113498,113500,113502,113504],{"class":202,"line":300},[200,113497,26999],{"class":214},[200,113499,26808],{"class":379},[200,113501,611],{"class":214},[200,113503,239],{"class":214},[200,113505,280],{"class":214},[200,113507,113508,113510,113512,113514,113516,113518,113520,113522],{"class":202,"line":310},[200,113509,27031],{"class":214},[200,113511,3280],{"class":1978},[200,113513,611],{"class":214},[200,113515,239],{"class":214},[200,113517,27008],{"class":214},[200,113519,34793],{"class":245},[200,113521,611],{"class":214},[200,113523,252],{"class":214},[200,113525,113526,113528,113530,113532,113534,113536,113538],{"class":202,"line":336},[200,113527,27031],{"class":214},[200,113529,51786],{"class":1978},[200,113531,611],{"class":214},[200,113533,239],{"class":214},[200,113535,27008],{"class":214},[200,113537,56000],{"class":245},[200,113539,617],{"class":214},[200,113541,113542],{"class":202,"line":342},[200,113543,4490],{"class":214},[200,113545,113546],{"class":202,"line":365},[200,113547,1130],{"class":214},[35,113549,56013,113550],{},[1217,113551,56016],{},[35,113553,56019,113554,1638,113557,56028,113560,56033],{},[39,113555,56022],{"href":56022,"rel":113556},[77],[39,113558,56026],{"href":56026,"rel":113559},[77],[56030,113561,56032],{},[192,113563,113564],{"className":1370,"code":56036,"language":1373,"meta":13,"style":13},[156,113565,113566,113588,113606,113624,113642,113660,113678,113696,113714,113722,113740,113758,113776,113780,113788,113832,113836],{"__ignoreMap":13},[200,113567,113568,113570,113572,113574,113576,113578,113580,113582,113584,113586],{"class":202,"line":203},[200,113569,443],{"class":379},[200,113571,52933],{"class":210},[200,113573,449],{"class":214},[200,113575,452],{"class":206},[200,113577,47508],{"class":223},[200,113579,1532],{"class":210},[200,113581,215],{"class":214},[200,113583,770],{"class":223},[200,113585,227],{"class":210},[200,113587,230],{"class":214},[200,113589,113590,113592,113594,113596,113598,113600,113602,113604],{"class":202,"line":14},[200,113591,49799],{"class":235},[200,113593,239],{"class":214},[200,113595,47527],{"class":210},[200,113597,215],{"class":214},[200,113599,51759],{"class":210},[200,113601,215],{"class":214},[200,113603,3280],{"class":210},[200,113605,252],{"class":214},[200,113607,113608,113610,113612,113614,113616,113618,113620,113622],{"class":202,"line":255},[200,113609,23659],{"class":235},[200,113611,239],{"class":214},[200,113613,47527],{"class":210},[200,113615,215],{"class":214},[200,113617,51759],{"class":210},[200,113619,215],{"class":214},[200,113621,3566],{"class":210},[200,113623,252],{"class":214},[200,113625,113626,113628,113630,113632,113634,113636,113638,113640],{"class":202,"line":272},[200,113627,258],{"class":235},[200,113629,239],{"class":214},[200,113631,47527],{"class":210},[200,113633,215],{"class":214},[200,113635,51759],{"class":210},[200,113637,215],{"class":214},[200,113639,3582],{"class":210},[200,113641,252],{"class":214},[200,113643,113644,113646,113648,113650,113652,113654,113656,113658],{"class":202,"line":283},[200,113645,53079],{"class":235},[200,113647,239],{"class":214},[200,113649,47527],{"class":210},[200,113651,215],{"class":214},[200,113653,51759],{"class":210},[200,113655,215],{"class":214},[200,113657,39751],{"class":210},[200,113659,252],{"class":214},[200,113661,113662,113664,113666,113668,113670,113672,113674,113676],{"class":202,"line":300},[200,113663,53159],{"class":235},[200,113665,239],{"class":214},[200,113667,47527],{"class":210},[200,113669,215],{"class":214},[200,113671,51759],{"class":210},[200,113673,215],{"class":214},[200,113675,53907],{"class":210},[200,113677,252],{"class":214},[200,113679,113680,113682,113684,113686,113688,113690,113692,113694],{"class":202,"line":310},[200,113681,53230],{"class":235},[200,113683,239],{"class":214},[200,113685,47527],{"class":210},[200,113687,215],{"class":214},[200,113689,51759],{"class":210},[200,113691,215],{"class":214},[200,113693,53922],{"class":210},[200,113695,252],{"class":214},[200,113697,113698,113700,113702,113704,113706,113708,113710,113712],{"class":202,"line":336},[200,113699,53252],{"class":235},[200,113701,239],{"class":214},[200,113703,47527],{"class":210},[200,113705,215],{"class":214},[200,113707,51759],{"class":210},[200,113709,215],{"class":214},[200,113711,53937],{"class":210},[200,113713,252],{"class":214},[200,113715,113716,113718,113720],{"class":202,"line":342},[200,113717,56191],{"class":235},[200,113719,239],{"class":214},[200,113721,280],{"class":214},[200,113723,113724,113726,113728,113730,113732,113734,113736,113738],{"class":202,"line":365},[200,113725,11797],{"class":235},[200,113727,239],{"class":214},[200,113729,47527],{"class":210},[200,113731,215],{"class":214},[200,113733,47325],{"class":210},[200,113735,215],{"class":214},[200,113737,20089],{"class":210},[200,113739,252],{"class":214},[200,113741,113742,113744,113746,113748,113750,113752,113754,113756],{"class":202,"line":371},[200,113743,56218],{"class":235},[200,113745,239],{"class":214},[200,113747,47527],{"class":210},[200,113749,215],{"class":214},[200,113751,47325],{"class":210},[200,113753,215],{"class":214},[200,113755,51786],{"class":210},[200,113757,252],{"class":214},[200,113759,113760,113762,113764,113766,113768,113770,113772,113774],{"class":202,"line":398},[200,113761,56237],{"class":235},[200,113763,239],{"class":214},[200,113765,47527],{"class":210},[200,113767,215],{"class":214},[200,113769,47325],{"class":210},[200,113771,215],{"class":214},[200,113773,55694],{"class":210},[200,113775,252],{"class":214},[200,113777,113778],{"class":202,"line":405},[200,113779,368],{"class":214},[200,113781,113782,113784,113786],{"class":202,"line":410},[200,113783,56260],{"class":235},[200,113785,239],{"class":214},[200,113787,280],{"class":214},[200,113789,113790,113792,113794,113796,113798,113800,113802,113804,113806,113808,113810,113812,113814,113816,113818,113820,113822,113824,113826,113828,113830],{"class":202,"line":861},[200,113791,11797],{"class":235},[200,113793,239],{"class":214},[200,113795,52124],{"class":223},[200,113797,600],{"class":214},[200,113799,328],{"class":1978},[200,113801,675],{"class":214},[200,113803,3304],{"class":214},[200,113805,47588],{"class":210},[200,113807,215],{"class":214},[200,113809,47331],{"class":210},[200,113811,215],{"class":214},[200,113813,20089],{"class":210},[200,113815,23001],{"class":214},[200,113817,215],{"class":214},[200,113819,44211],{"class":223},[200,113821,227],{"class":210},[200,113823,249],{"class":214},[200,113825,56303],{"class":245},[200,113827,249],{"class":214},[200,113829,2089],{"class":210},[200,113831,252],{"class":214},[200,113833,113834],{"class":202,"line":890},[200,113835,368],{"class":214},[200,113837,113838,113840,113842,113844,113846,113848,113850,113852,113854,113856,113858,113860,113862,113864,113866,113868,113870,113872,113874,113876,113878,113880,113882,113884,113886,113888,113890,113892,113894,113896,113898,113900,113902,113904,113906,113908,113910,113912,113914,113916,113918,113920,113922],{"class":202,"line":900},[200,113839,413],{"class":214},[200,113841,2089],{"class":210},[200,113843,215],{"class":214},[200,113845,1472],{"class":223},[200,113847,51210],{"class":210},[200,113849,215],{"class":214},[200,113851,56330],{"class":210},[200,113853,215],{"class":214},[200,113855,54125],{"class":223},[200,113857,51210],{"class":210},[200,113859,215],{"class":214},[200,113861,47325],{"class":210},[200,113863,550],{"class":214},[200,113865,54138],{"class":223},[200,113867,51210],{"class":210},[200,113869,215],{"class":214},[200,113871,51759],{"class":210},[200,113873,215],{"class":214},[200,113875,53417],{"class":210},[200,113877,550],{"class":214},[200,113879,47527],{"class":210},[200,113881,215],{"class":214},[200,113883,47325],{"class":210},[200,113885,215],{"class":214},[200,113887,56367],{"class":210},[200,113889,215],{"class":214},[200,113891,54125],{"class":223},[200,113893,51210],{"class":210},[200,113895,215],{"class":214},[200,113897,47331],{"class":210},[200,113899,550],{"class":214},[200,113901,54138],{"class":223},[200,113903,51210],{"class":210},[200,113905,215],{"class":214},[200,113907,51759],{"class":210},[200,113909,215],{"class":214},[200,113911,53428],{"class":210},[200,113913,550],{"class":214},[200,113915,47527],{"class":210},[200,113917,215],{"class":214},[200,113919,47331],{"class":210},[200,113921,215],{"class":214},[200,113923,56404],{"class":210},[35,113925,113926,56410,113928,5557,113930,56415,113932,1638,113934,56420],{},[156,113927,56409],{},[156,113929,20089],{},[156,113931,56303],{},[156,113933,26808],{},[156,113935,54585],{},[1913,113937,30681],{"id":56423},[35,113939,56426],{},[35,113941,56429],{},[51,113943,56433],{"id":56432},[35,113945,113946],{},[1690,113947],{"alt":56438,"src":56439},[35,113949,113950],{},[1217,113951,113952,46057,113955,46062,113958,46066,113961,215],{},[39,113953,38639],{"href":46025,"rel":113954},[77],[39,113956,46061],{"href":18567,"rel":113957},[77],[39,113959,19412],{"href":46016,"rel":113960},[77],[39,113962,11622],{"href":46025,"rel":113963},[77],[35,113965,56458],{},[1576,113967,113968,113970,113972],{},[71,113969,56463],{},[71,113971,56466],{},[71,113973,56469],{},[35,113975,56472,113976],{},[1217,113977,56475],{},[1913,113979,56479],{"id":56478},[35,113981,56482,113982,56486,113984,56489,113986,56492,113988,56495,113990,56498,113992,56501,113994,56504],{},[156,113983,56485],{},[156,113985,47555],{},[156,113987,53003],{},[156,113989,47555],{},[156,113991,53003],{},[156,113993,47555],{},[156,113995,53003],{},[35,113997,56507],{},[192,113999,114000],{"className":1370,"code":56510,"language":1373,"meta":13,"style":13},[156,114001,114002,114014],{"__ignoreMap":13},[200,114003,114004,114006,114008,114010,114012],{"class":202,"line":203},[200,114005,443],{"class":379},[200,114007,56519],{"class":210},[200,114009,449],{"class":214},[200,114011,56524],{"class":223},[200,114013,466],{"class":210},[200,114015,114016,114018,114020,114022,114024,114026,114028,114030,114032],{"class":202,"line":14},[200,114017,443],{"class":379},[200,114019,56533],{"class":210},[200,114021,449],{"class":214},[200,114023,56538],{"class":210},[200,114025,215],{"class":214},[200,114027,20084],{"class":210},[200,114029,215],{"class":214},[200,114031,56547],{"class":210},[200,114033,56550],{"class":401},[35,114035,56553,114036,56556,114038,1638,114040,56561,114042,56564,114044,36817,114046,239],{},[156,114037,47555],{},[156,114039,47555],{},[156,114041,53003],{},[156,114043,47555],{},[156,114045,47555],{},[156,114047,53003],{},[192,114049,114050],{"className":1370,"code":56571,"language":1373,"meta":13,"style":13},[156,114051,114052],{"__ignoreMap":13},[200,114053,114054,114056,114058,114060,114062,114064,114066],{"class":202,"line":203},[200,114055,47555],{"class":210},[200,114057,215],{"class":214},[200,114059,3928],{"class":223},[200,114061,227],{"class":210},[200,114063,56586],{"class":214},[200,114065,56589],{"class":2045},[200,114067,416],{"class":210},[35,114069,56594],{},[192,114071,114072],{"className":21417,"code":56597,"language":12060,"meta":13,"style":13},[156,114073,114074,114094,114106,114124,114158,114164,114204,114214,114220,114228,114232,114240,114246,114258,114262,114266,114274],{"__ignoreMap":13},[200,114075,114076,114078,114080,114082,114084,114086,114088,114090,114092],{"class":202,"line":203},[200,114077,600],{"class":214},[200,114079,14892],{"class":235},[200,114081,14590],{"class":379},[200,114083,449],{"class":214},[200,114085,611],{"class":214},[200,114087,1373],{"class":245},[200,114089,611],{"class":214},[200,114091,21781],{"class":379},[200,114093,648],{"class":214},[200,114095,114096,114098,114100,114102,114104],{"class":202,"line":14},[200,114097,443],{"class":379},[200,114099,56519],{"class":210},[200,114101,449],{"class":214},[200,114103,56524],{"class":223},[200,114105,466],{"class":210},[200,114107,114108,114110,114112,114114,114116,114118,114120,114122],{"class":202,"line":255},[200,114109,443],{"class":379},[200,114111,56638],{"class":210},[200,114113,449],{"class":214},[200,114115,56643],{"class":223},[200,114117,227],{"class":210},[200,114119,1532],{"class":214},[200,114121,393],{"class":379},[200,114123,280],{"class":214},[200,114125,114126,114128,114130,114132,114134,114136,114138,114140,114142,114144,114146,114148,114150,114152,114154,114156],{"class":202,"line":272},[200,114127,3743],{"class":206},[200,114129,1095],{"class":235},[200,114131,56660],{"class":210},[200,114133,215],{"class":214},[200,114135,20084],{"class":210},[200,114137,215],{"class":214},[200,114139,47555],{"class":210},[200,114141,14674],{"class":206},[200,114143,22708],{"class":1978},[200,114145,2089],{"class":235},[200,114147,215],{"class":214},[200,114149,3928],{"class":223},[200,114151,227],{"class":235},[200,114153,56586],{"class":214},[200,114155,56589],{"class":2045},[200,114157,416],{"class":235},[200,114159,114160,114162],{"class":202,"line":283},[200,114161,413],{"class":214},[200,114163,416],{"class":210},[200,114165,114166,114168,114170,114172,114174,114176,114178,114180,114182,114184,114186,114188,114190,114192,114194,114196,114198,114200,114202],{"class":202,"line":300},[200,114167,443],{"class":379},[200,114169,318],{"class":214},[200,114171,5041],{"class":235},[200,114173,239],{"class":214},[200,114175,56705],{"class":210},[200,114177,413],{"class":214},[200,114179,510],{"class":214},[200,114181,452],{"class":206},[200,114183,48105],{"class":223},[200,114185,227],{"class":210},[200,114187,3318],{"class":214},[200,114189,56720],{"class":245},[200,114191,4853],{"class":214},[200,114193,53003],{"class":210},[200,114195,215],{"class":214},[200,114197,15837],{"class":210},[200,114199,23001],{"class":214},[200,114201,550],{"class":214},[200,114203,280],{"class":214},[200,114205,114206,114208,114210,114212],{"class":202,"line":310},[200,114207,48123],{"class":235},[200,114209,239],{"class":214},[200,114211,16166],{"class":2423},[200,114213,252],{"class":214},[200,114215,114216,114218],{"class":202,"line":336},[200,114217,413],{"class":214},[200,114219,416],{"class":210},[200,114221,114222,114224,114226],{"class":202,"line":342},[200,114223,681],{"class":214},[200,114225,14892],{"class":235},[200,114227,648],{"class":214},[200,114229,114230],{"class":202,"line":365},[200,114231,529],{"emptyLinePlaceholder":20},[200,114233,114234,114236,114238],{"class":202,"line":371},[200,114235,600],{"class":214},[200,114237,21428],{"class":235},[200,114239,648],{"class":214},[200,114241,114242,114244],{"class":202,"line":398},[200,114243,657],{"class":214},[200,114245,56777],{"class":235},[200,114247,114248,114250,114252,114254,114256],{"class":202,"line":405},[200,114249,56782],{"class":379},[200,114251,449],{"class":214},[200,114253,611],{"class":214},[200,114255,21428],{"class":245},[200,114257,617],{"class":214},[200,114259,114260],{"class":202,"line":410},[200,114261,11536],{"class":214},[200,114263,114264],{"class":202,"line":861},[200,114265,56799],{"class":210},[200,114267,114268,114270,114272],{"class":202,"line":890},[200,114269,893],{"class":214},[200,114271,56806],{"class":235},[200,114273,648],{"class":214},[200,114275,114276,114278,114280],{"class":202,"line":900},[200,114277,681],{"class":214},[200,114279,21428],{"class":235},[200,114281,648],{"class":214},[35,114283,56819,114284,56822],{},[156,114285,49030],{},[192,114287,114288],{"className":1370,"code":56825,"language":1373,"meta":13,"style":13},[156,114289,114290,114312,114316,114338,114362,114384,114396,114400,114414,114418,114478,114482,114488],{"__ignoreMap":13},[200,114291,114292,114294,114296,114298,114300,114302,114304,114306,114308,114310],{"class":202,"line":203},[200,114293,973],{"class":206},[200,114295,318],{"class":214},[200,114297,42872],{"class":210},[200,114299,550],{"class":214},[200,114301,22708],{"class":210},[200,114303,981],{"class":214},[200,114305,984],{"class":206},[200,114307,242],{"class":214},[200,114309,19790],{"class":245},[200,114311,992],{"class":214},[200,114313,114314],{"class":202,"line":14},[200,114315,529],{"emptyLinePlaceholder":20},[200,114317,114318,114320,114322,114324,114326,114328,114330,114332,114334,114336],{"class":202,"line":255},[200,114319,1380],{"class":206},[200,114321,1383],{"class":206},[200,114323,38960],{"class":223},[200,114325,227],{"class":210},[200,114327,3711],{"class":379},[200,114329,1095],{"class":214},[200,114331,25386],{"class":386},[200,114333,2089],{"class":214},[200,114335,393],{"class":379},[200,114337,280],{"class":214},[200,114339,114340,114342,114344,114346,114348,114350,114352,114354,114356,114358,114360],{"class":202,"line":272},[200,114341,3723],{"class":379},[200,114343,49052],{"class":210},[200,114345,510],{"class":214},[200,114347,452],{"class":206},[200,114349,56888],{"class":223},[200,114351,227],{"class":235},[200,114353,25386],{"class":210},[200,114355,550],{"class":214},[200,114357,42872],{"class":223},[200,114359,227],{"class":235},[200,114361,230],{"class":214},[200,114363,114364,114366,114368,114370,114372,114374,114376,114378,114380,114382],{"class":202,"line":283},[200,114365,53837],{"class":235},[200,114367,239],{"class":214},[200,114369,22708],{"class":223},[200,114371,1532],{"class":235},[200,114373,215],{"class":214},[200,114375,56915],{"class":223},[200,114377,227],{"class":235},[200,114379,56589],{"class":2045},[200,114381,2089],{"class":235},[200,114383,252],{"class":214},[200,114385,114386,114388,114390,114392,114394],{"class":202,"line":300},[200,114387,5080],{"class":214},[200,114389,2089],{"class":235},[200,114391,215],{"class":214},[200,114393,41223],{"class":210},[200,114395,416],{"class":235},[200,114397,114398],{"class":202,"line":310},[200,114399,529],{"emptyLinePlaceholder":20},[200,114401,114402,114404,114406,114408,114410,114412],{"class":202,"line":336},[200,114403,3723],{"class":379},[200,114405,318],{"class":214},[200,114407,56948],{"class":210},[200,114409,981],{"class":214},[200,114411,510],{"class":214},[200,114413,56955],{"class":210},[200,114415,114416],{"class":202,"line":342},[200,114417,529],{"emptyLinePlaceholder":20},[200,114419,114420,114422,114424,114426,114428,114430,114432,114434,114436,114438,114440,114442,114444,114446,114448,114450,114452,114454,114456,114458,114460,114462,114464,114466,114468,114470,114472,114474,114476],{"class":202,"line":365},[200,114421,3723],{"class":379},[200,114423,56966],{"class":210},[200,114425,510],{"class":214},[200,114427,452],{"class":206},[200,114429,47508],{"class":223},[200,114431,1532],{"class":235},[200,114433,215],{"class":214},[200,114435,770],{"class":223},[200,114437,1532],{"class":235},[200,114439,215],{"class":214},[200,114441,1472],{"class":223},[200,114443,227],{"class":235},[200,114445,47588],{"class":210},[200,114447,215],{"class":214},[200,114449,51759],{"class":210},[200,114451,2089],{"class":235},[200,114453,215],{"class":214},[200,114455,49030],{"class":223},[200,114457,227],{"class":235},[200,114459,49035],{"class":223},[200,114461,227],{"class":235},[200,114463,47588],{"class":210},[200,114465,215],{"class":214},[200,114467,51759],{"class":210},[200,114469,215],{"class":214},[200,114471,53003],{"class":210},[200,114473,550],{"class":214},[200,114475,56948],{"class":210},[200,114477,585],{"class":235},[200,114479,114480],{"class":202,"line":371},[200,114481,529],{"emptyLinePlaceholder":20},[200,114483,114484,114486],{"class":202,"line":398},[200,114485,3743],{"class":206},[200,114487,57031],{"class":210},[200,114489,114490,114492],{"class":202,"line":405},[200,114491,413],{"class":214},[200,114493,416],{"class":210},[35,114495,57040],{},[1913,114497,57044],{"id":57043},[35,114499,57047],{},[35,114501,57050],{},[192,114503,114504],{"className":591,"code":57053,"language":593,"meta":13,"style":13},[156,114505,114506,114522],{"__ignoreMap":13},[200,114507,114508,114510,114512,114514,114516,114518,114520],{"class":202,"line":203},[200,114509,600],{"class":214},[200,114511,35],{"class":235},[200,114513,675],{"class":214},[200,114515,57066],{"class":210},[200,114517,681],{"class":214},[200,114519,35],{"class":235},[200,114521,648],{"class":214},[200,114523,114524,114526,114528,114530,114532,114534,114536,114538,114540,114542,114544,114546,114548,114550],{"class":202,"line":14},[200,114525,600],{"class":214},[200,114527,14892],{"class":235},[200,114529,675],{"class":214},[200,114531,57083],{"class":210},[200,114533,215],{"class":214},[200,114535,1118],{"class":223},[200,114537,227],{"class":210},[200,114539,249],{"class":214},[200,114541,57094],{"class":245},[200,114543,249],{"class":214},[200,114545,5267],{"class":210},[200,114547,681],{"class":214},[200,114549,14892],{"class":235},[200,114551,648],{"class":214},[35,114553,57107,114554,57111],{},[156,114555,57110],{},[35,114557,57114,114558,57117,114560,57121],{},[156,114559,9674],{},[6850,114561,57120],{},[35,114563,57124,114564,57127,114566,57130,114568,57134],{},[156,114565,9674],{},[156,114567,53559],{},[156,114569,57133],{},[192,114571,114572],{"className":1370,"code":57137,"language":1373,"meta":13,"style":13},[156,114573,114574,114600,114612,114628,114642,114656,114670,114684,114698,114712,114728,114742],{"__ignoreMap":13},[200,114575,114576,114578,114580,114582,114584,114586,114588,114590,114592,114594,114596,114598],{"class":202,"line":203},[200,114577,207],{"class":206},[200,114579,47508],{"class":223},[200,114581,1532],{"class":210},[200,114583,215],{"class":214},[200,114585,47696],{"class":223},[200,114587,51210],{"class":210},[200,114589,215],{"class":214},[200,114591,56330],{"class":210},[200,114593,215],{"class":214},[200,114595,47711],{"class":223},[200,114597,227],{"class":210},[200,114599,230],{"class":214},[200,114601,114602,114604,114606,114608,114610],{"class":202,"line":14},[200,114603,52992],{"class":235},[200,114605,239],{"class":214},[200,114607,53842],{"class":223},[200,114609,1532],{"class":210},[200,114611,252],{"class":214},[200,114613,114614,114616,114618,114620,114622,114624,114626],{"class":202,"line":255},[200,114615,53026],{"class":235},[200,114617,239],{"class":214},[200,114619,47724],{"class":223},[200,114621,57188],{"class":210},[200,114623,215],{"class":214},[200,114625,57193],{"class":210},[200,114627,252],{"class":214},[200,114629,114630,114632,114634,114636,114638,114640],{"class":202,"line":272},[200,114631,23659],{"class":235},[200,114633,239],{"class":214},[200,114635,41038],{"class":210},[200,114637,215],{"class":214},[200,114639,3566],{"class":210},[200,114641,252],{"class":214},[200,114643,114644,114646,114648,114650,114652,114654],{"class":202,"line":283},[200,114645,53351],{"class":235},[200,114647,239],{"class":214},[200,114649,41038],{"class":210},[200,114651,215],{"class":214},[200,114653,53428],{"class":210},[200,114655,252],{"class":214},[200,114657,114658,114660,114662,114664,114666,114668],{"class":202,"line":300},[200,114659,53159],{"class":235},[200,114661,239],{"class":214},[200,114663,41038],{"class":210},[200,114665,215],{"class":214},[200,114667,53907],{"class":210},[200,114669,252],{"class":214},[200,114671,114672,114674,114676,114678,114680,114682],{"class":202,"line":310},[200,114673,53230],{"class":235},[200,114675,239],{"class":214},[200,114677,41038],{"class":210},[200,114679,215],{"class":214},[200,114681,53922],{"class":210},[200,114683,252],{"class":214},[200,114685,114686,114688,114690,114692,114694,114696],{"class":202,"line":336},[200,114687,53252],{"class":235},[200,114689,239],{"class":214},[200,114691,41038],{"class":210},[200,114693,215],{"class":214},[200,114695,53937],{"class":210},[200,114697,252],{"class":214},[200,114699,114700,114702,114704,114706,114708,114710],{"class":202,"line":342},[200,114701,258],{"class":235},[200,114703,239],{"class":214},[200,114705,41038],{"class":210},[200,114707,215],{"class":214},[200,114709,3582],{"class":210},[200,114711,252],{"class":214},[200,114713,114714,114716,114718,114720,114722,114724,114726],{"class":202,"line":365},[200,114715,57284],{"class":235},[200,114717,239],{"class":214},[200,114719,57289],{"class":223},[200,114721,57188],{"class":210},[200,114723,215],{"class":214},[200,114725,57296],{"class":210},[200,114727,252],{"class":214},[200,114729,114730,114732,114734,114736,114738,114740],{"class":202,"line":371},[200,114731,53306],{"class":235},[200,114733,239],{"class":214},[200,114735,34776],{"class":210},[200,114737,215],{"class":214},[200,114739,3280],{"class":210},[200,114741,252],{"class":214},[200,114743,114744,114746,114748,114750,114752],{"class":202,"line":398},[200,114745,413],{"class":214},[200,114747,2089],{"class":210},[200,114749,215],{"class":214},[200,114751,47761],{"class":223},[200,114753,466],{"class":210},[35,114755,419,114756,57330,114758,57333],{},[156,114757,57329],{},[156,114759,9674],{},[1913,114761,57337],{"id":57336},[35,114763,114764],{},[1217,114765,57342],{},[35,114767,114768],{},[1217,114769,57347],{},[35,114771,57350,114772,57354],{},[1217,114773,57353],{},[1576,114775,114776,114778,114780,114782,114784,114786,114788,114790],{},[71,114777,57359],{},[71,114779,34247],{},[71,114781,57364],{},[71,114783,57367],{},[71,114785,34252],{},[71,114787,57372],{},[71,114789,57375],{},[71,114791,57378],{},[35,114793,57381],{},[35,114795,57384,114796],{},[1217,114797,57387],{},[35,114799,57390,114800,57394,114802],{},[156,114801,57393],{},[1217,114803,114804,57400],{},[156,114805,57399],{},[192,114807,114808],{"className":1370,"code":57403,"language":1373,"meta":13,"style":13},[156,114809,114810,114824,114864,114878],{"__ignoreMap":13},[200,114811,114812,114814,114816,114818,114820,114822],{"class":202,"line":203},[200,114813,57393],{"class":223},[200,114815,227],{"class":210},[200,114817,3711],{"class":379},[200,114819,3714],{"class":214},[200,114821,393],{"class":379},[200,114823,280],{"class":214},[200,114825,114826,114828,114830,114832,114834,114836,114838,114840,114842,114844,114846,114848,114850,114852,114854,114856,114858,114860,114862],{"class":202,"line":14},[200,114827,3723],{"class":379},[200,114829,57426],{"class":210},[200,114831,510],{"class":214},[200,114833,452],{"class":206},[200,114835,27725],{"class":214},[200,114837,227],{"class":235},[200,114839,249],{"class":214},[200,114841,57439],{"class":245},[200,114843,249],{"class":214},[200,114845,2089],{"class":235},[200,114847,215],{"class":214},[200,114849,10929],{"class":223},[200,114851,227],{"class":235},[200,114853,57452],{"class":386},[200,114855,393],{"class":379},[200,114857,57457],{"class":210},[200,114859,215],{"class":214},[200,114861,52073],{"class":210},[200,114863,416],{"class":235},[200,114865,114866,114868,114870,114872,114874,114876],{"class":202,"line":255},[200,114867,3723],{"class":379},[200,114869,57470],{"class":210},[200,114871,510],{"class":214},[200,114873,12997],{"class":214},[200,114875,57426],{"class":223},[200,114877,466],{"class":235},[200,114879,114880,114882],{"class":202,"line":272},[200,114881,413],{"class":214},[200,114883,416],{"class":210},[35,114885,57487],{},[35,114887,114888],{},[1690,114889],{"alt":57492,"src":57493},[35,114891,57496],{},[192,114893,114894],{"className":21417,"code":57499,"language":12060,"meta":13,"style":13},[156,114895,114896,114916,114926],{"__ignoreMap":13},[200,114897,114898,114900,114902,114904,114906,114908,114910,114912,114914],{"class":202,"line":203},[200,114899,600],{"class":214},[200,114901,14892],{"class":235},[200,114903,14590],{"class":379},[200,114905,449],{"class":214},[200,114907,611],{"class":214},[200,114909,1373],{"class":245},[200,114911,611],{"class":214},[200,114913,21781],{"class":379},[200,114915,648],{"class":214},[200,114917,114918,114920,114922,114924],{"class":202,"line":14},[200,114919,973],{"class":206},[200,114921,242],{"class":214},[200,114923,57530],{"class":245},[200,114925,992],{"class":214},[200,114927,114928,114930,114932],{"class":202,"line":255},[200,114929,681],{"class":214},[200,114931,14892],{"class":235},[200,114933,648],{"class":214},[35,114935,114936],{},[1690,114937],{"alt":57545,"src":57546},[35,114939,57549],{},[35,114941,57552],{},[192,114943,114944],{"className":21417,"code":57555,"language":12060,"meta":13,"style":13},[156,114945,114946,114966,114988,115002,115042,115056,115084,115102,115108,115114],{"__ignoreMap":13},[200,114947,114948,114950,114952,114954,114956,114958,114960,114962,114964],{"class":202,"line":203},[200,114949,600],{"class":214},[200,114951,14892],{"class":235},[200,114953,14590],{"class":379},[200,114955,449],{"class":214},[200,114957,611],{"class":214},[200,114959,1373],{"class":245},[200,114961,611],{"class":214},[200,114963,21781],{"class":379},[200,114965,648],{"class":214},[200,114967,114968,114970,114972,114974,114976,114978,114980,114982,114984,114986],{"class":202,"line":14},[200,114969,443],{"class":379},[200,114971,22666],{"class":210},[200,114973,449],{"class":214},[200,114975,18685],{"class":223},[200,114977,600],{"class":214},[200,114979,328],{"class":1978},[200,114981,675],{"class":214},[200,114983,227],{"class":210},[200,114985,22656],{"class":214},[200,114987,416],{"class":210},[200,114989,114990,114992,114994,114996,114998,115000],{"class":202,"line":255},[200,114991,57393],{"class":223},[200,114993,227],{"class":210},[200,114995,3711],{"class":379},[200,114997,3714],{"class":214},[200,114999,393],{"class":379},[200,115001,280],{"class":214},[200,115003,115004,115006,115008,115010,115012,115014,115016,115018,115020,115022,115024,115026,115028,115030,115032,115034,115036,115038,115040],{"class":202,"line":272},[200,115005,3723],{"class":379},[200,115007,57426],{"class":210},[200,115009,510],{"class":214},[200,115011,452],{"class":206},[200,115013,27725],{"class":214},[200,115015,227],{"class":235},[200,115017,249],{"class":214},[200,115019,57439],{"class":245},[200,115021,249],{"class":214},[200,115023,2089],{"class":235},[200,115025,215],{"class":214},[200,115027,10929],{"class":223},[200,115029,227],{"class":235},[200,115031,57452],{"class":386},[200,115033,393],{"class":379},[200,115035,57457],{"class":210},[200,115037,215],{"class":214},[200,115039,52073],{"class":210},[200,115041,416],{"class":235},[200,115043,115044,115046,115048,115050,115052,115054],{"class":202,"line":283},[200,115045,3723],{"class":379},[200,115047,57470],{"class":210},[200,115049,510],{"class":214},[200,115051,12997],{"class":214},[200,115053,57426],{"class":223},[200,115055,466],{"class":235},[200,115057,115058,115060,115062,115064,115066,115068,115070,115072,115074,115076,115078,115080,115082],{"class":202,"line":300},[200,115059,57672],{"class":210},[200,115061,215],{"class":214},[200,115063,57677],{"class":210},[200,115065,215],{"class":214},[200,115067,25370],{"class":223},[200,115069,227],{"class":235},[200,115071,249],{"class":214},[200,115073,57688],{"class":245},[200,115075,249],{"class":214},[200,115077,550],{"class":214},[200,115079,3714],{"class":214},[200,115081,393],{"class":379},[200,115083,280],{"class":214},[200,115085,115086,115088,115090,115092,115094,115096,115098,115100],{"class":202,"line":310},[200,115087,3136],{"class":210},[200,115089,215],{"class":214},[200,115091,15837],{"class":210},[200,115093,510],{"class":214},[200,115095,57711],{"class":210},[200,115097,215],{"class":214},[200,115099,15837],{"class":223},[200,115101,466],{"class":235},[200,115103,115104,115106],{"class":202,"line":336},[200,115105,5080],{"class":214},[200,115107,416],{"class":235},[200,115109,115110,115112],{"class":202,"line":342},[200,115111,413],{"class":214},[200,115113,416],{"class":210},[200,115115,115116,115118,115120],{"class":202,"line":365},[200,115117,681],{"class":214},[200,115119,14892],{"class":235},[200,115121,648],{"class":214},[35,115123,57740],{},[1913,115125,57744],{"id":57743},[35,115127,57747],{},[35,115129,57750],{},[51,115131,57754],{"id":57753},[35,115133,115134],{},[1217,115135,57759],{},[35,115137,57762],{},[35,115139,57765,115140],{},[1217,115141,57768],{},[35,115143,57771,115144,57774,115146,51797],{},[156,115145,3582],{},[156,115147,57133],{},[1913,115149,57780],{"id":57779},[35,115151,57783],{},[35,115153,57786],{},[192,115155,115156],{"className":591,"code":57789,"language":593,"meta":13,"style":13},[156,115157,115158,115174],{"__ignoreMap":13},[200,115159,115160,115162,115164,115166,115168,115170,115172],{"class":202,"line":203},[200,115161,600],{"class":214},[200,115163,35],{"class":235},[200,115165,675],{"class":214},[200,115167,57802],{"class":210},[200,115169,681],{"class":214},[200,115171,35],{"class":235},[200,115173,648],{"class":214},[200,115175,115176,115178,115180,115182,115184,115186,115188,115190,115192,115194,115196,115198,115200,115202],{"class":202,"line":14},[200,115177,600],{"class":214},[200,115179,14892],{"class":235},[200,115181,675],{"class":214},[200,115183,57083],{"class":210},[200,115185,215],{"class":214},[200,115187,1118],{"class":223},[200,115189,227],{"class":210},[200,115191,249],{"class":214},[200,115193,57094],{"class":245},[200,115195,249],{"class":214},[200,115197,5267],{"class":210},[200,115199,681],{"class":214},[200,115201,14892],{"class":235},[200,115203,648],{"class":214},[35,115205,57841,115206,57844,115208,57848],{},[156,115207,57110],{},[156,115209,57847],{},[35,115211,57851,115212,57855],{},[156,115213,57854],{},[192,115215,115216],{"className":591,"code":57858,"language":593,"meta":13,"style":13},[156,115217,115218],{"__ignoreMap":13},[200,115219,115220,115222,115224,115226,115228,115230,115232],{"class":202,"line":203},[200,115221,600],{"class":214},[200,115223,35],{"class":235},[200,115225,675],{"class":214},[200,115227,57802],{"class":210},[200,115229,681],{"class":214},[200,115231,35],{"class":235},[200,115233,648],{"class":214},[35,115235,57879],{},[192,115237,115238],{"className":1370,"code":57882,"language":1373,"meta":13,"style":13},[156,115239,115240,115266,115292],{"__ignoreMap":13},[200,115241,115242,115244,115246,115248,115250,115252,115254,115256,115258,115260,115262,115264],{"class":202,"line":203},[200,115243,207],{"class":206},[200,115245,47508],{"class":223},[200,115247,1532],{"class":210},[200,115249,215],{"class":214},[200,115251,47696],{"class":223},[200,115253,51210],{"class":210},[200,115255,215],{"class":214},[200,115257,56330],{"class":210},[200,115259,215],{"class":214},[200,115261,47711],{"class":223},[200,115263,227],{"class":210},[200,115265,230],{"class":214},[200,115267,115268,115270,115272,115274,115276,115278,115280,115282,115284,115286,115288,115290],{"class":202,"line":14},[200,115269,258],{"class":235},[200,115271,239],{"class":214},[200,115273,41038],{"class":210},[200,115275,215],{"class":214},[200,115277,3818],{"class":210},[200,115279,28004],{"class":214},[200,115281,57927],{"class":223},[200,115283,57188],{"class":210},[200,115285,215],{"class":214},[200,115287,57934],{"class":210},[200,115289,239],{"class":214},[200,115291,11896],{"class":214},[200,115293,115294,115296],{"class":202,"line":255},[200,115295,413],{"class":214},[200,115297,416],{"class":210},[35,115299,419,115300,57950,115302,57953],{},[156,115301,57949],{},[156,115303,57854],{},[1913,115305,57957],{"id":57956},[35,115307,57960,115308,57963,115310,57966],{},[156,115309,57133],{},[156,115311,3582],{},[192,115313,115314],{"className":57969,"code":57970,"language":1373,"meta":13,"style":13},[156,115315,115316,115340,115360,115381],{"__ignoreMap":13},[200,115317,115318,115320,115322,115324,115326,115328,115330,115332,115334,115336,115338],{"class":202,"line":203},[200,115319,1380],{"class":206},[200,115321,14301],{"class":379},[200,115323,52933],{"class":210},[200,115325,449],{"class":214},[200,115327,50977],{"class":223},[200,115329,227],{"class":210},[200,115331,249],{"class":214},[200,115333,51759],{"class":245},[200,115335,249],{"class":214},[200,115337,550],{"class":214},[200,115339,280],{"class":214},[200,115341,115342,115344,115346,115348,115350,115352,115354,115356,115358],{"class":202,"line":14},[200,115343,258],{"class":235},[200,115345,239],{"class":214},[200,115347,51039],{"class":223},[200,115349,227],{"class":210},[200,115351,249],{"class":214},[200,115353,3582],{"class":245},[200,115355,249],{"class":214},[200,115357,2089],{"class":210},[200,115359,252],{"class":214},[200,115361,115363,115365,115367,115369,115371,115373,115375,115377,115379],{"class":115362,"line":255},[202,58020,37394],[200,115364,57284],{"class":235},[200,115366,239],{"class":214},[200,115368,51039],{"class":223},[200,115370,227],{"class":210},[200,115372,249],{"class":214},[200,115374,58033],{"class":245},[200,115376,249],{"class":214},[200,115378,2089],{"class":210},[200,115380,550],{"class":214},[200,115382,115383,115385],{"class":202,"line":272},[200,115384,413],{"class":214},[200,115386,416],{"class":210},[1913,115388,58049],{"id":58048},[35,115390,58052],{},[192,115392,115393],{"className":21417,"code":58055,"language":12060,"meta":13,"style":13},[156,115394,115395,115415,115425,115429,115443,115483,115509,115523,115529,115569,115597,115603,115609,115617,115621,115629,115647],{"__ignoreMap":13},[200,115396,115397,115399,115401,115403,115405,115407,115409,115411,115413],{"class":202,"line":203},[200,115398,600],{"class":214},[200,115400,14892],{"class":235},[200,115402,14590],{"class":379},[200,115404,449],{"class":214},[200,115406,611],{"class":214},[200,115408,1373],{"class":245},[200,115410,611],{"class":214},[200,115412,21781],{"class":379},[200,115414,648],{"class":214},[200,115416,115417,115419,115421,115423],{"class":202,"line":14},[200,115418,973],{"class":206},[200,115420,242],{"class":214},[200,115422,58086],{"class":245},[200,115424,992],{"class":214},[200,115426,115427],{"class":202,"line":255},[200,115428,529],{"emptyLinePlaceholder":20},[200,115430,115431,115433,115435,115437,115439,115441],{"class":202,"line":272},[200,115432,57399],{"class":223},[200,115434,227],{"class":210},[200,115436,3711],{"class":379},[200,115438,3714],{"class":214},[200,115440,393],{"class":379},[200,115442,280],{"class":214},[200,115444,115445,115447,115449,115451,115453,115455,115457,115459,115461,115463,115465,115467,115469,115471,115473,115475,115477,115479,115481],{"class":202,"line":283},[200,115446,3723],{"class":379},[200,115448,58113],{"class":210},[200,115450,510],{"class":214},[200,115452,452],{"class":206},[200,115454,27725],{"class":214},[200,115456,227],{"class":235},[200,115458,249],{"class":214},[200,115460,58126],{"class":245},[200,115462,249],{"class":214},[200,115464,2089],{"class":235},[200,115466,215],{"class":214},[200,115468,10929],{"class":223},[200,115470,227],{"class":235},[200,115472,57452],{"class":386},[200,115474,393],{"class":379},[200,115476,57457],{"class":210},[200,115478,215],{"class":214},[200,115480,52073],{"class":210},[200,115482,416],{"class":235},[200,115484,115485,115487,115489,115491,115493,115495,115497,115499,115501,115503,115505,115507],{"class":202,"line":300},[200,115486,58153],{"class":210},[200,115488,215],{"class":214},[200,115490,15837],{"class":210},[200,115492,510],{"class":214},[200,115494,12997],{"class":214},[200,115496,58113],{"class":223},[200,115498,227],{"class":235},[200,115500,249],{"class":214},[200,115502,58170],{"class":245},[200,115504,249],{"class":214},[200,115506,550],{"class":214},[200,115508,280],{"class":214},[200,115510,115511,115513,115515,115517,115519,115521],{"class":202,"line":310},[200,115512,58181],{"class":235},[200,115514,239],{"class":214},[200,115516,242],{"class":214},[200,115518,58188],{"class":245},[200,115520,249],{"class":214},[200,115522,252],{"class":214},[200,115524,115525,115527],{"class":202,"line":336},[200,115526,5080],{"class":214},[200,115528,416],{"class":235},[200,115530,115531,115533,115535,115537,115539,115541,115543,115545,115547,115549,115551,115553,115555,115557,115559,115561,115563,115565,115567],{"class":202,"line":342},[200,115532,58153],{"class":210},[200,115534,215],{"class":214},[200,115536,15837],{"class":210},[200,115538,6524],{"class":214},[200,115540,25370],{"class":223},[200,115542,227],{"class":235},[200,115544,249],{"class":214},[200,115546,58217],{"class":245},[200,115548,249],{"class":214},[200,115550,550],{"class":214},[200,115552,1095],{"class":214},[200,115554,19200],{"class":386},[200,115556,550],{"class":214},[200,115558,58230],{"class":386},[200,115560,550],{"class":214},[200,115562,58235],{"class":386},[200,115564,2089],{"class":214},[200,115566,393],{"class":379},[200,115568,280],{"class":214},[200,115570,115571,115573,115575,115577,115579,115581,115583,115585,115587,115589,115591,115593,115595],{"class":202,"line":365},[200,115572,27430],{"class":210},[200,115574,215],{"class":214},[200,115576,3582],{"class":210},[200,115578,510],{"class":214},[200,115580,58254],{"class":210},[200,115582,215],{"class":214},[200,115584,15837],{"class":210},[200,115586,6524],{"class":214},[200,115588,58263],{"class":210},[200,115590,215],{"class":214},[200,115592,15967],{"class":210},[200,115594,3891],{"class":214},[200,115596,18875],{"class":214},[200,115598,115599,115601],{"class":202,"line":371},[200,115600,5080],{"class":214},[200,115602,416],{"class":235},[200,115604,115605,115607],{"class":202,"line":398},[200,115606,413],{"class":214},[200,115608,416],{"class":210},[200,115610,115611,115613,115615],{"class":202,"line":405},[200,115612,681],{"class":214},[200,115614,14892],{"class":235},[200,115616,648],{"class":214},[200,115618,115619],{"class":202,"line":410},[200,115620,529],{"emptyLinePlaceholder":20},[200,115622,115623,115625,115627],{"class":202,"line":861},[200,115624,600],{"class":214},[200,115626,21428],{"class":235},[200,115628,648],{"class":214},[200,115630,115631,115633,115635,115637,115639,115641,115643,115645],{"class":202,"line":890},[200,115632,657],{"class":214},[200,115634,11386],{"class":235},[200,115636,3224],{"class":379},[200,115638,449],{"class":214},[200,115640,611],{"class":214},[200,115642,58318],{"class":245},[200,115644,611],{"class":214},[200,115646,21487],{"class":214},[200,115648,115649,115651,115653],{"class":202,"line":900},[200,115650,681],{"class":214},[200,115652,21428],{"class":235},[200,115654,648],{"class":214},[35,115656,58333],{},[35,115658,58336],{},[192,115660,115661],{"className":21417,"code":58339,"language":12060,"meta":13,"style":13},[156,115662,115663,115683,115695,115699,115713,115753,115781,115815,115821,115827,115835,115839,115847,115853,115865,115877,115907,115911,115929,115937],{"__ignoreMap":13},[200,115664,115665,115667,115669,115671,115673,115675,115677,115679,115681],{"class":202,"line":203},[200,115666,600],{"class":214},[200,115668,14892],{"class":235},[200,115670,14590],{"class":379},[200,115672,449],{"class":214},[200,115674,611],{"class":214},[200,115676,1373],{"class":245},[200,115678,611],{"class":214},[200,115680,21781],{"class":379},[200,115682,648],{"class":214},[200,115684,115685,115687,115689,115691,115693],{"class":202,"line":14},[200,115686,443],{"class":379},[200,115688,58368],{"class":210},[200,115690,449],{"class":214},[200,115692,18685],{"class":223},[200,115694,466],{"class":210},[200,115696,115697],{"class":202,"line":255},[200,115698,529],{"emptyLinePlaceholder":20},[200,115700,115701,115703,115705,115707,115709,115711],{"class":202,"line":272},[200,115702,57399],{"class":223},[200,115704,227],{"class":210},[200,115706,3711],{"class":379},[200,115708,3714],{"class":214},[200,115710,393],{"class":379},[200,115712,280],{"class":214},[200,115714,115715,115717,115719,115721,115723,115725,115727,115729,115731,115733,115735,115737,115739,115741,115743,115745,115747,115749,115751],{"class":202,"line":283},[200,115716,58153],{"class":210},[200,115718,215],{"class":214},[200,115720,15837],{"class":210},[200,115722,6524],{"class":214},[200,115724,25370],{"class":223},[200,115726,227],{"class":235},[200,115728,249],{"class":214},[200,115730,58217],{"class":245},[200,115732,249],{"class":214},[200,115734,550],{"class":214},[200,115736,1095],{"class":214},[200,115738,19200],{"class":386},[200,115740,550],{"class":214},[200,115742,58230],{"class":386},[200,115744,550],{"class":214},[200,115746,58235],{"class":386},[200,115748,2089],{"class":214},[200,115750,393],{"class":379},[200,115752,280],{"class":214},[200,115754,115755,115757,115759,115761,115763,115765,115767,115769,115771,115773,115775,115777,115779],{"class":202,"line":300},[200,115756,27430],{"class":210},[200,115758,215],{"class":214},[200,115760,3582],{"class":210},[200,115762,510],{"class":214},[200,115764,58254],{"class":210},[200,115766,215],{"class":214},[200,115768,15837],{"class":210},[200,115770,6524],{"class":214},[200,115772,58263],{"class":210},[200,115774,215],{"class":214},[200,115776,15967],{"class":210},[200,115778,3891],{"class":214},[200,115780,18875],{"class":214},[200,115782,115783,115785,115787,115789,115791,115793,115795,115797,115799,115801,115803,115805,115807,115809,115811,115813],{"class":202,"line":310},[200,115784,58465],{"class":210},[200,115786,215],{"class":214},[200,115788,15837],{"class":210},[200,115790,215],{"class":214},[200,115792,58474],{"class":223},[200,115794,227],{"class":235},[200,115796,249],{"class":214},[200,115798,3582],{"class":245},[200,115800,249],{"class":214},[200,115802,550],{"class":214},[200,115804,318],{"class":214},[200,115806,58489],{"class":235},[200,115808,239],{"class":214},[200,115810,2424],{"class":2423},[200,115812,981],{"class":214},[200,115814,416],{"class":235},[200,115816,115817,115819],{"class":202,"line":336},[200,115818,5080],{"class":214},[200,115820,416],{"class":235},[200,115822,115823,115825],{"class":202,"line":342},[200,115824,413],{"class":214},[200,115826,416],{"class":210},[200,115828,115829,115831,115833],{"class":202,"line":365},[200,115830,681],{"class":214},[200,115832,14892],{"class":235},[200,115834,648],{"class":214},[200,115836,115837],{"class":202,"line":371},[200,115838,529],{"emptyLinePlaceholder":20},[200,115840,115841,115843,115845],{"class":202,"line":398},[200,115842,600],{"class":214},[200,115844,21428],{"class":235},[200,115846,648],{"class":214},[200,115848,115849,115851],{"class":202,"line":405},[200,115850,657],{"class":214},[200,115852,58536],{"class":235},[200,115854,115855,115857,115859,115861,115863],{"class":202,"line":410},[200,115856,58541],{"class":379},[200,115858,449],{"class":214},[200,115860,611],{"class":214},[200,115862,54564],{"class":245},[200,115864,617],{"class":214},[200,115866,115867,115869,115871,115873,115875],{"class":202,"line":861},[200,115868,11797],{"class":379},[200,115870,449],{"class":214},[200,115872,611],{"class":214},[200,115874,3582],{"class":245},[200,115876,617],{"class":214},[200,115878,115879,115881,115883,115885,115887,115889,115891,115893,115895,115897,115899,115901,115903,115905],{"class":202,"line":890},[200,115880,23821],{"class":214},[200,115882,58568],{"class":379},[200,115884,449],{"class":214},[200,115886,58573],{"class":214},[200,115888,26836],{"class":210},[200,115890,215],{"class":214},[200,115892,3582],{"class":210},[200,115894,6524],{"class":214},[200,115896,51343],{"class":210},[200,115898,14981],{"class":214},[200,115900,51368],{"class":2045},[200,115902,413],{"class":214},[200,115904,58592],{"class":245},[200,115906,58595],{"class":214},[200,115908,115909],{"class":202,"line":900},[200,115910,11536],{"class":214},[200,115912,115913,115915,115917,115919,115921,115923,115925,115927],{"class":202,"line":905},[200,115914,804],{"class":214},[200,115916,11386],{"class":235},[200,115918,3224],{"class":379},[200,115920,449],{"class":214},[200,115922,611],{"class":214},[200,115924,58318],{"class":245},[200,115926,611],{"class":214},[200,115928,21487],{"class":214},[200,115930,115931,115933,115935],{"class":202,"line":931},[200,115932,893],{"class":214},[200,115934,48706],{"class":235},[200,115936,648],{"class":214},[200,115938,115939,115941,115943],{"class":202,"line":3640},[200,115940,681],{"class":214},[200,115942,21428],{"class":235},[200,115944,648],{"class":214},[35,115946,58636],{},[35,115948,115949],{},[1690,115950],{"alt":58641,"src":58642},[1913,115952,30681],{"id":58645},[35,115954,58648],{},[51,115956,58652],{"id":58651},[35,115958,115959],{},[1690,115960],{"alt":58657,"src":58658},[35,115962,115963],{},[1217,115964,115965,46057,115968,46062,115971,46066,115974,215],{},[39,115966,38639],{"href":46025,"rel":115967},[77],[39,115969,46061],{"href":18567,"rel":115970},[77],[39,115972,19412],{"href":46016,"rel":115973},[77],[39,115975,11622],{"href":46025,"rel":115976},[77],[35,115978,58677],{},[35,115980,58680],{},[35,115982,58683],{},[1913,115984,48572],{"id":48664},[35,115986,58688,115987],{},[1217,115988,58691],{},[35,115990,115991],{},[1690,115992],{"alt":58696,"src":58697},[35,115994,58700,115995,58703,115997,19487],{},[156,115996,51759],{},[156,115998,58706],{},[192,116000,116001],{"className":1370,"code":58709,"language":1373,"meta":13,"style":13},[156,116002,116003,116027,116065,116091,116097,116101,116125,116163,116195,116201,116205,116229,116271,116313],{"__ignoreMap":13},[200,116004,116005,116007,116009,116011,116013,116015,116017,116019,116021,116023,116025],{"class":202,"line":203},[200,116006,1380],{"class":206},[200,116008,14301],{"class":379},[200,116010,52933],{"class":210},[200,116012,449],{"class":214},[200,116014,50977],{"class":223},[200,116016,227],{"class":210},[200,116018,249],{"class":214},[200,116020,51759],{"class":245},[200,116022,249],{"class":214},[200,116024,550],{"class":214},[200,116026,280],{"class":214},[200,116028,116029,116031,116033,116035,116037,116039,116041,116043,116045,116047,116049,116051,116053,116055,116057,116059,116061,116063],{"class":202,"line":14},[200,116030,49799],{"class":235},[200,116032,239],{"class":214},[200,116034,50998],{"class":223},[200,116036,227],{"class":210},[200,116038,249],{"class":214},[200,116040,3280],{"class":245},[200,116042,249],{"class":214},[200,116044,2089],{"class":210},[200,116046,215],{"class":214},[200,116048,51013],{"class":223},[200,116050,227],{"class":210},[200,116052,2752],{"class":214},[200,116054,51020],{"class":235},[200,116056,239],{"class":214},[200,116058,2424],{"class":2423},[200,116060,981],{"class":214},[200,116062,2089],{"class":210},[200,116064,252],{"class":214},[200,116066,116067,116069,116071,116073,116075,116077,116079,116081,116083,116085,116087,116089],{"class":202,"line":255},[200,116068,23659],{"class":235},[200,116070,239],{"class":214},[200,116072,51039],{"class":223},[200,116074,227],{"class":210},[200,116076,249],{"class":214},[200,116078,3566],{"class":245},[200,116080,249],{"class":214},[200,116082,2089],{"class":210},[200,116084,215],{"class":214},[200,116086,51054],{"class":223},[200,116088,1532],{"class":210},[200,116090,252],{"class":214},[200,116092,116093,116095],{"class":202,"line":272},[200,116094,413],{"class":214},[200,116096,416],{"class":210},[200,116098,116099],{"class":202,"line":283},[200,116100,529],{"emptyLinePlaceholder":20},[200,116102,116103,116105,116107,116109,116111,116113,116115,116117,116119,116121,116123],{"class":202,"line":300},[200,116104,1380],{"class":206},[200,116106,14301],{"class":379},[200,116108,50834],{"class":210},[200,116110,449],{"class":214},[200,116112,50977],{"class":223},[200,116114,227],{"class":210},[200,116116,249],{"class":214},[200,116118,2461],{"class":245},[200,116120,249],{"class":214},[200,116122,550],{"class":214},[200,116124,280],{"class":214},[200,116126,116127,116129,116131,116133,116135,116137,116139,116141,116143,116145,116147,116149,116151,116153,116155,116157,116159,116161],{"class":202,"line":310},[200,116128,49799],{"class":235},[200,116130,239],{"class":214},[200,116132,50998],{"class":223},[200,116134,227],{"class":210},[200,116136,249],{"class":214},[200,116138,3280],{"class":245},[200,116140,249],{"class":214},[200,116142,2089],{"class":210},[200,116144,215],{"class":214},[200,116146,51013],{"class":223},[200,116148,227],{"class":210},[200,116150,2752],{"class":214},[200,116152,51020],{"class":235},[200,116154,239],{"class":214},[200,116156,2424],{"class":2423},[200,116158,981],{"class":214},[200,116160,2089],{"class":210},[200,116162,252],{"class":214},[200,116164,116165,116167,116169,116171,116173,116175,116177,116179,116181,116183,116185,116187,116189,116191,116193],{"class":202,"line":336},[200,116166,236],{"class":235},[200,116168,239],{"class":214},[200,116170,51039],{"class":223},[200,116172,227],{"class":210},[200,116174,249],{"class":214},[200,116176,20089],{"class":245},[200,116178,249],{"class":214},[200,116180,2089],{"class":210},[200,116182,215],{"class":214},[200,116184,51054],{"class":223},[200,116186,1532],{"class":210},[200,116188,215],{"class":214},[200,116190,51061],{"class":223},[200,116192,1532],{"class":210},[200,116194,252],{"class":214},[200,116196,116197,116199],{"class":202,"line":342},[200,116198,413],{"class":214},[200,116200,416],{"class":210},[200,116202,116203],{"class":202,"line":365},[200,116204,529],{"emptyLinePlaceholder":20},[200,116206,116207,116209,116211,116213,116215,116217,116219,116221,116223,116225,116227],{"class":202,"line":371},[200,116208,1380],{"class":206},[200,116210,14301],{"class":379},[200,116212,58922],{"class":210},[200,116214,449],{"class":214},[200,116216,50977],{"class":223},[200,116218,227],{"class":210},[200,116220,249],{"class":214},[200,116222,58933],{"class":245},[200,116224,249],{"class":214},[200,116226,550],{"class":214},[200,116228,280],{"class":214},[200,116230,116231,116233,116235,116237,116239,116241,116243,116245,116247,116249,116251,116253,116255,116257,116259,116261,116263,116265,116267,116269],{"class":202,"line":398},[200,116232,58944],{"class":235},[200,116234,239],{"class":214},[200,116236,50998],{"class":223},[200,116238,227],{"class":210},[200,116240,249],{"class":214},[200,116242,58955],{"class":245},[200,116244,249],{"class":214},[200,116246,2089],{"class":210},[200,116248,215],{"class":214},[200,116250,51054],{"class":223},[200,116252,1532],{"class":210},[200,116254,215],{"class":214},[200,116256,53331],{"class":223},[200,116258,227],{"class":210},[200,116260,1532],{"class":214},[200,116262,393],{"class":379},[200,116264,51401],{"class":210},[200,116266,215],{"class":214},[200,116268,53344],{"class":210},[200,116270,252],{"class":214},[200,116272,116273,116275,116277,116279,116281,116283,116285,116287,116289,116291,116293,116295,116297,116299,116301,116303,116305,116307,116309,116311],{"class":202,"line":405},[200,116274,58988],{"class":235},[200,116276,239],{"class":214},[200,116278,50998],{"class":223},[200,116280,227],{"class":210},[200,116282,249],{"class":214},[200,116284,58999],{"class":245},[200,116286,249],{"class":214},[200,116288,2089],{"class":210},[200,116290,215],{"class":214},[200,116292,51054],{"class":223},[200,116294,1532],{"class":210},[200,116296,215],{"class":214},[200,116298,53331],{"class":223},[200,116300,227],{"class":210},[200,116302,1532],{"class":214},[200,116304,393],{"class":379},[200,116306,54086],{"class":210},[200,116308,215],{"class":214},[200,116310,53344],{"class":210},[200,116312,252],{"class":214},[200,116314,116315,116317],{"class":202,"line":410},[200,116316,413],{"class":214},[200,116318,416],{"class":210},[1913,116320,59037],{"id":59036},[35,116322,59040],{},[38677,116324,59044],{"id":59043},[35,116326,59047,116327,59051],{},[156,116328,59050],{},[192,116330,116331],{"className":21417,"code":59054,"language":12060,"meta":13,"style":13},[156,116332,116333,116353,116385,116395,116409,116415,116423,116427,116435,116441,116455,116459,116471,116483,116487],{"__ignoreMap":13},[200,116334,116335,116337,116339,116341,116343,116345,116347,116349,116351],{"class":202,"line":203},[200,116336,600],{"class":214},[200,116338,14892],{"class":235},[200,116340,14590],{"class":379},[200,116342,449],{"class":214},[200,116344,611],{"class":214},[200,116346,1373],{"class":245},[200,116348,611],{"class":214},[200,116350,21781],{"class":379},[200,116352,648],{"class":214},[200,116354,116355,116357,116359,116361,116363,116365,116367,116369,116371,116373,116375,116377,116379,116381,116383],{"class":202,"line":14},[200,116356,443],{"class":379},[200,116358,318],{"class":214},[200,116360,5041],{"class":235},[200,116362,239],{"class":214},[200,116364,50834],{"class":210},[200,116366,413],{"class":214},[200,116368,510],{"class":214},[200,116370,452],{"class":206},[200,116372,48105],{"class":223},[200,116374,227],{"class":210},[200,116376,249],{"class":214},[200,116378,59103],{"class":245},[200,116380,249],{"class":214},[200,116382,550],{"class":214},[200,116384,280],{"class":214},[200,116386,116387,116389,116391,116393],{"class":202,"line":255},[200,116388,48123],{"class":235},[200,116390,239],{"class":214},[200,116392,16166],{"class":2423},[200,116394,252],{"class":214},[200,116396,116397,116399,116401,116403,116405,116407],{"class":202,"line":272},[200,116398,50621],{"class":223},[200,116400,239],{"class":214},[200,116402,3714],{"class":214},[200,116404,393],{"class":379},[200,116406,27960],{"class":210},[200,116408,252],{"class":214},[200,116410,116411,116413],{"class":202,"line":283},[200,116412,413],{"class":214},[200,116414,416],{"class":210},[200,116416,116417,116419,116421],{"class":202,"line":300},[200,116418,681],{"class":214},[200,116420,14892],{"class":235},[200,116422,648],{"class":214},[200,116424,116425],{"class":202,"line":310},[200,116426,529],{"emptyLinePlaceholder":20},[200,116428,116429,116431,116433],{"class":202,"line":336},[200,116430,600],{"class":214},[200,116432,21428],{"class":235},[200,116434,648],{"class":214},[200,116436,116437,116439],{"class":202,"line":342},[200,116438,657],{"class":214},[200,116440,59166],{"class":235},[200,116442,116443,116445,116447,116449,116451,116453],{"class":202,"line":365},[200,116444,23821],{"class":214},[200,116446,30152],{"class":379},[200,116448,449],{"class":214},[200,116450,611],{"class":214},[200,116452,2461],{"class":210},[200,116454,617],{"class":214},[200,116456,116457],{"class":202,"line":371},[200,116458,59185],{"class":379},[200,116460,116461,116463,116465,116467,116469],{"class":202,"line":398},[200,116462,59190],{"class":379},[200,116464,449],{"class":214},[200,116466,611],{"class":214},[200,116468,3280],{"class":245},[200,116470,617],{"class":214},[200,116472,116473,116475,116477,116479,116481],{"class":202,"line":405},[200,116474,59203],{"class":379},[200,116476,449],{"class":214},[200,116478,611],{"class":214},[200,116480,20089],{"class":245},[200,116482,617],{"class":214},[200,116484,116485],{"class":202,"line":410},[200,116486,23321],{"class":214},[200,116488,116489,116491,116493],{"class":202,"line":861},[200,116490,681],{"class":214},[200,116492,21428],{"class":235},[200,116494,648],{"class":214},[38677,116496,59227],{"id":59226},[35,116498,59230,116499,59234],{},[156,116500,59233],{},[192,116502,116503],{"className":1370,"code":59237,"language":1373,"meta":13,"style":13},[156,116504,116505,116535,116539,116555,116583,116631],{"__ignoreMap":13},[200,116506,116507,116509,116511,116513,116515,116517,116519,116521,116523,116525,116527,116529,116531,116533],{"class":202,"line":203},[200,116508,973],{"class":206},[200,116510,318],{"class":214},[200,116512,59248],{"class":210},[200,116514,550],{"class":214},[200,116516,20132],{"class":210},[200,116518,550],{"class":214},[200,116520,42872],{"class":210},[200,116522,550],{"class":214},[200,116524,22708],{"class":210},[200,116526,981],{"class":214},[200,116528,984],{"class":206},[200,116530,242],{"class":214},[200,116532,19790],{"class":245},[200,116534,992],{"class":214},[200,116536,116537],{"class":202,"line":14},[200,116538,529],{"emptyLinePlaceholder":20},[200,116540,116541,116543,116545,116547,116549,116551,116553],{"class":202,"line":255},[200,116542,1380],{"class":206},[200,116544,14301],{"class":379},[200,116546,59283],{"class":210},[200,116548,449],{"class":214},[200,116550,42872],{"class":223},[200,116552,227],{"class":210},[200,116554,230],{"class":214},[200,116556,116557,116559,116561,116563,116565,116567,116569,116571,116573,116575,116577,116579,116581],{"class":202,"line":272},[200,116558,23659],{"class":235},[200,116560,239],{"class":214},[200,116562,22708],{"class":223},[200,116564,227],{"class":210},[200,116566,2752],{"class":214},[200,116568,39347],{"class":235},[200,116570,239],{"class":214},[200,116572,242],{"class":214},[200,116574,41947],{"class":245},[200,116576,249],{"class":214},[200,116578,981],{"class":214},[200,116580,2089],{"class":210},[200,116582,252],{"class":214},[200,116584,116585,116587,116589,116591,116593,116595,116597,116599,116601,116603,116605,116607,116609,116611,116613,116615,116617,116619,116621,116623,116625,116627,116629],{"class":202,"line":283},[200,116586,59324],{"class":235},[200,116588,239],{"class":214},[200,116590,59248],{"class":223},[200,116592,227],{"class":210},[200,116594,59333],{"class":223},[200,116596,59336],{"class":210},[200,116598,215],{"class":214},[200,116600,4123],{"class":223},[200,116602,227],{"class":210},[200,116604,59345],{"class":2045},[200,116606,550],{"class":214},[200,116608,318],{"class":214},[200,116610,39347],{"class":235},[200,116612,239],{"class":214},[200,116614,242],{"class":214},[200,116616,59358],{"class":245},[200,116618,249],{"class":214},[200,116620,981],{"class":214},[200,116622,2089],{"class":210},[200,116624,215],{"class":214},[200,116626,53746],{"class":223},[200,116628,1532],{"class":210},[200,116630,252],{"class":214},[200,116632,116633,116635],{"class":202,"line":300},[200,116634,413],{"class":214},[200,116636,416],{"class":210},[35,116638,59381],{},[38677,116640,59385],{"id":59384},[35,116642,59388],{},[192,116644,116645],{"className":1370,"code":59391,"language":1373,"meta":13,"style":13},[156,116646,116647,116665,116669,116691,116713,116717,116743,116747,116787,116801,116815,116833,116839,116843,116857,116933,116937,116953],{"__ignoreMap":13},[200,116648,116649,116651,116653,116655,116657,116659,116661,116663],{"class":202,"line":203},[200,116650,973],{"class":206},[200,116652,318],{"class":214},[200,116654,59402],{"class":210},[200,116656,981],{"class":214},[200,116658,984],{"class":206},[200,116660,242],{"class":214},[200,116662,42580],{"class":245},[200,116664,992],{"class":214},[200,116666,116667],{"class":202,"line":14},[200,116668,529],{"emptyLinePlaceholder":20},[200,116670,116671,116673,116675,116677,116679,116681,116683,116685,116687,116689],{"class":202,"line":255},[200,116672,1380],{"class":206},[200,116674,1383],{"class":206},[200,116676,38960],{"class":223},[200,116678,227],{"class":210},[200,116680,3711],{"class":379},[200,116682,1095],{"class":214},[200,116684,25386],{"class":386},[200,116686,2089],{"class":214},[200,116688,393],{"class":379},[200,116690,280],{"class":214},[200,116692,116693,116695,116697,116699,116701,116703,116705,116707,116709,116711],{"class":202,"line":272},[200,116694,3723],{"class":379},[200,116696,318],{"class":214},[200,116698,34776],{"class":210},[200,116700,981],{"class":214},[200,116702,510],{"class":214},[200,116704,452],{"class":206},[200,116706,39235],{"class":223},[200,116708,227],{"class":235},[200,116710,25386],{"class":210},[200,116712,416],{"class":235},[200,116714,116715],{"class":202,"line":283},[200,116716,529],{"emptyLinePlaceholder":20},[200,116718,116719,116721,116723,116725,116727,116729,116731,116733,116735,116737,116739,116741],{"class":202,"line":300},[200,116720,3723],{"class":379},[200,116722,41038],{"class":210},[200,116724,510],{"class":214},[200,116726,452],{"class":206},[200,116728,41180],{"class":223},[200,116730,227],{"class":235},[200,116732,25386],{"class":210},[200,116734,550],{"class":214},[200,116736,59402],{"class":210},[200,116738,215],{"class":214},[200,116740,41223],{"class":210},[200,116742,416],{"class":235},[200,116744,116745],{"class":202,"line":310},[200,116746,529],{"emptyLinePlaceholder":20},[200,116748,116749,116751,116753,116755,116757,116759,116761,116763,116765,116767,116769,116771,116773,116775,116777,116779,116781,116783,116785],{"class":202,"line":336},[200,116750,3723],{"class":379},[200,116752,350],{"class":214},[200,116754,21428],{"class":210},[200,116756,360],{"class":214},[200,116758,510],{"class":214},[200,116760,452],{"class":206},[200,116762,47508],{"class":223},[200,116764,1532],{"class":235},[200,116766,215],{"class":214},[200,116768,47696],{"class":223},[200,116770,227],{"class":235},[200,116772,47588],{"class":210},[200,116774,215],{"class":214},[200,116776,51759],{"class":210},[200,116778,2089],{"class":235},[200,116780,215],{"class":214},[200,116782,47711],{"class":223},[200,116784,227],{"class":235},[200,116786,230],{"class":214},[200,116788,116789,116791,116793,116795,116797,116799],{"class":202,"line":342},[200,116790,21442],{"class":235},[200,116792,239],{"class":214},[200,116794,41038],{"class":210},[200,116796,215],{"class":214},[200,116798,3566],{"class":210},[200,116800,252],{"class":214},[200,116802,116803,116805,116807,116809,116811,116813],{"class":202,"line":365},[200,116804,5080],{"class":214},[200,116806,2089],{"class":235},[200,116808,215],{"class":214},[200,116810,52685],{"class":223},[200,116812,227],{"class":235},[200,116814,230],{"class":214},[200,116816,116817,116819,116821,116823,116825,116827,116829,116831],{"class":202,"line":371},[200,116818,15858],{"class":235},[200,116820,239],{"class":214},[200,116822,47527],{"class":210},[200,116824,215],{"class":214},[200,116826,51759],{"class":210},[200,116828,215],{"class":214},[200,116830,3280],{"class":210},[200,116832,252],{"class":214},[200,116834,116835,116837],{"class":202,"line":398},[200,116836,5080],{"class":214},[200,116838,416],{"class":235},[200,116840,116841],{"class":202,"line":405},[200,116842,529],{"emptyLinePlaceholder":20},[200,116844,116845,116847,116849,116851,116853,116855],{"class":202,"line":410},[200,116846,20075],{"class":206},[200,116848,1095],{"class":235},[200,116850,23870],{"class":210},[200,116852,215],{"class":214},[200,116854,59233],{"class":210},[200,116856,416],{"class":235},[200,116858,116859,116861,116863,116865,116867,116869,116871,116873,116875,116877,116879,116881,116883,116885,116887,116889,116891,116893,116895,116897,116899,116901,116903,116905,116907,116909,116911,116913,116915,116917,116919,116921,116923,116925,116927,116929,116931],{"class":202,"line":861},[200,116860,19216],{"class":206},[200,116862,47508],{"class":223},[200,116864,1532],{"class":235},[200,116866,215],{"class":214},[200,116868,47696],{"class":223},[200,116870,227],{"class":235},[200,116872,47588],{"class":210},[200,116874,215],{"class":214},[200,116876,58933],{"class":210},[200,116878,2089],{"class":235},[200,116880,215],{"class":214},[200,116882,47711],{"class":223},[200,116884,227],{"class":235},[200,116886,23870],{"class":210},[200,116888,215],{"class":214},[200,116890,59233],{"class":210},[200,116892,215],{"class":214},[200,116894,3526],{"class":223},[200,116896,227],{"class":235},[200,116898,3280],{"class":386},[200,116900,393],{"class":379},[200,116902,1095],{"class":235},[200,116904,2752],{"class":214},[200,116906,59655],{"class":235},[200,116908,239],{"class":214},[200,116910,3224],{"class":210},[200,116912,550],{"class":214},[200,116914,59664],{"class":235},[200,116916,239],{"class":214},[200,116918,56966],{"class":210},[200,116920,215],{"class":214},[200,116922,3280],{"class":210},[200,116924,981],{"class":214},[200,116926,59677],{"class":235},[200,116928,215],{"class":214},[200,116930,47761],{"class":223},[200,116932,466],{"class":235},[200,116934,116935],{"class":202,"line":890},[200,116936,529],{"emptyLinePlaceholder":20},[200,116938,116939,116941,116943,116945,116947,116949,116951],{"class":202,"line":900},[200,116940,3743],{"class":206},[200,116942,41071],{"class":223},[200,116944,227],{"class":235},[200,116946,25386],{"class":210},[200,116948,550],{"class":214},[200,116950,41080],{"class":2045},[200,116952,416],{"class":235},[200,116954,116955,116957],{"class":202,"line":905},[200,116956,413],{"class":214},[200,116958,416],{"class":210},[35,116960,116961],{},[1217,116962,59714],{},[1913,116964,59718],{"id":59717},[35,116966,116967],{},[1217,116968,59723],{},[35,116970,59726,116971,59729,116973,59732,116975,59735,116977,215],{},[156,116972,51759],{},[156,116974,47325],{},[156,116976,47331],{},[39,116978,59739],{"href":59738},[35,116980,59742,116981,59745,116983,59748,116985,59751],{},[156,116982,2461],{},[156,116984,58933],{},[156,116986,2461],{},[35,116988,59754],{},[192,116990,116991],{"className":26986,"code":59757,"language":26988,"meta":13,"style":13},[156,116992,116993,116997,117011,117029,117041,117073,117105,117109],{"__ignoreMap":13},[200,116994,116995],{"class":202,"line":203},[200,116996,230],{"class":214},[200,116998,116999,117001,117003,117005,117007,117009],{"class":202,"line":14},[200,117000,26999],{"class":214},[200,117002,3280],{"class":379},[200,117004,611],{"class":214},[200,117006,239],{"class":214},[200,117008,3938],{"class":2045},[200,117010,252],{"class":214},[200,117012,117013,117015,117017,117019,117021,117023,117025,117027],{"class":202,"line":255},[200,117014,26999],{"class":214},[200,117016,3566],{"class":379},[200,117018,611],{"class":214},[200,117020,239],{"class":214},[200,117022,27008],{"class":214},[200,117024,55603],{"class":245},[200,117026,611],{"class":214},[200,117028,252],{"class":214},[200,117030,117031,117033,117035,117037,117039],{"class":202,"line":272},[200,117032,26999],{"class":214},[200,117034,2461],{"class":379},[200,117036,611],{"class":214},[200,117038,239],{"class":214},[200,117040,1400],{"class":214},[200,117042,117043,117045,117047,117049,117051,117053,117055,117057,117059,117061,117063,117065,117067,117069,117071],{"class":202,"line":283},[200,117044,59812],{"class":214},[200,117046,27008],{"class":214},[200,117048,3280],{"class":1978},[200,117050,611],{"class":214},[200,117052,239],{"class":214},[200,117054,3938],{"class":2045},[200,117056,550],{"class":214},[200,117058,27008],{"class":214},[200,117060,20089],{"class":1978},[200,117062,611],{"class":214},[200,117064,239],{"class":214},[200,117066,27008],{"class":214},[200,117068,59837],{"class":245},[200,117070,611],{"class":214},[200,117072,333],{"class":214},[200,117074,117075,117077,117079,117081,117083,117085,117087,117089,117091,117093,117095,117097,117099,117101,117103],{"class":202,"line":300},[200,117076,59812],{"class":214},[200,117078,27008],{"class":214},[200,117080,3280],{"class":1978},[200,117082,611],{"class":214},[200,117084,239],{"class":214},[200,117086,59856],{"class":2045},[200,117088,550],{"class":214},[200,117090,27008],{"class":214},[200,117092,20089],{"class":1978},[200,117094,611],{"class":214},[200,117096,239],{"class":214},[200,117098,27008],{"class":214},[200,117100,59871],{"class":245},[200,117102,611],{"class":214},[200,117104,34880],{"class":214},[200,117106,117107],{"class":202,"line":310},[200,117108,13674],{"class":214},[200,117110,117111],{"class":202,"line":336},[200,117112,1130],{"class":214},[35,117114,59886,117115,59890],{},[156,117116,59889],{},[35,117118,59893,117119,59904],{},[39,117120,117122,59901,117124,6577],{"href":59896,"rel":117121},[77],[156,117123,59900],{},[156,117125,40340],{},[35,117127,59907,117128,59910,117130,59913],{},[156,117129,58933],{},[156,117131,58933],{},[192,117133,117134],{"className":1370,"code":59916,"language":1373,"meta":13,"style":13},[156,117135,117136,117140,117172,117176,117188,117194,117198,117202,117230,117244,117258,117272,117280,117294,117308,117322,117330,117336,117340,117344,117376,117380,117392],{"__ignoreMap":13},[200,117137,117138],{"class":202,"line":203},[200,117139,59923],{"class":401},[200,117141,117142,117144,117146,117148,117150,117152,117154,117156,117158,117160,117162,117164,117166,117168,117170],{"class":202,"line":14},[200,117143,1380],{"class":206},[200,117145,14301],{"class":379},[200,117147,59932],{"class":210},[200,117149,449],{"class":214},[200,117151,53456],{"class":223},[200,117153,59939],{"class":210},[200,117155,550],{"class":214},[200,117157,383],{"class":214},[200,117159,59946],{"class":386},[200,117161,550],{"class":214},[200,117163,53466],{"class":386},[200,117165,390],{"class":214},[200,117167,393],{"class":379},[200,117169,1095],{"class":210},[200,117171,230],{"class":214},[200,117173,117174],{"class":202,"line":255},[200,117175,59963],{"class":401},[200,117177,117178,117180,117182,117184,117186],{"class":202,"line":272},[200,117179,1395],{"class":235},[200,117181,239],{"class":214},[200,117183,53466],{"class":223},[200,117185,59974],{"class":210},[200,117187,252],{"class":214},[200,117189,117190,117192],{"class":202,"line":283},[200,117191,413],{"class":214},[200,117193,585],{"class":210},[200,117195,117196],{"class":202,"line":300},[200,117197,529],{"emptyLinePlaceholder":20},[200,117199,117200],{"class":202,"line":310},[200,117201,59991],{"class":401},[200,117203,117204,117206,117208,117210,117212,117214,117216,117218,117220,117222,117224,117226,117228],{"class":202,"line":336},[200,117205,1380],{"class":206},[200,117207,14301],{"class":379},[200,117209,60000],{"class":210},[200,117211,449],{"class":214},[200,117213,53456],{"class":223},[200,117215,60007],{"class":210},[200,117217,550],{"class":214},[200,117219,383],{"class":214},[200,117221,59946],{"class":386},[200,117223,390],{"class":214},[200,117225,393],{"class":379},[200,117227,1095],{"class":210},[200,117229,230],{"class":214},[200,117231,117232,117234,117236,117238,117240,117242],{"class":202,"line":342},[200,117233,60026],{"class":235},[200,117235,239],{"class":214},[200,117237,59946],{"class":223},[200,117239,51338],{"class":210},[200,117241,550],{"class":214},[200,117243,280],{"class":214},[200,117245,117246,117248,117250,117252,117254,117256],{"class":202,"line":365},[200,117247,60041],{"class":235},[200,117249,239],{"class":214},[200,117251,60046],{"class":210},[200,117253,215],{"class":214},[200,117255,60051],{"class":210},[200,117257,252],{"class":214},[200,117259,117260,117262,117264,117266,117268,117270],{"class":202,"line":371},[200,117261,60058],{"class":235},[200,117263,239],{"class":214},[200,117265,60063],{"class":210},[200,117267,215],{"class":214},[200,117269,60068],{"class":210},[200,117271,252],{"class":214},[200,117273,117274,117276,117278],{"class":202,"line":398},[200,117275,5080],{"class":214},[200,117277,2089],{"class":210},[200,117279,252],{"class":214},[200,117281,117282,117284,117286,117288,117290,117292],{"class":202,"line":405},[200,117283,15697],{"class":235},[200,117285,239],{"class":214},[200,117287,59946],{"class":223},[200,117289,59939],{"class":210},[200,117291,550],{"class":214},[200,117293,280],{"class":214},[200,117295,117296,117298,117300,117302,117304,117306],{"class":202,"line":410},[200,117297,60041],{"class":235},[200,117299,239],{"class":214},[200,117301,60046],{"class":210},[200,117303,215],{"class":214},[200,117305,60105],{"class":210},[200,117307,252],{"class":214},[200,117309,117310,117312,117314,117316,117318,117320],{"class":202,"line":861},[200,117311,60058],{"class":235},[200,117313,239],{"class":214},[200,117315,60116],{"class":210},[200,117317,215],{"class":214},[200,117319,60068],{"class":210},[200,117321,252],{"class":214},[200,117323,117324,117326,117328],{"class":202,"line":890},[200,117325,5080],{"class":214},[200,117327,2089],{"class":210},[200,117329,252],{"class":214},[200,117331,117332,117334],{"class":202,"line":900},[200,117333,413],{"class":214},[200,117335,585],{"class":210},[200,117337,117338],{"class":202,"line":905},[200,117339,529],{"emptyLinePlaceholder":20},[200,117341,117342],{"class":202,"line":931},[200,117343,60145],{"class":401},[200,117345,117346,117348,117350,117352,117354,117356,117358,117360,117362,117364,117366,117368,117370,117372,117374],{"class":202,"line":3640},[200,117347,1380],{"class":206},[200,117349,14301],{"class":379},[200,117351,60154],{"class":210},[200,117353,449],{"class":214},[200,117355,53456],{"class":223},[200,117357,51338],{"class":210},[200,117359,550],{"class":214},[200,117361,383],{"class":214},[200,117363,59946],{"class":386},[200,117365,550],{"class":214},[200,117367,53466],{"class":386},[200,117369,390],{"class":214},[200,117371,393],{"class":379},[200,117373,1095],{"class":210},[200,117375,230],{"class":214},[200,117377,117378],{"class":202,"line":3645},[200,117379,60183],{"class":401},[200,117381,117382,117384,117386,117388,117390],{"class":202,"line":4020},[200,117383,53479],{"class":235},[200,117385,239],{"class":214},[200,117387,53466],{"class":223},[200,117389,59974],{"class":210},[200,117391,252],{"class":214},[200,117393,117394,117396],{"class":202,"line":4475},[200,117395,413],{"class":214},[200,117397,585],{"class":210},[35,117399,60204,117400,60210],{},[39,117401,60209],{"href":60207,"rel":117402},[77],[35,117404,60213],{},[192,117406,117407],{"className":1370,"code":60216,"language":1373,"meta":13,"style":13},[156,117408,117409,117427,117457,117465,117475,117485,117493,117501,117509,117513,117517,117521,117527,117531,117537],{"__ignoreMap":13},[200,117410,117411,117413,117415,117417,117419,117421,117423,117425],{"class":202,"line":203},[200,117412,1380],{"class":206},[200,117414,1383],{"class":206},[200,117416,38960],{"class":223},[200,117418,227],{"class":210},[200,117420,3711],{"class":379},[200,117422,3714],{"class":214},[200,117424,393],{"class":379},[200,117426,280],{"class":214},[200,117428,117429,117431,117433,117435,117437,117439,117441,117443,117445,117447,117449,117451,117453,117455],{"class":202,"line":14},[200,117430,3723],{"class":379},[200,117432,54086],{"class":210},[200,117434,510],{"class":214},[200,117436,452],{"class":206},[200,117438,47508],{"class":223},[200,117440,1532],{"class":235},[200,117442,215],{"class":214},[200,117444,3509],{"class":210},[200,117446,215],{"class":214},[200,117448,51759],{"class":210},[200,117450,215],{"class":214},[200,117452,40340],{"class":223},[200,117454,227],{"class":235},[200,117456,230],{"class":214},[200,117458,117459,117461,117463],{"class":202,"line":255},[200,117460,60271],{"class":235},[200,117462,239],{"class":214},[200,117464,280],{"class":214},[200,117466,117467,117469,117471,117473],{"class":202,"line":272},[200,117468,60280],{"class":235},[200,117470,239],{"class":214},[200,117472,2424],{"class":2423},[200,117474,252],{"class":214},[200,117476,117477,117479,117481,117483],{"class":202,"line":283},[200,117478,60291],{"class":235},[200,117480,239],{"class":214},[200,117482,2424],{"class":2423},[200,117484,252],{"class":214},[200,117486,117487,117489,117491],{"class":202,"line":300},[200,117488,60302],{"class":235},[200,117490,239],{"class":214},[200,117492,280],{"class":214},[200,117494,117495,117497,117499],{"class":202,"line":310},[200,117496,32732],{"class":235},[200,117498,239],{"class":214},[200,117500,280],{"class":214},[200,117502,117503,117505,117507],{"class":202,"line":336},[200,117504,60319],{"class":235},[200,117506,239],{"class":214},[200,117508,22192],{"class":2423},[200,117510,117511],{"class":202,"line":342},[200,117512,4950],{"class":214},[200,117514,117515],{"class":202,"line":365},[200,117516,12083],{"class":214},[200,117518,117519],{"class":202,"line":371},[200,117520,339],{"class":214},[200,117522,117523,117525],{"class":202,"line":398},[200,117524,5080],{"class":214},[200,117526,416],{"class":235},[200,117528,117529],{"class":202,"line":405},[200,117530,529],{"emptyLinePlaceholder":20},[200,117532,117533,117535],{"class":202,"line":410},[200,117534,3743],{"class":206},[200,117536,54219],{"class":210},[200,117538,117539,117541],{"class":202,"line":861},[200,117540,413],{"class":214},[200,117542,416],{"class":210},[35,117544,60360,117545,60363],{},[156,117546,59900],{},[1913,117548,30681],{"id":60366},[35,117550,60369],{},[35,117552,60372],{},[1171,117554,60375],{},{"title":13,"searchDepth":14,"depth":14,"links":117556},[117557,117558],{"id":46042,"depth":14,"text":46043},{"id":46324,"depth":14,"text":46325},{},{"title":45992,"description":60381},{"loc":60383,"images":117562},[117563,117564,117565,117566,117567,117568,117569,117570,117571,117572,117573,117574,117575,117576,117577,117578,117579,117580,117581,117582,117583,117584,117585,117586],{"loc":46049},{"loc":46081},{"loc":46331},{"loc":46376},{"loc":46453},{"loc":46469},{"loc":46485},{"loc":46532},{"loc":47264},{"loc":47305},{"loc":47456},{"loc":48860},{"loc":50524},{"loc":50658},{"loc":50667},{"loc":51697},{"loc":52888},{"loc":54257},{"loc":56439},{"loc":57493},{"loc":57546},{"loc":58642},{"loc":58658},{"loc":58697},{"id":60415,"title":60416,"body":117588,"contentId":20826,"date":60845,"description":60846,"extension":18,"meta":117879,"navigation":20,"path":60848,"rawbody":60849,"robots":23,"schemaOrg":23,"seo":117880,"sitemap":117881,"stem":60859,"__hash__":60860},{"type":7,"value":117589,"toc":117872},[117590,117596,117601,117605],[35,117591,60421,117592,60425,117594,215],{},[6850,117593,60424],{},[6850,117595,60428],{},[35,117597,60431,117598,60436],{},[39,117599,60435],{"href":7143,"rel":117600},[77],[35,117602,117603],{},[1690,117604],{"alt":60441,"src":60442},[7139,117606,117607,117609,117615,117620,117626,117628,117738,117747,117749,117753,117758,117762,117766,117768,117770,117772,117774,117778,117782,117786,117790,117794,117798,117802,117806,117808,117812,117814,117816,117830,117832,117834,117836,117838,117840,117842],{"w-full":13,"icon":7141,"text":60445,"to":60446,"target":7144},[51,117608,6822],{"id":6821},[939,117610,117611],{},[35,117612,117613,60455],{},[200,117614,945],{},[35,117616,60458,117617,60464],{},[39,117618,60463],{"href":60461,"rel":117619},[77],[35,117621,60467,117622,1638,117624,60474],{},[6850,117623,60470],{},[6850,117625,60473],{},[35,117627,60477],{},[1576,117629,117630,117662,117698],{},[71,117631,60482,117632],{},[1576,117633,117634,117638,117642,117646,117650,117654,117658],{},[71,117635,117636,60490],{},[6850,117637,60489],{},[71,117639,117640,60496],{},[6850,117641,60495],{},[71,117643,117644,60502],{},[6850,117645,60501],{},[71,117647,117648,60508],{},[6850,117649,60507],{},[71,117651,117652,60514],{},[6850,117653,60513],{},[71,117655,117656,60520],{},[6850,117657,60519],{},[71,117659,117660,60526],{},[6850,117661,60525],{},[71,117663,60529,117664],{},[1576,117665,117666,117670,117674,117678,117682,117686,117690,117694],{},[71,117667,117668,60537],{},[6850,117669,60536],{},[71,117671,117672,60543],{},[6850,117673,60542],{},[71,117675,117676,60549],{},[6850,117677,60548],{},[71,117679,117680,60555],{},[6850,117681,60554],{},[71,117683,117684,60561],{},[6850,117685,60560],{},[71,117687,117688,60567],{},[6850,117689,60566],{},[71,117691,117692,60573],{},[6850,117693,60572],{},[71,117695,117696,60579],{},[6850,117697,60578],{},[71,117699,60582,117700],{},[1576,117701,117702,117706,117710,117714,117718,117722,117726,117730,117734],{},[71,117703,117704,60590],{},[6850,117705,60589],{},[71,117707,117708,60596],{},[6850,117709,60595],{},[71,117711,117712,60602],{},[6850,117713,60601],{},[71,117715,117716,60608],{},[6850,117717,60607],{},[71,117719,117720,60614],{},[6850,117721,60613],{},[71,117723,117724,60620],{},[6850,117725,60619],{},[71,117727,117728,60626],{},[6850,117729,60625],{},[71,117731,117732,60632],{},[6850,117733,60631],{},[71,117735,117736,60638],{},[6850,117737,60637],{},[939,117739,117740],{},[35,117741,117742,60645,117744,215],{},[200,117743,945],{},[39,117745,60650],{"href":60648,"rel":117746},[77],[35,117748,60653],{},[35,117750,60656,117751,60660],{},[6850,117752,60659],{},[35,117754,60663,117755,60667],{},[39,117756,20998],{"href":7143,"rel":117757},[77],[35,117759,60670,117760,60674],{},[6850,117761,60673],{},[35,117763,117764],{},[1690,117765],{"alt":60679,"src":60680},[1913,117767,60684],{"id":60683},[35,117769,60687],{},[1913,117771,60691],{"id":60690},[35,117773,60694],{},[35,117775,60697,117776,60701],{},[6850,117777,60700],{},[35,117779,117780],{},[1690,117781],{"alt":60706,"src":60707},[35,117783,60710,117784,60714],{},[6850,117785,60713],{},[35,117787,117788],{},[1690,117789],{"alt":60719,"src":60720},[35,117791,60723,117792,60726],{},[6850,117793,60470],{},[35,117795,60729,117796,60733],{},[6850,117797,60732],{},[35,117799,117800],{},[1690,117801],{"alt":60738,"src":60739},[35,117803,60742,117804,60746],{},[6850,117805,60745],{},[35,117807,60749],{},[35,117809,117810],{},[1690,117811],{"alt":60754,"src":60755},[51,117813,7206],{"id":7205},[35,117815,60760],{},[1576,117817,117818,117822,117826],{},[71,117819,117820,60768],{},[6850,117821,60767],{},[71,117823,117824,60774],{},[6850,117825,60773],{},[71,117827,117828,60780],{},[6850,117829,60779],{},[35,117831,60783],{},[35,117833,60786],{},[35,117835,60789],{},[35,117837,60792],{},[35,117839,60795],{},[35,117841,60798],{},[7905,117843,117844,117849],{"id":30890},[35,117845,60803,117846,215],{},[39,117847,20998],{"href":7143,"rel":117848},[77],[7139,117850,117851,117855,117857],{"w-full":13,"icon":7141,"text":60445,"to":60446,"target":7144},[35,117852,117853],{},[1217,117854,60813],{},[35,117856,60816],{},[60818,117858,117859,117861,117863,117868,117870],{},[51,117860,19334],{"id":19333},[35,117862,60824],{},[35,117864,60827,117865,60831],{},[39,117866,60435],{"href":7143,"rel":117867},[77],[35,117869,60834],{},[35,117871,60837],{},{"title":13,"searchDepth":14,"depth":14,"links":117873},[117874,117878],{"id":6821,"depth":14,"text":6822,"children":117875},[117876,117877],{"id":60683,"depth":255,"text":60684},{"id":60690,"depth":255,"text":60691},{"id":7205,"depth":14,"text":7206},{},{"title":60416,"description":60846},{"loc":60848,"images":117882},[117883,117884,117885,117886,117887,117888],{"loc":60442},{"loc":60680},{"loc":60707},{"loc":60720},{"loc":60739},{"loc":60755},{"id":60862,"title":60863,"body":117890,"contentId":30890,"date":61048,"description":61049,"extension":18,"meta":118011,"navigation":20,"path":61051,"rawbody":61052,"robots":23,"schemaOrg":23,"seo":118012,"sitemap":118013,"stem":61062,"__hash__":61063},{"type":7,"value":117891,"toc":118004},[117892,117894,117896,117900,117904,117906,117908,117910,117915,117920,117922,117924,117926,117931,117933,117938,117940,117942,117947,117953,117955,117957,117959,117963,117965,117969,117971,117975,117977,117981,117983,117985,117989,117991,117993,117995,118000],[35,117893,60868],{},[35,117895,60871],{},[35,117897,117898],{},[1690,117899],{"alt":60876,"src":60877},[35,117901,117902],{},[1217,117903,60882],{},[35,117905,60885],{},[51,117907,60889],{"id":60888},[35,117909,60892],{},[35,117911,60895,117912,60901],{},[39,117913,60900],{"href":60898,"rel":117914},[77],[35,117916,60904,117917,60910],{},[39,117918,60909],{"href":60907,"rel":117919},[77],[35,117921,60913],{},[51,117923,60917],{"id":60916},[35,117925,60920],{},[35,117927,60923,117928,60928],{},[39,117929,60927],{"href":35152,"rel":117930},[77],[35,117932,60931],{},[35,117934,60934,117935,60939],{},[39,117936,60938],{"href":7907,"rel":117937},[77],[51,117939,60943],{"id":60942},[35,117941,60946],{},[35,117943,60949,117944,60954],{},[39,117945,16108],{"href":60952,"rel":117946},[77],[35,117948,60957,117949,60961,117951,60965],{},[1217,117950,60960],{},[1217,117952,60964],{},[51,117954,60969],{"id":60968},[35,117956,60972],{},[35,117958,60975],{},[35,117960,117961],{},[1690,117962],{"alt":60980,"src":60981},[35,117964,60984],{},[35,117966,117967],{},[1690,117968],{"alt":60989,"src":60990},[35,117970,60993],{},[35,117972,117973],{},[1690,117974],{"alt":60998,"src":60999},[35,117976,61002],{},[35,117978,117979],{},[1690,117980],{"alt":61007,"src":61008},[35,117982,61011],{},[35,117984,61014],{},[35,117986,117987],{},[1690,117988],{"alt":61019,"src":61020},[51,117990,19334],{"id":19333},[35,117992,61025],{},[35,117994,61028],{},[35,117996,61031,117997,215],{},[39,117998,61035],{"href":36517,"rel":117999},[77],[35,118001,118002],{},[1217,118003,61040],{},{"title":13,"searchDepth":14,"depth":14,"links":118005},[118006,118007,118008,118009,118010],{"id":60888,"depth":14,"text":60889},{"id":60916,"depth":14,"text":60917},{"id":60942,"depth":14,"text":60943},{"id":60968,"depth":14,"text":60969},{"id":19333,"depth":14,"text":19334},{},{"title":60863,"description":61049},{"loc":61051,"images":118014},[118015,118016,118017,118018,118019,118020],{"loc":60877},{"loc":60981},{"loc":60990},{"loc":60999},{"loc":61008},{"loc":61020},{"id":61065,"title":61066,"body":118022,"contentId":63599,"date":63600,"description":63601,"extension":18,"meta":120362,"navigation":20,"path":63603,"rawbody":63604,"robots":23,"schemaOrg":23,"seo":120363,"sitemap":120364,"stem":63610,"__hash__":63611},{"type":7,"value":118023,"toc":120352},[118024,118028,118032,118034,118036,118038,118040,118044,118052,118056,118372,118378,118380,118390,118392,118396,118530,118532,118534,118536,118540,118822,118836,118838,118840,118842,119128,119134,119138,119362,119364,119366,119368,119372,119374,119376,119848,119854,119858,119864,119878,119884,119886,119890,119894,119896,120310,120316,120320,120324,120326,120330,120332,120334,120336,120346,120348,120350],[35,118025,61071,118026,61075],{},[6850,118027,61074],{},[35,118029,61078,118030,61083],{},[39,118031,61082],{"href":61081},[35,118033,61086],{},[51,118035,61090],{"id":61089},[35,118037,61093],{},[51,118039,61097],{"id":61096},[35,118041,61100,118042,61104],{},[156,118043,61103],{},[1576,118045,118046,118048,118050],{},[71,118047,61109],{},[71,118049,61112],{},[71,118051,61115],{},[35,118053,61118,118054,61121],{},[156,118055,28444],{},[192,118057,118058],{"className":25029,"code":61124,"language":25031,"meta":13,"style":13},[156,118059,118060,118078,118096,118114,118118,118132,118142,118146,118172,118176,118188,118214,118218,118248,118272,118276,118290,118300,118306,118310,118332,118366],{"__ignoreMap":13},[200,118061,118062,118064,118066,118068,118070,118072,118074,118076],{"class":202,"line":203},[200,118063,973],{"class":206},[200,118065,318],{"class":214},[200,118067,61135],{"class":210},[200,118069,981],{"class":214},[200,118071,984],{"class":206},[200,118073,242],{"class":214},[200,118075,61144],{"class":245},[200,118077,992],{"class":214},[200,118079,118080,118082,118084,118086,118088,118090,118092,118094],{"class":202,"line":14},[200,118081,973],{"class":206},[200,118083,318],{"class":214},[200,118085,61155],{"class":210},[200,118087,981],{"class":214},[200,118089,984],{"class":206},[200,118091,242],{"class":214},[200,118093,61164],{"class":245},[200,118095,992],{"class":214},[200,118097,118098,118100,118102,118104,118106,118108,118110,118112],{"class":202,"line":255},[200,118099,973],{"class":206},[200,118101,318],{"class":214},[200,118103,12462],{"class":210},[200,118105,981],{"class":214},[200,118107,984],{"class":206},[200,118109,242],{"class":214},[200,118111,12471],{"class":245},[200,118113,992],{"class":214},[200,118115,118116],{"class":202,"line":272},[200,118117,529],{"emptyLinePlaceholder":20},[200,118119,118120,118122,118124,118126,118128,118130],{"class":202,"line":283},[200,118121,443],{"class":379},[200,118123,61195],{"class":210},[200,118125,449],{"class":214},[200,118127,242],{"class":214},[200,118129,61202],{"class":245},[200,118131,992],{"class":214},[200,118133,118134,118136,118138,118140],{"class":202,"line":300},[200,118135,443],{"class":379},[200,118137,61211],{"class":210},[200,118139,449],{"class":214},[200,118141,61216],{"class":2045},[200,118143,118144],{"class":202,"line":310},[200,118145,529],{"emptyLinePlaceholder":20},[200,118147,118148,118150,118152,118154,118156,118158,118160,118162,118164,118166,118168,118170],{"class":202,"line":336},[200,118149,443],{"class":379},[200,118151,19823],{"class":210},[200,118153,449],{"class":214},[200,118155,61155],{"class":223},[200,118157,227],{"class":210},[200,118159,227],{"class":214},[200,118161,61237],{"class":386},[200,118163,550],{"class":214},[200,118165,10939],{"class":386},[200,118167,2089],{"class":214},[200,118169,393],{"class":379},[200,118171,280],{"class":214},[200,118173,118174],{"class":202,"line":342},[200,118175,61252],{"class":401},[200,118177,118178,118180,118182,118184,118186],{"class":202,"line":365},[200,118179,61257],{"class":210},[200,118181,215],{"class":214},[200,118183,39459],{"class":210},[200,118185,510],{"class":214},[200,118187,61266],{"class":2045},[200,118189,118190,118192,118194,118196,118198,118200,118202,118204,118206,118208,118210,118212],{"class":202,"line":371},[200,118191,61257],{"class":210},[200,118193,215],{"class":214},[200,118195,61275],{"class":223},[200,118197,227],{"class":235},[200,118199,249],{"class":214},[200,118201,61282],{"class":245},[200,118203,249],{"class":214},[200,118205,550],{"class":214},[200,118207,242],{"class":214},[200,118209,61291],{"class":245},[200,118211,249],{"class":214},[200,118213,416],{"class":235},[200,118215,118216],{"class":202,"line":398},[200,118217,529],{"emptyLinePlaceholder":20},[200,118219,118220,118222,118224,118226,118228,118230,118232,118234,118236,118238,118240,118242,118244,118246],{"class":202,"line":405},[200,118221,3723],{"class":379},[200,118223,61306],{"class":210},[200,118225,510],{"class":214},[200,118227,12462],{"class":223},[200,118229,227],{"class":235},[200,118231,15565],{"class":210},[200,118233,215],{"class":214},[200,118235,61319],{"class":223},[200,118237,1532],{"class":235},[200,118239,550],{"class":214},[200,118241,242],{"class":214},[200,118243,61328],{"class":245},[200,118245,249],{"class":214},[200,118247,416],{"class":235},[200,118249,118250,118252,118254,118256,118258,118260,118262,118264,118266,118268,118270],{"class":202,"line":410},[200,118251,3723],{"class":379},[200,118253,10276],{"class":210},[200,118255,510],{"class":214},[200,118257,61135],{"class":223},[200,118259,227],{"class":235},[200,118261,61347],{"class":210},[200,118263,550],{"class":214},[200,118265,242],{"class":214},[200,118267,14746],{"class":245},[200,118269,249],{"class":214},[200,118271,416],{"class":235},[200,118273,118274],{"class":202,"line":861},[200,118275,529],{"emptyLinePlaceholder":20},[200,118277,118278,118280,118282,118284,118286,118288],{"class":202,"line":890},[200,118279,61257],{"class":210},[200,118281,215],{"class":214},[200,118283,61370],{"class":223},[200,118285,227],{"class":235},[200,118287,593],{"class":210},[200,118289,416],{"class":235},[200,118291,118292,118294,118296,118298],{"class":202,"line":900},[200,118293,61257],{"class":210},[200,118295,215],{"class":214},[200,118297,28841],{"class":223},[200,118299,466],{"class":235},[200,118301,118302,118304],{"class":202,"line":905},[200,118303,413],{"class":214},[200,118305,416],{"class":210},[200,118307,118308],{"class":202,"line":931},[200,118309,529],{"emptyLinePlaceholder":20},[200,118311,118312,118314,118316,118318,118320,118322,118324,118326,118328,118330],{"class":202,"line":3640},[200,118313,3108],{"class":210},[200,118315,215],{"class":214},[200,118317,61405],{"class":223},[200,118319,61408],{"class":210},[200,118321,550],{"class":214},[200,118323,61413],{"class":210},[200,118325,550],{"class":214},[200,118327,3714],{"class":214},[200,118329,393],{"class":379},[200,118331,280],{"class":214},[200,118333,118334,118336,118338,118340,118342,118344,118346,118348,118350,118352,118354,118356,118358,118360,118362,118364],{"class":202,"line":3645},[200,118335,1113],{"class":210},[200,118337,215],{"class":214},[200,118339,1118],{"class":223},[200,118341,227],{"class":235},[200,118343,3318],{"class":214},[200,118345,61436],{"class":245},[200,118347,4853],{"class":214},[200,118349,61441],{"class":210},[200,118351,413],{"class":214},[200,118353,239],{"class":245},[200,118355,4853],{"class":214},[200,118357,61450],{"class":210},[200,118359,413],{"class":214},[200,118361,2882],{"class":245},[200,118363,3318],{"class":214},[200,118365,416],{"class":235},[200,118367,118368,118370],{"class":202,"line":4020},[200,118369,413],{"class":214},[200,118371,416],{"class":210},[35,118373,61467,118374,61470,118376,215],{},[156,118375,61328],{},[156,118377,61473],{},[35,118379,61476],{},[192,118381,118382],{"className":1355,"code":61479,"language":1358,"meta":13,"style":13},[156,118383,118384],{"__ignoreMap":13},[200,118385,118386,118388],{"class":202,"line":203},[200,118387,10026],{"class":1978},[200,118389,61488],{"class":245},[51,118391,61492],{"id":61491},[35,118393,61100,118394,61497],{},[156,118395,61328],{},[192,118397,118398],{"className":591,"code":61500,"language":593,"meta":13,"style":13},[156,118399,118400,118410,118428,118436,118454,118482,118498,118506,118514,118522],{"__ignoreMap":13},[200,118401,118402,118404,118406,118408],{"class":202,"line":203},[200,118403,61507],{"class":214},[200,118405,61510],{"class":235},[200,118407,10276],{"class":379},[200,118409,648],{"class":214},[200,118411,118412,118414,118416,118418,118420,118422,118424,118426],{"class":202,"line":14},[200,118413,600],{"class":214},[200,118415,593],{"class":235},[200,118417,14590],{"class":379},[200,118419,449],{"class":214},[200,118421,611],{"class":214},[200,118423,14597],{"class":245},[200,118425,611],{"class":214},[200,118427,648],{"class":214},[200,118429,118430,118432,118434],{"class":202,"line":255},[200,118431,657],{"class":214},[200,118433,14574],{"class":235},[200,118435,648],{"class":214},[200,118437,118438,118440,118442,118444,118446,118448,118450,118452],{"class":202,"line":272},[200,118439,804],{"class":214},[200,118441,2015],{"class":235},[200,118443,14739],{"class":379},[200,118445,449],{"class":214},[200,118447,611],{"class":214},[200,118449,61555],{"class":245},[200,118451,611],{"class":214},[200,118453,21487],{"class":214},[200,118455,118456,118458,118460,118462,118464,118466,118468,118470,118472,118474,118476,118478,118480],{"class":202,"line":283},[200,118457,804],{"class":214},[200,118459,2015],{"class":235},[200,118461,701],{"class":379},[200,118463,449],{"class":214},[200,118465,611],{"class":214},[200,118467,14794],{"class":245},[200,118469,611],{"class":214},[200,118471,6041],{"class":379},[200,118473,449],{"class":214},[200,118475,611],{"class":214},[200,118477,61584],{"class":245},[200,118479,611],{"class":214},[200,118481,21487],{"class":214},[200,118483,118484,118486,118488,118490,118492,118494,118496],{"class":202,"line":300},[200,118485,804],{"class":214},[200,118487,3566],{"class":235},[200,118489,675],{"class":214},[200,118491,61599],{"class":210},[200,118493,681],{"class":214},[200,118495,3566],{"class":235},[200,118497,648],{"class":214},[200,118499,118500,118502,118504],{"class":202,"line":310},[200,118501,893],{"class":214},[200,118503,14574],{"class":235},[200,118505,648],{"class":214},[200,118507,118508,118510,118512],{"class":202,"line":336},[200,118509,657],{"class":214},[200,118511,23870],{"class":235},[200,118513,648],{"class":214},[200,118515,118516,118518,118520],{"class":202,"line":342},[200,118517,893],{"class":214},[200,118519,23870],{"class":235},[200,118521,648],{"class":214},[200,118523,118524,118526,118528],{"class":202,"line":365},[200,118525,681],{"class":214},[200,118527,593],{"class":235},[200,118529,648],{"class":214},[35,118531,61640],{},[51,118533,61644],{"id":61643},[35,118535,61647],{},[35,118537,61650,118538,61654],{},[156,118539,61653],{},[192,118541,118542],{"className":25029,"code":61657,"language":25031,"meta":13,"style":13},[156,118543,118544,118548,118552,118578,118582,118606,118632,118658,118662,118666,118670,118676,118680,118692,118718,118722,118752,118776,118780,118794,118804,118808,118814,118818],{"__ignoreMap":13},[200,118545,118546],{"class":202,"line":203},[200,118547,33144],{"class":401},[200,118549,118550],{"class":202,"line":14},[200,118551,529],{"emptyLinePlaceholder":20},[200,118553,118554,118556,118558,118560,118562,118564,118566,118568,118570,118572,118574,118576],{"class":202,"line":255},[200,118555,443],{"class":379},[200,118557,19823],{"class":210},[200,118559,449],{"class":214},[200,118561,61155],{"class":223},[200,118563,227],{"class":210},[200,118565,227],{"class":214},[200,118567,61237],{"class":386},[200,118569,550],{"class":214},[200,118571,10939],{"class":386},[200,118573,2089],{"class":214},[200,118575,393],{"class":379},[200,118577,280],{"class":214},[200,118579,118580],{"class":202,"line":272},[200,118581,61698],{"class":401},[200,118583,118584,118586,118588,118590,118592,118594,118596,118598,118600,118602,118604],{"class":202,"line":283},[200,118585,20075],{"class":206},[200,118587,1095],{"class":235},[200,118589,61237],{"class":210},[200,118591,215],{"class":214},[200,118593,2020],{"class":210},[200,118595,3283],{"class":214},[200,118597,242],{"class":214},[200,118599,61653],{"class":245},[200,118601,249],{"class":214},[200,118603,5267],{"class":235},[200,118605,230],{"class":214},[200,118607,118608,118610,118612,118614,118616,118618,118620,118622,118624,118626,118628,118630],{"class":202,"line":300},[200,118609,61727],{"class":210},[200,118611,215],{"class":214},[200,118613,61275],{"class":223},[200,118615,227],{"class":235},[200,118617,249],{"class":214},[200,118619,61282],{"class":245},[200,118621,249],{"class":214},[200,118623,550],{"class":214},[200,118625,242],{"class":214},[200,118627,61746],{"class":245},[200,118629,249],{"class":214},[200,118631,416],{"class":235},[200,118633,118634,118636,118638,118640,118642,118644,118646,118648,118650,118652,118654,118656],{"class":202,"line":310},[200,118635,61727],{"class":210},[200,118637,215],{"class":214},[200,118639,61275],{"class":223},[200,118641,227],{"class":235},[200,118643,249],{"class":214},[200,118645,61765],{"class":245},[200,118647,249],{"class":214},[200,118649,550],{"class":214},[200,118651,242],{"class":214},[200,118653,61774],{"class":245},[200,118655,249],{"class":214},[200,118657,416],{"class":235},[200,118659,118660],{"class":202,"line":336},[200,118661,529],{"emptyLinePlaceholder":20},[200,118663,118664],{"class":202,"line":342},[200,118665,61787],{"class":401},[200,118667,118668],{"class":202,"line":365},[200,118669,4490],{"class":214},[200,118671,118672,118674],{"class":202,"line":371},[200,118673,45892],{"class":206},[200,118675,280],{"class":214},[200,118677,118678],{"class":202,"line":398},[200,118679,61802],{"class":401},[200,118681,118682,118684,118686,118688,118690],{"class":202,"line":405},[200,118683,61727],{"class":210},[200,118685,215],{"class":214},[200,118687,39459],{"class":210},[200,118689,510],{"class":214},[200,118691,61266],{"class":2045},[200,118693,118694,118696,118698,118700,118702,118704,118706,118708,118710,118712,118714,118716],{"class":202,"line":410},[200,118695,61727],{"class":210},[200,118697,215],{"class":214},[200,118699,61275],{"class":223},[200,118701,227],{"class":235},[200,118703,249],{"class":214},[200,118705,61282],{"class":245},[200,118707,249],{"class":214},[200,118709,550],{"class":214},[200,118711,242],{"class":214},[200,118713,61291],{"class":245},[200,118715,249],{"class":214},[200,118717,416],{"class":235},[200,118719,118720],{"class":202,"line":861},[200,118721,529],{"emptyLinePlaceholder":20},[200,118723,118724,118726,118728,118730,118732,118734,118736,118738,118740,118742,118744,118746,118748,118750],{"class":202,"line":890},[200,118725,1999],{"class":379},[200,118727,61306],{"class":210},[200,118729,510],{"class":214},[200,118731,12462],{"class":223},[200,118733,227],{"class":235},[200,118735,15565],{"class":210},[200,118737,215],{"class":214},[200,118739,61319],{"class":223},[200,118741,1532],{"class":235},[200,118743,550],{"class":214},[200,118745,242],{"class":214},[200,118747,61328],{"class":245},[200,118749,249],{"class":214},[200,118751,416],{"class":235},[200,118753,118754,118756,118758,118760,118762,118764,118766,118768,118770,118772,118774],{"class":202,"line":900},[200,118755,1999],{"class":379},[200,118757,10276],{"class":210},[200,118759,510],{"class":214},[200,118761,61135],{"class":223},[200,118763,227],{"class":235},[200,118765,61347],{"class":210},[200,118767,550],{"class":214},[200,118769,242],{"class":214},[200,118771,14746],{"class":245},[200,118773,249],{"class":214},[200,118775,416],{"class":235},[200,118777,118778],{"class":202,"line":905},[200,118779,529],{"emptyLinePlaceholder":20},[200,118781,118782,118784,118786,118788,118790,118792],{"class":202,"line":931},[200,118783,61727],{"class":210},[200,118785,215],{"class":214},[200,118787,61370],{"class":223},[200,118789,227],{"class":235},[200,118791,593],{"class":210},[200,118793,416],{"class":235},[200,118795,118796,118798,118800,118802],{"class":202,"line":3640},[200,118797,61727],{"class":210},[200,118799,215],{"class":214},[200,118801,28841],{"class":223},[200,118803,466],{"class":235},[200,118805,118806],{"class":202,"line":3645},[200,118807,4490],{"class":214},[200,118809,118810,118812],{"class":202,"line":4020},[200,118811,413],{"class":214},[200,118813,416],{"class":210},[200,118815,118816],{"class":202,"line":4475},[200,118817,529],{"emptyLinePlaceholder":20},[200,118819,118820],{"class":202,"line":4481},[200,118821,33144],{"class":401},[35,118823,61947,118824,61950,118826,3080,118828,61957,118830,61961,118832,61965,118834,61968],{},[156,118825,61653],{},[156,118827,61953],{},[156,118829,61956],{},[156,118831,61960],{},[6850,118833,61964],{},[156,118835,61960],{},[51,118837,61972],{"id":61971},[35,118839,61975],{},[35,118841,61978],{},[192,118843,118844],{"className":25029,"code":61981,"language":25031,"meta":13,"style":13},[156,118845,118846,118850,118854,118880,118884,118908,118912,118916,118920,118924,118950,118960,118964,118992,119000,119006,119010,119034,119038,119042,119046,119058,119084,119094,119100,119104,119110,119114,119118,119122],{"__ignoreMap":13},[200,118847,118848],{"class":202,"line":203},[200,118849,33144],{"class":401},[200,118851,118852],{"class":202,"line":14},[200,118853,529],{"emptyLinePlaceholder":20},[200,118855,118856,118858,118860,118862,118864,118866,118868,118870,118872,118874,118876,118878],{"class":202,"line":255},[200,118857,443],{"class":379},[200,118859,19823],{"class":210},[200,118861,449],{"class":214},[200,118863,61155],{"class":223},[200,118865,227],{"class":210},[200,118867,227],{"class":214},[200,118869,61237],{"class":386},[200,118871,550],{"class":214},[200,118873,10939],{"class":386},[200,118875,2089],{"class":214},[200,118877,393],{"class":379},[200,118879,280],{"class":214},[200,118881,118882],{"class":202,"line":272},[200,118883,62022],{"class":401},[200,118885,118886,118888,118890,118892,118894,118896,118898,118900,118902,118904,118906],{"class":202,"line":283},[200,118887,20075],{"class":206},[200,118889,1095],{"class":235},[200,118891,61237],{"class":210},[200,118893,215],{"class":214},[200,118895,2020],{"class":210},[200,118897,3283],{"class":214},[200,118899,242],{"class":214},[200,118901,61653],{"class":245},[200,118903,249],{"class":214},[200,118905,5267],{"class":235},[200,118907,230],{"class":214},[200,118909,118910],{"class":202,"line":300},[200,118911,13669],{"class":401},[200,118913,118914],{"class":202,"line":310},[200,118915,529],{"emptyLinePlaceholder":20},[200,118917,118918],{"class":202,"line":336},[200,118919,62059],{"class":401},[200,118921,118922],{"class":202,"line":342},[200,118923,4490],{"class":214},[200,118925,118926,118928,118930,118932,118934,118936,118938,118940,118942,118944,118946,118948],{"class":202,"line":365},[200,118927,45892],{"class":206},[200,118929,45895],{"class":206},[200,118931,1095],{"class":235},[200,118933,61237],{"class":210},[200,118935,215],{"class":214},[200,118937,2020],{"class":210},[200,118939,3283],{"class":214},[200,118941,242],{"class":214},[200,118943,62084],{"class":245},[200,118945,249],{"class":214},[200,118947,5267],{"class":235},[200,118949,230],{"class":214},[200,118951,118952,118954,118956,118958],{"class":202,"line":371},[200,118953,52339],{"class":379},[200,118955,41038],{"class":210},[200,118957,510],{"class":214},[200,118959,18875],{"class":214},[200,118961,118962],{"class":202,"line":398},[200,118963,529],{"emptyLinePlaceholder":20},[200,118965,118966,118968,118970,118972,118974,118976,118978,118980,118982,118984,118986,118988,118990],{"class":202,"line":405},[200,118967,62109],{"class":210},[200,118969,215],{"class":214},[200,118971,25370],{"class":223},[200,118973,227],{"class":235},[200,118975,249],{"class":214},[200,118977,12726],{"class":245},[200,118979,249],{"class":214},[200,118981,550],{"class":214},[200,118983,1095],{"class":214},[200,118985,3598],{"class":386},[200,118987,2089],{"class":214},[200,118989,393],{"class":379},[200,118991,280],{"class":214},[200,118993,118994,118996,118998],{"class":202,"line":410},[200,118995,42082],{"class":210},[200,118997,62140],{"class":214},[200,118999,62143],{"class":210},[200,119001,119002,119004],{"class":202,"line":861},[200,119003,2096],{"class":214},[200,119005,416],{"class":235},[200,119007,119008],{"class":202,"line":890},[200,119009,529],{"emptyLinePlaceholder":20},[200,119011,119012,119014,119016,119018,119020,119022,119024,119026,119028,119030,119032],{"class":202,"line":900},[200,119013,62109],{"class":210},[200,119015,215],{"class":214},[200,119017,25370],{"class":223},[200,119019,227],{"class":235},[200,119021,249],{"class":214},[200,119023,28841],{"class":245},[200,119025,249],{"class":214},[200,119027,550],{"class":214},[200,119029,3714],{"class":214},[200,119031,393],{"class":379},[200,119033,280],{"class":214},[200,119035,119036],{"class":202,"line":905},[200,119037,62182],{"class":401},[200,119039,119040],{"class":202,"line":931},[200,119041,529],{"emptyLinePlaceholder":20},[200,119043,119044],{"class":202,"line":3640},[200,119045,62191],{"class":401},[200,119047,119048,119050,119052,119054,119056],{"class":202,"line":3645},[200,119049,62196],{"class":210},[200,119051,215],{"class":214},[200,119053,39459],{"class":210},[200,119055,510],{"class":214},[200,119057,62205],{"class":2045},[200,119059,119060,119062,119064,119066,119068,119070,119072,119074,119076,119078,119080,119082],{"class":202,"line":4020},[200,119061,62196],{"class":210},[200,119063,215],{"class":214},[200,119065,61275],{"class":223},[200,119067,227],{"class":235},[200,119069,249],{"class":214},[200,119071,62220],{"class":245},[200,119073,249],{"class":214},[200,119075,550],{"class":214},[200,119077,242],{"class":214},[200,119079,2882],{"class":245},[200,119081,249],{"class":214},[200,119083,416],{"class":235},[200,119085,119086,119088,119090,119092],{"class":202,"line":4475},[200,119087,62196],{"class":210},[200,119089,215],{"class":214},[200,119091,28841],{"class":223},[200,119093,466],{"class":235},[200,119095,119096,119098],{"class":202,"line":4481},[200,119097,2096],{"class":214},[200,119099,416],{"class":235},[200,119101,119102],{"class":202,"line":4487},[200,119103,4490],{"class":214},[200,119105,119106,119108],{"class":202,"line":4493},[200,119107,45892],{"class":206},[200,119109,280],{"class":214},[200,119111,119112],{"class":202,"line":5454},[200,119113,61802],{"class":401},[200,119115,119116],{"class":202,"line":5459},[200,119117,13669],{"class":401},[200,119119,119120],{"class":202,"line":13227},[200,119121,4490],{"class":214},[200,119123,119124,119126],{"class":202,"line":13253},[200,119125,413],{"class":214},[200,119127,416],{"class":210},[35,119129,62279,119130,62282,119132,62285],{},[156,119131,62084],{},[156,119133,61328],{},[35,119135,30272,119136,62290],{},[156,119137,61328],{},[192,119139,119140],{"className":591,"code":62293,"language":593,"meta":13,"style":13},[156,119141,119142,119152,119170,119178,119196,119224,119240,119248,119256,119284,119312,119338,119346,119354],{"__ignoreMap":13},[200,119143,119144,119146,119148,119150],{"class":202,"line":203},[200,119145,61507],{"class":214},[200,119147,61510],{"class":235},[200,119149,10276],{"class":379},[200,119151,648],{"class":214},[200,119153,119154,119156,119158,119160,119162,119164,119166,119168],{"class":202,"line":14},[200,119155,600],{"class":214},[200,119157,593],{"class":235},[200,119159,14590],{"class":379},[200,119161,449],{"class":214},[200,119163,611],{"class":214},[200,119165,14597],{"class":245},[200,119167,611],{"class":214},[200,119169,648],{"class":214},[200,119171,119172,119174,119176],{"class":202,"line":255},[200,119173,657],{"class":214},[200,119175,14574],{"class":235},[200,119177,648],{"class":214},[200,119179,119180,119182,119184,119186,119188,119190,119192,119194],{"class":202,"line":272},[200,119181,804],{"class":214},[200,119183,2015],{"class":235},[200,119185,14739],{"class":379},[200,119187,449],{"class":214},[200,119189,611],{"class":214},[200,119191,61555],{"class":245},[200,119193,611],{"class":214},[200,119195,21487],{"class":214},[200,119197,119198,119200,119202,119204,119206,119208,119210,119212,119214,119216,119218,119220,119222],{"class":202,"line":283},[200,119199,804],{"class":214},[200,119201,2015],{"class":235},[200,119203,701],{"class":379},[200,119205,449],{"class":214},[200,119207,611],{"class":214},[200,119209,14794],{"class":245},[200,119211,611],{"class":214},[200,119213,6041],{"class":379},[200,119215,449],{"class":214},[200,119217,611],{"class":214},[200,119219,61584],{"class":245},[200,119221,611],{"class":214},[200,119223,21487],{"class":214},[200,119225,119226,119228,119230,119232,119234,119236,119238],{"class":202,"line":300},[200,119227,804],{"class":214},[200,119229,3566],{"class":235},[200,119231,675],{"class":214},[200,119233,61599],{"class":210},[200,119235,681],{"class":214},[200,119237,3566],{"class":235},[200,119239,648],{"class":214},[200,119241,119242,119244,119246],{"class":202,"line":310},[200,119243,893],{"class":214},[200,119245,14574],{"class":235},[200,119247,648],{"class":214},[200,119249,119250,119252,119254],{"class":202,"line":336},[200,119251,657],{"class":214},[200,119253,23870],{"class":235},[200,119255,648],{"class":214},[200,119257,119258,119260,119262,119264,119266,119268,119270,119272,119274,119276,119278,119280,119282],{"class":202,"line":342},[200,119259,804],{"class":214},[200,119261,603],{"class":235},[200,119263,40910],{"class":379},[200,119265,449],{"class":214},[200,119267,611],{"class":214},[200,119269,62084],{"class":245},[200,119271,611],{"class":214},[200,119273,6577],{"class":379},[200,119275,449],{"class":214},[200,119277,611],{"class":214},[200,119279,3778],{"class":245},[200,119281,611],{"class":214},[200,119283,648],{"class":214},[200,119285,119286,119288,119290,119292,119294,119296,119298,119300,119302,119304,119306,119308,119310],{"class":202,"line":365},[200,119287,22027],{"class":214},[200,119289,355],{"class":235},[200,119291,321],{"class":379},[200,119293,449],{"class":214},[200,119295,611],{"class":214},[200,119297,698],{"class":245},[200,119299,611],{"class":214},[200,119301,701],{"class":379},[200,119303,449],{"class":214},[200,119305,611],{"class":214},[200,119307,29574],{"class":245},[200,119309,611],{"class":214},[200,119311,21487],{"class":214},[200,119313,119314,119316,119318,119320,119322,119324,119326,119328,119330,119332,119334,119336],{"class":202,"line":371},[200,119315,22027],{"class":214},[200,119317,910],{"class":235},[200,119319,321],{"class":379},[200,119321,449],{"class":214},[200,119323,611],{"class":214},[200,119325,917],{"class":245},[200,119327,611],{"class":214},[200,119329,675],{"class":214},[200,119331,62486],{"class":210},[200,119333,681],{"class":214},[200,119335,910],{"class":235},[200,119337,648],{"class":214},[200,119339,119340,119342,119344],{"class":202,"line":398},[200,119341,15074],{"class":214},[200,119343,603],{"class":235},[200,119345,648],{"class":214},[200,119347,119348,119350,119352],{"class":202,"line":405},[200,119349,893],{"class":214},[200,119351,23870],{"class":235},[200,119353,648],{"class":214},[200,119355,119356,119358,119360],{"class":202,"line":410},[200,119357,681],{"class":214},[200,119359,593],{"class":235},[200,119361,648],{"class":214},[51,119363,62520],{"id":62519},[35,119365,62523],{},[35,119367,62526],{},[35,119369,119370],{},[1690,119371],{"alt":62531,"src":62532},[35,119373,62535],{},[35,119375,62538],{},[192,119377,119378],{"className":25029,"code":62541,"language":25031,"meta":13,"style":13},[156,119379,119380,119384,119388,119392,119406,119410,119436,119440,119464,119490,119516,119520,119534,119538,119562,119576,119582,119586,119590,119594,119620,119630,119634,119662,119670,119676,119680,119704,119708,119724,119750,119754,119758,119770,119796,119806,119812,119816,119822,119826,119830,119834,119840,119844],{"__ignoreMap":13},[200,119381,119382],{"class":202,"line":203},[200,119383,33144],{"class":401},[200,119385,119386],{"class":202,"line":14},[200,119387,529],{"emptyLinePlaceholder":20},[200,119389,119390],{"class":202,"line":255},[200,119391,62556],{"class":401},[200,119393,119394,119396,119398,119400,119402,119404],{"class":202,"line":272},[200,119395,443],{"class":379},[200,119397,62563],{"class":210},[200,119399,449],{"class":214},[200,119401,12997],{"class":214},[200,119403,15418],{"class":223},[200,119405,466],{"class":210},[200,119407,119408],{"class":202,"line":283},[200,119409,529],{"emptyLinePlaceholder":20},[200,119411,119412,119414,119416,119418,119420,119422,119424,119426,119428,119430,119432,119434],{"class":202,"line":300},[200,119413,443],{"class":379},[200,119415,19823],{"class":210},[200,119417,449],{"class":214},[200,119419,61155],{"class":223},[200,119421,227],{"class":210},[200,119423,227],{"class":214},[200,119425,61237],{"class":386},[200,119427,550],{"class":214},[200,119429,10939],{"class":386},[200,119431,2089],{"class":214},[200,119433,393],{"class":379},[200,119435,280],{"class":214},[200,119437,119438],{"class":202,"line":310},[200,119439,61698],{"class":401},[200,119441,119442,119444,119446,119448,119450,119452,119454,119456,119458,119460,119462],{"class":202,"line":336},[200,119443,20075],{"class":206},[200,119445,1095],{"class":235},[200,119447,61237],{"class":210},[200,119449,215],{"class":214},[200,119451,2020],{"class":210},[200,119453,3283],{"class":214},[200,119455,242],{"class":214},[200,119457,61653],{"class":245},[200,119459,249],{"class":214},[200,119461,5267],{"class":235},[200,119463,230],{"class":214},[200,119465,119466,119468,119470,119472,119474,119476,119478,119480,119482,119484,119486,119488],{"class":202,"line":342},[200,119467,61727],{"class":210},[200,119469,215],{"class":214},[200,119471,61275],{"class":223},[200,119473,227],{"class":235},[200,119475,249],{"class":214},[200,119477,61282],{"class":245},[200,119479,249],{"class":214},[200,119481,550],{"class":214},[200,119483,242],{"class":214},[200,119485,61746],{"class":245},[200,119487,249],{"class":214},[200,119489,416],{"class":235},[200,119491,119492,119494,119496,119498,119500,119502,119504,119506,119508,119510,119512,119514],{"class":202,"line":365},[200,119493,61727],{"class":210},[200,119495,215],{"class":214},[200,119497,61275],{"class":223},[200,119499,227],{"class":235},[200,119501,249],{"class":214},[200,119503,61765],{"class":245},[200,119505,249],{"class":214},[200,119507,550],{"class":214},[200,119509,242],{"class":214},[200,119511,61774],{"class":245},[200,119513,249],{"class":214},[200,119515,416],{"class":235},[200,119517,119518],{"class":202,"line":371},[200,119519,529],{"emptyLinePlaceholder":20},[200,119521,119522,119524,119526,119528,119530,119532],{"class":202,"line":398},[200,119523,62690],{"class":210},[200,119525,215],{"class":214},[200,119527,62695],{"class":223},[200,119529,227],{"class":235},[200,119531,10934],{"class":210},[200,119533,416],{"class":235},[200,119535,119536],{"class":202,"line":405},[200,119537,529],{"emptyLinePlaceholder":20},[200,119539,119540,119542,119544,119546,119548,119550,119552,119554,119556,119558,119560],{"class":202,"line":410},[200,119541,62109],{"class":210},[200,119543,215],{"class":214},[200,119545,25370],{"class":223},[200,119547,227],{"class":235},[200,119549,249],{"class":214},[200,119551,22565],{"class":245},[200,119553,249],{"class":214},[200,119555,550],{"class":214},[200,119557,3714],{"class":214},[200,119559,393],{"class":379},[200,119561,280],{"class":214},[200,119563,119564,119566,119568,119570,119572,119574],{"class":202,"line":861},[200,119565,62734],{"class":210},[200,119567,215],{"class":214},[200,119569,62739],{"class":223},[200,119571,227],{"class":235},[200,119573,10934],{"class":210},[200,119575,416],{"class":235},[200,119577,119578,119580],{"class":202,"line":890},[200,119579,2096],{"class":214},[200,119581,416],{"class":235},[200,119583,119584],{"class":202,"line":900},[200,119585,529],{"emptyLinePlaceholder":20},[200,119587,119588],{"class":202,"line":905},[200,119589,62059],{"class":401},[200,119591,119592],{"class":202,"line":931},[200,119593,4490],{"class":214},[200,119595,119596,119598,119600,119602,119604,119606,119608,119610,119612,119614,119616,119618],{"class":202,"line":3640},[200,119597,45892],{"class":206},[200,119599,45895],{"class":206},[200,119601,1095],{"class":235},[200,119603,61237],{"class":210},[200,119605,215],{"class":214},[200,119607,2020],{"class":210},[200,119609,3283],{"class":214},[200,119611,242],{"class":214},[200,119613,62084],{"class":245},[200,119615,249],{"class":214},[200,119617,5267],{"class":235},[200,119619,230],{"class":214},[200,119621,119622,119624,119626,119628],{"class":202,"line":3645},[200,119623,52339],{"class":379},[200,119625,41038],{"class":210},[200,119627,510],{"class":214},[200,119629,18875],{"class":214},[200,119631,119632],{"class":202,"line":4020},[200,119633,529],{"emptyLinePlaceholder":20},[200,119635,119636,119638,119640,119642,119644,119646,119648,119650,119652,119654,119656,119658,119660],{"class":202,"line":4475},[200,119637,62109],{"class":210},[200,119639,215],{"class":214},[200,119641,25370],{"class":223},[200,119643,227],{"class":235},[200,119645,249],{"class":214},[200,119647,12726],{"class":245},[200,119649,249],{"class":214},[200,119651,550],{"class":214},[200,119653,1095],{"class":214},[200,119655,3598],{"class":386},[200,119657,2089],{"class":214},[200,119659,393],{"class":379},[200,119661,280],{"class":214},[200,119663,119664,119666,119668],{"class":202,"line":4481},[200,119665,42082],{"class":210},[200,119667,62140],{"class":214},[200,119669,62143],{"class":210},[200,119671,119672,119674],{"class":202,"line":4487},[200,119673,2096],{"class":214},[200,119675,416],{"class":235},[200,119677,119678],{"class":202,"line":4493},[200,119679,529],{"emptyLinePlaceholder":20},[200,119681,119682,119684,119686,119688,119690,119692,119694,119696,119698,119700,119702],{"class":202,"line":5454},[200,119683,62109],{"class":210},[200,119685,215],{"class":214},[200,119687,25370],{"class":223},[200,119689,227],{"class":235},[200,119691,249],{"class":214},[200,119693,28841],{"class":245},[200,119695,249],{"class":214},[200,119697,550],{"class":214},[200,119699,3714],{"class":214},[200,119701,393],{"class":379},[200,119703,280],{"class":214},[200,119705,119706],{"class":202,"line":5459},[200,119707,62878],{"class":401},[200,119709,119710,119712,119714,119716,119718,119720,119722],{"class":202,"line":13227},[200,119711,62883],{"class":206},[200,119713,1095],{"class":235},[200,119715,443],{"class":379},[200,119717,62890],{"class":210},[200,119719,6445],{"class":214},[200,119721,62895],{"class":210},[200,119723,416],{"class":235},[200,119725,119726,119728,119730,119732,119734,119736,119738,119740,119742,119744,119746,119748],{"class":202,"line":13253},[200,119727,62902],{"class":210},[200,119729,215],{"class":214},[200,119731,61370],{"class":223},[200,119733,227],{"class":235},[200,119735,3318],{"class":214},[200,119737,62913],{"class":245},[200,119739,4853],{"class":214},[200,119741,23870],{"class":210},[200,119743,413],{"class":214},[200,119745,62922],{"class":210},[200,119747,3318],{"class":214},[200,119749,416],{"class":235},[200,119751,119752],{"class":202,"line":13269},[200,119753,529],{"emptyLinePlaceholder":20},[200,119755,119756],{"class":202,"line":13313},[200,119757,62191],{"class":401},[200,119759,119760,119762,119764,119766,119768],{"class":202,"line":13319},[200,119761,62196],{"class":210},[200,119763,215],{"class":214},[200,119765,39459],{"class":210},[200,119767,510],{"class":214},[200,119769,62205],{"class":2045},[200,119771,119772,119774,119776,119778,119780,119782,119784,119786,119788,119790,119792,119794],{"class":202,"line":13324},[200,119773,62196],{"class":210},[200,119775,215],{"class":214},[200,119777,61275],{"class":223},[200,119779,227],{"class":235},[200,119781,249],{"class":214},[200,119783,62220],{"class":245},[200,119785,249],{"class":214},[200,119787,550],{"class":214},[200,119789,242],{"class":214},[200,119791,2882],{"class":245},[200,119793,249],{"class":214},[200,119795,416],{"class":235},[200,119797,119798,119800,119802,119804],{"class":202,"line":13350},[200,119799,62196],{"class":210},[200,119801,215],{"class":214},[200,119803,28841],{"class":223},[200,119805,466],{"class":235},[200,119807,119808,119810],{"class":202,"line":13355},[200,119809,2096],{"class":214},[200,119811,416],{"class":235},[200,119813,119814],{"class":202,"line":13375},[200,119815,4490],{"class":214},[200,119817,119818,119820],{"class":202,"line":13395},[200,119819,45892],{"class":206},[200,119821,280],{"class":214},[200,119823,119824],{"class":202,"line":13400},[200,119825,61802],{"class":401},[200,119827,119828],{"class":202,"line":13408},[200,119829,13669],{"class":401},[200,119831,119832],{"class":202,"line":13426},[200,119833,4490],{"class":214},[200,119835,119836,119838],{"class":202,"line":13456},[200,119837,413],{"class":214},[200,119839,416],{"class":210},[200,119841,119842],{"class":202,"line":13462},[200,119843,529],{"emptyLinePlaceholder":20},[200,119845,119846],{"class":202,"line":13467},[200,119847,33144],{"class":401},[35,119849,63027,119850,63030,119852,63034],{},[156,119851,61653],{},[156,119853,63033],{},[35,119855,63037,119856,63040],{},[156,119857,61370],{},[35,119859,63043,119860,63046,119862,63049],{},[156,119861,62084],{},[156,119863,63033],{},[35,119865,63052,119866,63055,119868,63058,119870,63061,119872,63064,119874,63067,119876,215],{},[156,119867,12726],{},[156,119869,6086],{},[156,119871,12726],{},[156,119873,6086],{},[156,119875,12726],{},[156,119877,6086],{},[35,119879,63072,119880,63076,119882,215],{},[156,119881,63075],{},[156,119883,63079],{},[51,119885,63083],{"id":63082},[35,119887,63086,119888,215],{},[156,119889,61653],{},[35,119891,63091,119892,63095],{},[156,119893,63094],{},[35,119895,63098],{},[192,119897,119898],{"className":591,"code":63101,"language":593,"meta":13,"style":13},[156,119899,119900,119910,119928,119936,119954,119982,119998,120006,120010,120034,120038,120064,120068,120072,120092,120096,120122,120140,120144,120160,120164,120172,120180,120188,120200,120204,120232,120260,120286,120294,120302],{"__ignoreMap":13},[200,119901,119902,119904,119906,119908],{"class":202,"line":203},[200,119903,61507],{"class":214},[200,119905,61510],{"class":235},[200,119907,10276],{"class":379},[200,119909,648],{"class":214},[200,119911,119912,119914,119916,119918,119920,119922,119924,119926],{"class":202,"line":14},[200,119913,600],{"class":214},[200,119915,593],{"class":235},[200,119917,14590],{"class":379},[200,119919,449],{"class":214},[200,119921,611],{"class":214},[200,119923,14597],{"class":245},[200,119925,611],{"class":214},[200,119927,648],{"class":214},[200,119929,119930,119932,119934],{"class":202,"line":255},[200,119931,657],{"class":214},[200,119933,14574],{"class":235},[200,119935,648],{"class":214},[200,119937,119938,119940,119942,119944,119946,119948,119950,119952],{"class":202,"line":272},[200,119939,804],{"class":214},[200,119941,2015],{"class":235},[200,119943,14739],{"class":379},[200,119945,449],{"class":214},[200,119947,611],{"class":214},[200,119949,61555],{"class":245},[200,119951,611],{"class":214},[200,119953,21487],{"class":214},[200,119955,119956,119958,119960,119962,119964,119966,119968,119970,119972,119974,119976,119978,119980],{"class":202,"line":283},[200,119957,804],{"class":214},[200,119959,2015],{"class":235},[200,119961,701],{"class":379},[200,119963,449],{"class":214},[200,119965,611],{"class":214},[200,119967,14794],{"class":245},[200,119969,611],{"class":214},[200,119971,6041],{"class":379},[200,119973,449],{"class":214},[200,119975,611],{"class":214},[200,119977,61584],{"class":245},[200,119979,611],{"class":214},[200,119981,21487],{"class":214},[200,119983,119984,119986,119988,119990,119992,119994,119996],{"class":202,"line":300},[200,119985,804],{"class":214},[200,119987,3566],{"class":235},[200,119989,675],{"class":214},[200,119991,61599],{"class":210},[200,119993,681],{"class":214},[200,119995,3566],{"class":235},[200,119997,648],{"class":214},[200,119999,120000,120002,120004],{"class":202,"line":310},[200,120001,804],{"class":214},[200,120003,14892],{"class":235},[200,120005,648],{"class":214},[200,120007,120008],{"class":202,"line":336},[200,120009,63214],{"class":401},[200,120011,120012,120014,120016,120018,120020,120022,120024,120026,120028,120030,120032],{"class":202,"line":342},[200,120013,20113],{"class":379},[200,120015,63221],{"class":210},[200,120017,449],{"class":214},[200,120019,12997],{"class":214},[200,120021,63228],{"class":223},[200,120023,227],{"class":210},[200,120025,611],{"class":214},[200,120027,61653],{"class":245},[200,120029,611],{"class":214},[200,120031,2089],{"class":210},[200,120033,5932],{"class":214},[200,120035,120036],{"class":202,"line":365},[200,120037,529],{"emptyLinePlaceholder":20},[200,120039,120040,120042,120044,120046,120048,120050,120052,120054,120056,120058,120060,120062],{"class":202,"line":371},[200,120041,20113],{"class":379},[200,120043,63251],{"class":210},[200,120045,449],{"class":214},[200,120047,211],{"class":210},[200,120049,215],{"class":214},[200,120051,63260],{"class":223},[200,120053,227],{"class":210},[200,120055,611],{"class":214},[200,120057,1576],{"class":245},[200,120059,611],{"class":214},[200,120061,2089],{"class":210},[200,120063,5932],{"class":214},[200,120065,120066],{"class":202,"line":398},[200,120067,529],{"emptyLinePlaceholder":20},[200,120069,120070],{"class":202,"line":405},[200,120071,63281],{"class":401},[200,120073,120074,120076,120078,120080,120082,120084,120086,120088,120090],{"class":202,"line":410},[200,120075,63286],{"class":210},[200,120077,215],{"class":214},[200,120079,29409],{"class":223},[200,120081,510],{"class":214},[200,120083,1095],{"class":214},[200,120085,25386],{"class":386},[200,120087,2089],{"class":214},[200,120089,393],{"class":379},[200,120091,280],{"class":214},[200,120093,120094],{"class":202,"line":861},[200,120095,63307],{"class":401},[200,120097,120098,120100,120102,120104,120106,120108,120110,120112,120114,120116,120118,120120],{"class":202,"line":890},[200,120099,12989],{"class":379},[200,120101,63314],{"class":210},[200,120103,510],{"class":214},[200,120105,211],{"class":210},[200,120107,215],{"class":214},[200,120109,15944],{"class":223},[200,120111,227],{"class":235},[200,120113,611],{"class":214},[200,120115,71],{"class":245},[200,120117,611],{"class":214},[200,120119,2089],{"class":235},[200,120121,5932],{"class":214},[200,120123,120124,120126,120128,120130,120132,120134,120136,120138],{"class":202,"line":900},[200,120125,63339],{"class":210},[200,120127,215],{"class":214},[200,120129,63344],{"class":210},[200,120131,510],{"class":214},[200,120133,43652],{"class":210},[200,120135,215],{"class":214},[200,120137,12726],{"class":210},[200,120139,5932],{"class":214},[200,120141,120142],{"class":202,"line":905},[200,120143,529],{"emptyLinePlaceholder":20},[200,120145,120146,120148,120150,120152,120154,120156,120158],{"class":202,"line":931},[200,120147,63363],{"class":210},[200,120149,215],{"class":214},[200,120151,16014],{"class":223},[200,120153,227],{"class":235},[200,120155,71],{"class":210},[200,120157,2089],{"class":235},[200,120159,5932],{"class":214},[200,120161,120162],{"class":202,"line":3640},[200,120163,63380],{"class":214},[200,120165,120166,120168,120170],{"class":202,"line":3645},[200,120167,15074],{"class":214},[200,120169,14892],{"class":235},[200,120171,648],{"class":214},[200,120173,120174,120176,120178],{"class":202,"line":4020},[200,120175,893],{"class":214},[200,120177,14574],{"class":235},[200,120179,648],{"class":214},[200,120181,120182,120184,120186],{"class":202,"line":4475},[200,120183,657],{"class":214},[200,120185,23870],{"class":235},[200,120187,648],{"class":214},[200,120189,120190,120192,120194,120196,120198],{"class":202,"line":4481},[200,120191,804],{"class":214},[200,120193,1576],{"class":235},[200,120195,10192],{"class":214},[200,120197,1576],{"class":235},[200,120199,648],{"class":214},[200,120201,120202],{"class":202,"line":4487},[200,120203,529],{"emptyLinePlaceholder":20},[200,120205,120206,120208,120210,120212,120214,120216,120218,120220,120222,120224,120226,120228,120230],{"class":202,"line":4493},[200,120207,804],{"class":214},[200,120209,603],{"class":235},[200,120211,40910],{"class":379},[200,120213,449],{"class":214},[200,120215,611],{"class":214},[200,120217,62084],{"class":245},[200,120219,611],{"class":214},[200,120221,6577],{"class":379},[200,120223,449],{"class":214},[200,120225,611],{"class":214},[200,120227,3778],{"class":245},[200,120229,611],{"class":214},[200,120231,648],{"class":214},[200,120233,120234,120236,120238,120240,120242,120244,120246,120248,120250,120252,120254,120256,120258],{"class":202,"line":5454},[200,120235,22027],{"class":214},[200,120237,355],{"class":235},[200,120239,321],{"class":379},[200,120241,449],{"class":214},[200,120243,611],{"class":214},[200,120245,698],{"class":245},[200,120247,611],{"class":214},[200,120249,701],{"class":379},[200,120251,449],{"class":214},[200,120253,611],{"class":214},[200,120255,29574],{"class":245},[200,120257,611],{"class":214},[200,120259,21487],{"class":214},[200,120261,120262,120264,120266,120268,120270,120272,120274,120276,120278,120280,120282,120284],{"class":202,"line":5459},[200,120263,22027],{"class":214},[200,120265,910],{"class":235},[200,120267,321],{"class":379},[200,120269,449],{"class":214},[200,120271,611],{"class":214},[200,120273,917],{"class":245},[200,120275,611],{"class":214},[200,120277,675],{"class":214},[200,120279,62486],{"class":210},[200,120281,681],{"class":214},[200,120283,910],{"class":235},[200,120285,648],{"class":214},[200,120287,120288,120290,120292],{"class":202,"line":13227},[200,120289,15074],{"class":214},[200,120291,603],{"class":235},[200,120293,648],{"class":214},[200,120295,120296,120298,120300],{"class":202,"line":13253},[200,120297,893],{"class":214},[200,120299,23870],{"class":235},[200,120301,648],{"class":214},[200,120303,120304,120306,120308],{"class":202,"line":13269},[200,120305,681],{"class":214},[200,120307,593],{"class":235},[200,120309,648],{"class":214},[35,120311,63529,120312,63532,120314,63535],{},[156,120313,61653],{},[156,120315,63094],{},[35,120317,63538,120318,63541],{},[156,120319,29409],{},[35,120321,63544,120322,63547],{},[156,120323,71],{},[35,120325,63550],{},[35,120327,120328],{},[1690,120329],{"alt":63555,"src":63556},[51,120331,63560],{"id":63559},[35,120333,63563],{},[35,120335,63566],{},[1576,120337,120338,120342],{},[71,120339,12142,120340,63573],{},[156,120341,63094],{},[71,120343,63576,120344,63579],{},[156,120345,61953],{},[35,120347,63582],{},[35,120349,63585],{},[1171,120351,63588],{},{"title":13,"searchDepth":14,"depth":14,"links":120353},[120354,120355,120356,120357,120358,120359,120360,120361],{"id":61089,"depth":14,"text":61090},{"id":61096,"depth":14,"text":61097},{"id":61491,"depth":14,"text":61492},{"id":61643,"depth":14,"text":61644},{"id":61971,"depth":14,"text":61972},{"id":62519,"depth":14,"text":62520},{"id":63082,"depth":14,"text":63083},{"id":63559,"depth":14,"text":63560},{},{"title":61066,"description":63601},{"loc":63603,"images":120365},[120366,120367],{"loc":62532},{"loc":63556},{"id":63613,"title":63614,"body":120369,"contentId":63890,"date":63891,"description":63892,"extension":18,"meta":120549,"navigation":20,"path":61081,"rawbody":63894,"robots":23,"schemaOrg":23,"seo":120550,"sitemap":120551,"stem":63901,"__hash__":63902},{"type":7,"value":120370,"toc":120540},[120371,120377,120381,120387,120389,120393,120401,120405,120407,120409,120425,120427,120431,120433,120437,120441,120445,120449,120453,120457,120461,120465,120467,120475,120483,120487,120489,120493,120497,120501,120505,120511,120515,120519],[35,120372,63619,120373,63623,120375,63627],{},[6850,120374,63622],{},[6850,120376,63626],{},[35,120378,120379],{},[1217,120380,63632],{},[35,120382,63635,120383,63638,120385,63642],{},[6850,120384,33634],{},[6850,120386,63641],{},[51,120388,63646],{"id":63645},[35,120390,63649,120391,63653],{},[6850,120392,63652],{},[35,120394,63656,120395,63660,120397,63664,120399],{},[6850,120396,63659],{},[6850,120398,63663],{},[1582,120400,215],{"repo":63667},[35,120402,120403],{},[1217,120404,63672],{},[51,120406,63676],{"id":63675},[35,120408,63679],{},[35,120410,63682,120411,63690,120416,63694,120418,63697,120420,215],{},[6850,120412,120413],{},[39,120414,63689],{"href":63687,"rel":120415},[77],[6850,120417,63693],{},[6850,120419,17986],{},[6850,120421,120422],{},[39,120423,63704],{"href":63702,"rel":120424},[77],[35,120426,63707],{},[35,120428,120429],{},[1217,120430,63712],{},[51,120432,63716],{"id":63715},[35,120434,63719,120435,63723],{},[6850,120436,63722],{},[35,120438,120439],{},[1690,120440],{"alt":63722,"src":63728},[35,120442,120443,63733],{},[6850,120444,63659],{},[35,120446,120447],{},[1690,120448],{"alt":63659,"src":63738},[35,120450,63741,120451,63744],{},[6850,120452,33630],{},[35,120454,63747,120455,63751],{},[6850,120456,63750],{},[35,120458,63754,120459,63758],{},[6850,120460,63757],{},[35,120462,120463],{},[1217,120464,63763],{},[51,120466,63767],{"id":63766},[35,120468,63770,120469,63775,120472,63780],{},[39,120470,60909],{"href":63773,"rel":120471},[77],[39,120473,30093],{"href":63778,"rel":120474},[77],[35,120476,63783,120477,3076,120479,63790,120481,63794],{},[6850,120478,63786],{},[6850,120480,63789],{},[6850,120482,63793],{},[35,120484,63797,120485,215],{},[1217,120486,63800],{},[1913,120488,63804],{"id":63803},[35,120490,120491,63810],{},[6850,120492,63809],{},[35,120494,120495],{},[1690,120496],{"alt":63815,"src":63816},[35,120498,63819,120499,63822],{},[156,120500,63094],{},[35,120502,63825,120503,63828],{},[6850,120504,63750],{},[35,120506,63831,120507,13731,120509,63838],{},[156,120508,63834],{},[156,120510,63837],{},[35,120512,120513],{},[1217,120514,63843],{},[35,120516,63846,120517,215],{},[6850,120518,63849],{},[7905,120520,120521,120525,120527,120531,120533],{"text":63852,"href":63853},[35,120522,120523],{},[1217,120524,63858],{},[51,120526,19334],{"id":19333},[35,120528,63863,120529],{},[1217,120530,63866],{},[35,120532,63869],{},[35,120534,120535],{},[1217,120536,63874,120537,63880],{},[39,120538,63879],{"href":63877,"rel":120539},[77],{"title":13,"searchDepth":14,"depth":14,"links":120541},[120542,120543,120544,120545,120548],{"id":63645,"depth":14,"text":63646},{"id":63675,"depth":14,"text":63676},{"id":63715,"depth":14,"text":63716},{"id":63766,"depth":14,"text":63767,"children":120546},[120547],{"id":63803,"depth":255,"text":63804},{"id":19333,"depth":14,"text":19334},{},{"title":63614,"description":63892},{"loc":61081,"images":120552},[120553,120554,120555],{"loc":63728},{"loc":63738},{"loc":63816},{"id":63904,"title":63905,"body":120557,"contentId":30815,"date":64120,"description":64121,"extension":18,"meta":120694,"navigation":20,"path":64123,"rawbody":64124,"robots":23,"schemaOrg":23,"seo":120695,"sitemap":120696,"stem":64127,"__hash__":64128},{"type":7,"value":120558,"toc":120686},[120559,120561,120563,120570,120578,120584,120586,120590,120592,120596,120598,120603,120605,120609,120613,120615,120617,120619,120621,120623,120627,120629,120631,120633,120635,120637,120642,120646,120650,120652,120654,120659,120663,120665,120667,120673,120678,120682,120684],[35,120560,63910],{},[51,120562,63914],{"id":63913},[35,120564,63917,120565],{},[1582,120566,63921,120567,215],{"repo":63920},[39,120568,33630],{"href":63924,"rel":120569},[77],[35,120571,63928,120572],{},[1582,120573,63932,120574],{"repo":63931},[1582,120575,63936,120576],{"repo":63935},[1217,120577,63939],{},[35,120579,63942,120580,63946,120582,63950],{},[6850,120581,63945],{},[6850,120583,63949],{},[51,120585,63954],{"id":63953},[35,120587,120588],{},[1217,120589,63959],{},[35,120591,63962],{},[35,120593,63965,120594],{},[1217,120595,63968],{},[35,120597,63971],{},[35,120599,63974,120600,63980],{},[39,120601,63979],{"href":63977,"rel":120602},[77],[35,120604,63983],{},[35,120606,63986,120607,215],{},[6850,120608,63989],{},[35,120610,120611],{},[1217,120612,63994],{},[51,120614,63998],{"id":63997},[35,120616,64001],{},[35,120618,64004],{},[35,120620,64007],{},[35,120622,64010],{},[35,120624,64013,120625,215],{},[6850,120626,64016],{},[51,120628,64020],{"id":64019},[35,120630,64023],{},[35,120632,64026],{},[35,120634,64029],{},[35,120636,64032],{},[35,120638,64035,120639,64040],{},[39,120640,64039],{"href":31330,"rel":120641},[77],[35,120643,64043,120644,64047],{},[6850,120645,64046],{},[35,120647,64050,120648],{},[1217,120649,64053],{},[51,120651,64057],{"id":64056},[35,120653,64060],{},[35,120655,64063,120656,64069],{},[39,120657,64068],{"href":64066,"rel":120658},[77],[939,120660,120661],{},[35,120662,64074],{},[35,120664,64077],{},[51,120666,64081],{"id":64080},[35,120668,64084,120669,64088,120671,64092],{},[6850,120670,64087],{},[6850,120672,64091],{},[35,120674,64095,120675,64099],{},[39,120676,18624],{"href":34562,"rel":120677},[77],[35,120679,64102,120680],{},[1217,120681,64105],{},[35,120683,64108],{},[35,120685,64111],{},{"title":13,"searchDepth":14,"depth":14,"links":120687},[120688,120689,120690,120691,120692,120693],{"id":63913,"depth":14,"text":63914},{"id":63953,"depth":14,"text":63954},{"id":63997,"depth":14,"text":63998},{"id":64019,"depth":14,"text":64020},{"id":64056,"depth":14,"text":64057},{"id":64080,"depth":14,"text":64081},{},{"title":63905,"description":64121},{"loc":64123},{"id":64130,"title":64131,"body":120698,"contentId":30821,"date":66934,"description":66935,"extension":18,"meta":123207,"navigation":20,"path":66937,"rawbody":66938,"robots":23,"schemaOrg":23,"seo":123208,"sitemap":123209,"stem":66941,"__hash__":66942},{"type":7,"value":120699,"toc":123200},[120700,120714,120718,120724,120733,120741,120743,120783,120785,120789,120803,120807,120823,120827,120839,120844,120846,120848,120862,120864,120874,121266,121272,121278,121282,121472,121478,121480,121484,121496,121508,122024,122032,122040,122048,122058,123198],[35,120701,64136,120702,64140,120704,64147,120709,215],{},[6850,120703,64139],{},[39,120705,120707],{"href":64143,"rel":120706},[77],[6850,120708,36816],{},[39,120710,120712],{"href":64150,"rel":120711},[77],[6850,120713,38775],{},[35,120715,64156,120716],{},[1582,120717,215],{"repo":64159},[35,120719,120720,64164,120722,64168],{},[6850,120721,36816],{},[6850,120723,64167],{},[35,120725,120726,64173,120728,64180],{},[6850,120727,38775],{},[39,120729,120731],{"href":38788,"rel":120730},[77],[6850,120732,64179],{},[35,120734,64183,120735,64186,120737,64189,120739,64193],{},[6850,120736,64139],{},[6850,120738,38790],{},[6850,120740,64192],{},[35,120742,64196],{},[1576,120744,120745,120756,120765,120776],{},[71,120746,120747,64208,120752,64211,120754,215],{},[39,120748,120750],{"href":64203,"rel":120749},[77],[6850,120751,64207],{},[6850,120753,64179],{},[6850,120755,64214],{},[71,120757,120758,64224,120763,64227],{},[39,120759,120761],{"href":64219,"rel":120760},[77],[6850,120762,64223],{},[6850,120764,53003],{},[71,120766,120767,64237,120772,35697,120774,215],{},[39,120768,120770],{"href":64232,"rel":120769},[77],[6850,120771,64236],{},[6850,120773,64240],{},[6850,120775,64243],{},[71,120777,120778,64253],{},[39,120779,120781],{"href":64248,"rel":120780},[77],[6850,120782,64252],{},[51,120784,64257],{"id":64256},[35,120786,64260,120787,239],{},[6850,120788,64263],{},[192,120790,120791],{"className":2151,"code":64266,"language":2153,"meta":13,"style":13},[156,120792,120793],{"__ignoreMap":13},[200,120794,120795,120797,120799,120801],{"class":202,"line":203},[200,120796,31244],{"class":1978},[200,120798,64275],{"class":245},[200,120800,64278],{"class":245},[200,120802,64281],{"class":245},[35,120804,64284,120805,239],{},[6850,120806,64287],{},[192,120808,120809],{"className":2151,"code":64290,"language":2153,"meta":13,"style":13},[156,120810,120811,120817],{"__ignoreMap":13},[200,120812,120813,120815],{"class":202,"line":203},[200,120814,36292],{"class":223},[200,120816,64281],{"class":245},[200,120818,120819,120821],{"class":202,"line":14},[200,120820,36332],{"class":1978},[200,120822,36172],{"class":245},[35,120824,64307,120825,64311],{},[6850,120826,64310],{},[192,120828,120829],{"className":2151,"code":64314,"language":2153,"meta":13,"style":13},[156,120830,120831],{"__ignoreMap":13},[200,120832,120833,120835,120837],{"class":202,"line":203},[200,120834,36332],{"class":1978},[200,120836,32709],{"class":245},[200,120838,13695],{"class":245},[35,120840,64327,120841,64332],{},[39,120842,64330],{"href":64330,"rel":120843},[77],[51,120845,64336],{"id":64335},[35,120847,64339],{},[192,120849,120850],{"className":2151,"code":64342,"language":2153,"meta":13,"style":13},[156,120851,120852],{"__ignoreMap":13},[200,120853,120854,120856,120858,120860],{"class":202,"line":203},[200,120855,36332],{"class":1978},[200,120857,12288],{"class":245},[200,120859,64353],{"class":245},[200,120861,64356],{"class":245},[1913,120863,64360],{"id":64359},[35,120865,32355,120866,64365,120868,64369,120870,64373,120872,64377],{},[6850,120867,56660],{},[156,120869,64368],{},[156,120871,64372],{},[6850,120873,64376],{},[192,120875,120876],{"className":64380,"code":64381,"filename":64382,"language":64383,"meta":13,"style":13},[156,120877,120878,120906,120924,120928,120944,120958,120964,120972,120980,120996,121004,121020,121048,121096,121122,121130,121138,121142,121146,121166,121194,121198,121212,121218,121224,121230,121234,121248,121254,121260],{"__ignoreMap":13},[200,120879,120880,120882,120884,120886,120888,120890,120892,120894,120896,120898,120900,120902,120904],{"class":202,"line":203},[200,120881,973],{"class":206},[200,120883,318],{"class":214},[200,120885,64394],{"class":210},[200,120887,550],{"class":214},[200,120889,64399],{"class":210},[200,120891,550],{"class":214},[200,120893,64404],{"class":210},[200,120895,981],{"class":214},[200,120897,984],{"class":206},[200,120899,242],{"class":214},[200,120901,64413],{"class":245},[200,120903,249],{"class":214},[200,120905,64418],{"class":401},[200,120907,120908,120910,120912,120914,120916,120918,120920,120922],{"class":202,"line":14},[200,120909,973],{"class":206},[200,120911,318],{"class":214},[200,120913,64427],{"class":210},[200,120915,981],{"class":214},[200,120917,984],{"class":206},[200,120919,242],{"class":214},[200,120921,64436],{"class":245},[200,120923,992],{"class":214},[200,120925,120926],{"class":202,"line":255},[200,120927,529],{"emptyLinePlaceholder":20},[200,120929,120930,120932,120934,120936,120938,120940,120942],{"class":202,"line":272},[200,120931,1380],{"class":206},[200,120933,1383],{"class":206},[200,120935,64451],{"class":223},[200,120937,227],{"class":210},[200,120939,1532],{"class":214},[200,120941,393],{"class":379},[200,120943,280],{"class":214},[200,120945,120946,120948,120950,120952,120954,120956],{"class":202,"line":283},[200,120947,3723],{"class":379},[200,120949,64466],{"class":210},[200,120951,510],{"class":214},[200,120953,3714],{"class":214},[200,120955,393],{"class":379},[200,120957,280],{"class":214},[200,120959,120960,120962],{"class":202,"line":300},[200,120961,3296],{"class":206},[200,120963,64481],{"class":235},[200,120965,120966,120968,120970],{"class":202,"line":310},[200,120967,22027],{"class":214},[200,120969,11386],{"class":235},[200,120971,648],{"class":214},[200,120973,120974,120976,120978],{"class":202,"line":336},[200,120975,49602],{"class":214},[200,120977,64496],{"class":1978},[200,120979,648],{"class":214},[200,120981,120982,120984,120986,120988,120990,120992,120994],{"class":202,"line":342},[200,120983,49629],{"class":214},[200,120985,3566],{"class":235},[200,120987,675],{"class":214},[200,120989,64509],{"class":210},[200,120991,681],{"class":214},[200,120993,3566],{"class":235},[200,120995,648],{"class":214},[200,120997,120998,121000,121002],{"class":202,"line":365},[200,120999,49710],{"class":214},[200,121001,64496],{"class":1978},[200,121003,648],{"class":214},[200,121005,121006,121008,121010,121012,121014,121016,121018],{"class":202,"line":371},[200,121007,49602],{"class":214},[200,121009,51],{"class":235},[200,121011,675],{"class":214},[200,121013,64534],{"class":210},[200,121015,681],{"class":214},[200,121017,51],{"class":235},[200,121019,648],{"class":214},[200,121021,121022,121024,121026,121028,121030,121032,121034,121036,121038,121040,121042,121044,121046],{"class":202,"line":398},[200,121023,49602],{"class":214},[200,121025,603],{"class":235},[200,121027,40910],{"class":379},[200,121029,449],{"class":214},[200,121031,611],{"class":214},[200,121033,64555],{"class":245},[200,121035,611],{"class":214},[200,121037,6577],{"class":379},[200,121039,449],{"class":214},[200,121041,611],{"class":214},[200,121043,18766],{"class":245},[200,121045,611],{"class":214},[200,121047,648],{"class":214},[200,121049,121050,121052,121054,121056,121058,121060,121062,121064,121066,121068,121070,121072,121074,121076,121078,121080,121082,121084,121086,121088,121090,121092,121094],{"class":202,"line":405},[200,121051,49629],{"class":214},[200,121053,355],{"class":235},[200,121055,321],{"class":379},[200,121057,449],{"class":214},[200,121059,611],{"class":214},[200,121061,2020],{"class":245},[200,121063,611],{"class":214},[200,121065,701],{"class":379},[200,121067,449],{"class":214},[200,121069,611],{"class":214},[200,121071,2020],{"class":245},[200,121073,611],{"class":214},[200,121075,40940],{"class":379},[200,121077,449],{"class":214},[200,121079,611],{"class":214},[200,121081,64604],{"class":245},[200,121083,611],{"class":214},[200,121085,64609],{"class":379},[200,121087,449],{"class":214},[200,121089,611],{"class":214},[200,121091,64616],{"class":245},[200,121093,611],{"class":214},[200,121095,21487],{"class":214},[200,121097,121098,121100,121102,121104,121106,121108,121110,121112,121114,121116,121118,121120],{"class":202,"line":410},[200,121099,49629],{"class":214},[200,121101,910],{"class":235},[200,121103,321],{"class":379},[200,121105,449],{"class":214},[200,121107,611],{"class":214},[200,121109,917],{"class":245},[200,121111,611],{"class":214},[200,121113,675],{"class":214},[200,121115,64641],{"class":210},[200,121117,681],{"class":214},[200,121119,910],{"class":235},[200,121121,648],{"class":214},[200,121123,121124,121126,121128],{"class":202,"line":861},[200,121125,49710],{"class":214},[200,121127,603],{"class":235},[200,121129,648],{"class":214},[200,121131,121132,121134,121136],{"class":202,"line":890},[200,121133,43992],{"class":214},[200,121135,11386],{"class":235},[200,121137,648],{"class":214},[200,121139,121140],{"class":202,"line":900},[200,121141,3854],{"class":235},[200,121143,121144],{"class":202,"line":905},[200,121145,4490],{"class":214},[200,121147,121148,121150,121152,121154,121156,121158,121160,121162,121164],{"class":202,"line":931},[200,121149,3723],{"class":379},[200,121151,14330],{"class":210},[200,121153,510],{"class":214},[200,121155,64404],{"class":223},[200,121157,227],{"class":235},[200,121159,600],{"class":214},[200,121161,64688],{"class":1978},[200,121163,22055],{"class":214},[200,121165,416],{"class":235},[200,121167,121168,121170,121172,121174,121176,121178,121180,121182,121184,121186,121188,121190,121192],{"class":202,"line":3640},[200,121169,3723],{"class":379},[200,121171,318],{"class":214},[200,121173,41038],{"class":210},[200,121175,550],{"class":214},[200,121177,64705],{"class":210},[200,121179,981],{"class":214},[200,121181,510],{"class":214},[200,121183,64399],{"class":210},[200,121185,215],{"class":214},[200,121187,14492],{"class":223},[200,121189,227],{"class":235},[200,121191,26746],{"class":210},[200,121193,416],{"class":235},[200,121195,121196],{"class":202,"line":3645},[200,121197,529],{"emptyLinePlaceholder":20},[200,121199,121200,121202,121204,121206,121208,121210],{"class":202,"line":4020},[200,121201,3723],{"class":379},[200,121203,3257],{"class":210},[200,121205,510],{"class":214},[200,121207,64427],{"class":223},[200,121209,227],{"class":235},[200,121211,230],{"class":214},[200,121213,121214,121216],{"class":202,"line":4475},[200,121215,18775],{"class":210},[200,121217,252],{"class":214},[200,121219,121220,121222],{"class":202,"line":4481},[200,121221,64750],{"class":210},[200,121223,252],{"class":214},[200,121225,121226,121228],{"class":202,"line":4487},[200,121227,5080],{"class":214},[200,121229,416],{"class":235},[200,121231,121232],{"class":202,"line":4493},[200,121233,529],{"emptyLinePlaceholder":20},[200,121235,121236,121238,121240,121242,121244,121246],{"class":202,"line":5454},[200,121237,3743],{"class":206},[200,121239,38960],{"class":223},[200,121241,227],{"class":235},[200,121243,1532],{"class":214},[200,121245,393],{"class":379},[200,121247,280],{"class":214},[200,121249,121250,121252],{"class":202,"line":5459},[200,121251,3296],{"class":206},[200,121253,64783],{"class":210},[200,121255,121256,121258],{"class":202,"line":13227},[200,121257,5080],{"class":214},[200,121259,416],{"class":235},[200,121261,121262,121264],{"class":202,"line":13253},[200,121263,413],{"class":214},[200,121265,416],{"class":210},[35,121267,64798,121268,64802,121270,64805],{},[6850,121269,64801],{},[156,121271,64555],{},[35,121273,47322,121274,64811,121276,64815],{},[6850,121275,64810],{},[6850,121277,64814],{},[35,121279,419,121280,64821],{},[156,121281,64820],{},[192,121283,121284],{"className":1370,"code":64824,"filename":64825,"language":1373,"meta":13,"style":13},[156,121285,121286,121294,121302,121312,121316,121320,121340,121358,121362,121372,121376,121380,121384,121388,121392,121414,121418,121422,121426,121430,121434,121438,121442,121446,121454,121458,121462,121468],{"__ignoreMap":13},[200,121287,121288,121290,121292],{"class":202,"line":203},[200,121289,64832],{"class":379},[200,121291,64835],{"class":1978},[200,121293,280],{"class":214},[200,121295,121296,121298,121300],{"class":202,"line":14},[200,121297,64842],{"class":235},[200,121299,239],{"class":214},[200,121301,23664],{"class":1978},[200,121303,121304,121306,121308,121310],{"class":202,"line":255},[200,121305,64851],{"class":235},[200,121307,239],{"class":214},[200,121309,22708],{"class":1978},[200,121311,64858],{"class":210},[200,121313,121314],{"class":202,"line":272},[200,121315,1130],{"class":214},[200,121317,121318],{"class":202,"line":283},[200,121319,529],{"emptyLinePlaceholder":20},[200,121321,121322,121324,121326,121328,121330,121332,121334,121336,121338],{"class":202,"line":300},[200,121323,1380],{"class":206},[200,121325,4987],{"class":379},[200,121327,64427],{"class":223},[200,121329,227],{"class":214},[200,121331,23830],{"class":386},[200,121333,239],{"class":214},[200,121335,64835],{"class":1978},[200,121337,2089],{"class":214},[200,121339,280],{"class":214},[200,121341,121342,121344,121346,121348,121350,121352,121354,121356],{"class":202,"line":310},[200,121343,3723],{"class":379},[200,121345,318],{"class":214},[200,121347,64705],{"class":210},[200,121349,550],{"class":214},[200,121351,41038],{"class":210},[200,121353,981],{"class":214},[200,121355,510],{"class":214},[200,121357,64905],{"class":210},[200,121359,121360],{"class":202,"line":336},[200,121361,529],{"emptyLinePlaceholder":20},[200,121363,121364,121366,121368,121370],{"class":202,"line":342},[200,121365,3743],{"class":206},[200,121367,64916],{"class":401},[200,121369,3318],{"class":214},[200,121371,64921],{"class":245},[200,121373,121374],{"class":202,"line":365},[200,121375,64926],{"class":245},[200,121377,121378],{"class":202,"line":371},[200,121379,64931],{"class":245},[200,121381,121382],{"class":202,"line":398},[200,121383,64936],{"class":245},[200,121385,121386],{"class":202,"line":405},[200,121387,64941],{"class":245},[200,121389,121390],{"class":202,"line":410},[200,121391,64946],{"class":245},[200,121393,121394,121396,121398,121400,121402,121404,121406,121408,121410,121412],{"class":202,"line":861},[200,121395,64951],{"class":214},[200,121397,14574],{"class":210},[200,121399,215],{"class":214},[200,121401,6190],{"class":223},[200,121403,227],{"class":210},[200,121405,249],{"class":214},[200,121407,6086],{"class":210},[200,121409,249],{"class":214},[200,121411,2089],{"class":210},[200,121413,1130],{"class":214},[200,121415,121416],{"class":202,"line":890},[200,121417,64974],{"class":245},[200,121419,121420],{"class":202,"line":900},[200,121421,64979],{"class":245},[200,121423,121424],{"class":202,"line":905},[200,121425,64984],{"class":245},[200,121427,121428],{"class":202,"line":931},[200,121429,64989],{"class":245},[200,121431,121432],{"class":202,"line":3640},[200,121433,64994],{"class":245},[200,121435,121436],{"class":202,"line":3645},[200,121437,64999],{"class":245},[200,121439,121440],{"class":202,"line":4020},[200,121441,65004],{"class":245},[200,121443,121444],{"class":202,"line":4475},[200,121445,65009],{"class":245},[200,121447,121448,121450,121452],{"class":202,"line":4481},[200,121449,65014],{"class":214},[200,121451,23870],{"class":210},[200,121453,1130],{"class":214},[200,121455,121456],{"class":202,"line":4487},[200,121457,65023],{"class":245},[200,121459,121460],{"class":202,"line":4493},[200,121461,65028],{"class":245},[200,121463,121464,121466],{"class":202,"line":5454},[200,121465,65033],{"class":245},[200,121467,12762],{"class":214},[200,121469,121470],{"class":202,"line":5459},[200,121471,1130],{"class":214},[35,121473,65042,121474,65046,121476,215],{},[6850,121475,65045],{},[6850,121477,64413],{},[1913,121479,65052],{"id":65051},[35,121481,65055,121482,65058],{},[156,121483,19790],{},[192,121485,121486],{"className":2151,"code":65061,"language":2153,"meta":13,"style":13},[156,121487,121488],{"__ignoreMap":13},[200,121489,121490,121492,121494],{"class":202,"line":203},[200,121491,36332],{"class":1978},[200,121493,12288],{"class":245},[200,121495,65072],{"class":245},[35,121497,65075,121498,65078,121500,65081,121502,65084,121504,65088,121506,65091],{},[156,121499,64555],{},[6850,121501,64801],{},[6850,121503,64179],{},[156,121505,65087],{},[156,121507,64372],{},[192,121509,121510],{"className":64380,"code":65094,"filename":65095,"language":64383,"meta":13,"style":13},[156,121511,121512,121538,121556,121576,121594,121598,121620,121648,121670,121682,121686,121702,121722,121756,121760,121794,121798,121812,121818,121826,121834,121850,121858,121874,121880,121892,121902,121906,121910,121918,121922,121926,121930,121950,121982,121986,121996,122006,122012,122018],{"__ignoreMap":13},[200,121513,121514,121516,121518,121520,121522,121524,121526,121528,121530,121532,121534,121536],{"class":202,"line":203},[200,121515,973],{"class":206},[200,121517,318],{"class":214},[200,121519,64394],{"class":210},[200,121521,550],{"class":214},[200,121523,64399],{"class":210},[200,121525,550],{"class":214},[200,121527,64404],{"class":210},[200,121529,981],{"class":214},[200,121531,984],{"class":206},[200,121533,242],{"class":214},[200,121535,64413],{"class":245},[200,121537,992],{"class":214},[200,121539,121540,121542,121544,121546,121548,121550,121552,121554],{"class":202,"line":14},[200,121541,973],{"class":206},[200,121543,318],{"class":214},[200,121545,56948],{"class":210},[200,121547,981],{"class":214},[200,121549,984],{"class":206},[200,121551,242],{"class":214},[200,121553,64223],{"class":245},[200,121555,992],{"class":214},[200,121557,121558,121560,121562,121564,121566,121568,121570,121572,121574],{"class":202,"line":255},[200,121559,973],{"class":206},[200,121561,318],{"class":214},[200,121563,3165],{"class":210},[200,121565,981],{"class":214},[200,121567,984],{"class":206},[200,121569,242],{"class":214},[200,121571,19790],{"class":245},[200,121573,249],{"class":214},[200,121575,65162],{"class":401},[200,121577,121578,121580,121582,121584,121586,121588,121590,121592],{"class":202,"line":272},[200,121579,973],{"class":206},[200,121581,318],{"class":214},[200,121583,64427],{"class":210},[200,121585,981],{"class":214},[200,121587,984],{"class":206},[200,121589,242],{"class":214},[200,121591,64436],{"class":245},[200,121593,992],{"class":214},[200,121595,121596],{"class":202,"line":283},[200,121597,529],{"emptyLinePlaceholder":20},[200,121599,121600,121602,121604,121606,121608,121610,121612,121614,121616,121618],{"class":202,"line":300},[200,121601,1380],{"class":206},[200,121603,1383],{"class":206},[200,121605,38960],{"class":223},[200,121607,227],{"class":210},[200,121609,3711],{"class":379},[200,121611,1095],{"class":214},[200,121613,25386],{"class":386},[200,121615,2089],{"class":214},[200,121617,393],{"class":379},[200,121619,280],{"class":214},[200,121621,121622,121624,121626,121628,121630,121632,121634,121636,121638,121640,121642,121644,121646],{"class":202,"line":310},[200,121623,3723],{"class":379},[200,121625,41038],{"class":210},[200,121627,510],{"class":214},[200,121629,452],{"class":206},[200,121631,41180],{"class":223},[200,121633,227],{"class":235},[200,121635,25386],{"class":210},[200,121637,550],{"class":214},[200,121639,3165],{"class":210},[200,121641,215],{"class":214},[200,121643,293],{"class":223},[200,121645,227],{"class":235},[200,121647,230],{"class":214},[200,121649,121650,121652,121654,121656,121658,121660,121662,121664,121666,121668],{"class":202,"line":336},[200,121651,5192],{"class":235},[200,121653,239],{"class":214},[200,121655,3165],{"class":210},[200,121657,215],{"class":214},[200,121659,328],{"class":223},[200,121661,1532],{"class":235},[200,121663,215],{"class":214},[200,121665,2020],{"class":223},[200,121667,1532],{"class":235},[200,121669,252],{"class":214},[200,121671,121672,121674,121676,121678,121680],{"class":202,"line":342},[200,121673,5080],{"class":214},[200,121675,2089],{"class":235},[200,121677,215],{"class":214},[200,121679,41223],{"class":210},[200,121681,416],{"class":235},[200,121683,121684],{"class":202,"line":365},[200,121685,529],{"emptyLinePlaceholder":20},[200,121687,121688,121690,121692,121694,121696,121698,121700],{"class":202,"line":371},[200,121689,3723],{"class":379},[200,121691,65279],{"class":210},[200,121693,510],{"class":214},[200,121695,46807],{"class":223},[200,121697,227],{"class":235},[200,121699,25386],{"class":210},[200,121701,416],{"class":235},[200,121703,121704,121706,121708,121710,121712,121714,121716,121718,121720],{"class":202,"line":398},[200,121705,3723],{"class":379},[200,121707,3224],{"class":210},[200,121709,510],{"class":214},[200,121711,56948],{"class":223},[200,121713,227],{"class":235},[200,121715,23870],{"class":210},[200,121717,215],{"class":214},[200,121719,2020],{"class":210},[200,121721,416],{"class":235},[200,121723,121724,121726,121728,121730,121732,121734,121736,121738,121740,121742,121744,121746,121748,121750,121752,121754],{"class":202,"line":405},[200,121725,3723],{"class":379},[200,121727,65316],{"class":210},[200,121729,510],{"class":214},[200,121731,12997],{"class":214},[200,121733,65323],{"class":223},[200,121735,227],{"class":235},[200,121737,3318],{"class":214},[200,121739,2882],{"class":245},[200,121741,4853],{"class":214},[200,121743,3280],{"class":210},[200,121745,23001],{"class":214},[200,121747,550],{"class":214},[200,121749,65279],{"class":210},[200,121751,2089],{"class":235},[200,121753,215],{"class":214},[200,121755,46818],{"class":210},[200,121757,121758],{"class":202,"line":410},[200,121759,529],{"emptyLinePlaceholder":20},[200,121761,121762,121764,121766,121768,121770,121772,121774,121776,121778,121780,121782,121784,121786,121788,121790,121792],{"class":202,"line":861},[200,121763,6165],{"class":206},[200,121765,65356],{"class":223},[200,121767,227],{"class":235},[200,121769,249],{"class":214},[200,121771,12726],{"class":245},[200,121773,249],{"class":214},[200,121775,2089],{"class":235},[200,121777,215],{"class":214},[200,121779,65371],{"class":223},[200,121781,227],{"class":235},[200,121783,3280],{"class":210},[200,121785,550],{"class":214},[200,121787,41038],{"class":210},[200,121789,215],{"class":214},[200,121791,2020],{"class":210},[200,121793,416],{"class":235},[200,121795,121796],{"class":202,"line":890},[200,121797,529],{"emptyLinePlaceholder":20},[200,121799,121800,121802,121804,121806,121808,121810],{"class":202,"line":900},[200,121801,3723],{"class":379},[200,121803,64466],{"class":210},[200,121805,510],{"class":214},[200,121807,3714],{"class":214},[200,121809,393],{"class":379},[200,121811,280],{"class":214},[200,121813,121814,121816],{"class":202,"line":905},[200,121815,3296],{"class":206},[200,121817,64481],{"class":235},[200,121819,121820,121822,121824],{"class":202,"line":931},[200,121821,22027],{"class":214},[200,121823,11386],{"class":235},[200,121825,648],{"class":214},[200,121827,121828,121830,121832],{"class":202,"line":3640},[200,121829,49602],{"class":214},[200,121831,64496],{"class":1978},[200,121833,648],{"class":214},[200,121835,121836,121838,121840,121842,121844,121846,121848],{"class":202,"line":3645},[200,121837,49629],{"class":214},[200,121839,3566],{"class":235},[200,121841,675],{"class":214},[200,121843,65436],{"class":210},[200,121845,681],{"class":214},[200,121847,3566],{"class":235},[200,121849,648],{"class":214},[200,121851,121852,121854,121856],{"class":202,"line":4020},[200,121853,49710],{"class":214},[200,121855,64496],{"class":1978},[200,121857,648],{"class":214},[200,121859,121860,121862,121864,121866,121868,121870,121872],{"class":202,"line":4475},[200,121861,49602],{"class":214},[200,121863,51],{"class":235},[200,121865,675],{"class":214},[200,121867,65461],{"class":210},[200,121869,681],{"class":214},[200,121871,51],{"class":235},[200,121873,648],{"class":214},[200,121875,121876,121878],{"class":202,"line":4481},[200,121877,49602],{"class":214},[200,121879,65474],{"class":235},[200,121881,121882,121884,121886,121888,121890],{"class":202,"line":4487},[200,121883,4914],{"class":379},[200,121885,449],{"class":214},[200,121887,611],{"class":214},[200,121889,698],{"class":245},[200,121891,617],{"class":214},[200,121893,121894,121896,121898,121900],{"class":202,"line":4493},[200,121895,65491],{"class":379},[200,121897,65494],{"class":214},[200,121899,65497],{"class":210},[200,121901,1130],{"class":214},[200,121903,121904],{"class":202,"line":5454},[200,121905,65504],{"class":379},[200,121907,121908],{"class":202,"line":5459},[200,121909,64946],{"class":214},[200,121911,121912,121914,121916],{"class":202,"line":13227},[200,121913,43992],{"class":214},[200,121915,11386],{"class":235},[200,121917,648],{"class":214},[200,121919,121920],{"class":202,"line":13253},[200,121921,3854],{"class":235},[200,121923,121924],{"class":202,"line":13269},[200,121925,4490],{"class":214},[200,121927,121928],{"class":202,"line":13313},[200,121929,529],{"emptyLinePlaceholder":20},[200,121931,121932,121934,121936,121938,121940,121942,121944,121946,121948],{"class":202,"line":13319},[200,121933,3723],{"class":379},[200,121935,14330],{"class":210},[200,121937,510],{"class":214},[200,121939,64404],{"class":223},[200,121941,227],{"class":235},[200,121943,600],{"class":214},[200,121945,64688],{"class":1978},[200,121947,22055],{"class":214},[200,121949,416],{"class":235},[200,121951,121952,121954,121956,121958,121960,121962,121964,121966,121968,121970,121972,121974,121976,121978,121980],{"class":202,"line":13324},[200,121953,3723],{"class":379},[200,121955,318],{"class":214},[200,121957,41038],{"class":235},[200,121959,239],{"class":214},[200,121961,65561],{"class":210},[200,121963,550],{"class":214},[200,121965,64705],{"class":210},[200,121967,981],{"class":214},[200,121969,510],{"class":214},[200,121971,64399],{"class":210},[200,121973,215],{"class":214},[200,121975,14492],{"class":223},[200,121977,227],{"class":235},[200,121979,26746],{"class":210},[200,121981,416],{"class":235},[200,121983,121984],{"class":202,"line":13350},[200,121985,529],{"emptyLinePlaceholder":20},[200,121987,121988,121990,121992,121994],{"class":202,"line":13355},[200,121989,3743],{"class":206},[200,121991,64427],{"class":223},[200,121993,227],{"class":235},[200,121995,230],{"class":214},[200,121997,121998,122000,122002,122004],{"class":202,"line":13375},[200,121999,18775],{"class":235},[200,122001,239],{"class":214},[200,122003,65561],{"class":210},[200,122005,252],{"class":214},[200,122007,122008,122010],{"class":202,"line":13395},[200,122009,64750],{"class":210},[200,122011,252],{"class":214},[200,122013,122014,122016],{"class":202,"line":13400},[200,122015,5080],{"class":214},[200,122017,416],{"class":235},[200,122019,122020,122022],{"class":202,"line":13408},[200,122021,413],{"class":214},[200,122023,416],{"class":210},[35,122025,65626,122026,65629,122028,65633,122030,65636],{},[156,122027,41118],{},[6850,122029,65632],{},[156,122031,2020],{},[35,122033,65639,122034,42844,122036,65646,122038,215],{},[6850,122035,65642],{},[156,122037,65645],{},[156,122039,1913],{},[35,122041,419,122042,65653,122044,65646,122046,65658],{},[6850,122043,53003],{},[156,122045,53003],{},[6850,122047,64223],{},[35,122049,65661,122050,42844,122052,65646,122054,65669,122056,65672],{},[6850,122051,64179],{},[156,122053,65666],{},[6850,122055,64207],{},[156,122057,12726],{},[7905,122059,122060,122062,122066,122154,122168],{"text":65675,"href":65676},[35,122061,65679],{},[35,122063,65682,122064,65686],{},[156,122065,65685],{},[192,122067,122068],{"className":1370,"code":65689,"filename":65685,"language":1373,"meta":13,"style":13},[156,122069,122070,122082,122096,122104,122144,122148],{"__ignoreMap":13},[200,122071,122072,122074,122076,122078,122080],{"class":202,"line":203},[200,122073,1380],{"class":206},[200,122075,1383],{"class":206},[200,122077,65700],{"class":223},[200,122079,227],{"class":210},[200,122081,230],{"class":214},[200,122083,122084,122086,122088,122090,122092,122094],{"class":202,"line":14},[200,122085,65709],{"class":235},[200,122087,239],{"class":214},[200,122089,242],{"class":214},[200,122091,3108],{"class":245},[200,122093,249],{"class":214},[200,122095,252],{"class":214},[200,122097,122098,122100,122102],{"class":202,"line":255},[200,122099,65724],{"class":235},[200,122101,239],{"class":214},[200,122103,280],{"class":214},[200,122105,122106,122108,122110,122112,122114,122116,122118,122120,122122,122124,122126,122128,122130,122132,122134,122136,122138,122140,122142],{"class":202,"line":272},[200,122107,65733],{"class":235},[200,122109,239],{"class":214},[200,122111,318],{"class":214},[200,122113,5041],{"class":235},[200,122115,239],{"class":214},[200,122117,318],{"class":214},[200,122119,65746],{"class":235},[200,122121,239],{"class":214},[200,122123,242],{"class":214},[200,122125,65753],{"class":245},[200,122127,249],{"class":214},[200,122129,550],{"class":214},[200,122131,65760],{"class":235},[200,122133,239],{"class":214},[200,122135,242],{"class":214},[200,122137,65767],{"class":245},[200,122139,249],{"class":214},[200,122141,981],{"class":214},[200,122143,333],{"class":214},[200,122145,122146],{"class":202,"line":283},[200,122147,368],{"class":214},[200,122149,122150,122152],{"class":202,"line":300},[200,122151,413],{"class":214},[200,122153,416],{"class":210},[35,122155,65786,122156,65789,122158,65792,122160,65795,122162,65799,122164,65803,122166,215],{},[156,122157,12726],{},[6850,122159,65753],{},[156,122161,65767],{},[156,122163,65798],{},[6850,122165,65802],{},[6850,122167,38790],{},[7905,122169,122170,122172,122178,122358,122370,122372,122378],{"text":65808,"href":65809},[1913,122171,65813],{"id":65812},[35,122173,65816,122174,65820,122176,65091],{},[156,122175,65819],{},[156,122177,64372],{},[192,122179,122180],{"className":1370,"code":65825,"filename":65826,"language":1373,"meta":13,"style":13},[156,122181,122182,122204,122228,122232,122270,122274,122288,122298,122308,122322,122328,122332,122336,122352],{"__ignoreMap":13},[200,122183,122184,122186,122188,122190,122192,122194,122196,122198,122200,122202],{"class":202,"line":203},[200,122185,1380],{"class":206},[200,122187,1383],{"class":206},[200,122189,38960],{"class":223},[200,122191,227],{"class":210},[200,122193,3711],{"class":379},[200,122195,1095],{"class":214},[200,122197,25386],{"class":386},[200,122199,2089],{"class":214},[200,122201,393],{"class":379},[200,122203,280],{"class":214},[200,122205,122206,122208,122210,122212,122214,122216,122218,122220,122222,122224,122226],{"class":202,"line":14},[200,122207,3723],{"class":379},[200,122209,3224],{"class":210},[200,122211,510],{"class":214},[200,122213,65861],{"class":223},[200,122215,227],{"class":235},[200,122217,25386],{"class":210},[200,122219,550],{"class":214},[200,122221,242],{"class":214},[200,122223,65872],{"class":245},[200,122225,249],{"class":214},[200,122227,416],{"class":235},[200,122229,122230],{"class":202,"line":255},[200,122231,529],{"emptyLinePlaceholder":20},[200,122233,122234,122236,122238,122240,122242,122244,122246,122248,122250,122252,122254,122256,122258,122260,122262,122264,122266,122268],{"class":202,"line":272},[200,122235,3723],{"class":379},[200,122237,810],{"class":210},[200,122239,510],{"class":214},[200,122241,452],{"class":206},[200,122243,65356],{"class":223},[200,122245,600],{"class":214},[200,122247,328],{"class":1978},[200,122249,675],{"class":214},[200,122251,227],{"class":235},[200,122253,249],{"class":214},[200,122255,12726],{"class":245},[200,122257,249],{"class":214},[200,122259,2089],{"class":235},[200,122261,215],{"class":214},[200,122263,14967],{"class":223},[200,122265,227],{"class":235},[200,122267,3280],{"class":210},[200,122269,416],{"class":235},[200,122271,122272],{"class":202,"line":283},[200,122273,529],{"emptyLinePlaceholder":20},[200,122275,122276,122278,122280,122282,122284,122286],{"class":202,"line":300},[200,122277,20075],{"class":206},[200,122279,1095],{"class":235},[200,122281,11837],{"class":214},[200,122283,15837],{"class":210},[200,122285,5267],{"class":235},[200,122287,230],{"class":214},[200,122289,122290,122292,122294,122296],{"class":202,"line":310},[200,122291,39438],{"class":206},[200,122293,39441],{"class":223},[200,122295,227],{"class":235},[200,122297,230],{"class":214},[200,122299,122300,122302,122304,122306],{"class":202,"line":336},[200,122301,39450],{"class":235},[200,122303,239],{"class":214},[200,122305,40582],{"class":2045},[200,122307,252],{"class":214},[200,122309,122310,122312,122314,122316,122318,122320],{"class":202,"line":342},[200,122311,65961],{"class":235},[200,122313,239],{"class":214},[200,122315,242],{"class":214},[200,122317,40595],{"class":245},[200,122319,249],{"class":214},[200,122321,252],{"class":214},[200,122323,122324,122326],{"class":202,"line":365},[200,122325,2096],{"class":214},[200,122327,416],{"class":235},[200,122329,122330],{"class":202,"line":371},[200,122331,4490],{"class":214},[200,122333,122334],{"class":202,"line":398},[200,122335,529],{"emptyLinePlaceholder":20},[200,122337,122338,122340,122342,122344,122346,122348,122350],{"class":202,"line":405},[200,122339,3743],{"class":206},[200,122341,46247],{"class":223},[200,122343,227],{"class":235},[200,122345,25386],{"class":210},[200,122347,550],{"class":214},[200,122349,810],{"class":210},[200,122351,416],{"class":235},[200,122353,122354,122356],{"class":202,"line":410},[200,122355,413],{"class":214},[200,122357,416],{"class":210},[35,122359,66010,122360,66013,122362,66016,122364,35149,122366,66022,122368,66026],{},[156,122361,3280],{},[156,122363,65666],{},[6850,122365,66019],{},[6850,122367,64179],{},[6850,122369,66025],{},[51,122371,66030],{"id":66029},[35,122373,66033,122374,66037,122376,215],{},[6850,122375,66036],{},[6850,122377,66040],{},[7905,122379,122380,122386,122396,122400,122410,122420,122426,122428,122434,123152,123156,123171,123173,123183,123189],{"text":66043,"href":66044},[939,122381,122382],{},[35,122383,122384,66051],{},[200,122385,29700],{},[35,122387,66054,122388,66058,122390,66061,122392,66065,122394,66069],{},[156,122389,66057],{},[156,122391,5139],{},[156,122393,66064],{},[156,122395,66068],{},[35,122397,66072,122398,215],{},[6850,122399,38775],{},[35,122401,122402,66080,122404,66084,122406,66088,122408,66091],{},[6850,122403,66079],{},[6850,122405,66083],{},[6850,122407,66087],{},[156,122409,65767],{},[939,122411,122412],{},[35,122413,122414,66098,122416,66101,122418,66105],{},[200,122415,945],{},[6850,122417,66083],{},[6850,122419,66104],{},[939,122421,122422],{},[35,122423,122424,66112],{},[200,122425,945],{},[51,122427,66116],{"id":66115},[35,122429,66119,122430,66123,122432,66126],{},[6850,122431,66122],{},[156,122433,65087],{},[192,122435,122436],{"className":64380,"code":66129,"filename":65095,"language":64383,"meta":13,"style":13},[156,122437,122438,122464,122482,122500,122518,122522,122534,122552,122572,122596,122600,122614,122624,122634,122648,122654,122658,122662,122678,122688,122698,122712,122718,122722,122726,122744,122772,122794,122806,122810,122826,122846,122880,122884,122918,122922,122936,122942,122950,122958,122974,122982,122998,123004,123016,123026,123030,123034,123042,123046,123050,123054,123074,123106,123110,123120,123130,123136,123142,123146],{"__ignoreMap":13},[200,122439,122440,122442,122444,122446,122448,122450,122452,122454,122456,122458,122460,122462],{"class":202,"line":203},[200,122441,973],{"class":206},[200,122443,318],{"class":214},[200,122445,64394],{"class":210},[200,122447,550],{"class":214},[200,122449,64399],{"class":210},[200,122451,550],{"class":214},[200,122453,64404],{"class":210},[200,122455,981],{"class":214},[200,122457,984],{"class":206},[200,122459,242],{"class":214},[200,122461,64413],{"class":245},[200,122463,992],{"class":214},[200,122465,122466,122468,122470,122472,122474,122476,122478,122480],{"class":202,"line":14},[200,122467,973],{"class":206},[200,122469,318],{"class":214},[200,122471,56948],{"class":210},[200,122473,981],{"class":214},[200,122475,984],{"class":206},[200,122477,242],{"class":214},[200,122479,64223],{"class":245},[200,122481,992],{"class":214},[200,122483,122484,122486,122488,122490,122492,122494,122496,122498],{"class":202,"line":255},[200,122485,973],{"class":206},[200,122487,318],{"class":214},[200,122489,3165],{"class":210},[200,122491,981],{"class":214},[200,122493,984],{"class":206},[200,122495,242],{"class":214},[200,122497,19790],{"class":245},[200,122499,992],{"class":214},[200,122501,122502,122504,122506,122508,122510,122512,122514,122516],{"class":202,"line":272},[200,122503,973],{"class":206},[200,122505,318],{"class":214},[200,122507,64427],{"class":210},[200,122509,981],{"class":214},[200,122511,984],{"class":206},[200,122513,242],{"class":214},[200,122515,64436],{"class":245},[200,122517,992],{"class":214},[200,122519,122520],{"class":202,"line":283},[200,122521,529],{"emptyLinePlaceholder":20},[200,122523,122524,122526,122528,122530,122532],{"class":202,"line":300},[200,122525,1380],{"class":206},[200,122527,1383],{"class":206},[200,122529,38960],{"class":223},[200,122531,227],{"class":210},[200,122533,230],{"class":214},[200,122535,122536,122538,122540,122542,122544,122546,122548,122550],{"class":202,"line":310},[200,122537,66232],{"class":223},[200,122539,239],{"class":214},[200,122541,380],{"class":379},[200,122543,1095],{"class":214},[200,122545,25386],{"class":386},[200,122547,2089],{"class":214},[200,122549,393],{"class":379},[200,122551,280],{"class":214},[200,122553,122554,122556,122558,122560,122562,122564,122566,122568,122570],{"class":202,"line":336},[200,122555,1999],{"class":379},[200,122557,65279],{"class":210},[200,122559,510],{"class":214},[200,122561,46807],{"class":223},[200,122563,227],{"class":235},[200,122565,25386],{"class":210},[200,122567,2089],{"class":235},[200,122569,215],{"class":214},[200,122571,66267],{"class":210},[200,122573,122574,122576,122578,122580,122582,122584,122586,122588,122590,122592,122594],{"class":202,"line":342},[200,122575,1999],{"class":379},[200,122577,29888],{"class":210},[200,122579,510],{"class":214},[200,122581,66278],{"class":223},[200,122583,227],{"class":235},[200,122585,25386],{"class":210},[200,122587,550],{"class":214},[200,122589,242],{"class":214},[200,122591,66289],{"class":245},[200,122593,249],{"class":214},[200,122595,416],{"class":235},[200,122597,122598],{"class":202,"line":365},[200,122599,529],{"emptyLinePlaceholder":20},[200,122601,122602,122604,122606,122608,122610,122612],{"class":202,"line":371},[200,122603,5252],{"class":206},[200,122605,1095],{"class":235},[200,122607,11837],{"class":214},[200,122609,66289],{"class":210},[200,122611,5267],{"class":235},[200,122613,230],{"class":214},[200,122615,122616,122618,122620,122622],{"class":202,"line":398},[200,122617,20227],{"class":206},[200,122619,39441],{"class":223},[200,122621,227],{"class":235},[200,122623,230],{"class":214},[200,122625,122626,122628,122630,122632],{"class":202,"line":405},[200,122627,66326],{"class":235},[200,122629,239],{"class":214},[200,122631,41427],{"class":2045},[200,122633,252],{"class":214},[200,122635,122636,122638,122640,122642,122644,122646],{"class":202,"line":410},[200,122637,66337],{"class":235},[200,122639,239],{"class":214},[200,122641,242],{"class":214},[200,122643,66344],{"class":245},[200,122645,249],{"class":214},[200,122647,252],{"class":214},[200,122649,122650,122652],{"class":202,"line":861},[200,122651,2632],{"class":214},[200,122653,416],{"class":235},[200,122655,122656],{"class":202,"line":890},[200,122657,4962],{"class":214},[200,122659,122660],{"class":202,"line":900},[200,122661,529],{"emptyLinePlaceholder":20},[200,122663,122664,122666,122668,122670,122672,122674,122676],{"class":202,"line":905},[200,122665,5252],{"class":206},[200,122667,1095],{"class":235},[200,122669,66289],{"class":210},[200,122671,13388],{"class":214},[200,122673,65279],{"class":210},[200,122675,5267],{"class":235},[200,122677,230],{"class":214},[200,122679,122680,122682,122684,122686],{"class":202,"line":931},[200,122681,20227],{"class":206},[200,122683,39441],{"class":223},[200,122685,227],{"class":235},[200,122687,230],{"class":214},[200,122689,122690,122692,122694,122696],{"class":202,"line":3640},[200,122691,66326],{"class":235},[200,122693,239],{"class":214},[200,122695,39464],{"class":2045},[200,122697,252],{"class":214},[200,122699,122700,122702,122704,122706,122708,122710],{"class":202,"line":3645},[200,122701,66337],{"class":235},[200,122703,239],{"class":214},[200,122705,242],{"class":214},[200,122707,39965],{"class":245},[200,122709,249],{"class":214},[200,122711,252],{"class":214},[200,122713,122714,122716],{"class":202,"line":4020},[200,122715,2632],{"class":214},[200,122717,416],{"class":235},[200,122719,122720],{"class":202,"line":4475},[200,122721,4962],{"class":214},[200,122723,122724],{"class":202,"line":4481},[200,122725,368],{"class":214},[200,122727,122728,122730,122732,122734,122736,122738,122740,122742],{"class":202,"line":4487},[200,122729,66431],{"class":223},[200,122731,239],{"class":214},[200,122733,380],{"class":379},[200,122735,1095],{"class":214},[200,122737,25386],{"class":386},[200,122739,2089],{"class":214},[200,122741,393],{"class":379},[200,122743,280],{"class":214},[200,122745,122746,122748,122750,122752,122754,122756,122758,122760,122762,122764,122766,122768,122770],{"class":202,"line":4493},[200,122747,1999],{"class":379},[200,122749,41038],{"class":210},[200,122751,510],{"class":214},[200,122753,452],{"class":206},[200,122755,41180],{"class":223},[200,122757,227],{"class":235},[200,122759,25386],{"class":210},[200,122761,550],{"class":214},[200,122763,3165],{"class":210},[200,122765,215],{"class":214},[200,122767,293],{"class":223},[200,122769,227],{"class":235},[200,122771,230],{"class":214},[200,122773,122774,122776,122778,122780,122782,122784,122786,122788,122790,122792],{"class":202,"line":5454},[200,122775,3605],{"class":235},[200,122777,239],{"class":214},[200,122779,3165],{"class":210},[200,122781,215],{"class":214},[200,122783,328],{"class":223},[200,122785,1532],{"class":235},[200,122787,215],{"class":214},[200,122789,2020],{"class":223},[200,122791,1532],{"class":235},[200,122793,252],{"class":214},[200,122795,122796,122798,122800,122802,122804],{"class":202,"line":5459},[200,122797,2096],{"class":214},[200,122799,2089],{"class":235},[200,122801,215],{"class":214},[200,122803,41223],{"class":210},[200,122805,416],{"class":235},[200,122807,122808],{"class":202,"line":13227},[200,122809,529],{"emptyLinePlaceholder":20},[200,122811,122812,122814,122816,122818,122820,122822,122824],{"class":202,"line":13253},[200,122813,1999],{"class":379},[200,122815,65279],{"class":210},[200,122817,510],{"class":214},[200,122819,46807],{"class":223},[200,122821,227],{"class":235},[200,122823,25386],{"class":210},[200,122825,416],{"class":235},[200,122827,122828,122830,122832,122834,122836,122838,122840,122842,122844],{"class":202,"line":13269},[200,122829,1999],{"class":379},[200,122831,3224],{"class":210},[200,122833,510],{"class":214},[200,122835,56948],{"class":223},[200,122837,227],{"class":235},[200,122839,23870],{"class":210},[200,122841,215],{"class":214},[200,122843,2020],{"class":210},[200,122845,416],{"class":235},[200,122847,122848,122850,122852,122854,122856,122858,122860,122862,122864,122866,122868,122870,122872,122874,122876,122878],{"class":202,"line":13313},[200,122849,1999],{"class":379},[200,122851,65316],{"class":210},[200,122853,510],{"class":214},[200,122855,12997],{"class":214},[200,122857,65323],{"class":223},[200,122859,227],{"class":235},[200,122861,3318],{"class":214},[200,122863,2882],{"class":245},[200,122865,4853],{"class":214},[200,122867,3280],{"class":210},[200,122869,23001],{"class":214},[200,122871,550],{"class":214},[200,122873,65279],{"class":210},[200,122875,2089],{"class":235},[200,122877,215],{"class":214},[200,122879,46818],{"class":210},[200,122881,122882],{"class":202,"line":13319},[200,122883,529],{"emptyLinePlaceholder":20},[200,122885,122886,122888,122890,122892,122894,122896,122898,122900,122902,122904,122906,122908,122910,122912,122914,122916],{"class":202,"line":13324},[200,122887,19216],{"class":206},[200,122889,65356],{"class":223},[200,122891,227],{"class":235},[200,122893,249],{"class":214},[200,122895,12726],{"class":245},[200,122897,249],{"class":214},[200,122899,2089],{"class":235},[200,122901,215],{"class":214},[200,122903,65371],{"class":223},[200,122905,227],{"class":235},[200,122907,3280],{"class":210},[200,122909,550],{"class":214},[200,122911,41038],{"class":210},[200,122913,215],{"class":214},[200,122915,2020],{"class":210},[200,122917,416],{"class":235},[200,122919,122920],{"class":202,"line":13350},[200,122921,529],{"emptyLinePlaceholder":20},[200,122923,122924,122926,122928,122930,122932,122934],{"class":202,"line":13355},[200,122925,1999],{"class":379},[200,122927,64466],{"class":210},[200,122929,510],{"class":214},[200,122931,3714],{"class":214},[200,122933,393],{"class":379},[200,122935,280],{"class":214},[200,122937,122938,122940],{"class":202,"line":13375},[200,122939,11715],{"class":206},[200,122941,64481],{"class":235},[200,122943,122944,122946,122948],{"class":202,"line":13395},[200,122945,49602],{"class":214},[200,122947,11386],{"class":235},[200,122949,648],{"class":214},[200,122951,122952,122954,122956],{"class":202,"line":13400},[200,122953,49629],{"class":214},[200,122955,64496],{"class":1978},[200,122957,648],{"class":214},[200,122959,122960,122962,122964,122966,122968,122970,122972],{"class":202,"line":13408},[200,122961,66664],{"class":214},[200,122963,3566],{"class":235},[200,122965,675],{"class":214},[200,122967,65436],{"class":210},[200,122969,681],{"class":214},[200,122971,3566],{"class":235},[200,122973,648],{"class":214},[200,122975,122976,122978,122980],{"class":202,"line":13426},[200,122977,49701],{"class":214},[200,122979,64496],{"class":1978},[200,122981,648],{"class":214},[200,122983,122984,122986,122988,122990,122992,122994,122996],{"class":202,"line":13456},[200,122985,49629],{"class":214},[200,122987,51],{"class":235},[200,122989,675],{"class":214},[200,122991,65461],{"class":210},[200,122993,681],{"class":214},[200,122995,51],{"class":235},[200,122997,648],{"class":214},[200,122999,123000,123002],{"class":202,"line":13462},[200,123001,49629],{"class":214},[200,123003,65474],{"class":235},[200,123005,123006,123008,123010,123012,123014],{"class":202,"line":13467},[200,123007,66711],{"class":379},[200,123009,449],{"class":214},[200,123011,611],{"class":214},[200,123013,698],{"class":245},[200,123015,617],{"class":214},[200,123017,123018,123020,123022,123024],{"class":202,"line":13472},[200,123019,66724],{"class":379},[200,123021,65494],{"class":214},[200,123023,65497],{"class":210},[200,123025,1130],{"class":214},[200,123027,123028],{"class":202,"line":13487},[200,123029,66735],{"class":379},[200,123031,123032],{"class":202,"line":13498},[200,123033,66740],{"class":214},[200,123035,123036,123038,123040],{"class":202,"line":13503},[200,123037,49710],{"class":214},[200,123039,11386],{"class":235},[200,123041,648],{"class":214},[200,123043,123044],{"class":202,"line":13508},[200,123045,46937],{"class":235},[200,123047,123048],{"class":202,"line":20224},[200,123049,4962],{"class":214},[200,123051,123052],{"class":202,"line":20246},[200,123053,529],{"emptyLinePlaceholder":20},[200,123055,123056,123058,123060,123062,123064,123066,123068,123070,123072],{"class":202,"line":20251},[200,123057,1999],{"class":379},[200,123059,14330],{"class":210},[200,123061,510],{"class":214},[200,123063,64404],{"class":223},[200,123065,227],{"class":235},[200,123067,600],{"class":214},[200,123069,64688],{"class":1978},[200,123071,22055],{"class":214},[200,123073,416],{"class":235},[200,123075,123076,123078,123080,123082,123084,123086,123088,123090,123092,123094,123096,123098,123100,123102,123104],{"class":202,"line":20256},[200,123077,1999],{"class":379},[200,123079,318],{"class":214},[200,123081,41038],{"class":235},[200,123083,239],{"class":214},[200,123085,65561],{"class":210},[200,123087,550],{"class":214},[200,123089,64705],{"class":210},[200,123091,981],{"class":214},[200,123093,510],{"class":214},[200,123095,64399],{"class":210},[200,123097,215],{"class":214},[200,123099,14492],{"class":223},[200,123101,227],{"class":235},[200,123103,26746],{"class":210},[200,123105,416],{"class":235},[200,123107,123108],{"class":202,"line":20261},[200,123109,529],{"emptyLinePlaceholder":20},[200,123111,123112,123114,123116,123118],{"class":202,"line":20282},[200,123113,3296],{"class":206},[200,123115,64427],{"class":223},[200,123117,227],{"class":235},[200,123119,230],{"class":214},[200,123121,123122,123124,123126,123128],{"class":202,"line":20289},[200,123123,42082],{"class":235},[200,123125,239],{"class":214},[200,123127,65561],{"class":210},[200,123129,252],{"class":214},[200,123131,123132,123134],{"class":202,"line":20294},[200,123133,66841],{"class":210},[200,123135,252],{"class":214},[200,123137,123138,123140],{"class":202,"line":20310},[200,123139,2096],{"class":214},[200,123141,416],{"class":235},[200,123143,123144],{"class":202,"line":47145},[200,123145,368],{"class":214},[200,123147,123148,123150],{"class":202,"line":47150},[200,123149,413],{"class":214},[200,123151,416],{"class":210},[35,123153,66862,123154,66865],{},[6850,123155,66289],{},[939,123157,123158],{},[35,123159,123160,66872,123162,66875,123164,66881,123167,66884,123169,66888],{},[200,123161,945],{},[6850,123163,66122],{},[39,123165,66880],{"href":66878,"rel":123166},[77],[6850,123168,66289],{},[6850,123170,66887],{},[51,123172,30681],{"id":30680},[35,123174,66893,123175,1638,123177,66898,123179,66901,123181,215],{},[6850,123176,36816],{},[6850,123178,38775],{},[6850,123180,64243],{},[6850,123182,64214],{},[35,123184,66906,123185,1638,123187,66911],{},[6850,123186,36816],{},[6850,123188,38775],{},[939,123190,123191],{},[35,123192,123193,66918,123195,215],{},[200,123194,945],{},[39,123196,66923],{"href":66921,"rel":123197},[77],[1171,123199,66926],{},{"title":13,"searchDepth":14,"depth":14,"links":123201},[123202,123203],{"id":64256,"depth":14,"text":64257},{"id":64335,"depth":14,"text":64336,"children":123204},[123205,123206],{"id":64359,"depth":255,"text":64360},{"id":65051,"depth":255,"text":65052},{},{"title":64131,"description":66935},{"loc":66937},1790086897823]