<script setup lang="ts">
const props = defineProps({
px: {
type: String,
required: false,
},
bg: {
type: String,
default: 'transparent',
},
rounded: {
type: String,
default: 'none',
}
})
const classes = []
for (const [key, value] of Object.entries(props)) {
(value !== undefined) && classes.push(`${key}-${value}`)
}
</script>
<template>
<div :class="classes" class="overflow-hidden">
<slot></slot>
</div>
</template>
Because the props is reactive, so I think the DOM will finish rendering before script's process.
How can I let the component wait for script's process? Thanks.
the classed has been added but it's reactive so they don't effect...
Don't construct class names dynamically
If you use string interpolation or concatenate partial class names together, Tailwind will not find them and therefore will not generate the corresponding CSS
How to use dynamic class names in tailwindcss -> use dynamic class name in tailwindcss
<!-- wrong ❌ -->
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>
<!-- correct ✅ -->
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps({
pxClass: {
type: String,
required: false,
default: 'px-4'
},
bgClass: {
type: String,
default: 'bg-transparent',
},
roundedClass: {
type: String,
default: 'rouned-none',
}
});
const classes = computed(() => {
let result = [];
for (const [key, value] of Object.entries(props)) {
result.push(value)
}
return result;
})
</script>
<template>
<div :class="classes" class="overflow-hidden">
<slot></slot>
</div>
</template>
This answer do have a really valid point of warning about dynamic classes. If you don't do that, you'll get your whole CSS payload bloated because Tailwind will not be able to generate only the needed utility classes.
Defeating the whole purpose of Tailwind. You can't have dynamic classes + utility classes at the same time mainly.
Here is an approach on how to do dynamic stuff in Vue with Tailwind's classes if you prefer to have an Options API approach on how to solve things.
As of today, this mapping is still the official way to go (confirmed by founders on Github discussions).
Related
parent.vue
<template>
<input :type="computedType"/>
</template>
<script setup>
import { ref, computed } from 'vue';
const props = defineProps({
type: {
required: false,
type: String,
default: 'text',
},
});
const showPassword = ref(false);
const computedType = computed(() => {
if (props.type === 'password') {
return showPassword.value ? 'text' : 'password';
}
return props.type;
});
</script>
<script>
export default {
data() {
return {
uuid: getRandomUuid(),
}
}
}
</script>
child.vue
<template>
<input :type="computedType"/>
</template>
<script>
import Parent from '~/components/parent.vue'
export default {
extends: Parent
}
</script>
In Vue3, I have a child.vue component which is inherited from parent.vue, I defined a computedType computed attribute in parent.vue, but it's missing in child.vue, however, uuid which is defined in parent.vue is accessible in child.vue.
[Vue warn]: Property "computedType" was accessed during render but is not defined on instance.
How to get computedType and any other attributes defined in <script setup> of parent.vue in child.vue?
Really appreciate any help provided!
update:
Only if I define all the attributes in <script>(but not in setup()) instead of <script setup>, they could be accessible
There are a few specific instances where you can have more than one script tag, and they are all outlined here in the documentation. Still, besides those 3 specific use cases, you shouldn't use more than one script tag. Your case of a separate script tag for one data variable is not a valid use case.
I recommend writing the component entirely with the options API (the only API that supports the "extends" option), or writing entirely with the composition API where you would use composable functions to effectively extend your component
defineExpose({computedType});
try this
I'm trying to use tiptap with Vue.js with the <script setup> approach of creating a Single File Component (SFC).
TextEditor.vue
<template>
<editor-content :editor="editor" class="editor" />
</template>
<script lang="ts" setup>
import { useEditor, EditorContent } from '#tiptap/vue-3'
import StarterKit from '#tiptap/starter-kit'
const props = defineProps({
modelValue: {
type: String,
default: "",
}
})
const emit = defineEmits(['update:modelValue'])
const editor = useEditor({
content: props.modelValue,
extensions: [StarterKit],
onUpdate: ({editor}) => {
let content = editor.getHTML()
emit('update:modelValue', content)
}
})
</script>
I then use this component like this:
<template>
<text-editor v-model="myModel.content" />
</template>
This works when <text-editor> is loaded after myModel.content is defined.
However, if <text-editor> loads before myModel.content is set from my database API, then the text content remains blank. From what I understand from looking at the examples in the tiptap docs, I need to somehow use watch to update my editor when props.modelValue is changed using something like this:
watch(() => props.modelValue, (newValue, oldValue) => {
const isSame = newValue === oldValue
console.log(`Same: ${isSame}`)
if (isSame) {
return
}
editor.commands.setContent(newValue, false)
})
However, in the snippet above, editor is a ShallowRef type and doesn't have a reference to commands to call setContent.
What is the best way to get the above example to work when loading tiptap with the <script setup> approach?
You need to access the ref actual value with .value
editor.value?.commands.setContent('<p>test</p>', false)
I have a Vue component inside a NuxtJS app and I'm using the #nuxtjs/composition-api.
I have this component which is a <Link> component and I would like to make the code clearer.
I have a computed property that determines to color of my UiIcon from iconColor, iconColorHover, IconActive. But most importantly, I want to set it to a specific color if I have a disable class on my root component. It works like that but it doesn't look too good I believe.
I found out that undefined is the only value that I can use to take UiIcon default props if not defined. Empty string like '' would make more sense to more but it's considered as a valid value. I would have to do some ternary conditions in my UiIcon and I'd like to avoid that.
<template>
<div ref="rootRef" class="row">
<UiIcon
v-if="linkIcon"
:type="linkIcon"
:color="linkIconColor"
class="icon"
/>
<a
class="link"
:href="linkHref"
:target="linkTarget"
:rel="linkTarget === 'blank' ? 'noopener noreferrer' : null"
#mouseover="linkActive = true"
#mouseout="linkActive = false"
>
<slot></slot>
</a>
</div>
</template>
<script lang="ts">
import {
defineComponent,
computed,
ref,
toRefs,
nextTick,
onBeforeMount,
} from '#nuxtjs/composition-api';
import { Colors } from '~/helpers/styles';
export default defineComponent({
name: 'Link',
props: {
href: {
type: String,
default: undefined,
},
target: {
type: String as () => '_blank' | '_self' | '_parent' | '_top',
default: '_self',
},
icon: {
type: String,
default: undefined,
},
iconColor: {
type: String,
default: undefined,
},
iconHoverColor: {
type: String,
default: undefined,
},
},
setup(props) {
const { href, target, icon, iconColor, iconHoverColor } = toRefs(props);
const linkActive = ref(false);
const rootRef = ref<HTMLDivElement | null>(null);
const writableIconColor = ref('');
const linkIconColor = computed({
get: () => {
const linkDisabled = rootRef.value?.classList.contains('disabled');
if (linkDisabled) {
return Colors.DARK_GREY;
}
if (linkActive.value && iconHoverColor.value) {
return iconHoverColor.value;
}
return iconColor.value;
},
set: (value) => {
writableIconColor.value = value;
},
});
onBeforeMount(() => {
nextTick(() => {
const linkDisabled = rootRef.value?.classList.contains('disabled');
if (linkDisabled) {
linkIconColor.value = Colors.DARK_GREY;
}
});
});
return {
rootRef,
linkHref: href,
linkTarget: target,
linkIcon: icon,
linkIconColor,
linkActive,
};
},
});
</script>
Implementing disabled status for a component means it will handle two factors: style (disabled color) and function. Displaying a disabled color is only a matter of style/css. implementing it in programmatical way means it'll take longer time to render completely on user's side and it'll lose more SEO scores. examine UiIcon's DOM from browser and override styles using Deep selectors.
If I were handling this case, I would have described the color with css and try to minimize programmatic manipulation of style.
<template>
<div :disabled="disabled">
</div>
</template>
<script>
export default {
props: {
disabled: {
type: Boolean,
default: false,
}
}
}
</script>
// it does not have to be scss.
// just use anything that's
// easier to handle variables.
<style lang="scss">
// I would normally import css with prepend option from webpack,
// but this is just to illustrate the usage.
#import 'custom-styles.scss';
&::v-deep button[disabled] {
color: $disabled-color;
}
</style>
attach validator function on the props object. it'll automatically throw errors on exceptions.
{
props: {
icon: {
type: String,
default: "default-icon",
validator(val) {
return val !== "";
// or something like,
// return val.includes(['iconA', 'iconB'])
},
},
}
}
I have an issue in the two way binding of a reactive component in vue 3 using the composition API.
The setup:
The parent calling code is:
<template>
<h1>{{ message.test }}</h1>
<Message v-model="message" />
</template>
<script>
import Message from '#/components/Message.vue';
import { reactive } from 'vue';
export default {
name: 'Home',
components: { Message },
setup() {
const message = reactive({ test: '123' });
return {
message
};
}
};
</script>
The child component code is:
<template>
<label>
<input v-model="message" type="text" />
</label>
</template>
<script>
import { computed } from 'vue';
export default {
props: {
messageObj: {
type: Object,
default: () => {},
},
},
emits: ['update:messageObj'],
setup(props, { emit }) {
const message = computed({
get: () => props.messageObj.test,
set: (value) => emit('update:messageObj', value),
});
return {
message,
};
},
};
</script>
The problem:
When the component is loaded, the default value from the object is shown in the input field.
This is as it should be, however, when I update the value in the input box the H1 in the parent view is not getting updated with the new input box value.
I have searched through the stackoverflow board and google but have not found any hint as to what needs to be done to make the object reactive.
I read through the reactivity documentation but still have not found any solution for my issue.
For testing I have changed message to be a ref and using this single ref value the data remains reactive and everything is working as expected.
Any pointers on what can be the issue with the reactive object not updating?
Here
<div id="app">
<h1>{{ message.test }}</h1>
<child v-model="message"></child>
</div>
const { createApp, reactive, computed } = Vue;
// -------------------------------------------------------------- child
const child = {
template: `<input v-model="message.test" type="text" />`,
props: {
modelValue: {
type: Object,
default: () => ({}),
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const message = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
});
return { message };
}
};
// ------------------------------------------------------------- parent
createApp({
components: { child },
setup() {
const message = reactive({ test: 'Karamazov' });
return { message };
}
}).mount('#app');
Solution and observations:
In the parent view which is calling the component you can use v-model and add a parameter to that v-model if you need to pass only one of the values in the object.
<template>
<h1>{{ message.test }}</h1>
<!-- <h1>{{ message }}</h1> -->
<Message v-model:test="message" />
</template>
<script>
import Message from '#/components/Message.vue';
import { reactive } from 'vue';
export default {
name: 'Home',
components: { Message },
setup() {
const message = reactive({ test: '123' });
return {
message
};
}
};
</script>
In the receiving component you then register the parameter of the object that was passed in props as an object.
<template>
<label>
<input v-model="message.test" type="text" />
</label>
</template>
<script>
import { computed } from 'vue';
export default {
props: {
test: {
type: Object,
default: () => {}
},
},
emits: ['update:test'],
setup(props, { emit }) {
const message = computed({
get: () => props.test,
set: (value) => emit('update:test', value),
});
return {
message,
};
},
};
</script>
If you need to pass the whole object you need to use as a prop in the component the name modelValue.
Change in parent compared to previous code:
<template>
<h1>{{ message.test }}</h1>
<!-- <h1>{{ message }}</h1> -->
<Message v-model="message" />
</template>
Code of the component:
<template>
<label>
<input v-model="message.test" type="text" />
</label>
</template>
<script>
import { computed } from 'vue';
export default {
props: {
modelValue: {
type: Object,
default: () => {}
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const message = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value),
});
return {
message,
};
},
};
</script>
Should be pretty straight forward, and no computed is needed. See example below.
The messageObj was replaced with message in the child component for the emit to work (which would break due to case sensitivity in this demo)
const app = Vue.createApp({
setup() {
const message = Vue.reactive({ test: '123' , foo: "bark"});
return {
message,
};
}
})
app.component('Message', {
props: {
message: {
type: Object,
default: () => {},
},
},
emits: ['update:message'],
setup(props, { emit }) {
const message = props.message;
return { message };
},
template: document.querySelector('#t_child')
})
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.2/dist/vue.global.prod.js"></script>
<fieldset>
<div id="app">
<h1>{{ message.test }} || {{ message.foo }}</h1>
<fieldset><Message v-model:message="message"/></fieldset>
</div>
</fieldset>
<template id="t_child">
<label>
<h4>{{message}}</h4>
<input v-model="message.test" type="text" />
<input v-model="message.foo" type="text" />
</label>
</template>
Your initial problem is quite simple. In Vue 3 v-model defaults to to a prop called modelValue and emits come from update:modelValue. Other answers here have assumed that in their solutions but not directly addressed it.
You can either rename your messageObj prop to use the default prop OR use the multi-model features in Vue 3:
<Message v-model:messageObj="message" />
However our problems run deeper.
All (current) answers will work but aren't quite correct. They all fail the idiomatic "One-way Data Flow" rule.
Consider this JSFiddle, modified from this answer.
const child = {
template: `<input v-model="message.test" type="text" />`,
setup(props, { emit }) {
const message = computed({
get: () => props.modelValue,
// No set() ?
});
return { message };
}
}
In this example, the child component never 'emits' - yet the data is still updating in the parent component. This violates the "One-way" rule. Data must be propagated from child components using only emits and not via prop proxies.
The problem in here is that props.modelValue is reactive when arrives in the child component. One can verify this with the isReactive() helper. When it's passed through the computed() it retains that reactiveness and will continue to proxy updates through itself into the parent component.
A solution:
JSFiddle here
const { createApp, ref, computed } = Vue;
const child = {
template: `<input v-model="message" type="text" />`,
props: {
modelValue: {
type: Object,
default: () => ({}),
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const message = computed({
get: () => props.modelValue.test,
set: (test) => emit('update:modelValue', ({...props.modelValue, test })),
});
return { message };
}
};
createApp({
components: { child },
setup() {
const message = ref({ test: 'Karamazov' });
return { message };
}
}).mount('#app');
The solution is three parts:
The computed getter must not return the proxy object from the parent component. Once this happens you're in danger of violating the "one-way" rule [note 1]. In this example props.modelValue.test is a string so we're safe.
The computed setter must emit the whole object, but again it must not be a reactive type. So we clone the modelValue using spread and include in the updated test field. This can also be achieved with Object.assign({}, props.modelValue, {test}) [note 2].
The message variable in the parent component cannot be a reactive() and must be a ref(). When the v-model receives the newly emitted object the message variable is clobbered and no longer reactive [note 3]. Even with refs the props.modelValue will still fully reactive when it arrives in the child component, so the cloning steps are still important.
Alternatively:
I should also mention that values from computed() are not deeply reactive. As in, setting values on a computed object will not trigger the computed setter.
An alternate solution for passing the whole object through to your template:
setup(props, { emit }) {
const message = reactive({...props.modelValue});
watch(message, message => emit('update:modelValue', ({...message})));
return { message };
}
In this, the whole message object will emit whenever the .test field is updated. E.g. <input v-model="message.test" />. This still obeys the "one-way" data rule because emits are the only way data is given to parent component.
Reasoning:
"One-way" data flow is important [4]. Consider this:
<child :modelValue="message"></child>
On a first (and a sensible) glance, this appears to pass data into 'child' but not out of 'child'. But, given a reactive object that is not handled by the child correctly, this will emit changes into my own component.
Observing this code I don't expect this behaviour so it's very important that the child component gets it right.
Notes:
[1]: Testing violations of the "one-way" rule are surprisingly simple. Remove any emit and if the parent receives updates - you've broken it. Or replacing v-model with v-bind also works.
[2]: Object.assign() and {...} spread are indeed different. But shouldn't affect our uses here.
[3]: I haven't found any clear documentation about this behaviour regarding reactive() and v-model. If anyone wants to chime in, that'd be great.
[4]: The Vue docs stress the importance of one-way bind. Evan himself (creator of Vue) even provides examples about how to use v-model with objects (in Vue 2, but the principles still apply).
I feel it's also important to note later in the same thread Evan suggests objects that are nested more than 1-level are considered misuse of v-model.
It turns out that 2 way binding of object properties with Vue 3 is even easier than demonstrated in any of the previous answers.
Parent Code (App.vue):
<script setup>
import Controller from './components/Controller.vue';
import { reactive } from 'vue';
const object1 = reactive({name: "Bruce", age: 38});
const object2 = reactive({name: "Alex", age: 6});
</script>
<template>
<div>
{{object1}}<br/>
{{object2}}
<Controller :my-object="object1"/>
<Controller :my-object="object2"/>
</div>
</template>
Component code (Controller.vue):
<script setup>
import { computed } from 'vue'
const props = defineProps({
myObject: {
type: Object,
default: () => {}
}
})
const name = computed({
get () {
return props.myObject.name
},
set (value) {
props.myObject.name = value
}
})
const age = computed({
get () {
return props.myObject.age
},
set (value) {
props.myObject.age = parseInt(value)
}
})
</script>
<template>
<div>
<input v-model="name"/><br/>
<input v-model="age" type="number"/>
</div>
</template>
Explanation:
The <component :my-object="object1" /> syntax uses a : to tell Vue that we are passing an object (object1), rather than a string to the component and assigning it to property myObject. It turns out that when the child component receives this property, its reactivity is still intact. Therefore, as long as we don't mutate myObject itself, but instead only modify its properties, there is no need to emit any events or even pass it with as a property called v-model (we can call the property whatever we want). Instead the javascript proxy that the reactive keyword creates will do all the work tracking the changes and re-rendering it.
Some testing reveals that it is even possible to add new properties to the object or change deep properties and still maintain reactivity.
I am just a beginner with Vue, so there may be reasons why using this method are an anti-pattern, with unintended future consequences...
I am terribly new to Vue, so forgive me if my terminology is off. I have a .NET Core MVC project with small, separate vue pages. On my current page, I return a view from the controller that just has:
#model long;
<div id="faq-category" v-bind:faqCategoryId="#Model"></div>
#section Scripts {
<script src="~/scripts/js/faqCategory.js"></script>
}
Where I send in the id of the item this page will go grab and create the edit form for. faqCategory.js is the compiled vue app. I need to pass in the long parameter to the vue app on initialization, so it can go fetch the full object. I mount it with a main.ts like:
import { createApp } from 'vue'
import FaqCategoryPage from './FaqCategoryPage.vue'
createApp(FaqCategoryPage)
.mount('#faq-category');
How can I get my faqCategoryId into my vue app to kick off the initialization and load the object? My v-bind attempt seems to not work - I have a #Prop(Number) readonly faqCategoryId: number = 0; on the vue component, but it is always 0.
My FaqCategoryPAge.vue script is simply:
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { Prop } from 'vue-property-decorator'
import Card from "#/Card.vue";
import axios from "axios";
import FaqCategory from "../shared/FaqCategory";
#Options({
components: {
Card,
},
})
export default class FaqCategoryPage extends Vue {
#Prop(Number) readonly faqCategoryId: number = 0;
mounted() {
console.log(this.faqCategoryId);
}
}
</script>
It seems passing props to root instance vie attributes placed on element the app is mounting on is not supported
You can solve it using data- attributes easily
Vue 2
const mountEl = document.querySelector("#app");
new Vue({
propsData: { ...mountEl.dataset },
props: ["message"]
}).$mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app" data-message="Hello from HTML">
{{ message }}
</div>
Vue 3
const mountEl = document.querySelector("#app");
Vue.createApp({
props: ["message"]
}, { ...mountEl.dataset }).mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0/vue.global.js"></script>
<div id="app" data-message="Hello from HTML">
{{ message }}
</div>
Biggest disadvantage of this is that everything taken from data- attributes is a string so if your component expects something else (Number, Boolean etc) you need to make conversion yourself.
One more option of course is pushing your component one level down. As long as you use v-bind (:counter), proper JS type is passed into the component:
Vue.createApp({
components: {
MyComponent: {
props: {
message: String,
counter: Number
},
template: '<div> {{ message }} (counter: {{ counter }}) </div>'
}
},
}).mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0/vue.global.js"></script>
<div id="app">
<my-component :message="'Hello from HTML'" :counter="10" />
</div>
Just an idea (not a real problem)
Not really sure but it can be a problem with Props casing
HTML attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase. That means when you're using in-DOM templates, camelCased prop names need to use their kebab-cased (hyphen-delimited) equivalents
Try to change your MVC view into this:
<div id="faq-category" v-bind:faq-category-id="#Model"></div>
Further to Michal Levý's answer regarding Vue 3, you can also implement that pattern with a Single File Component:
app.html
<div id="app" data-message="My Message"/>
app.js
import { createApp } from 'vue';
import MyComponent from './my-component.vue';
const mountEl = document.querySelector("#app");
Vue.createApp(MyComponent, { ...mountEl.dataset }).mount("#app");
my-component.vue
<template>
{{ message }}
</template>
<script>
export default {
props: {
message: String
}
};
</script>
Or you could even grab data from anywhere on the parent HTML page, eg:
app.html
<h1>My Message</h1>
<div id="app"/>
app.js
import { createApp } from 'vue';
import MyComponent from './my-component.vue';
const message = document.querySelector('h1').innerText;
Vue.createApp(MyComponent, { message }).mount("#app");
my-component.vue
<template>
{{ message }}
</template>
<script>
export default {
props: {
message: String
}
};
</script>
To answer TheStoryCoder's question: you would need to use a data prop. My answers above demonstrate how to pass a value from the parent DOM to the Vue app when it is mounted. If you wanted to then change the value of message after it was mounted, you would need to do something like this (I've called the data prop myMessage for clarity, but you could also just use the same prop name message):
<template>
{{ myMessage }}
<button #click="myMessage = 'foo'">Foo me</button>
</template>
<script>
export default {
props: {
message: String
},
data() {
return {
myMessage: this.message
}
}
};
</script>
So I'm not at all familiar with .NET and what model does, but Vue will treat the DOM element as a placeholder only and it does not extend to it the same functionality as the components within the app have.
so v-bind is not going to work, even without the value being reactive, the option is not there to do it.
you could try a hack to access the value and assign to a data such as...
const app = Vue.createApp({
data(){
return {
faqCategoryId: null
}
},
mounted() {
const props = ["faqCategoryId"]
const el = this.$el.parentElement;
props.forEach((key) => {
const val = el.getAttribute(key);
if(val !== null) this[key] = (val);
})
}
})
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app" faqCategoryId="12">
<h1>Faq Category Id: {{faqCategoryId}}</h1>
</div>
where you get the value from the html dom element, and assign to a data. The reason I'm suggesting data instead of props is that props are setup to be write only, so you wouldn't be able to override them, so instead I've used a variable props to define the props to look for in the dom element.
Another option
is to use inject/provide
it's easier to just use js to provide the variable, but assuming you want to use this in an mvc framework, so that it is managed through the view only. In addition, you can make it simpler by picking the exact attributes you want to pass to the application, but this provides a better "framework" for reuse.
const mount = ($el) => {
const app = Vue.createApp({
inject: {
faqCategoryId: {
default: 'optional'
},
},
})
const el = document.querySelector($el)
Object.keys(app._component.inject).forEach(key => {
if (el.getAttribute(key) !== null) {
app.provide(key, el.getAttribute(key))
}
})
app.mount('#app')
}
mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app" faqCategoryId="66">
<h1>Faq Category Id: {{faqCategoryId}}</h1>
</div>
As i tried in the following example
https://codepen.io/boussadjra/pen/vYGvXvq
you could do :
mounted() {
console.log(this.$el.parentElement.getAttribute("faqCategoryId"));
}
All other answers might be valid, but for Vue 3 the simple way is here:
import {createApp} from 'vue'
import rootComponent from './app.vue'
let rootProps = {};
createApp(rootComponent, rootProps)
.mount('#somewhere')