[{"data":1,"prerenderedAt":5070},["ShallowReactive",2],{"post-\u002Fposts\u002Fa-journey-to-craft-interactive-ui-experiences-with-dialogs":3,"post-link-en-f9754148-40bd-4df2-a144-f686e894b200":2998},{"id":4,"title":5,"body":6,"contentId":2986,"date":2987,"description":2988,"extension":2989,"meta":2990,"navigation":361,"path":2991,"rawbody":2992,"robots":2993,"schemaOrg":2993,"seo":2994,"sitemap":2995,"stem":2996,"__hash__":2997},"posts\u002Fposts\u002Fa-journey-to-craft-interactive-ui-experiences-with-dialogs.md","A Journey to Craft Interactive UI Experiences with Dialogs",{"type":7,"value":8,"toc":2978},"minimark",[9,18,2974],[10,11,12,13,17],"p",{},"As far as I remember, this challenge has been the most persistent one since I began my journey in ",[14,15,16],"strong",{},"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.",[19,20,22,30,35,50,53,72,75,86,89,92,211,294,297,300,303,349,352,355,371,375,382,462,465,473,738,741,745,748,755,868,871,1044,1047,1174,1366,1369,1627,1654,1658,1665,1672,1835,1838,1857,1955,1958,1977,2134,2141,2144,2148,2151,2153,2403,2760,2894,2896,2909,2923,2927,2930,2947,2971],"callout-read-more",{"id":21},"f9754148-40bd-4df2-a144-f686e894b200",[10,23,24,25,29],{},"Today, I'm thrilled to share the solution I found, ",[26,27,28],"em",{},"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.",[31,32,34],"h2",{"id":33},"understanding-the-issue","Understanding the issue",[10,36,37,38,41,42,45,46,49],{},"Before delving into the problem, let's align on what we're discussing. This article focuses on ",[14,39,40],{},"dialogs",", ",[14,43,44],{},"modals",", and ",[14,47,48],{},"slideovers",". Each serves to display information or solicit confirmation, differing mainly in presentation and usage.",[10,51,52],{},"Try to open the dialog by clicking the button below.",[54,55,59,60],"div",{"className":56},[57,58],"flex","justify-center","\n  ",[61,62,66,67],"modal",{"title":63,"description":64,":ui":65},"Dialog","This is a dialog","{ content: 'min-w-[280px]' }","\n    ",[68,69,71],"button",{"label":70},"Open Dialog","\n  \n",[10,73,74],{},"Similarly, you can open a slideover by clicking the button.",[54,76,59,78],{"className":77},[57,58],[79,80,66,83],"slideover",{"title":81,"description":82,":ui":65},"Slideover","This is a slideover",[68,84,71],{"label":85},"Open Slideover",[10,87,88],{},"The design and dialog types might seem secondary; the important aspect lies in the underlying code. Let's examine it:",[10,90,91],{},"::: code-group",[93,94,100],"pre",{"className":95,"code":96,"filename":97,"language":98,"meta":99,"style":99},"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","vue","",[101,102,103,119,128,147,161,167,190,201],"code",{"__ignoreMap":99},[104,105,108,112,116],"span",{"class":106,"line":107},"line",1,[104,109,111],{"class":110},"sMK4o","\u003C",[104,113,115],{"class":114},"swJcz","template",[104,117,118],{"class":110},">\n",[104,120,122,125],{"class":106,"line":121},2,[104,123,124],{"class":110},"  \u003C",[104,126,127],{"class":114},"Modal\n",[104,129,131,135,138,141,144],{"class":106,"line":130},3,[104,132,134],{"class":133},"spNyl","    title",[104,136,137],{"class":110},"=",[104,139,140],{"class":110},"\"",[104,142,63],{"class":143},"sfazB",[104,145,146],{"class":110},"\"\n",[104,148,150,153,155,157,159],{"class":106,"line":149},4,[104,151,152],{"class":133},"    description",[104,154,137],{"class":110},[104,156,140],{"class":110},[104,158,64],{"class":143},[104,160,146],{"class":110},[104,162,164],{"class":106,"line":163},5,[104,165,166],{"class":110},"  >\n",[104,168,170,173,176,179,181,183,185,187],{"class":106,"line":169},6,[104,171,172],{"class":110},"    \u003C",[104,174,175],{"class":114},"Button",[104,177,178],{"class":133}," label",[104,180,137],{"class":110},[104,182,140],{"class":110},[104,184,70],{"class":143},[104,186,140],{"class":110},[104,188,189],{"class":110}," \u002F>\n",[104,191,193,196,199],{"class":106,"line":192},7,[104,194,195],{"class":110},"  \u003C\u002F",[104,197,198],{"class":114},"Modal",[104,200,118],{"class":110},[104,202,204,207,209],{"class":106,"line":203},8,[104,205,206],{"class":110},"\u003C\u002F",[104,208,115],{"class":114},[104,210,118],{"class":110},[93,212,215],{"className":95,"code":213,"filename":214,"language":98,"meta":99,"style":99},"\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",[101,216,217,225,232,244,256,260,278,286],{"__ignoreMap":99},[104,218,219,221,223],{"class":106,"line":107},[104,220,111],{"class":110},[104,222,115],{"class":114},[104,224,118],{"class":110},[104,226,227,229],{"class":106,"line":121},[104,228,124],{"class":110},[104,230,231],{"class":114},"Slideover\n",[104,233,234,236,238,240,242],{"class":106,"line":130},[104,235,134],{"class":133},[104,237,137],{"class":110},[104,239,140],{"class":110},[104,241,81],{"class":143},[104,243,146],{"class":110},[104,245,246,248,250,252,254],{"class":106,"line":149},[104,247,152],{"class":133},[104,249,137],{"class":110},[104,251,140],{"class":110},[104,253,82],{"class":143},[104,255,146],{"class":110},[104,257,258],{"class":106,"line":163},[104,259,166],{"class":110},[104,261,262,264,266,268,270,272,274,276],{"class":106,"line":169},[104,263,172],{"class":110},[104,265,175],{"class":114},[104,267,178],{"class":133},[104,269,137],{"class":110},[104,271,140],{"class":110},[104,273,85],{"class":143},[104,275,140],{"class":110},[104,277,189],{"class":110},[104,279,280,282,284],{"class":106,"line":192},[104,281,195],{"class":110},[104,283,81],{"class":114},[104,285,118],{"class":110},[104,287,288,290,292],{"class":106,"line":203},[104,289,206],{"class":110},[104,291,115],{"class":114},[104,293,118],{"class":110},[10,295,296],{},":::",[10,298,299],{},"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.",[10,301,302],{},"Dialogs are a special type of component in an application:",[304,305,306,310,325],"ol",{},[307,308,309],"li",{},"They can be invoked from anywhere within the application.",[307,311,312,313],{},"They open through various methods, such as:",[314,315,316,319,322],"ul",{},[307,317,318],{},"Button clicks",[307,320,321],{},"Key presses",[307,323,324],{},"Route navigations",[307,326,327,328,345,348],{},"The ubiquitous nature makes dialog-free applications nearly impractical without sacrificing user experience:",[314,329,330,333,336,339,342],{},[307,331,332],{},"Creating items",[307,334,335],{},"Editing items",[307,337,338],{},"Action confirmations",[307,340,341],{},"Item visualizations",[307,343,344],{},"Etc.",[346,347],"br",{},"Each action might lead to immediate consequences or await a server response, complicating dialog usage possibilities.",[10,350,351],{},"Due to these factors, dialogs demand meticulous care in development and usage; otherwise, they and their logic can quickly become chaotic.",[10,353,354],{},"Additionally, it has always been a personal preference of mine to enable dialog stacking with smooth animations, like this:",[356,357,59,358,59,367],"figure",{},[359,360,66,362,59],"video",{"autoPlay":361,"loop":361,"muted":361,"playsInline":361},true,[363,364],"source",{"src":365,"type":366},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fa-journey-to-craft-interactive-ui-experiences-with-dialogs\u002Fmodal-stacking.mp4","video\u002Fmp4",[368,369,370],"figcaption",{},"Stacking dialogs with a sleek animation.",[31,372,374],{"id":373},"inline-dialogs","Inline dialogs",[10,376,377,378,381],{},"An initial attempt to address this challenge is via ",[14,379,380],{},"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.",[93,383,384],{"className":95,"code":96,"filename":97,"language":98,"meta":99,"style":99},[101,385,386,394,400,412,424,428,446,454],{"__ignoreMap":99},[104,387,388,390,392],{"class":106,"line":107},[104,389,111],{"class":110},[104,391,115],{"class":114},[104,393,118],{"class":110},[104,395,396,398],{"class":106,"line":121},[104,397,124],{"class":110},[104,399,127],{"class":114},[104,401,402,404,406,408,410],{"class":106,"line":130},[104,403,134],{"class":133},[104,405,137],{"class":110},[104,407,140],{"class":110},[104,409,63],{"class":143},[104,411,146],{"class":110},[104,413,414,416,418,420,422],{"class":106,"line":149},[104,415,152],{"class":133},[104,417,137],{"class":110},[104,419,140],{"class":110},[104,421,64],{"class":143},[104,423,146],{"class":110},[104,425,426],{"class":106,"line":163},[104,427,166],{"class":110},[104,429,430,432,434,436,438,440,442,444],{"class":106,"line":169},[104,431,172],{"class":110},[104,433,175],{"class":114},[104,435,178],{"class":133},[104,437,137],{"class":110},[104,439,140],{"class":110},[104,441,70],{"class":143},[104,443,140],{"class":110},[104,445,189],{"class":110},[104,447,448,450,452],{"class":106,"line":192},[104,449,195],{"class":110},[104,451,198],{"class":114},[104,453,118],{"class":110},[104,455,456,458,460],{"class":106,"line":203},[104,457,206],{"class":110},[104,459,115],{"class":114},[104,461,118],{"class":110},[10,463,464],{},"Despite the straightforward implementation, this approach presents at least two significant drawbacks:",[304,466,467,470],{},[307,468,469],{},"Dialog components become tightly coupled with the utilizing component, complicating comprehension and refactoring.",[307,471,472],{},"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:",[93,474,477],{"className":95,"code":475,"filename":476,"language":98,"meta":99,"style":99},"\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",[101,478,479,503,530,535,558,562,572,589,614,620,628,637,642,651,660,667,686,700,714,720,729],{"__ignoreMap":99},[104,480,481,483,486,489,491,493,496,498,501],{"class":106,"line":107},[104,482,111],{"class":110},[104,484,485],{"class":114},"script",[104,487,488],{"class":133}," lang",[104,490,137],{"class":110},[104,492,140],{"class":110},[104,494,495],{"class":143},"ts",[104,497,140],{"class":110},[104,499,500],{"class":133}," setup",[104,502,118],{"class":110},[104,504,505,509,512,516,519,522,525,527],{"class":106,"line":121},[104,506,508],{"class":507},"s7zQu","import",[104,510,511],{"class":110}," {",[104,513,515],{"class":514},"sTEyZ"," ref",[104,517,518],{"class":110}," }",[104,520,521],{"class":507}," from",[104,523,524],{"class":110}," '",[104,526,98],{"class":143},[104,528,529],{"class":110},"'\n",[104,531,532],{"class":106,"line":130},[104,533,534],{"emptyLinePlaceholder":361},"\n",[104,536,537,540,543,545,548,551,555],{"class":106,"line":149},[104,538,539],{"class":133},"const",[104,541,542],{"class":514}," isDialogOpen ",[104,544,137],{"class":110},[104,546,515],{"class":547},"s2Zo4",[104,549,550],{"class":514},"(",[104,552,554],{"class":553},"sfNiH","false",[104,556,557],{"class":514},")\n",[104,559,560],{"class":106,"line":163},[104,561,534],{"emptyLinePlaceholder":361},[104,563,564,567,569],{"class":106,"line":169},[104,565,566],{"class":547},"defineShortcuts",[104,568,550],{"class":514},[104,570,571],{"class":110},"{\n",[104,573,574,577,580,583,586],{"class":106,"line":192},[104,575,576],{"class":547},"  meta_k",[104,578,579],{"class":110},":",[104,581,582],{"class":110}," ()",[104,584,585],{"class":133}," =>",[104,587,588],{"class":110}," {\n",[104,590,591,594,597,600,603,606,609,611],{"class":106,"line":203},[104,592,593],{"class":514},"    isDialogOpen",[104,595,596],{"class":110},".",[104,598,599],{"class":514},"value",[104,601,602],{"class":110}," =",[104,604,605],{"class":110}," !",[104,607,608],{"class":514},"isDialogOpen",[104,610,596],{"class":110},[104,612,613],{"class":514},"value\n",[104,615,617],{"class":106,"line":616},9,[104,618,619],{"class":110},"  }\n",[104,621,623,626],{"class":106,"line":622},10,[104,624,625],{"class":110},"}",[104,627,557],{"class":514},[104,629,631,633,635],{"class":106,"line":630},11,[104,632,206],{"class":110},[104,634,485],{"class":114},[104,636,118],{"class":110},[104,638,640],{"class":106,"line":639},12,[104,641,534],{"emptyLinePlaceholder":361},[104,643,645,647,649],{"class":106,"line":644},13,[104,646,111],{"class":110},[104,648,115],{"class":114},[104,650,118],{"class":110},[104,652,654,656,658],{"class":106,"line":653},14,[104,655,124],{"class":110},[104,657,54],{"class":114},[104,659,118],{"class":110},[104,661,663,665],{"class":106,"line":662},15,[104,664,172],{"class":110},[104,666,127],{"class":114},[104,668,670,673,675,678,680,682,684],{"class":106,"line":669},16,[104,671,672],{"class":133},"      v-model",[104,674,579],{"class":110},[104,676,677],{"class":133},"open",[104,679,137],{"class":110},[104,681,140],{"class":110},[104,683,608],{"class":514},[104,685,146],{"class":110},[104,687,689,692,694,696,698],{"class":106,"line":688},17,[104,690,691],{"class":133},"      title",[104,693,137],{"class":110},[104,695,140],{"class":110},[104,697,63],{"class":143},[104,699,146],{"class":110},[104,701,703,706,708,710,712],{"class":106,"line":702},18,[104,704,705],{"class":133},"      description",[104,707,137],{"class":110},[104,709,140],{"class":110},[104,711,64],{"class":143},[104,713,146],{"class":110},[104,715,717],{"class":106,"line":716},19,[104,718,719],{"class":110},"    \u002F>\n",[104,721,723,725,727],{"class":106,"line":722},20,[104,724,195],{"class":110},[104,726,54],{"class":114},[104,728,118],{"class":110},[104,730,732,734,736],{"class":106,"line":731},21,[104,733,206],{"class":110},[104,735,115],{"class":114},[104,737,118],{"class":110},[10,739,740],{},"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.",[31,742,744],{"id":743},"global-dialogs","Global dialogs",[10,746,747],{},"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.",[10,749,750,751,754],{},"An ",[101,752,753],{},"App.vue"," file might look like this:",[93,756,760],{"className":757,"code":758,"filename":759,"language":98,"meta":99,"style":99},"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",[101,761,762,770,778,787,797,806,810,822,832,842,846,852,860],{"__ignoreMap":99},[104,763,764,766,768],{"class":106,"line":107},[104,765,111],{"class":110},[104,767,115],{"class":114},[104,769,118],{"class":110},[104,771,772,774,776],{"class":106,"line":121},[104,773,124],{"class":110},[104,775,54],{"class":114},[104,777,118],{"class":110},[104,779,780,782,785],{"class":106,"line":130},[104,781,172],{"class":110},[104,783,784],{"class":114},"Layout",[104,786,118],{"class":110},[104,788,789,792,795],{"class":106,"line":149},[104,790,791],{"class":110},"      \u003C",[104,793,794],{"class":114},"RouterView",[104,796,189],{"class":110},[104,798,799,802,804],{"class":106,"line":163},[104,800,801],{"class":110},"    \u003C\u002F",[104,803,784],{"class":114},[104,805,118],{"class":110},[104,807,808],{"class":106,"line":169},[104,809,534],{"emptyLinePlaceholder":361},[104,811,814,816,819],{"class":812,"line":192},[106,813],"highlighted",[104,815,172],{"class":110},[104,817,818],{"class":114},"ModalFeature1",[104,820,821],{"class":110}," \u002F>",[104,823,825,827,830],{"class":824,"line":203},[106,813],[104,826,172],{"class":110},[104,828,829],{"class":114},"ModalFeature2",[104,831,821],{"class":110},[104,833,835,837,840],{"class":834,"line":616},[106,813],[104,836,172],{"class":110},[104,838,839],{"class":114},"ModalFeature3",[104,841,821],{"class":110},[104,843,844],{"class":106,"line":622},[104,845,534],{"emptyLinePlaceholder":361},[104,847,848],{"class":106,"line":630},[104,849,851],{"class":850},"sHwdD","    \u003C!-- And so on... -->\n",[104,853,854,856,858],{"class":106,"line":639},[104,855,195],{"class":110},[104,857,54],{"class":114},[104,859,118],{"class":110},[104,861,862,864,866],{"class":106,"line":644},[104,863,206],{"class":110},[104,865,115],{"class":114},[104,867,118],{"class":110},[10,869,870],{},"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.",[93,872,876],{"className":873,"code":874,"filename":875,"language":495,"meta":99,"style":99},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight has-highlighted","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",[101,877,878,896,900,919,923,939,951,965,969,973,984,997,1001,1005,1012,1019,1023,1030,1035,1039],{"__ignoreMap":99},[104,879,880,882,884,886,888,890,892,894],{"class":106,"line":107},[104,881,508],{"class":507},[104,883,511],{"class":110},[104,885,515],{"class":514},[104,887,518],{"class":110},[104,889,521],{"class":507},[104,891,524],{"class":110},[104,893,98],{"class":143},[104,895,529],{"class":110},[104,897,898],{"class":106,"line":121},[104,899,534],{"emptyLinePlaceholder":361},[104,901,903,905,908,910,912,914,916],{"class":902,"line":130},[106,813],[104,904,539],{"class":133},[104,906,907],{"class":514}," isOpen ",[104,909,137],{"class":110},[104,911,515],{"class":547},[104,913,550],{"class":514},[104,915,554],{"class":553},[104,917,918],{"class":514},") ",[104,920,921],{"class":106,"line":149},[104,922,534],{"emptyLinePlaceholder":361},[104,924,925,928,931,934,937],{"class":106,"line":163},[104,926,927],{"class":507},"export",[104,929,930],{"class":133}," function",[104,932,933],{"class":547}," useDialog",[104,935,936],{"class":110},"()",[104,938,588],{"class":110},[104,940,941,944,947,949],{"class":106,"line":169},[104,942,943],{"class":133},"  function",[104,945,946],{"class":547}," open",[104,948,936],{"class":110},[104,950,588],{"class":110},[104,952,953,956,958,960,962],{"class":106,"line":192},[104,954,955],{"class":514},"    isOpen",[104,957,596],{"class":110},[104,959,599],{"class":514},[104,961,602],{"class":110},[104,963,964],{"class":553}," true\n",[104,966,967],{"class":106,"line":203},[104,968,619],{"class":110},[104,970,971],{"class":106,"line":616},[104,972,534],{"emptyLinePlaceholder":361},[104,974,975,977,980,982],{"class":106,"line":622},[104,976,943],{"class":133},[104,978,979],{"class":547}," close",[104,981,936],{"class":110},[104,983,588],{"class":110},[104,985,986,988,990,992,994],{"class":106,"line":630},[104,987,955],{"class":514},[104,989,596],{"class":110},[104,991,599],{"class":514},[104,993,602],{"class":110},[104,995,996],{"class":553}," false\n",[104,998,999],{"class":106,"line":639},[104,1000,619],{"class":110},[104,1002,1003],{"class":106,"line":644},[104,1004,534],{"emptyLinePlaceholder":361},[104,1006,1007,1010],{"class":106,"line":653},[104,1008,1009],{"class":507},"  return",[104,1011,588],{"class":110},[104,1013,1014,1016],{"class":106,"line":662},[104,1015,955],{"class":514},[104,1017,1018],{"class":110},",\n",[104,1020,1021],{"class":106,"line":669},[104,1022,534],{"emptyLinePlaceholder":361},[104,1024,1025,1028],{"class":106,"line":688},[104,1026,1027],{"class":514},"    open",[104,1029,1018],{"class":110},[104,1031,1032],{"class":106,"line":702},[104,1033,1034],{"class":514},"    close\n",[104,1036,1037],{"class":106,"line":716},[104,1038,619],{"class":110},[104,1040,1041],{"class":106,"line":722},[104,1042,1043],{"class":110},"}\n",[10,1045,1046],{},"Utilize this composable for both the dialog-opening component and the dialog itself.",[93,1048,1050],{"className":757,"code":1049,"filename":759,"language":98,"meta":99,"style":99},"\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",[101,1051,1052,1072,1091,1095,1114,1122,1126,1134,1166],{"__ignoreMap":99},[104,1053,1054,1056,1058,1060,1062,1064,1066,1068,1070],{"class":106,"line":107},[104,1055,111],{"class":110},[104,1057,485],{"class":114},[104,1059,488],{"class":133},[104,1061,137],{"class":110},[104,1063,140],{"class":110},[104,1065,495],{"class":143},[104,1067,140],{"class":110},[104,1069,500],{"class":133},[104,1071,118],{"class":110},[104,1073,1074,1076,1078,1080,1082,1084,1086,1089],{"class":106,"line":121},[104,1075,508],{"class":507},[104,1077,511],{"class":110},[104,1079,933],{"class":514},[104,1081,518],{"class":110},[104,1083,521],{"class":507},[104,1085,524],{"class":110},[104,1087,1088],{"class":143},"@\u002Fcomposables\u002FuseDialog",[104,1090,529],{"class":110},[104,1092,1093],{"class":106,"line":130},[104,1094,534],{"emptyLinePlaceholder":361},[104,1096,1098,1100,1102,1105,1107,1109,1111],{"class":1097,"line":149},[106,813],[104,1099,539],{"class":133},[104,1101,511],{"class":110},[104,1103,1104],{"class":514}," open ",[104,1106,625],{"class":110},[104,1108,602],{"class":110},[104,1110,933],{"class":547},[104,1112,1113],{"class":514},"() ",[104,1115,1116,1118,1120],{"class":106,"line":163},[104,1117,206],{"class":110},[104,1119,485],{"class":114},[104,1121,118],{"class":110},[104,1123,1124],{"class":106,"line":169},[104,1125,534],{"emptyLinePlaceholder":361},[104,1127,1128,1130,1132],{"class":106,"line":192},[104,1129,111],{"class":110},[104,1131,115],{"class":114},[104,1133,118],{"class":110},[104,1135,1136,1138,1140,1142,1144,1146,1148,1150,1153,1156,1158,1160,1162,1164],{"class":106,"line":203},[104,1137,124],{"class":110},[104,1139,175],{"class":114},[104,1141,178],{"class":133},[104,1143,137],{"class":110},[104,1145,140],{"class":110},[104,1147,70],{"class":143},[104,1149,140],{"class":110},[104,1151,1152],{"class":110}," @",[104,1154,1155],{"class":133},"click",[104,1157,137],{"class":110},[104,1159,140],{"class":110},[104,1161,677],{"class":514},[104,1163,140],{"class":110},[104,1165,189],{"class":110},[104,1167,1168,1170,1172],{"class":106,"line":616},[104,1169,206],{"class":110},[104,1171,115],{"class":114},[104,1173,118],{"class":110},[93,1175,1178],{"className":757,"code":1176,"filename":1177,"language":98,"meta":99,"style":99},"\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",[101,1179,1180,1200,1218,1222,1246,1254,1258,1266,1272,1290,1302,1314,1318,1350,1358],{"__ignoreMap":99},[104,1181,1182,1184,1186,1188,1190,1192,1194,1196,1198],{"class":106,"line":107},[104,1183,111],{"class":110},[104,1185,485],{"class":114},[104,1187,488],{"class":133},[104,1189,137],{"class":110},[104,1191,140],{"class":110},[104,1193,495],{"class":143},[104,1195,140],{"class":110},[104,1197,500],{"class":133},[104,1199,118],{"class":110},[104,1201,1202,1204,1206,1208,1210,1212,1214,1216],{"class":106,"line":121},[104,1203,508],{"class":507},[104,1205,511],{"class":110},[104,1207,933],{"class":514},[104,1209,518],{"class":110},[104,1211,521],{"class":507},[104,1213,524],{"class":110},[104,1215,1088],{"class":143},[104,1217,529],{"class":110},[104,1219,1220],{"class":106,"line":130},[104,1221,534],{"emptyLinePlaceholder":361},[104,1223,1225,1227,1229,1232,1235,1238,1240,1242,1244],{"class":1224,"line":149},[106,813],[104,1226,539],{"class":133},[104,1228,511],{"class":110},[104,1230,1231],{"class":514}," isOpen",[104,1233,1234],{"class":110},",",[104,1236,1237],{"class":514}," close ",[104,1239,625],{"class":110},[104,1241,602],{"class":110},[104,1243,933],{"class":547},[104,1245,1113],{"class":514},[104,1247,1248,1250,1252],{"class":106,"line":163},[104,1249,206],{"class":110},[104,1251,485],{"class":114},[104,1253,118],{"class":110},[104,1255,1256],{"class":106,"line":169},[104,1257,534],{"emptyLinePlaceholder":361},[104,1259,1260,1262,1264],{"class":106,"line":192},[104,1261,111],{"class":110},[104,1263,115],{"class":114},[104,1265,118],{"class":110},[104,1267,1268,1270],{"class":106,"line":203},[104,1269,124],{"class":110},[104,1271,127],{"class":114},[104,1273,1274,1277,1279,1281,1283,1285,1288],{"class":106,"line":616},[104,1275,1276],{"class":133},"    v-model",[104,1278,579],{"class":110},[104,1280,677],{"class":133},[104,1282,137],{"class":110},[104,1284,140],{"class":110},[104,1286,1287],{"class":514},"isOpen",[104,1289,146],{"class":110},[104,1291,1292,1294,1296,1298,1300],{"class":106,"line":622},[104,1293,134],{"class":133},[104,1295,137],{"class":110},[104,1297,140],{"class":110},[104,1299,63],{"class":143},[104,1301,146],{"class":110},[104,1303,1304,1306,1308,1310,1312],{"class":106,"line":630},[104,1305,152],{"class":133},[104,1307,137],{"class":110},[104,1309,140],{"class":110},[104,1311,64],{"class":143},[104,1313,146],{"class":110},[104,1315,1316],{"class":106,"line":639},[104,1317,166],{"class":110},[104,1319,1320,1322,1324,1326,1328,1330,1333,1335,1337,1339,1341,1343,1346,1348],{"class":106,"line":644},[104,1321,172],{"class":110},[104,1323,175],{"class":114},[104,1325,178],{"class":133},[104,1327,137],{"class":110},[104,1329,140],{"class":110},[104,1331,1332],{"class":143},"Close",[104,1334,140],{"class":110},[104,1336,1152],{"class":110},[104,1338,1155],{"class":133},[104,1340,137],{"class":110},[104,1342,140],{"class":110},[104,1344,1345],{"class":514},"close",[104,1347,140],{"class":110},[104,1349,189],{"class":110},[104,1351,1352,1354,1356],{"class":106,"line":653},[104,1353,195],{"class":110},[104,1355,198],{"class":114},[104,1357,118],{"class":110},[104,1359,1360,1362,1364],{"class":106,"line":662},[104,1361,206],{"class":110},[104,1363,115],{"class":114},[104,1365,118],{"class":110},[10,1367,1368],{},"This approach is certainly superior to the inline method but has drawbacks:",[304,1370,1371,1374],{},[307,1372,1373],{},"Inclusion of modals at the app (or route) top-level creates potential code redundancy.",[307,1375,1376,1377],{},"Dialog management becomes elaborate, particularly with numerous dialogs or props, each requiring passage through the composable.",[93,1378,1380],{"className":873,"code":1379,"language":495,"meta":99,"style":99},"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",[101,1381,1382,1400,1404,1420,1439,1457,1461,1473,1505,1518,1531,1543,1547,1551,1561,1573,1577,1581,1587,1593,1599,1605,1612,1617,1622],{"__ignoreMap":99},[104,1383,1384,1386,1388,1390,1392,1394,1396,1398],{"class":106,"line":107},[104,1385,508],{"class":507},[104,1387,511],{"class":110},[104,1389,515],{"class":514},[104,1391,518],{"class":110},[104,1393,521],{"class":507},[104,1395,524],{"class":110},[104,1397,98],{"class":143},[104,1399,529],{"class":110},[104,1401,1402],{"class":106,"line":121},[104,1403,534],{"emptyLinePlaceholder":361},[104,1405,1406,1408,1410,1412,1414,1416,1418],{"class":106,"line":130},[104,1407,539],{"class":133},[104,1409,907],{"class":514},[104,1411,137],{"class":110},[104,1413,515],{"class":547},[104,1415,550],{"class":514},[104,1417,554],{"class":553},[104,1419,557],{"class":514},[104,1421,1423,1425,1428,1430,1432,1434,1437],{"class":1422,"line":149},[106,813],[104,1424,539],{"class":133},[104,1426,1427],{"class":514}," title ",[104,1429,137],{"class":110},[104,1431,515],{"class":547},[104,1433,550],{"class":514},[104,1435,1436],{"class":110},"''",[104,1438,918],{"class":514},[104,1440,1442,1444,1447,1449,1451,1453,1455],{"class":1441,"line":163},[106,813],[104,1443,539],{"class":133},[104,1445,1446],{"class":514}," description ",[104,1448,137],{"class":110},[104,1450,515],{"class":547},[104,1452,550],{"class":514},[104,1454,1436],{"class":110},[104,1456,918],{"class":514},[104,1458,1459],{"class":106,"line":169},[104,1460,534],{"emptyLinePlaceholder":361},[104,1462,1463,1465,1467,1469,1471],{"class":106,"line":192},[104,1464,927],{"class":507},[104,1466,930],{"class":133},[104,1468,933],{"class":547},[104,1470,936],{"class":110},[104,1472,588],{"class":110},[104,1474,1475,1477,1479,1481,1485,1487,1491,1493,1496,1498,1500,1503],{"class":106,"line":203},[104,1476,943],{"class":133},[104,1478,946],{"class":547},[104,1480,550],{"class":110},[104,1482,1484],{"class":1483},"sHdIc","newTitle",[104,1486,579],{"class":110},[104,1488,1490],{"class":1489},"sBMFI"," string",[104,1492,1234],{"class":110},[104,1494,1495],{"class":1483}," newDescription",[104,1497,579],{"class":110},[104,1499,1490],{"class":1489},[104,1501,1502],{"class":110},")",[104,1504,588],{"class":110},[104,1506,1507,1509,1511,1513,1515],{"class":106,"line":616},[104,1508,134],{"class":514},[104,1510,596],{"class":110},[104,1512,599],{"class":514},[104,1514,602],{"class":110},[104,1516,1517],{"class":514}," newTitle\n",[104,1519,1520,1522,1524,1526,1528],{"class":106,"line":622},[104,1521,152],{"class":514},[104,1523,596],{"class":110},[104,1525,599],{"class":514},[104,1527,602],{"class":110},[104,1529,1530],{"class":514}," newDescription\n",[104,1532,1533,1535,1537,1539,1541],{"class":106,"line":630},[104,1534,955],{"class":514},[104,1536,596],{"class":110},[104,1538,599],{"class":514},[104,1540,602],{"class":110},[104,1542,964],{"class":553},[104,1544,1545],{"class":106,"line":639},[104,1546,619],{"class":110},[104,1548,1549],{"class":106,"line":644},[104,1550,534],{"emptyLinePlaceholder":361},[104,1552,1553,1555,1557,1559],{"class":106,"line":653},[104,1554,943],{"class":133},[104,1556,979],{"class":547},[104,1558,936],{"class":110},[104,1560,588],{"class":110},[104,1562,1563,1565,1567,1569,1571],{"class":106,"line":662},[104,1564,955],{"class":514},[104,1566,596],{"class":110},[104,1568,599],{"class":514},[104,1570,602],{"class":110},[104,1572,996],{"class":553},[104,1574,1575],{"class":106,"line":669},[104,1576,619],{"class":110},[104,1578,1579],{"class":106,"line":688},[104,1580,534],{"emptyLinePlaceholder":361},[104,1582,1583,1585],{"class":106,"line":702},[104,1584,1009],{"class":507},[104,1586,588],{"class":110},[104,1588,1589,1591],{"class":106,"line":716},[104,1590,955],{"class":514},[104,1592,1018],{"class":110},[104,1594,1595,1597],{"class":106,"line":722},[104,1596,134],{"class":514},[104,1598,1018],{"class":110},[104,1600,1601,1603],{"class":106,"line":731},[104,1602,152],{"class":514},[104,1604,1018],{"class":110},[104,1606,1608,1610],{"class":106,"line":1607},22,[104,1609,1027],{"class":514},[104,1611,1018],{"class":110},[104,1613,1615],{"class":106,"line":1614},23,[104,1616,1034],{"class":514},[104,1618,1620],{"class":106,"line":1619},24,[104,1621,619],{"class":110},[104,1623,1625],{"class":106,"line":1624},25,[104,1626,1043],{"class":110},[1628,1629,1630],"blockquote",{},[10,1631,1632,1635,1636,1645,1646,1653],{},[104,1633,1634],{},"!NOTE","\nSimplify composable writing with ",[1637,1638,1642],"a",{"href":1639,"rel":1640},"https:\u002F\u002Fvueuse.org\u002Fshared\u002FcreateSharedComposable\u002F",[1641],"nofollow",[101,1643,1644],{},"createSharedComposable"," from ",[1637,1647,1650],{"href":1648,"rel":1649},"https:\u002F\u002Fvueuse.org\u002F",[1641],[101,1651,1652],{},"@vueuse\u002Fcore"," to craft a singleton composable, automatically sharing the same state between all utilizing components.",[31,1655,1657],{"id":1656},"programmatic-dialogs","Programmatic dialogs",[10,1659,1660,1661,1664],{},"The latest, profoundly effective approach I discovered involves ",[14,1662,1663],{},"programmatically managing dialogs",". This method resolves the difficulties mentioned earlier by allowing complete dialog control through code rather than templates.",[10,1666,1667,1668,1671],{},"Having used programmatic dialogs in ",[14,1669,1670],{},"Angular"," with Angular Material, I appreciated its capacity for code simplification.",[93,1673,1677],{"className":1674,"code":1675,"filename":1676,"language":495,"meta":99,"style":99},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","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",[101,1678,1679,1702,1719,1735,1741,1745,1775,1806,1812,1816],{"__ignoreMap":99},[104,1680,1681,1683,1686,1688,1691,1693,1695,1698,1700],{"class":106,"line":107},[104,1682,539],{"class":133},[104,1684,1685],{"class":514}," dialogRef ",[104,1687,137],{"class":110},[104,1689,1690],{"class":514}," dialog",[104,1692,596],{"class":110},[104,1694,677],{"class":547},[104,1696,1697],{"class":514},"(UserProfileComponent",[104,1699,1234],{"class":110},[104,1701,588],{"class":110},[104,1703,1704,1707,1709,1711,1714,1717],{"class":106,"line":121},[104,1705,1706],{"class":114},"  height",[104,1708,579],{"class":110},[104,1710,524],{"class":110},[104,1712,1713],{"class":143},"400px",[104,1715,1716],{"class":110},"'",[104,1718,1018],{"class":110},[104,1720,1721,1724,1726,1728,1731,1733],{"class":106,"line":130},[104,1722,1723],{"class":114},"  width",[104,1725,579],{"class":110},[104,1727,524],{"class":110},[104,1729,1730],{"class":143},"600px",[104,1732,1716],{"class":110},[104,1734,1018],{"class":110},[104,1736,1737,1739],{"class":106,"line":149},[104,1738,625],{"class":110},[104,1740,557],{"class":514},[104,1742,1743],{"class":106,"line":163},[104,1744,534],{"emptyLinePlaceholder":361},[104,1746,1747,1750,1752,1755,1757,1759,1762,1764,1766,1769,1771,1773],{"class":106,"line":169},[104,1748,1749],{"class":514},"dialogRef",[104,1751,596],{"class":110},[104,1753,1754],{"class":547},"afterClosed",[104,1756,936],{"class":514},[104,1758,596],{"class":110},[104,1760,1761],{"class":547},"subscribe",[104,1763,550],{"class":514},[104,1765,550],{"class":110},[104,1767,1768],{"class":1483},"result",[104,1770,1502],{"class":110},[104,1772,585],{"class":133},[104,1774,588],{"class":110},[104,1776,1777,1780,1782,1785,1787,1790,1793,1796,1798,1801,1803],{"class":106,"line":192},[104,1778,1779],{"class":514},"  console",[104,1781,596],{"class":110},[104,1783,1784],{"class":547},"log",[104,1786,550],{"class":114},[104,1788,1789],{"class":110},"`",[104,1791,1792],{"class":143},"Dialog result: ",[104,1794,1795],{"class":110},"${",[104,1797,1768],{"class":514},[104,1799,1800],{"class":110},"}`",[104,1802,918],{"class":114},[104,1804,1805],{"class":850},"\u002F\u002F Pizza!\n",[104,1807,1808,1810],{"class":106,"line":203},[104,1809,625],{"class":110},[104,1811,557],{"class":514},[104,1813,1814],{"class":106,"line":616},[104,1815,534],{"emptyLinePlaceholder":361},[104,1817,1818,1820,1822,1824,1826,1828,1831,1833],{"class":106,"line":622},[104,1819,1749],{"class":514},[104,1821,596],{"class":110},[104,1823,1345],{"class":547},[104,1825,550],{"class":514},[104,1827,1716],{"class":110},[104,1829,1830],{"class":143},"Pizza!",[104,1832,1716],{"class":110},[104,1834,557],{"class":514},[10,1836,1837],{},"For a long time, I missed such a feature in Vue.",[10,1839,1840,1841,1846,1847,1852,1853,1856],{},"Until recently, when ",[1637,1842,1845],{"href":1843,"rel":1844},"https:\u002F\u002Fnuxt.com\u002F",[1641],"Nuxt"," released version 3 of ",[1637,1848,1851],{"href":1849,"rel":1850},"https:\u002F\u002Fui.nuxt.com\u002F",[1641],"Nuxt UI"," with the ",[101,1854,1855],{},"useOverlay"," composable. This shared application composable enables dialog opening from any location, mirroring Angular Material functionality.",[93,1858,1860],{"className":95,"code":1859,"filename":759,"language":98,"meta":99,"style":99},"\u003Cscript setup lang=\"ts\">\nconst overlay = useOverlay()\n\nasync function openModal() {\n  overlay\n    .create(MyModal)\n    .open()\n}\n\u003C\u002Fscript>\n",[101,1861,1862,1882,1897,1901,1915,1920,1935,1943,1947],{"__ignoreMap":99},[104,1863,1864,1866,1868,1870,1872,1874,1876,1878,1880],{"class":106,"line":107},[104,1865,111],{"class":110},[104,1867,485],{"class":114},[104,1869,500],{"class":133},[104,1871,488],{"class":133},[104,1873,137],{"class":110},[104,1875,140],{"class":110},[104,1877,495],{"class":143},[104,1879,140],{"class":110},[104,1881,118],{"class":110},[104,1883,1884,1886,1889,1891,1894],{"class":106,"line":121},[104,1885,539],{"class":133},[104,1887,1888],{"class":514}," overlay ",[104,1890,137],{"class":110},[104,1892,1893],{"class":547}," useOverlay",[104,1895,1896],{"class":514},"()\n",[104,1898,1899],{"class":106,"line":130},[104,1900,534],{"emptyLinePlaceholder":361},[104,1902,1903,1906,1908,1911,1913],{"class":106,"line":149},[104,1904,1905],{"class":133},"async",[104,1907,930],{"class":133},[104,1909,1910],{"class":547}," openModal",[104,1912,936],{"class":110},[104,1914,588],{"class":110},[104,1916,1917],{"class":106,"line":163},[104,1918,1919],{"class":514},"  overlay\n",[104,1921,1922,1925,1928,1930,1933],{"class":106,"line":169},[104,1923,1924],{"class":110},"    .",[104,1926,1927],{"class":547},"create",[104,1929,550],{"class":114},[104,1931,1932],{"class":514},"MyModal",[104,1934,557],{"class":114},[104,1936,1937,1939,1941],{"class":106,"line":192},[104,1938,1924],{"class":110},[104,1940,677],{"class":547},[104,1942,1896],{"class":114},[104,1944,1945],{"class":106,"line":203},[104,1946,1043],{"class":110},[104,1948,1949,1951,1953],{"class":106,"line":616},[104,1950,206],{"class":110},[104,1952,485],{"class":114},[104,1954,118],{"class":110},[10,1956,1957],{},"Beautiful, isn't it? 😍",[10,1959,1960,1961,1968,1969,1976],{},"Under the hood, this method utilizes the ",[1637,1962,1965],{"href":1963,"rel":1964},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fessentials\u002Fcomponent-basics.html#dynamic-components",[1641],[101,1966,1967],{},"component"," directive with a simple ",[1637,1970,1973],{"href":1971,"rel":1972},"https:\u002F\u002Fvuejs.org\u002Fapi\u002Fbuilt-in-directives.html#v-for",[1641],[101,1974,1975],{},"v-for"," loop to render dialogs.",[93,1978,1981],{"className":95,"code":1979,"filename":1980,"language":98,"meta":99,"style":99},"\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",[101,1982,1983,2003,2024,2032,2036,2044,2051,2065,2079,2093,2107,2121,2126],{"__ignoreMap":99},[104,1984,1985,1987,1989,1991,1993,1995,1997,1999,2001],{"class":106,"line":107},[104,1986,111],{"class":110},[104,1988,485],{"class":114},[104,1990,488],{"class":133},[104,1992,137],{"class":110},[104,1994,140],{"class":110},[104,1996,495],{"class":143},[104,1998,140],{"class":110},[104,2000,500],{"class":133},[104,2002,118],{"class":110},[104,2004,2005,2007,2009,2012,2014,2016,2018,2020,2022],{"class":106,"line":121},[104,2006,539],{"class":133},[104,2008,511],{"class":110},[104,2010,2011],{"class":514}," overlays",[104,2013,1234],{"class":110},[104,2015,1237],{"class":514},[104,2017,625],{"class":110},[104,2019,602],{"class":110},[104,2021,1893],{"class":547},[104,2023,1896],{"class":514},[104,2025,2026,2028,2030],{"class":106,"line":130},[104,2027,206],{"class":110},[104,2029,485],{"class":114},[104,2031,118],{"class":110},[104,2033,2034],{"class":106,"line":149},[104,2035,534],{"emptyLinePlaceholder":361},[104,2037,2038,2040,2042],{"class":106,"line":163},[104,2039,111],{"class":110},[104,2041,115],{"class":114},[104,2043,118],{"class":110},[104,2045,2046,2048],{"class":106,"line":169},[104,2047,124],{"class":110},[104,2049,2050],{"class":114},"component\n",[104,2052,2053,2056,2058,2060,2063],{"class":106,"line":192},[104,2054,2055],{"class":133},"    :is",[104,2057,137],{"class":110},[104,2059,140],{"class":110},[104,2061,2062],{"class":143},"overlay.component",[104,2064,146],{"class":110},[104,2066,2067,2070,2072,2074,2077],{"class":106,"line":203},[104,2068,2069],{"class":133},"    v-for",[104,2071,137],{"class":110},[104,2073,140],{"class":110},[104,2075,2076],{"class":143},"overlay in overlays",[104,2078,146],{"class":110},[104,2080,2081,2084,2086,2088,2091],{"class":106,"line":616},[104,2082,2083],{"class":133},"    :key",[104,2085,137],{"class":110},[104,2087,140],{"class":110},[104,2089,2090],{"class":143},"overlay.id",[104,2092,146],{"class":110},[104,2094,2095,2098,2100,2102,2105],{"class":106,"line":622},[104,2096,2097],{"class":133},"    v-bind",[104,2099,137],{"class":110},[104,2101,140],{"class":110},[104,2103,2104],{"class":143},"overlay.props",[104,2106,146],{"class":110},[104,2108,2109,2112,2114,2116,2119],{"class":106,"line":630},[104,2110,2111],{"class":133},"    v-model:open",[104,2113,137],{"class":110},[104,2115,140],{"class":110},[104,2117,2118],{"class":143},"overlay.modelValue",[104,2120,146],{"class":110},[104,2122,2123],{"class":106,"line":639},[104,2124,2125],{"class":110},"  \u002F>\n",[104,2127,2128,2130,2132],{"class":106,"line":644},[104,2129,206],{"class":110},[104,2131,115],{"class":114},[104,2133,118],{"class":110},[1628,2135,2136],{},[10,2137,2138,2140],{},[104,2139,1634],{},"\nThis is a simplified code version.",[10,2142,2143],{},"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.",[31,2145,2147],{"id":2146},"effortless-dialog-stacking","Effortless dialog stacking",[10,2149,2150],{},"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.",[10,2152,91],{},[93,2154,2156],{"className":95,"code":2155,"filename":759,"language":98,"meta":99,"style":99},"\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",[101,2157,2158,2178,2195,2199,2211,2223,2227,2242,2251,2267,2281,2286,2295,2302,2310,2314,2322,2326,2334,2342,2374,2378,2387,2395],{"__ignoreMap":99},[104,2159,2160,2162,2164,2166,2168,2170,2172,2174,2176],{"class":106,"line":107},[104,2161,111],{"class":110},[104,2163,485],{"class":114},[104,2165,488],{"class":133},[104,2167,137],{"class":110},[104,2169,140],{"class":110},[104,2171,495],{"class":143},[104,2173,140],{"class":110},[104,2175,500],{"class":133},[104,2177,118],{"class":110},[104,2179,2180,2182,2185,2188,2190,2193],{"class":106,"line":121},[104,2181,508],{"class":507},[104,2183,2184],{"class":514}," DialogFeature1 ",[104,2186,2187],{"class":507},"from",[104,2189,524],{"class":110},[104,2191,2192],{"class":143},".\u002Fcomponents\u002FDialogFeature1.vue",[104,2194,529],{"class":110},[104,2196,2197],{"class":106,"line":130},[104,2198,534],{"emptyLinePlaceholder":361},[104,2200,2201,2203,2205,2207,2209],{"class":106,"line":149},[104,2202,539],{"class":133},[104,2204,1888],{"class":514},[104,2206,137],{"class":110},[104,2208,1893],{"class":547},[104,2210,1896],{"class":514},[104,2212,2213,2216,2219,2221],{"class":106,"line":163},[104,2214,2215],{"class":133},"function",[104,2217,2218],{"class":547}," openOverlay",[104,2220,936],{"class":110},[104,2222,588],{"class":110},[104,2224,2225],{"class":106,"line":169},[104,2226,1919],{"class":514},[104,2228,2229,2231,2233,2235,2238,2240],{"class":106,"line":192},[104,2230,1924],{"class":110},[104,2232,1927],{"class":547},[104,2234,550],{"class":114},[104,2236,2237],{"class":514},"DialogFeature1",[104,2239,1234],{"class":110},[104,2241,588],{"class":110},[104,2243,2244,2247,2249],{"class":106,"line":203},[104,2245,2246],{"class":114},"      props",[104,2248,579],{"class":110},[104,2250,588],{"class":110},[104,2252,2253,2256,2258,2260,2263,2265],{"class":106,"line":616},[104,2254,2255],{"class":114},"        title",[104,2257,579],{"class":110},[104,2259,524],{"class":110},[104,2261,2262],{"class":143},"First Modal",[104,2264,1716],{"class":110},[104,2266,1018],{"class":110},[104,2268,2269,2272,2274,2276,2279],{"class":106,"line":622},[104,2270,2271],{"class":114},"        description",[104,2273,579],{"class":110},[104,2275,524],{"class":110},[104,2277,2278],{"class":143},"This is the first modal",[104,2280,529],{"class":110},[104,2282,2283],{"class":106,"line":630},[104,2284,2285],{"class":110},"      },\n",[104,2287,2288,2291,2293],{"class":106,"line":639},[104,2289,2290],{"class":114},"      destroyOnClose",[104,2292,579],{"class":110},[104,2294,964],{"class":553},[104,2296,2297,2300],{"class":106,"line":644},[104,2298,2299],{"class":110},"    }",[104,2301,557],{"class":114},[104,2303,2304,2306,2308],{"class":106,"line":653},[104,2305,1924],{"class":110},[104,2307,677],{"class":547},[104,2309,1896],{"class":114},[104,2311,2312],{"class":106,"line":662},[104,2313,1043],{"class":110},[104,2315,2316,2318,2320],{"class":106,"line":669},[104,2317,206],{"class":110},[104,2319,485],{"class":114},[104,2321,118],{"class":110},[104,2323,2324],{"class":106,"line":688},[104,2325,534],{"emptyLinePlaceholder":361},[104,2327,2328,2330,2332],{"class":106,"line":702},[104,2329,111],{"class":110},[104,2331,115],{"class":114},[104,2333,118],{"class":110},[104,2335,2336,2338,2340],{"class":106,"line":716},[104,2337,124],{"class":110},[104,2339,54],{"class":114},[104,2341,118],{"class":110},[104,2343,2344,2346,2348,2350,2352,2354,2357,2359,2361,2363,2365,2367,2370,2372],{"class":106,"line":722},[104,2345,172],{"class":110},[104,2347,175],{"class":114},[104,2349,178],{"class":133},[104,2351,137],{"class":110},[104,2353,140],{"class":110},[104,2355,2356],{"class":143},"Open First Overlay",[104,2358,140],{"class":110},[104,2360,1152],{"class":110},[104,2362,1155],{"class":133},[104,2364,137],{"class":110},[104,2366,140],{"class":110},[104,2368,2369],{"class":514},"openOverlay",[104,2371,140],{"class":110},[104,2373,189],{"class":110},[104,2375,2376],{"class":106,"line":731},[104,2377,534],{"emptyLinePlaceholder":361},[104,2379,2380,2382,2385],{"class":106,"line":1607},[104,2381,172],{"class":110},[104,2383,2384],{"class":114},"OverlayProvider",[104,2386,189],{"class":110},[104,2388,2389,2391,2393],{"class":106,"line":1614},[104,2390,195],{"class":110},[104,2392,54],{"class":114},[104,2394,118],{"class":110},[104,2396,2397,2399,2401],{"class":106,"line":1619},[104,2398,206],{"class":110},[104,2400,115],{"class":114},[104,2402,118],{"class":110},[93,2404,2407],{"className":95,"code":2405,"filename":2406,"language":98,"meta":99,"style":99},"\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",[101,2408,2409,2429,2445,2449,2464,2474,2483,2490,2494,2506,2516,2520,2535,2543,2558,2571,2575,2583,2589,2597,2601,2609,2613,2621,2627,2648,2668,2673,2688,2696,2711,2727,2733,2742,2751],{"__ignoreMap":99},[104,2410,2411,2413,2415,2417,2419,2421,2423,2425,2427],{"class":106,"line":107},[104,2412,111],{"class":110},[104,2414,485],{"class":114},[104,2416,488],{"class":133},[104,2418,137],{"class":110},[104,2420,140],{"class":110},[104,2422,495],{"class":143},[104,2424,140],{"class":110},[104,2426,500],{"class":133},[104,2428,118],{"class":110},[104,2430,2431,2433,2436,2438,2440,2443],{"class":106,"line":121},[104,2432,508],{"class":507},[104,2434,2435],{"class":514}," DialogFeature2 ",[104,2437,2187],{"class":507},[104,2439,524],{"class":110},[104,2441,2442],{"class":143},".\u002FDialogFeature2.vue",[104,2444,529],{"class":110},[104,2446,2447],{"class":106,"line":130},[104,2448,534],{"emptyLinePlaceholder":361},[104,2450,2451,2453,2456,2458,2461],{"class":106,"line":149},[104,2452,539],{"class":133},[104,2454,2455],{"class":514}," props ",[104,2457,137],{"class":110},[104,2459,2460],{"class":547}," defineProps",[104,2462,2463],{"class":110},"\u003C{\n",[104,2465,2466,2469,2471],{"class":106,"line":163},[104,2467,2468],{"class":114},"  title",[104,2470,579],{"class":110},[104,2472,2473],{"class":1489}," string\n",[104,2475,2476,2479,2481],{"class":106,"line":169},[104,2477,2478],{"class":114},"  description",[104,2480,579],{"class":110},[104,2482,2473],{"class":1489},[104,2484,2485,2488],{"class":106,"line":192},[104,2486,2487],{"class":110},"}>",[104,2489,1896],{"class":514},[104,2491,2492],{"class":106,"line":203},[104,2493,534],{"emptyLinePlaceholder":361},[104,2495,2496,2498,2500,2502,2504],{"class":106,"line":616},[104,2497,539],{"class":133},[104,2499,1888],{"class":514},[104,2501,137],{"class":110},[104,2503,1893],{"class":547},[104,2505,1896],{"class":514},[104,2507,2508,2510,2512,2514],{"class":106,"line":622},[104,2509,2215],{"class":133},[104,2511,2218],{"class":547},[104,2513,936],{"class":110},[104,2515,588],{"class":110},[104,2517,2518],{"class":106,"line":630},[104,2519,1919],{"class":514},[104,2521,2522,2524,2526,2528,2531,2533],{"class":106,"line":639},[104,2523,1924],{"class":110},[104,2525,1927],{"class":547},[104,2527,550],{"class":114},[104,2529,2530],{"class":514},"DialogFeature2",[104,2532,1234],{"class":110},[104,2534,588],{"class":110},[104,2536,2537,2539,2541],{"class":106,"line":644},[104,2538,2246],{"class":114},[104,2540,579],{"class":110},[104,2542,588],{"class":110},[104,2544,2545,2547,2549,2551,2554,2556],{"class":106,"line":653},[104,2546,2255],{"class":114},[104,2548,579],{"class":110},[104,2550,524],{"class":110},[104,2552,2553],{"class":143},"Second Modal",[104,2555,1716],{"class":110},[104,2557,1018],{"class":110},[104,2559,2560,2562,2564,2566,2569],{"class":106,"line":662},[104,2561,2271],{"class":114},[104,2563,579],{"class":110},[104,2565,524],{"class":110},[104,2567,2568],{"class":143},"This is the second modal",[104,2570,529],{"class":110},[104,2572,2573],{"class":106,"line":669},[104,2574,2285],{"class":110},[104,2576,2577,2579,2581],{"class":106,"line":688},[104,2578,2290],{"class":114},[104,2580,579],{"class":110},[104,2582,964],{"class":553},[104,2584,2585,2587],{"class":106,"line":702},[104,2586,2299],{"class":110},[104,2588,557],{"class":114},[104,2590,2591,2593,2595],{"class":106,"line":716},[104,2592,1924],{"class":110},[104,2594,677],{"class":547},[104,2596,1896],{"class":114},[104,2598,2599],{"class":106,"line":722},[104,2600,1043],{"class":110},[104,2602,2603,2605,2607],{"class":106,"line":731},[104,2604,206],{"class":110},[104,2606,485],{"class":114},[104,2608,118],{"class":110},[104,2610,2611],{"class":106,"line":1607},[104,2612,534],{"emptyLinePlaceholder":361},[104,2614,2615,2617,2619],{"class":106,"line":1614},[104,2616,111],{"class":110},[104,2618,115],{"class":114},[104,2620,118],{"class":110},[104,2622,2623,2625],{"class":106,"line":1619},[104,2624,124],{"class":110},[104,2626,127],{"class":114},[104,2628,2629,2632,2635,2637,2639,2642,2644,2646],{"class":106,"line":1624},[104,2630,2631],{"class":110},"    :",[104,2633,2634],{"class":133},"title",[104,2636,137],{"class":110},[104,2638,140],{"class":110},[104,2640,2641],{"class":514},"props",[104,2643,596],{"class":110},[104,2645,2634],{"class":514},[104,2647,146],{"class":110},[104,2649,2651,2653,2656,2658,2660,2662,2664,2666],{"class":106,"line":2650},26,[104,2652,2631],{"class":110},[104,2654,2655],{"class":133},"description",[104,2657,137],{"class":110},[104,2659,140],{"class":110},[104,2661,2641],{"class":514},[104,2663,596],{"class":110},[104,2665,2655],{"class":514},[104,2667,146],{"class":110},[104,2669,2671],{"class":106,"line":2670},27,[104,2672,166],{"class":110},[104,2674,2676,2678,2680,2683,2686],{"class":106,"line":2675},28,[104,2677,172],{"class":110},[104,2679,115],{"class":114},[104,2681,2682],{"class":110}," #",[104,2684,2685],{"class":133},"body",[104,2687,118],{"class":110},[104,2689,2691,2693],{"class":106,"line":2690},29,[104,2692,791],{"class":110},[104,2694,2695],{"class":114},"Button\n",[104,2697,2699,2702,2704,2706,2709],{"class":106,"line":2698},30,[104,2700,2701],{"class":133},"        label",[104,2703,137],{"class":110},[104,2705,140],{"class":110},[104,2707,2708],{"class":143},"Open Second Modal",[104,2710,146],{"class":110},[104,2712,2714,2717,2719,2721,2723,2725],{"class":106,"line":2713},31,[104,2715,2716],{"class":110},"        @",[104,2718,1155],{"class":133},[104,2720,137],{"class":110},[104,2722,140],{"class":110},[104,2724,2369],{"class":514},[104,2726,146],{"class":110},[104,2728,2730],{"class":106,"line":2729},32,[104,2731,2732],{"class":110},"      \u002F>\n",[104,2734,2736,2738,2740],{"class":106,"line":2735},33,[104,2737,801],{"class":110},[104,2739,115],{"class":114},[104,2741,118],{"class":110},[104,2743,2745,2747,2749],{"class":106,"line":2744},34,[104,2746,195],{"class":110},[104,2748,198],{"class":114},[104,2750,118],{"class":110},[104,2752,2754,2756,2758],{"class":106,"line":2753},35,[104,2755,206],{"class":110},[104,2757,115],{"class":114},[104,2759,118],{"class":110},[93,2761,2764],{"className":95,"code":2762,"filename":2763,"language":98,"meta":99,"style":99},"\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",[101,2765,2766,2786,2798,2806,2814,2820,2828,2832,2840,2846,2864,2882,2886],{"__ignoreMap":99},[104,2767,2768,2770,2772,2774,2776,2778,2780,2782,2784],{"class":106,"line":107},[104,2769,111],{"class":110},[104,2771,485],{"class":114},[104,2773,488],{"class":133},[104,2775,137],{"class":110},[104,2777,140],{"class":110},[104,2779,495],{"class":143},[104,2781,140],{"class":110},[104,2783,500],{"class":133},[104,2785,118],{"class":110},[104,2787,2788,2790,2792,2794,2796],{"class":106,"line":121},[104,2789,539],{"class":133},[104,2791,2455],{"class":514},[104,2793,137],{"class":110},[104,2795,2460],{"class":547},[104,2797,2463],{"class":110},[104,2799,2800,2802,2804],{"class":106,"line":130},[104,2801,2468],{"class":114},[104,2803,579],{"class":110},[104,2805,2473],{"class":1489},[104,2807,2808,2810,2812],{"class":106,"line":149},[104,2809,2478],{"class":114},[104,2811,579],{"class":110},[104,2813,2473],{"class":1489},[104,2815,2816,2818],{"class":106,"line":163},[104,2817,2487],{"class":110},[104,2819,1896],{"class":514},[104,2821,2822,2824,2826],{"class":106,"line":169},[104,2823,206],{"class":110},[104,2825,485],{"class":114},[104,2827,118],{"class":110},[104,2829,2830],{"class":106,"line":192},[104,2831,534],{"emptyLinePlaceholder":361},[104,2833,2834,2836,2838],{"class":106,"line":203},[104,2835,111],{"class":110},[104,2837,115],{"class":114},[104,2839,118],{"class":110},[104,2841,2842,2844],{"class":106,"line":616},[104,2843,124],{"class":110},[104,2845,127],{"class":114},[104,2847,2848,2850,2852,2854,2856,2858,2860,2862],{"class":106,"line":622},[104,2849,2631],{"class":110},[104,2851,2634],{"class":133},[104,2853,137],{"class":110},[104,2855,140],{"class":110},[104,2857,2641],{"class":514},[104,2859,596],{"class":110},[104,2861,2634],{"class":514},[104,2863,146],{"class":110},[104,2865,2866,2868,2870,2872,2874,2876,2878,2880],{"class":106,"line":630},[104,2867,2631],{"class":110},[104,2869,2655],{"class":133},[104,2871,137],{"class":110},[104,2873,140],{"class":110},[104,2875,2641],{"class":514},[104,2877,596],{"class":110},[104,2879,2655],{"class":514},[104,2881,146],{"class":110},[104,2883,2884],{"class":106,"line":639},[104,2885,2125],{"class":110},[104,2887,2888,2890,2892],{"class":106,"line":644},[104,2889,206],{"class":110},[104,2891,115],{"class":114},[104,2893,118],{"class":110},[10,2895,296],{},[10,2897,2898,2899],{},"Explore the complete code at ",[2900,2901,2903,2904,596],"git-hub-link",{"repo":2902},"barbapapazes\u002Fvue-dialog-stacking"," and even ",[1637,2905,2908],{"href":2906,"rel":2907},"https:\u002F\u002Fvue-dialog-stacking.barbapapazes.dev\u002F",[1641],"a live demo",[356,2910,59,2911,59,2920],{},[2912,2913],"iframe",{"src":2906,"width":2914,"height":2915,"frameBorder":2916,"scrolling":2917,"allowFullScreen":361,"className":2918},"100%",600,"0","no",[2919],"bg-white",[368,2921,2922],{},"Live demo of dialog stacking",[31,2924,2926],{"id":2925},"final-thoughts","Final thoughts",[10,2928,2929],{},"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.",[10,2931,2932,2933,2938,2939,2946],{},"I initially encountered this approach with Angular Material and longed for something similar in Vue. ",[1637,2934,2937],{"href":2935,"rel":2936},"https:\u002F\u002Fgithub.com\u002Fantfu",[1641],"Anthony Fu"," proposed a promising start with ",[1637,2940,2943],{"href":2941,"rel":2942},"https:\u002F\u002Fgithub.com\u002Fantfu\u002Fvue-template-promise",[1641],[101,2944,2945],{},"useTemplatePromise",", but it remained complex solely for dialog opening.",[10,2948,2949,2950,2955,2956,2961,2962,2967,2968,2970],{},"Finally, thanks to ",[1637,2951,2954],{"href":2952,"rel":2953},"https:\u002F\u002Fgithub.com\u002Fzernonia",[1641],"zernonia"," for ",[1637,2957,2960],{"href":2958,"rel":2959},"https:\u002F\u002Freka-ui.com\u002F",[1641],"Reka UI"," and ",[1637,2963,2966],{"href":2964,"rel":2965},"https:\u002F\u002Fgithub.com\u002Fgenu",[1641],"Eugen Istoc"," for the ",[101,2969,1855],{}," composable in Nuxt UI, I can confidently manage dialogs and modals properly in Vue.",[10,2972,2973],{},"I'm convinced this approach is a game changer for interactive UI experiences in Vue. Give it a try!",[2975,2976,2977],"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 .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":99,"searchDepth":121,"depth":121,"links":2979},[2980,2981,2982,2983,2984,2985],{"id":33,"depth":121,"text":34},{"id":373,"depth":121,"text":374},{"id":743,"depth":121,"text":744},{"id":1656,"depth":121,"text":1657},{"id":2146,"depth":121,"text":2147},{"id":2925,"depth":121,"text":2926},"67862d90-d724-476f-9667-3f35683445ed","2025-03-29","After years of searching for an effective approach to managing dialogs and modals, I finally discovered a reliable pattern.","md",{},"\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",null,{"title":5,"description":2988},{"loc":2991},"posts\u002Fa-journey-to-craft-interactive-ui-experiences-with-dialogs","EmRK6pQdmxLwiO94ztAc0TWnokwe-gvzsvtf5LWoEvA",{"id":2999,"title":3000,"body":3001,"contentId":21,"date":5061,"description":5062,"extension":2989,"meta":5063,"navigation":361,"path":5064,"rawbody":5065,"robots":2993,"schemaOrg":2993,"seo":5066,"sitemap":5067,"stem":5068,"__hash__":5069},"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":3002,"toc":5050},[3003,3013,3019,3045,5047],[10,3004,3005,3006],{},"TL;DR: ",[2900,3007,3009,3010],{"repo":3008},"barbapapazes\u002Fvue-library"," ",[26,3011,3012],{},"Continue reading to grasp the process behind it and understand the reasons for my choices.",[10,3014,3015,3016],{},"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. ",[26,3017,3018],{},"Ultimately, it's a method to share code across projects, a crucial skill that can significantly save time.",[10,3020,3021,3022,3027,3028,3033,3034,3039,3040,596],{},"However, building a component library with ",[1637,3023,3026],{"href":3024,"rel":3025},"https:\u002F\u002Fvuejs.org",[1641],"Vue.js"," is harder than it seems because of the ",[1637,3029,3032],{"href":3030,"rel":3031},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fscaling-up\u002Fsfc.html",[1641],"Single File Components"," (SFC). In a standard TypeScript project, you usually transpile to JavaScript and bundle your files with a tool like ",[1637,3035,3038],{"href":3036,"rel":3037},"https:\u002F\u002Ftsup.egoist.dev\u002F",[1641],"tsup"," or ",[1637,3041,3044],{"href":3042,"rel":3043},"https:\u002F\u002Fvitejs.dev\u002F",[1641],"Vite",[19,3046,3048,3062,3065,3068,3195,3198,3293,3299,3302,3310,3317,3377,3387,3535,3539,3546,3553,3559,3588,3595,3607,3621,3625,3644,3647,3650,3665,3668,3679,3684,3690,3713,3720,3866,3876,3880,3897,3915,3924,4090,4097,4101,4107,4158,4164,4170,4173,4184,4188,4191,4216,4219,4232,4258,4262,4265,4275,4356,4389,4400,4406,4557,4563,4569,4582,4586,4593,4596,4599,4607,4614,4644,4651,4672,4675,4688,4699,4705,4715,4795,4798,4819,4828,4832,4835,4842,4854,4863,4881,4886,4953,4964,4975,4978,4993,4996,5000,5003,5023,5032,5040,5045],{"id":3047},"55d2cdcf-ba27-4bfe-9bc1-492e866ff3b1",[1628,3049,3050],{},[10,3051,3052,3054,3055,3058,3059],{},[104,3053,1634],{},"\nRemember to ",[14,3056,3057],{},"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. ",[26,3060,3061],{},"Each case is unique, but this is a solid guideline to follow.",[10,3063,3064],{},"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.",[10,3066,3067],{},"Consider the following component:",[93,3069,3071],{"className":95,"code":3070,"language":98,"meta":99,"style":99},"\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",[101,3072,3073,3093,3113,3117,3134,3142,3146,3154,3174,3179,3187],{"__ignoreMap":99},[104,3074,3075,3077,3079,3081,3083,3085,3087,3089,3091],{"class":106,"line":107},[104,3076,111],{"class":110},[104,3078,485],{"class":114},[104,3080,488],{"class":133},[104,3082,137],{"class":110},[104,3084,140],{"class":110},[104,3086,495],{"class":143},[104,3088,140],{"class":110},[104,3090,500],{"class":133},[104,3092,118],{"class":110},[104,3094,3095,3097,3099,3102,3104,3106,3108,3111],{"class":106,"line":121},[104,3096,508],{"class":507},[104,3098,511],{"class":110},[104,3100,3101],{"class":514}," useUser",[104,3103,518],{"class":110},[104,3105,521],{"class":507},[104,3107,524],{"class":110},[104,3109,3110],{"class":143},"..\u002Fcomposables",[104,3112,529],{"class":110},[104,3114,3115],{"class":106,"line":130},[104,3116,534],{"emptyLinePlaceholder":361},[104,3118,3119,3121,3123,3126,3128,3130,3132],{"class":106,"line":149},[104,3120,539],{"class":133},[104,3122,511],{"class":110},[104,3124,3125],{"class":514}," user ",[104,3127,625],{"class":110},[104,3129,602],{"class":110},[104,3131,3101],{"class":547},[104,3133,1896],{"class":514},[104,3135,3136,3138,3140],{"class":106,"line":163},[104,3137,206],{"class":110},[104,3139,485],{"class":114},[104,3141,118],{"class":110},[104,3143,3144],{"class":106,"line":169},[104,3145,534],{"emptyLinePlaceholder":361},[104,3147,3148,3150,3152],{"class":106,"line":192},[104,3149,111],{"class":110},[104,3151,115],{"class":114},[104,3153,118],{"class":110},[104,3155,3156,3158,3160,3163,3165,3167,3170,3172],{"class":106,"line":203},[104,3157,124],{"class":110},[104,3159,54],{"class":114},[104,3161,3162],{"class":133}," v-if",[104,3164,137],{"class":110},[104,3166,140],{"class":110},[104,3168,3169],{"class":143},"user",[104,3171,140],{"class":110},[104,3173,118],{"class":110},[104,3175,3176],{"class":106,"line":616},[104,3177,3178],{"class":514},"    {{ user.name }}\n",[104,3180,3181,3183,3185],{"class":106,"line":622},[104,3182,195],{"class":110},[104,3184,54],{"class":114},[104,3186,118],{"class":110},[104,3188,3189,3191,3193],{"class":106,"line":630},[104,3190,206],{"class":110},[104,3192,115],{"class":114},[104,3194,118],{"class":110},[10,3196,3197],{},"The composable is as follows:",[93,3199,3201],{"className":1674,"code":3200,"language":495,"meta":99,"style":99},"import { ref } from 'vue'\n\nexport function useUser() {\n  const user = ref({ name: 'John Doe' })\n\n  return {\n    user\n  }\n}\n",[101,3202,3203,3221,3225,3237,3270,3274,3280,3285,3289],{"__ignoreMap":99},[104,3204,3205,3207,3209,3211,3213,3215,3217,3219],{"class":106,"line":107},[104,3206,508],{"class":507},[104,3208,511],{"class":110},[104,3210,515],{"class":514},[104,3212,518],{"class":110},[104,3214,521],{"class":507},[104,3216,524],{"class":110},[104,3218,98],{"class":143},[104,3220,529],{"class":110},[104,3222,3223],{"class":106,"line":121},[104,3224,534],{"emptyLinePlaceholder":361},[104,3226,3227,3229,3231,3233,3235],{"class":106,"line":130},[104,3228,927],{"class":507},[104,3230,930],{"class":133},[104,3232,3101],{"class":547},[104,3234,936],{"class":110},[104,3236,588],{"class":110},[104,3238,3239,3242,3245,3247,3249,3251,3254,3257,3259,3261,3264,3266,3268],{"class":106,"line":149},[104,3240,3241],{"class":133},"  const",[104,3243,3244],{"class":514}," user",[104,3246,602],{"class":110},[104,3248,515],{"class":547},[104,3250,550],{"class":114},[104,3252,3253],{"class":110},"{",[104,3255,3256],{"class":114}," name",[104,3258,579],{"class":110},[104,3260,524],{"class":110},[104,3262,3263],{"class":143},"John Doe",[104,3265,1716],{"class":110},[104,3267,518],{"class":110},[104,3269,557],{"class":114},[104,3271,3272],{"class":106,"line":163},[104,3273,534],{"emptyLinePlaceholder":361},[104,3275,3276,3278],{"class":106,"line":169},[104,3277,1009],{"class":507},[104,3279,588],{"class":110},[104,3281,3282],{"class":106,"line":192},[104,3283,3284],{"class":514},"    user\n",[104,3286,3287],{"class":106,"line":203},[104,3288,619],{"class":110},[104,3290,3291],{"class":106,"line":616},[104,3292,1043],{"class":110},[10,3294,3295,3296],{},"The component employs a composable imported from another file. ",[26,3297,3298],{},"It may seem trivial, but this poses a significant issue.",[10,3300,3301],{},"All of this follows the architecture below:",[93,3303,3308],{"className":3304,"code":3306,"language":3307},[3305],"language-text","src\u002F\n  components\u002F\n    User.vue\n  composables\u002F\n    useUser.ts\n  index.ts\n","text",[101,3309,3306],{"__ignoreMap":99},[10,3311,3312,3313,3316],{},"The ",[101,3314,3315],{},"index.ts"," file acts as the library's entry point and it looks like this:",[93,3318,3320],{"className":1674,"code":3319,"language":495,"meta":99,"style":99},"import User from '.\u002Fcomponents\u002FUser.vue'\n\nexport { User }\n\nexport { useUser } from '.\u002Fcomposables\u002FuseUser'\n",[101,3321,3322,3338,3342,3354,3358],{"__ignoreMap":99},[104,3323,3324,3326,3329,3331,3333,3336],{"class":106,"line":107},[104,3325,508],{"class":507},[104,3327,3328],{"class":514}," User ",[104,3330,2187],{"class":507},[104,3332,524],{"class":110},[104,3334,3335],{"class":143},".\u002Fcomponents\u002FUser.vue",[104,3337,529],{"class":110},[104,3339,3340],{"class":106,"line":121},[104,3341,534],{"emptyLinePlaceholder":361},[104,3343,3344,3346,3348,3351],{"class":106,"line":130},[104,3345,927],{"class":507},[104,3347,511],{"class":110},[104,3349,3350],{"class":514}," User",[104,3352,3353],{"class":110}," }\n",[104,3355,3356],{"class":106,"line":149},[104,3357,534],{"emptyLinePlaceholder":361},[104,3359,3360,3362,3364,3366,3368,3370,3372,3375],{"class":106,"line":163},[104,3361,927],{"class":507},[104,3363,511],{"class":110},[104,3365,3101],{"class":514},[104,3367,518],{"class":110},[104,3369,521],{"class":507},[104,3371,524],{"class":110},[104,3373,3374],{"class":143},".\u002Fcomposables\u002FuseUser",[104,3376,529],{"class":110},[10,3378,3379,3380,3383,3384,579],{},"Plus, the ",[101,3381,3382],{},"package.json"," includes some ",[101,3385,3386],{},"exports",[93,3388,3392],{"className":3389,"code":3390,"language":3391,"meta":99,"style":99},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\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","json",[101,3393,3394,3398,3420,3432,3445,3467,3484,3489,3494,3514,3531],{"__ignoreMap":99},[104,3395,3396],{"class":106,"line":107},[104,3397,571],{"class":110},[104,3399,3400,3403,3406,3408,3410,3413,3416,3418],{"class":106,"line":121},[104,3401,3402],{"class":110},"  \"",[104,3404,3405],{"class":133},"type",[104,3407,140],{"class":110},[104,3409,579],{"class":110},[104,3411,3412],{"class":110}," \"",[104,3414,3415],{"class":143},"module",[104,3417,140],{"class":110},[104,3419,1018],{"class":110},[104,3421,3422,3424,3426,3428,3430],{"class":106,"line":130},[104,3423,3402],{"class":110},[104,3425,3386],{"class":133},[104,3427,140],{"class":110},[104,3429,579],{"class":110},[104,3431,588],{"class":110},[104,3433,3434,3437,3439,3441,3443],{"class":106,"line":149},[104,3435,3436],{"class":110},"    \"",[104,3438,596],{"class":1489},[104,3440,140],{"class":110},[104,3442,579],{"class":110},[104,3444,588],{"class":110},[104,3446,3447,3450,3454,3456,3458,3460,3463,3465],{"class":106,"line":163},[104,3448,3449],{"class":110},"      \"",[104,3451,3453],{"class":3452},"sbssI","types",[104,3455,140],{"class":110},[104,3457,579],{"class":110},[104,3459,3412],{"class":110},[104,3461,3462],{"class":143},".\u002Fdist\u002Findex.d.ts",[104,3464,140],{"class":110},[104,3466,1018],{"class":110},[104,3468,3469,3471,3473,3475,3477,3479,3482],{"class":106,"line":169},[104,3470,3449],{"class":110},[104,3472,508],{"class":3452},[104,3474,140],{"class":110},[104,3476,579],{"class":110},[104,3478,3412],{"class":110},[104,3480,3481],{"class":143},".\u002Fdist\u002Findex.mjs",[104,3483,146],{"class":110},[104,3485,3486],{"class":106,"line":192},[104,3487,3488],{"class":110},"    }\n",[104,3490,3491],{"class":106,"line":203},[104,3492,3493],{"class":110},"  },\n",[104,3495,3496,3498,3501,3503,3505,3507,3510,3512],{"class":106,"line":616},[104,3497,3402],{"class":110},[104,3499,3500],{"class":133},"main",[104,3502,140],{"class":110},[104,3504,579],{"class":110},[104,3506,3412],{"class":110},[104,3508,3509],{"class":143},"dist\u002Findex.mjs",[104,3511,140],{"class":110},[104,3513,1018],{"class":110},[104,3515,3516,3518,3520,3522,3524,3526,3529],{"class":106,"line":622},[104,3517,3402],{"class":110},[104,3519,3453],{"class":133},[104,3521,140],{"class":110},[104,3523,579],{"class":110},[104,3525,3412],{"class":110},[104,3527,3528],{"class":143},"dist\u002Findex.d.ts",[104,3530,146],{"class":110},[104,3532,3533],{"class":106,"line":630},[104,3534,1043],{"class":110},[31,3536,3538],{"id":3537},"similar-to-a-standard-typescript-project","Similar to a standard TypeScript project",[1628,3540,3541],{},[10,3542,3543,3545],{},[104,3544,1634],{},"\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.",[10,3547,3548,3549,3552],{},"Now, if we attempt to bundle this project, our ",[101,3550,3551],{},"dist"," folder will appear like this:",[93,3554,3557],{"className":3555,"code":3556,"language":3307},[3305],"dist\u002F\n  index.cjs\n",[101,3558,3556],{"__ignoreMap":99},[10,3560,3561,3562,3565,3566,3569,3570],{},"Upon inspecting ",[101,3563,3564],{},"index.cjs",", you'll find a surprise: the ",[101,3567,3568],{},"useUser"," composable is inlined, but the component is entirely ignored. ",[26,3571,3572,3573,3576,3577,3039,3582,3587],{},"If it functions, the bundler might throw an error like ",[101,3574,3575],{},"No loader is configured for \".vue\" files",". Essentially, tools like ",[1637,3578,3581],{"href":3579,"rel":3580},"https:\u002F\u002Fesbuild.github.io",[1641],"esbuild",[1637,3583,3586],{"href":3584,"rel":3585},"https:\u002F\u002Fwww.typescriptlang.org\u002Fdocs\u002Fhandbook\u002Fcompiler-options.html",[1641],"tsc"," don't know how to handle Vue files. It's logical since Vue files aren't JavaScript files.",[10,3589,3590,3591,3594],{},"The problem becomes evident. We need some configuration to handle Vue files. Let's quickly browse the internet for something like ",[101,3592,3593],{},"vue loader",", as we're utilizing Vue files and need to transpile them to JavaScript.",[10,3596,3597,3598,3603,3604],{},"The top result is ",[1637,3599,3602],{"href":3600,"rel":3601},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fvue-loader",[1641],"Vue Loader",", which is a Webpack loader. ",[26,3605,3606],{},"No. I refuse to use Webpack. Vite is now a standard, and there's no justification for employing Webpack in this context.",[10,3608,3609,3610,3009,3612,3620],{},"But before delving further, let's revisit our mantra: ",[14,3611,3057],{},[26,3613,3614,3615],{},"and ",[1637,3616,3619],{"href":3617,"rel":3618},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FKISS_principle",[1641],"keep it simple, stupid!"," Transpiling Vue SFC files using Webpack seems contrary to this principle.",[31,3622,3624],{"id":3623},"ignoring-vue-files","Ignoring Vue files",[10,3626,3627,3628,3631,3632,3637,3638,3643],{},"To deliver ",[101,3629,3630],{},".vue"," files without transpiling them, we instruct our bundler to disregard them. This is achieved using a tool called ",[1637,3633,3636],{"href":3634,"rel":3635},"https:\u002F\u002Funbuild.unjs.io",[1641],"unbuild"," from the ",[1637,3639,3642],{"href":3640,"rel":3641},"https:\u002F\u002Funjs.io",[1641],"UnJS"," ecosystem.",[10,3645,3646],{},"Unbuild is a straightforward tool yet highly configurable since it's constructed atop rollup.",[10,3648,3649],{},"For our small project, we can directly experiment with it:",[93,3651,3655],{"className":3652,"code":3653,"language":3654,"meta":99,"style":99},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","npx unbuild\n","sh",[101,3656,3657],{"__ignoreMap":99},[104,3658,3659,3662],{"class":106,"line":107},[104,3660,3661],{"class":1489},"npx",[104,3663,3664],{"class":143}," unbuild\n",[10,3666,3667],{},"Unfortunately, it fails for the same reasons as before:",[93,3669,3673],{"className":3670,"code":3671,"language":3672,"meta":99,"style":99},"language-txt shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","src\u002Fcomponents\u002FShowGitHubUser.vue (1:0): Expression expected (Note that you need plugins to import files that are not JavaScript)\n","txt",[101,3674,3675],{"__ignoreMap":99},[104,3676,3677],{"class":106,"line":107},[104,3678,3671],{},[10,3680,3681],{},[26,3682,3683],{},"But unbuild has the potential for much more.",[10,3685,3686,3687,3689],{},"To tackle this problem, let's attempt the obvious solution. In the ",[101,3688,3315],{}," file, remove the component export and solely export TypeScript files:",[93,3691,3693],{"className":1674,"code":3692,"language":495,"meta":99,"style":99},"export { useUser } from '.\u002Fcomposables\u002FuseUser'\n",[101,3694,3695],{"__ignoreMap":99},[104,3696,3697,3699,3701,3703,3705,3707,3709,3711],{"class":106,"line":107},[104,3698,927],{"class":507},[104,3700,511],{"class":110},[104,3702,3101],{"class":514},[104,3704,518],{"class":110},[104,3706,521],{"class":507},[104,3708,524],{"class":110},[104,3710,3374],{"class":143},[104,3712,529],{"class":110},[10,3714,3715,3716,3719],{},"Now, the command ",[101,3717,3718],{},"npx unbuild"," functions properly.",[93,3721,3723],{"className":3652,"code":3722,"language":3654,"meta":99,"style":99},"➜ 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",[101,3724,3725,3735,3764,3774,3790,3806,3840,3844],{"__ignoreMap":99},[104,3726,3727,3730,3733],{"class":106,"line":107},[104,3728,3729],{"class":1489},"➜",[104,3731,3732],{"class":143}," npx",[104,3734,3664],{"class":143},[104,3736,3737,3740,3743,3746,3749,3752,3755,3758,3761],{"class":106,"line":121},[104,3738,3739],{"class":1489},"ℹ",[104,3741,3742],{"class":143}," Automatically",[104,3744,3745],{"class":143}," detected",[104,3747,3748],{"class":143}," entries:",[104,3750,3751],{"class":143}," src\u002Findex",[104,3753,3754],{"class":514}," [esm] ",[104,3756,3757],{"class":110},"[",[104,3759,3760],{"class":514},"dts",[104,3762,3763],{"class":110},"]\n",[104,3765,3766,3768,3771],{"class":106,"line":130},[104,3767,3739],{"class":1489},[104,3769,3770],{"class":143}," Building",[104,3772,3773],{"class":143}," vue-library\n",[104,3775,3776,3778,3781,3784,3787],{"class":106,"line":149},[104,3777,3739],{"class":1489},[104,3779,3780],{"class":143}," Cleaning",[104,3782,3783],{"class":143}," dist",[104,3785,3786],{"class":143}," directory:",[104,3788,3789],{"class":143}," .\u002Fdist\n",[104,3791,3792,3795,3798,3801,3804],{"class":106,"line":163},[104,3793,3794],{"class":1489},"✔",[104,3796,3797],{"class":143}," Build",[104,3799,3800],{"class":143}," succeeded",[104,3802,3803],{"class":143}," for",[104,3805,3773],{"class":143},[104,3807,3808,3811,3814,3817,3820,3823,3826,3829,3831,3833,3836,3838],{"class":106,"line":169},[104,3809,3810],{"class":1489},"  dist\u002Findex.mjs",[104,3812,3813],{"class":514}," (total ",[104,3815,3816],{"class":143},"size:",[104,3818,3819],{"class":3452}," 139",[104,3821,3822],{"class":143}," B,",[104,3824,3825],{"class":143}," chunk",[104,3827,3828],{"class":143}," size:",[104,3830,3819],{"class":3452},[104,3832,3822],{"class":143},[104,3834,3835],{"class":143}," exports:",[104,3837,3101],{"class":143},[104,3839,557],{"class":514},[104,3841,3842],{"class":106,"line":192},[104,3843,534],{"emptyLinePlaceholder":361},[104,3845,3846,3849,3852,3854,3857,3860,3863],{"class":106,"line":203},[104,3847,3848],{"class":1489},"Σ",[104,3850,3851],{"class":143}," Total",[104,3853,3783],{"class":143},[104,3855,3856],{"class":143}," size",[104,3858,3859],{"class":514}," (byte ",[104,3861,3862],{"class":143},"size",[104,3864,3865],{"class":514},"): 531 B\n",[10,3867,3868,3869,3872,3873,3875],{},"This is a good start, but our components remain in the ",[101,3870,3871],{},"src"," folder and no matter how diligently you search, they're missing from the ",[101,3874,3551],{}," folder.",[31,3877,3879],{"id":3878},"copying-vue-files","Copying Vue files",[10,3881,3882,3883,3886,3887,3889,3890,3892,3893,3896],{},"Now that ",[101,3884,3885],{},".ts"," files are processed correctly, we can attempt to copy the ",[101,3888,3630],{}," files to the ",[101,3891,3551],{}," folder using a simple ",[101,3894,3895],{},"cp"," command.",[93,3898,3900],{"className":3652,"code":3899,"language":3654,"meta":99,"style":99},"cp -r src\u002Fcomponents\u002F dist\u002Fcomponents\u002F\n",[101,3901,3902],{"__ignoreMap":99},[104,3903,3904,3906,3909,3912],{"class":106,"line":107},[104,3905,3895],{"class":1489},[104,3907,3908],{"class":143}," -r",[104,3910,3911],{"class":143}," src\u002Fcomponents\u002F",[104,3913,3914],{"class":143}," dist\u002Fcomponents\u002F\n",[10,3916,3917,3918,3920,3921,3923],{},"Subsequently, we add an ",[101,3919,3386],{}," field in the ",[101,3922,3382],{}," to inform the user where to locate the components:",[93,3925,3927],{"className":3389,"code":3926,"language":3391,"meta":99,"style":99},"{\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",[101,3928,3929,3933,3951,3963,3975,3993,4009,4014,4027,4044,4048,4052,4070,4086],{"__ignoreMap":99},[104,3930,3931],{"class":106,"line":107},[104,3932,571],{"class":110},[104,3934,3935,3937,3939,3941,3943,3945,3947,3949],{"class":106,"line":121},[104,3936,3402],{"class":110},[104,3938,3405],{"class":133},[104,3940,140],{"class":110},[104,3942,579],{"class":110},[104,3944,3412],{"class":110},[104,3946,3415],{"class":143},[104,3948,140],{"class":110},[104,3950,1018],{"class":110},[104,3952,3953,3955,3957,3959,3961],{"class":106,"line":130},[104,3954,3402],{"class":110},[104,3956,3386],{"class":133},[104,3958,140],{"class":110},[104,3960,579],{"class":110},[104,3962,588],{"class":110},[104,3964,3965,3967,3969,3971,3973],{"class":106,"line":149},[104,3966,3436],{"class":110},[104,3968,596],{"class":1489},[104,3970,140],{"class":110},[104,3972,579],{"class":110},[104,3974,588],{"class":110},[104,3976,3977,3979,3981,3983,3985,3987,3989,3991],{"class":106,"line":163},[104,3978,3449],{"class":110},[104,3980,3453],{"class":3452},[104,3982,140],{"class":110},[104,3984,579],{"class":110},[104,3986,3412],{"class":110},[104,3988,3462],{"class":143},[104,3990,140],{"class":110},[104,3992,1018],{"class":110},[104,3994,3995,3997,3999,4001,4003,4005,4007],{"class":106,"line":169},[104,3996,3449],{"class":110},[104,3998,508],{"class":3452},[104,4000,140],{"class":110},[104,4002,579],{"class":110},[104,4004,3412],{"class":110},[104,4006,3481],{"class":143},[104,4008,146],{"class":110},[104,4010,4011],{"class":106,"line":192},[104,4012,4013],{"class":110},"    },\n",[104,4015,4016,4018,4021,4023,4025],{"class":106,"line":203},[104,4017,3436],{"class":110},[104,4019,4020],{"class":1489},".\u002Fcomponents\u002F*",[104,4022,140],{"class":110},[104,4024,579],{"class":110},[104,4026,588],{"class":110},[104,4028,4029,4031,4033,4035,4037,4039,4042],{"class":106,"line":616},[104,4030,3449],{"class":110},[104,4032,508],{"class":3452},[104,4034,140],{"class":110},[104,4036,579],{"class":110},[104,4038,3412],{"class":110},[104,4040,4041],{"class":143},".\u002Fdist\u002Fcomponents\u002F*.vue",[104,4043,146],{"class":110},[104,4045,4046],{"class":106,"line":622},[104,4047,3488],{"class":110},[104,4049,4050],{"class":106,"line":630},[104,4051,3493],{"class":110},[104,4053,4054,4056,4058,4060,4062,4064,4066,4068],{"class":106,"line":639},[104,4055,3402],{"class":110},[104,4057,3500],{"class":133},[104,4059,140],{"class":110},[104,4061,579],{"class":110},[104,4063,3412],{"class":110},[104,4065,3509],{"class":143},[104,4067,140],{"class":110},[104,4069,1018],{"class":110},[104,4071,4072,4074,4076,4078,4080,4082,4084],{"class":106,"line":644},[104,4073,3402],{"class":110},[104,4075,3453],{"class":133},[104,4077,140],{"class":110},[104,4079,579],{"class":110},[104,4081,3412],{"class":110},[104,4083,3528],{"class":143},[104,4085,146],{"class":110},[104,4087,4088],{"class":106,"line":653},[104,4089,1043],{"class":110},[10,4091,4092,4093,4096],{},"Looks promising, doesn't it? ",[26,4094,4095],{},"But it isn't."," 😔",[31,4098,4100],{"id":4099},"the-import-path-dilemma","The import path dilemma",[10,4102,4103,4104,4106],{},"In our Vue component, the ",[101,4105,3568],{}," composable is imported like this:",[93,4108,4110],{"className":95,"code":4109,"language":98,"meta":99,"style":99},"\u003Cscript lang=\"ts\" setup>\nimport { useUser } from '..\u002Fcomposables'\n\u003C\u002Fscript>\n",[101,4111,4112,4132,4150],{"__ignoreMap":99},[104,4113,4114,4116,4118,4120,4122,4124,4126,4128,4130],{"class":106,"line":107},[104,4115,111],{"class":110},[104,4117,485],{"class":114},[104,4119,488],{"class":133},[104,4121,137],{"class":110},[104,4123,140],{"class":110},[104,4125,495],{"class":143},[104,4127,140],{"class":110},[104,4129,500],{"class":133},[104,4131,118],{"class":110},[104,4133,4134,4136,4138,4140,4142,4144,4146,4148],{"class":106,"line":121},[104,4135,508],{"class":507},[104,4137,511],{"class":110},[104,4139,3101],{"class":514},[104,4141,518],{"class":110},[104,4143,521],{"class":507},[104,4145,524],{"class":110},[104,4147,3110],{"class":143},[104,4149,529],{"class":110},[104,4151,4152,4154,4156],{"class":106,"line":130},[104,4153,206],{"class":110},[104,4155,485],{"class":114},[104,4157,118],{"class":110},[10,4159,4160,4161,4163],{},"Meanwhile, our ",[101,4162,3551],{}," folder is structured as follows:",[93,4165,4168],{"className":4166,"code":4167,"language":3307},[3305],"dist\u002F\n  components\u002F\n    User.vue\n  index.d.mts\n  index.d.ts\n  index.mjs\n",[101,4169,4167],{"__ignoreMap":99},[10,4171,4172],{},"Can you identify the problem? 👀",[10,4174,4175,4176,4179,4180,4183],{},"The component is unusable and will produce an error like ",[101,4177,4178],{},"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 ",[101,4181,4182],{},"index.mjs",", losing the project's structure entirely.",[31,4185,4187],{"id":4186},"to-bundle-or-not-to-bundle","To bundle or not to bundle?",[10,4189,4190],{},"From here, we have two choices:",[304,4192,4193,4204],{},[307,4194,4195,4198,4199,4203],{},[14,4196,4197],{},"Bundling the Vue files"," using ",[1637,4200,3044],{"href":4201,"rel":4202},"https:\u002F\u002Fvitejs.dev",[1641],", plugins, and extensive configuration.",[307,4205,4206,4209,4210,4215],{},[14,4207,4208],{},"Maintaining the directory structure"," and using a tool like ",[1637,4211,4214],{"href":4212,"rel":4213},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fmkdist",[1641],"mkdist"," for file-to-file transpiling (bundleless build) on TypeScript files.",[10,4217,4218],{},"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.",[10,4220,4221,4222,4224,4225,4227,4228,4231],{},"The goal is to transform the TypeScript files into JavaScript files, preserving the original structure of the ",[101,4223,3871],{}," folder in the ",[101,4226,3551],{}," folder and ",[26,4229,4230],{},"ignoring"," the Vue files.",[1628,4233,4234],{},[10,4235,4236,4238,4239,4242,4243,4246,4247,4252,4253,596],{},[104,4237,1634],{},"\nUsing a ",[101,4240,4241],{},"\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 ",[101,4244,4245],{},".vue.d.ts"," file to offer types for the Vue component. Remember that the ",[1637,4248,4251],{"href":4249,"rel":4250},"https:\u002F\u002Fgithub.com\u002Fvitejs\u002Fvite-plugin-vue\u002Ftree\u002Fmain\u002Fpackages\u002Fplugin-vue",[1641],"@vitejs\u002Fplugin-vue"," and Vite inherently understand TypeScript script blocks. For more details, refer to ",[1637,4254,4257],{"href":4255,"rel":4256},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fmkdist\u002Fissues\u002F14",[1641],"issue mkdist#14",[31,4259,4261],{"id":4260},"configuring-unbuild","Configuring Unbuild",[10,4263,4264],{},"Yes, we will configure unbuild since it incorporates mkdist, making it exceptionally user-friendly.",[10,4266,4267,4268,4271,4272,4274],{},"First, create a ",[101,4269,4270],{},"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 ",[101,4273,3382],{},", but we need to tell it to utilize mkdist.",[93,4276,4278],{"className":1674,"code":4277,"language":495,"meta":99,"style":99},"import { defineBuildConfig } from 'unbuild'\n\nexport default defineBuildConfig({\n  entries: ['.\u002Fsrc\u002F'],\n  declaration: true,\n})\n",[101,4279,4280,4299,4303,4316,4338,4350],{"__ignoreMap":99},[104,4281,4282,4284,4286,4289,4291,4293,4295,4297],{"class":106,"line":107},[104,4283,508],{"class":507},[104,4285,511],{"class":110},[104,4287,4288],{"class":514}," defineBuildConfig",[104,4290,518],{"class":110},[104,4292,521],{"class":507},[104,4294,524],{"class":110},[104,4296,3636],{"class":143},[104,4298,529],{"class":110},[104,4300,4301],{"class":106,"line":121},[104,4302,534],{"emptyLinePlaceholder":361},[104,4304,4305,4307,4310,4312,4314],{"class":106,"line":130},[104,4306,927],{"class":507},[104,4308,4309],{"class":507}," default",[104,4311,4288],{"class":547},[104,4313,550],{"class":514},[104,4315,571],{"class":110},[104,4317,4318,4321,4323,4326,4328,4331,4333,4336],{"class":106,"line":149},[104,4319,4320],{"class":114},"  entries",[104,4322,579],{"class":110},[104,4324,4325],{"class":514}," [",[104,4327,1716],{"class":110},[104,4329,4330],{"class":143},".\u002Fsrc\u002F",[104,4332,1716],{"class":110},[104,4334,4335],{"class":514},"]",[104,4337,1018],{"class":110},[104,4339,4340,4343,4345,4348],{"class":106,"line":163},[104,4341,4342],{"class":114},"  declaration",[104,4344,579],{"class":110},[104,4346,4347],{"class":553}," true",[104,4349,1018],{"class":110},[104,4351,4352,4354],{"class":106,"line":169},[104,4353,625],{"class":110},[104,4355,557],{"class":514},[10,4357,4358,4359,4362,4363,4366,4367,4369,4370,4373,4374,4377,4378,4380,4381,4384,4385,4388],{},"It's a very straightforward configuration file. ",[26,4360,4361],{},"And I adore it."," The ",[101,4364,4365],{},"entries"," key informs unbuild of the initial file to begin transpiling. However, ",[101,4368,4330],{}," is a directory, indicated by the ",[101,4371,4372],{},"\u002F"," at the end. With this hint, unbuild bypasses the default bundler ",[101,4375,4376],{},"rollup"," in favor of ",[101,4379,4214],{},". The ",[101,4382,4383],{},"declaration"," key prompts unbuild to create TypeScript declaration files (",[101,4386,4387],{},".d.ts",").",[10,4390,4391,4392,3920,4394,4396,4397,4399],{},"We can also remove the ",[101,4393,3386],{},[101,4395,3382],{}," since we won't be bundling files anymore and restore the component export in the ",[101,4398,3315],{}," file.",[10,4401,4402,4403,4405],{},"Let's rerun the ",[101,4404,3718],{}," command and witness the magic.",[93,4407,4409],{"className":3652,"code":4408,"language":3654,"meta":99,"style":99},"➜ 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",[101,4410,4411,4419,4427,4439,4451,4468,4484,4498,4512,4526,4540],{"__ignoreMap":99},[104,4412,4413,4415,4417],{"class":106,"line":107},[104,4414,3729],{"class":1489},[104,4416,3732],{"class":143},[104,4418,3664],{"class":143},[104,4420,4421,4423,4425],{"class":106,"line":121},[104,4422,3739],{"class":1489},[104,4424,3770],{"class":143},[104,4426,3773],{"class":143},[104,4428,4429,4431,4433,4435,4437],{"class":106,"line":130},[104,4430,3739],{"class":1489},[104,4432,3780],{"class":143},[104,4434,3783],{"class":143},[104,4436,3786],{"class":143},[104,4438,3789],{"class":143},[104,4440,4441,4443,4445,4447,4449],{"class":106,"line":149},[104,4442,3794],{"class":1489},[104,4444,3797],{"class":143},[104,4446,3800],{"class":143},[104,4448,3803],{"class":143},[104,4450,3773],{"class":143},[104,4452,4453,4456,4458,4460,4463,4466],{"class":106,"line":163},[104,4454,4455],{"class":1489},"  dist",[104,4457,3813],{"class":514},[104,4459,3816],{"class":143},[104,4461,4462],{"class":3452}," 600",[104,4464,4465],{"class":143}," B",[104,4467,557],{"class":514},[104,4469,4470,4473,4476,4479,4482],{"class":106,"line":169},[104,4471,4472],{"class":1489},"  └─",[104,4474,4475],{"class":143}," dist\u002Findex.d.ts",[104,4477,4478],{"class":514}," (108 ",[104,4480,4481],{"class":143},"B",[104,4483,557],{"class":514},[104,4485,4486,4488,4491,4494,4496],{"class":106,"line":192},[104,4487,4472],{"class":1489},[104,4489,4490],{"class":143}," dist\u002Findex.mjs",[104,4492,4493],{"class":514}," (112 ",[104,4495,4481],{"class":143},[104,4497,557],{"class":514},[104,4499,4500,4502,4505,4508,4510],{"class":106,"line":203},[104,4501,4472],{"class":1489},[104,4503,4504],{"class":143}," dist\u002Fcomposables\u002FuseUser.d.ts",[104,4506,4507],{"class":514}," (79 ",[104,4509,4481],{"class":143},[104,4511,557],{"class":514},[104,4513,4514,4516,4519,4522,4524],{"class":106,"line":616},[104,4515,4472],{"class":1489},[104,4517,4518],{"class":143}," dist\u002Fcomposables\u002FuseUser.mjs",[104,4520,4521],{"class":514}," (124 ",[104,4523,4481],{"class":143},[104,4525,557],{"class":514},[104,4527,4528,4530,4533,4536,4538],{"class":106,"line":622},[104,4529,4472],{"class":1489},[104,4531,4532],{"class":143}," dist\u002Fcomponents\u002FUser.vue",[104,4534,4535],{"class":514}," (177 ",[104,4537,4481],{"class":143},[104,4539,557],{"class":514},[104,4541,4542,4544,4546,4548,4550,4552,4554],{"class":106,"line":630},[104,4543,3848],{"class":1489},[104,4545,3851],{"class":143},[104,4547,3783],{"class":143},[104,4549,3856],{"class":143},[104,4551,3859],{"class":514},[104,4553,3862],{"class":143},[104,4555,4556],{"class":514},"): 600 B\n",[10,4558,4559,4560,4562],{},"Now, the ",[101,4561,3551],{}," folder appears like this:",[93,4564,4567],{"className":4565,"code":4566,"language":3307},[3305],"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",[101,4568,4566],{"__ignoreMap":99},[10,4570,4571,4572,4574,4575,4578,4579,4581],{},"This mirrors the structure of the ",[101,4573,3871],{}," folder, and the ",[101,4576,4577],{},"User.vue"," component is now usable in the ",[101,4580,3551],{}," folder. The relative import path remains intact, and the component can be utilized in any Vue project. :partying_face:",[31,4583,4585],{"id":4584},"local-development","Local development",[10,4587,4588,4589,4592],{},"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 ",[14,4590,4591],{},"see the outcome of your work","? If you can't test it while building it?",[10,4594,4595],{},"Basically, you can't.",[10,4597,4598],{},"Let's examine how to use the library in a local project, within the same repository for simplicity.",[10,4600,4601,4602,596],{},"I'll demonstrate the simplest method using a ",[1637,4603,4606],{"href":4604,"rel":4605},"https:\u002F\u002Fpnpm.io\u002F",[1641],"pnpm workspace",[10,4608,4609,4610,4613],{},"Start by creating a ",[101,4611,4612],{},"pnpm-workspace.yaml"," file at the project's root:",[93,4615,4619],{"className":4616,"code":4617,"language":4618,"meta":99,"style":99},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","packages:\n  - .\n  - playground\n","yaml",[101,4620,4621,4629,4637],{"__ignoreMap":99},[104,4622,4623,4626],{"class":106,"line":107},[104,4624,4625],{"class":114},"packages",[104,4627,4628],{"class":110},":\n",[104,4630,4631,4634],{"class":106,"line":121},[104,4632,4633],{"class":110},"  -",[104,4635,4636],{"class":3452}," .\n",[104,4638,4639,4641],{"class":106,"line":130},[104,4640,4633],{"class":110},[104,4642,4643],{"class":143}," playground\n",[10,4645,4646,4647,4650],{},"Then, create a new Vite project in a ",[101,4648,4649],{},"playground"," folder:",[93,4652,4654],{"className":3652,"code":4653,"language":3654,"meta":99,"style":99},"npx create-vite playground --template vue-ts\n",[101,4655,4656],{"__ignoreMap":99},[104,4657,4658,4660,4663,4666,4669],{"class":106,"line":107},[104,4659,3661],{"class":1489},[104,4661,4662],{"class":143}," create-vite",[104,4664,4665],{"class":143}," playground",[104,4667,4668],{"class":143}," --template",[104,4670,4671],{"class":143}," vue-ts\n",[10,4673,4674],{},"Since we're using a pnpm workspace, install the dependencies from the project's root:",[93,4676,4678],{"className":3652,"code":4677,"language":3654,"meta":99,"style":99},"pnpm install\n",[101,4679,4680],{"__ignoreMap":99},[104,4681,4682,4685],{"class":106,"line":107},[104,4683,4684],{"class":1489},"pnpm",[104,4686,4687],{"class":143}," install\n",[1628,4689,4690],{},[10,4691,4692,4694,4695,4698],{},[104,4693,1634],{},"\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 ",[101,4696,4697],{},"pnpm install"," installs dependencies from all workspaces.",[10,4700,4701,4702,4704],{},"Now, we can use this ",[101,4703,4649],{}," library within a \"real\" project to test our library.",[10,4706,4707,4708,4710,4711,4714],{},"For instance, open the ",[101,4709,759],{}," file and import the ",[101,4712,4713],{},"User"," component:",[93,4716,4718],{"className":95,"code":4717,"language":98,"meta":99,"style":99},"\u003Cscript setup lang=\"ts\">\nimport { User } from '..\u002F..\u002Fsrc'\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUser \u002F>\n\u003C\u002Ftemplate>\n",[101,4719,4720,4740,4759,4767,4771,4779,4787],{"__ignoreMap":99},[104,4721,4722,4724,4726,4728,4730,4732,4734,4736,4738],{"class":106,"line":107},[104,4723,111],{"class":110},[104,4725,485],{"class":114},[104,4727,500],{"class":133},[104,4729,488],{"class":133},[104,4731,137],{"class":110},[104,4733,140],{"class":110},[104,4735,495],{"class":143},[104,4737,140],{"class":110},[104,4739,118],{"class":110},[104,4741,4742,4744,4746,4748,4750,4752,4754,4757],{"class":106,"line":121},[104,4743,508],{"class":507},[104,4745,511],{"class":110},[104,4747,3350],{"class":514},[104,4749,518],{"class":110},[104,4751,521],{"class":507},[104,4753,524],{"class":110},[104,4755,4756],{"class":143},"..\u002F..\u002Fsrc",[104,4758,529],{"class":110},[104,4760,4761,4763,4765],{"class":106,"line":130},[104,4762,206],{"class":110},[104,4764,485],{"class":114},[104,4766,118],{"class":110},[104,4768,4769],{"class":106,"line":149},[104,4770,534],{"emptyLinePlaceholder":361},[104,4772,4773,4775,4777],{"class":106,"line":163},[104,4774,111],{"class":110},[104,4776,115],{"class":114},[104,4778,118],{"class":110},[104,4780,4781,4783,4785],{"class":106,"line":169},[104,4782,124],{"class":110},[104,4784,4713],{"class":114},[104,4786,189],{"class":110},[104,4788,4789,4791,4793],{"class":106,"line":192},[104,4790,206],{"class":110},[104,4792,115],{"class":114},[104,4794,118],{"class":110},[10,4796,4797],{},"And run the project:",[93,4799,4801],{"className":3652,"code":4800,"language":3654,"meta":99,"style":99},"cd playground && pnpm dev\n",[101,4802,4803],{"__ignoreMap":99},[104,4804,4805,4808,4810,4813,4816],{"class":106,"line":107},[104,4806,4807],{"class":547},"cd",[104,4809,4665],{"class":143},[104,4811,4812],{"class":110}," &&",[104,4814,4815],{"class":1489}," pnpm",[104,4817,4818],{"class":143}," dev\n",[10,4820,4821,4822,4824,4825],{},"You'll see the ",[101,4823,4713],{}," component displayed in the browser. 🎉 So simple, yet so powerful. ",[26,4826,4827],{},"I use this method for virtually every library I build, and it works brilliantly.",[31,4829,4831],{"id":4830},"publishing-to-npm","Publishing to npm",[10,4833,4834],{},"This part is straightforward once you grasp the workflow.",[10,4836,4837,4838,4841],{},"Begin by naming your library. I'll use ",[101,4839,4840],{},"@barbapapazes\u002Fvue-library"," for this example.",[10,4843,4844,4845,4850,4851,3896],{},"Next, create an account on ",[1637,4846,4849],{"href":4847,"rel":4848},"https:\u002F\u002Fwww.npmjs.com",[1641],"npm"," and log in using the ",[101,4852,4853],{},"npm login",[10,4855,4856,4857,4862],{},"Then, install ",[1637,4858,4861],{"href":4859,"rel":4860},"https:\u002F\u002Fchangelogen.unjs.io",[1641],"changelogen"," to generate a changelog, update the version number following semantic versioning and commit convention, and to pre-fill the GitHub release.",[93,4864,4866],{"className":3652,"code":4865,"language":3654,"meta":99,"style":99},"pnpm i -D changelogen\n",[101,4867,4868],{"__ignoreMap":99},[104,4869,4870,4872,4875,4878],{"class":106,"line":107},[104,4871,4684],{"class":1489},[104,4873,4874],{"class":143}," i",[104,4876,4877],{"class":143}," -D",[104,4879,4880],{"class":143}," changelogen\n",[10,4882,4883,4884,579],{},"Next, add two scripts to your ",[101,4885,3382],{},[93,4887,4889],{"className":3389,"code":4888,"language":3391,"meta":99,"style":99},"{\n  \"scripts\": {\n    \"prepack\": \"unbuild\",\n    \"release\": \"changelogen --release && npm publish --access public && git push --follow-tags\"\n  }\n}\n",[101,4890,4891,4895,4908,4927,4945,4949],{"__ignoreMap":99},[104,4892,4893],{"class":106,"line":107},[104,4894,571],{"class":110},[104,4896,4897,4899,4902,4904,4906],{"class":106,"line":121},[104,4898,3402],{"class":110},[104,4900,4901],{"class":133},"scripts",[104,4903,140],{"class":110},[104,4905,579],{"class":110},[104,4907,588],{"class":110},[104,4909,4910,4912,4915,4917,4919,4921,4923,4925],{"class":106,"line":130},[104,4911,3436],{"class":110},[104,4913,4914],{"class":1489},"prepack",[104,4916,140],{"class":110},[104,4918,579],{"class":110},[104,4920,3412],{"class":110},[104,4922,3636],{"class":143},[104,4924,140],{"class":110},[104,4926,1018],{"class":110},[104,4928,4929,4931,4934,4936,4938,4940,4943],{"class":106,"line":149},[104,4930,3436],{"class":110},[104,4932,4933],{"class":1489},"release",[104,4935,140],{"class":110},[104,4937,579],{"class":110},[104,4939,3412],{"class":110},[104,4941,4942],{"class":143},"changelogen --release && npm publish --access public && git push --follow-tags",[104,4944,146],{"class":110},[104,4946,4947],{"class":106,"line":163},[104,4948,619],{"class":110},[104,4950,4951],{"class":106,"line":169},[104,4952,1043],{"class":110},[10,4954,3312,4955,4957,4958,4960,4961,4963],{},[101,4956,4914],{}," script runs the ",[101,4959,3636],{}," command before publishing the package to npm. The ",[101,4962,4933],{}," script generates a changelog, publishes the package to npm, and pushes the tags to GitHub.",[1628,4965,4966],{},[10,4967,4968,4970,4971,4974],{},[104,4969,1634],{},"\nYour project should be on GitHub for the ",[101,4972,4973],{},"--release"," option to work. If not, omit it and create the release manually on your repository.",[10,4976,4977],{},"Now, publish your package to npm using the following command:",[93,4979,4981],{"className":3652,"code":4980,"language":3654,"meta":99,"style":99},"npm run release\n",[101,4982,4983],{"__ignoreMap":99},[104,4984,4985,4987,4990],{"class":106,"line":107},[104,4986,4849],{"class":1489},[104,4988,4989],{"class":143}," run",[104,4991,4992],{"class":143}," release\n",[10,4994,4995],{},"And that's it! You've just published your first Vue.js component library to npm. 🚀",[31,4997,4999],{"id":4998},"everything-is-ready","Everything is ready",[10,5001,5002],{},"We're done for today. We've covered numerous topics:",[314,5004,5005,5008,5011,5014,5017,5020],{},[307,5006,5007],{},"How to build a Vue.js component library with TypeScript.",[307,5009,5010],{},"How to manage Vue files in a TypeScript project.",[307,5012,5013],{},"How to use unbuild to transpile TypeScript files without bundling Vue files.",[307,5015,5016],{},"How to leverage mkdist to maintain the project structure.",[307,5018,5019],{},"How to utilize a pnpm workspace to test the library in a local project.",[307,5021,5022],{},"How to publish the library to npm.",[10,5024,5025,5026],{},"For a more detailed and complex example, check GitHub: ",[2900,5027,5028,5029,596],{"repo":3008}," but everything explained here stems from ",[14,5030,5031],{},"my real-world experience",[10,5033,5034,5035,596],{},"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 ",[1637,5036,5039],{"href":5037,"rel":5038},"https:\u002F\u002Fx.com\u002Fsoubiran_",[1641],"X (Twitter)",[10,5041,5042],{},[26,5043,5044],{},"You can go even further by using Vite to build your component library:",[19,5046],{"id":3047},[2975,5048,5049],{},"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":99,"searchDepth":121,"depth":121,"links":5051},[5052,5053,5054,5055,5056,5057,5058,5059,5060],{"id":3537,"depth":121,"text":3538},{"id":3623,"depth":121,"text":3624},{"id":3878,"depth":121,"text":3879},{"id":4099,"depth":121,"text":4100},{"id":4186,"depth":121,"text":4187},{"id":4260,"depth":121,"text":4261},{"id":4584,"depth":121,"text":4585},{"id":4830,"depth":121,"text":4831},{"id":4998,"depth":121,"text":4999},"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":3000,"description":5062},{"loc":5064},"posts\u002Fthe-simplest-method-to-create-a-vue-js-component-library","54sSthPedhwWu7jxhjUQZlFD1TvUKk6X4GktWQA3ikA",1790086900872]