// app.js
async function loadMathModule() {
const mathModule = await import("./math.js");
console.log(mathModule.add(5, 3));
}
// Or with .then()
import("./math.js")
.then((mathModule) => {
console.log(mathModule.PI);
})
.catch((error) => {
console.error("Failed to load module:", error);
});
<!-- 1 -->
<component
v-if="transport?.image"
:is="typeof transport.image === 'object' ? 'aw-img' : 'img'"
v-bind="
typeof transport.image === 'object'
? { image: transport.image, size: 'eop-icon', style: 'max-width: 40px' }
: { src: transport.image, size: 'eop-icon', style: 'max-width: 40px' }
"
/>
<!-- 2 h render -->
const transportImage = computed(() => {
if (!props.transport?.image) return null
if (typeof props.transport.image === 'object') {
return h(resolveComponent('aw-img'), {
image: props.transport.image,
size: 'eop-icon',
style: 'max-width: 40px'
})
} else {
return h('img', {
src: props.transport.image,
size: 'eop-icon',
style: 'max-width: 40px'
})
}
})
// https://learnvue.co/articles/v-once-v-memo
<script setup>
import { ref } from 'vue'
const subscribers = ref(4000)
const views = ref(10000)
const likes = ref(3000)
</script>
<template>
<div>
<div v-memo="[subscribers]">
<p>Subscribers: {{ subscribers }}</p>
<p>Views: {{ views }}</p>
<p>Likes: {{ likes }}</p>
</div>
<button @click="subscribers++">Subscribers++</button>
<button @click="views++">Views++</button>
<button @click="likes++">Likes++</button>
<div>
<p>Current state:</p>
<p>Subscribers: {{ subscribers }}</p>
<p>Views: {{ views }}</p>
<p>Likes: {{ likes }}</p>
</div>
</div>
</template>
<!-- Parent -->
<script setup>
provide('transportFree', useSlots().transportfree) // slot existuje i kdyz ho vubec nepouziju v komponente, ve smyslu <slot name="transportfree" />, staci zvenci <template #transportfree>content</template>
</script>
<!-- Child -->
<script setup>
const transportFree = inject('transportFree')
</script>
<template>
<component :is="transportFree" />
</template>
// composables/useMediaObjects.js
export function useMediaObjects() {
const transformMediaObjects = (productGallery, productVideos) => {
const finalObjects = []
// Photos
if (productGallery) {
finalObjects.push(
...productGallery.map((photo, index) => ({
...photo,
type: 'photo',
rank: index + 1
}))
)
}
// Videos
if (productVideos) {
finalObjects.push(
...productVideos.map((videoUrl, index) => {
const youtubeId = extractYouTubeId(videoUrl)
return {
url: videoUrl,
type: 'video',
rank: finalObjects.length + index + 1,
previewThumb: youtubeId
? `https://img.youtube.com/vi/${youtubeId}/sddefault.jpg`
: null
}
})
)
}
return finalObjects
}
const extractYouTubeId = (url) => {
return url.match(
/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
)?.[1]
}
return {
transformMediaObjects,
extractYouTubeId
}
}
// Product.vue
<script setup>
import { computed } from 'vue'
import { useMediaObjects } from '@/composables/useMediaObjects'
const props = defineProps(['productGallery', 'product'])
const { transformMediaObjects } = useMediaObjects()
const mediaObjects = computed(() => {
return transformMediaObjects(props.productGallery, props.product?.product_video)
})
</script>
// services/MediaTransformer.js
export class MediaTransformer {
static transform(productGallery, productVideos) {
const finalObjects = []
if (productGallery) {
finalObjects.push(
...productGallery.map((photo, index) => ({
...photo,
type: 'photo',
rank: index + 1
}))
)
}
if (productVideos) {
finalObjects.push(
...productVideos.map((videoUrl, index) => {
const youtubeId = this.extractYouTubeId(videoUrl)
return {
url: videoUrl,
type: 'video',
rank: finalObjects.length + index + 1,
previewThumb: youtubeId
? `https://img.youtube.com/vi/${youtubeId}/sddefault.jpg`
: null
}
})
)
}
return finalObjects
}
static extractYouTubeId(url) {
return url.match(
/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
)?.[1]
}
static generateThumbnail(youtubeId, quality = 'sddefault') {
return `https://img.youtube.com/vi/${youtubeId}/${quality}.jpg`
}
}
// Product.vue
<script setup>
import { computed } from 'vue'
import { MediaTransformer } from '@/services/MediaTransformer'
const props = defineProps(['productGallery', 'product'])
const mediaObjects = computed(() => {
return MediaTransformer.transform(props.productGallery, props.product?.product_video)
})
</script>
// services/MediaTransformer.js
export class MediaTransformer {
constructor(config = {}) {
this.thumbnailQuality = config.thumbnailQuality || 'sddefault'
this.defaultVideoType = config.defaultVideoType || 'video'
}
transform(productGallery, productVideos) {
const finalObjects = []
if (productGallery) {
finalObjects.push(
...productGallery.map((photo, index) => ({
...photo,
type: 'photo',
rank: index + 1
}))
)
}
if (productVideos) {
finalObjects.push(
...productVideos.map((videoUrl, index) => {
const youtubeId = this.extractYouTubeId(videoUrl)
return {
url: videoUrl,
type: this.defaultVideoType,
rank: finalObjects.length + index + 1,
previewThumb: youtubeId
? `https://img.youtube.com/vi/${youtubeId}/${this.thumbnailQuality}.jpg`
: null
}
})
)
}
return finalObjects
}
extractYouTubeId(url) {
return url.match(
/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
)?.[1]
}
setConfig(newConfig) {
Object.assign(this, newConfig)
return this
}
}
// Product.vue
<script setup>
import { computed, ref } from 'vue'
import { MediaTransformer } from '@/services/MediaTransformer'
const props = defineProps(['productGallery', 'product'])
const mediaTransformer = ref(new MediaTransformer({ thumbnailQuality: 'hqdefault' }))
const mediaObjects = computed(() => {
return mediaTransformer.value.transform(props.productGallery, props.product?.product_video)
})
</script>
// services/MediaTransformer.js
class MediaTransformer {
constructor(config = {}) {
this.thumbnailQuality = config.thumbnailQuality || 'sddefault'
}
transform(productGallery, productVideos) {
// stejná implementace jako výše...
}
extractYouTubeId(url) {
// stejná implementace...
}
setConfig(newConfig) {
Object.assign(this, newConfig)
return this
}
}
// Export instance
export const mediaTransformer = new MediaTransformer()
// Případně export obou
export { MediaTransformer }
// Product.vue
<script setup>
import { computed } from 'vue'
import { mediaTransformer } from '@/services/MediaTransformer'
const props = defineProps(['productGallery', 'product'])
const mediaObjects = computed(() => {
return mediaTransformer.transform(props.productGallery, props.product?.product_video)
})
// Změna konfigurace kdykoliv:
// mediaTransformer.setConfig({ thumbnailQuality: 'maxresdefault' })
</script>
// services/MediaTransformer.js
class MediaTransformer {
static _instance = null
constructor(config = {}) {
if (MediaTransformer._instance) {
return MediaTransformer._instance
}
this.thumbnailQuality = config.thumbnailQuality || 'sddefault'
MediaTransformer._instance = this
}
static getInstance(config = {}) {
if (!this._instance) {
this._instance = new MediaTransformer(config)
}
return this._instance
}
transform(productGallery, productVideos) {
// implementace...
}
extractYouTubeId(url) {
// implementace...
}
}
export default MediaTransformer
// Product.vue
<script setup>
import { computed } from 'vue'
import MediaTransformer from '@/services/MediaTransformer'
const props = defineProps(['productGallery', 'product'])
const mediaObjects = computed(() => {
const transformer = MediaTransformer.getInstance({ thumbnailQuality: 'hqdefault' })
return transformer.transform(props.productGallery, props.product?.product_video)
})
</script>
// services/MediaTransformer.js
class MediaTransformer {
constructor(config = {}) {
this.thumbnailQuality = config.thumbnailQuality || 'sddefault'
}
transform(productGallery, productVideos) {
// implementace...
}
extractYouTubeId(url) {
// implementace...
}
}
export const createMediaTransformer = (config = {}) => {
return new MediaTransformer(config)
}
// Export i třídy pro pokročilé použití
export { MediaTransformer }
// Product.vue
<script setup>
import { computed, ref } from 'vue'
import { createMediaTransformer } from '@/services/MediaTransformer'
const props = defineProps(['productGallery', 'product'])
const mediaTransformer = ref(createMediaTransformer({ thumbnailQuality: 'maxresdefault' }))
const mediaObjects = computed(() => {
return mediaTransformer.value.transform(props.productGallery, props.product?.product_video)
})
</script>
// services/mediaService.js
export const mediaService = {
defaultConfig: {
thumbnailQuality: 'sddefault'
},
transform(productGallery, productVideos, config = {}) {
const finalConfig = { ...this.defaultConfig, ...config }
const finalObjects = []
if (productGallery) {
finalObjects.push(
...productGallery.map((photo, index) => ({
...photo,
type: 'photo',
rank: index + 1
}))
)
}
if (productVideos) {
finalObjects.push(
...productVideos.map((videoUrl, index) => {
const youtubeId = this.extractYouTubeId(videoUrl)
return {
url: videoUrl,
type: 'video',
rank: finalObjects.length + index + 1,
previewThumb: youtubeId
? `https://img.youtube.com/vi/${youtubeId}/${finalConfig.thumbnailQuality}.jpg`
: null
}
})
)
}
return finalObjects
},
extractYouTubeId(url) {
return url.match(
/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
)?.[1]
}
}
// Product.vue
<script setup>
import { computed } from 'vue'
import { mediaService } from '@/services/mediaService'
const props = defineProps(['productGallery', 'product'])
const mediaObjects = computed(() => {
return mediaService.transform(
props.productGallery,
props.product?.product_video,
{ thumbnailQuality: 'hqdefault' }
)
})
</script>
// utils/mediaTransform.js
export const transformMediaObjects = (productGallery, productVideos, config = {}) => {
const { thumbnailQuality = 'sddefault' } = config
const finalObjects = []
if (productGallery) {
finalObjects.push(
...productGallery.map((photo, index) => ({
...photo,
type: 'photo',
rank: index + 1
}))
)
}
if (productVideos) {
finalObjects.push(
...productVideos.map((videoUrl, index) => {
const youtubeId = extractYouTubeId(videoUrl)
return {
url: videoUrl,
type: 'video',
rank: finalObjects.length + index + 1,
previewThumb: youtubeId
? `https://img.youtube.com/vi/${youtubeId}/${thumbnailQuality}.jpg`
: null
}
})
)
}
return finalObjects
}
export const extractYouTubeId = (url) => {
return url.match(
/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
)?.[1]
}
// Product.vue
<script setup>
import { computed } from 'vue'
import { transformMediaObjects } from '@/utils/mediaTransform'
const props = defineProps(['productGallery', 'product'])
const mediaObjects = computed(() => {
return transformMediaObjects(
props.productGallery,
props.product?.product_video,
{ thumbnailQuality: 'hqdefault' }
)
})
</script>
Varianta | Výhody | Nevýhody | Použití |
---|---|---|---|
Composable | Vue 3 native, reaktivita, auto-import | Vue specific | Vue 3 projekty |
Statické metody | Jednoduché, žádná konfigurace | Není flexibilní | Jednoduché transformace |
Export třídy | Flexibilní konfigurace | Musíš vytvářet instance | Různé konfigurace |
Export instance | Žádné new , sdílený stav |
Pevná konfigurace | Jednotná konfigurace |
Singleton | Jedna instance, lazy loading | Složitější pattern | Globální konfigurace |
Factory | Flexibilní vytváření | Extra abstrakce | Komplexní objekty |
Plain Object | Jednoduché, framework agnostic | Není OOP | Malé utility |
Pure Functions | Nejjednodušší, testovatelné | Žádná organizace | Funkcionální přístup |
setConfig
// https://play.vuejs.org/#eNqdVE2P2jAQ/SsjXwCJBlXtic2itohDK7W72u7Rl5BMwItjW7bDUiH+e8d2krJbRNXlgOyZN1/Pb3Jkn43J9i2yOctdaYXx4NC3ZsGVaIy2Ho5gsZ5C6/ARGyMLjw9YwwlqqxsYUehogH7XVSE7RzaLt5CbAKVWzkMTLCH69lW68Si6RpMbrvJZ6oM6oIvvQHS7LywqD/nawoyut3//Bl++br3XCj6VUpS7W876ypk2qMYTzhZ3dEgN57OEvhpXSu0wBi7D6UJkqNz9b7suEh9EX5+Js+SYDWOxKfOO2KnFJntyWtE7HLkC4KzUjRES7Z3xgtjjbA7RE3yFlPr5W7R52+K0t5dbLHcX7E/uEGyc3Vt0aPfI2eDzhd2gT+7Vzx94oPPgpK5bSegrzgd0WrahxwT70qqK2j7DxW6/RokItXl0q4NH5fqhQqMBeYp4zkgwyyuj/2n3Q/YxxnF1IhYHtQUln6kGYLkVsoJxBEzeJp//VM0/xVKJPezfua1+JrRwIStJo5SFc2SI8KVWxBINukgq6u75jGJfSijuyZXdfbWr/TKmsrSKBBnXhXQ44apuVRk4hzRxoj0hs30hWyR8eDAg0s/Q3cAX4TF1wnNVYS0Urg4m4I+xyjRFw4nKv9x953/J9ITZOSW9FkxRVaSnOby32NzAuih3G6tJfXMwsm3oUxJQDelFqA7U6YXKxNTs9BvEAbCg
// Parent
<script setup>
import { ref, useTemplateRef } from 'vue'
import Modal from './Modal.vue'
const modalRef = useTemplateRef('modal');
</script>
<template>
Parent <br />
=================== <br />
<button @click="modalRef.open()">Open Modal</button>
<button @click="modalRef.close()">Close Modal</button>
<br /><br /><hr />
<Modal ref="modal" />
</template>
// Child - Modal
<template>
Child (Modal) <br />
=================== <br />
<button @click="open()">Open Modal</button>
<button @click="close()">Close Modal</button>
<div v-show="isOpen" class="ModalContent">Modal Content</div>
</template>
<script setup>
import { ref } from 'vue'
const isOpen = ref(false)
function open() {
isOpen.value = true
}
function close() {
isOpen.value = false
}
defineExpose({ open, close })
</script>
<style>
.ModalContent {
padding: 1rem; background: plum;
margin: 1rem;
}
</style>
const showThumbs = (() => {
// really private ;-)
const state = ref(props.enableThumbs)
return computed({
get: () => state.value,
set: value => {
state.value = value
}
})
})()
/*
-----------------------------------
Nejčastější použití je právě pro:
- Vytvoření privátního scope
- Enkapsulaci dat a logiky
- Vyhnutí se globálnímu scope
- Zachování stavu přes closure
-----------------------------------
* 1. Vytvoření privátního scope
* - proměnné uvnitř neuniknou ven
* - čisté API pro vnější svět
*/
const counter = (() => {
let count = 0 // privátní proměnná
return {
increment() { count++ },
getCount() { return count }
}
})()
/**
* 2. Vyhnutí se globálnímu scope
* - dočasné proměnné zůstanou lokální
* - neznečišťuje globální namespace
*/
(() => {
const temp = 'nějaká dočasná proměnná'
// temp existuje jen uvnitř, neznečišťuje globální scope
})()
/**
* 3. Modulární pattern
* - privátní metody a proměnné
* - veřejné API
*/
const module = (() => {
const privateVar = 'private'
const privateMethod = () => {
console.log(privateVar)
}
return {
publicMethod() {
privateMethod() // má přístup k privátním věcem
}
}
})()
/**
* 4. Closure pro zachování stavu
* - privátní stav
* - reaktivní hodnota
*/
const showThumbs = (() => {
const state = ref(props.enableThumbs) // privátní stav
return computed({
get: () => state.value,
set: (value) => {
state.value = value
}
})
})()
/**
* 5. Vyhnutí se konfliktům
* - lokální aliasy
* - izolace kódu
*/
(() => {
const $ = jQuery // lokální alias pro jQuery
// používání $ uvnitř neovlivní jiné knihovny používající $
})()
/**
* 6. Inicializace
* - nastavení při načtení
* - konfigurace
* - event listenery
*/
(() => {
// nějaká inicializace
const config = {
apiKey: 'xxx',
endpoint: 'https://api.example.com'
}
// nastavení event listenerů
document.addEventListener('DOMContentLoaded', () => {
// inicializace UI
})
// další setup
const init = () => {
// ...
}
init()
})()
// https://www.reddit.com/r/tailwindcss/comments/1icfwbo/here_are_10_tailwind_tricks_shared_by_shadcn_they/
// https://x.com/shadcn/status/1842329158879420864
// 1. Dynamic CSS Variables in Tailwind
<div style={{ "--width": isCollapsed ? "8rem" : "14rem" }} className="w-[--width] transition-all" />
// 2. Data Attribute State Management
<div
data-state={isOpen ? "open" : "closed"}
className="data-[state=open]:bg-blue-500"
/>
// 3. Nested SVG Controll
<div
data-collapsed={isCollapsed}
className="[&[data-collapsed=true]_svg]:rotate-180"
>
<svg>...</svg>
</div>
// 4. Parent-Child Style Inheritance
<section :data-collapsed="isCollapsed"
<div className="[[data-collapsed=true]_&]:rotate-180">
{/* Child inherits rotation when parent has data-collapsed=true */}
</div>
</section>
// 5. Group Data States
<div className="group" data-collapsed={isCollapsed}>
<div className="group-data-[collapsed=true]:rotate-180"/>
</div>
// 6. Data Slots
<div className="data-[slot=action]:*:hover:mr-0">
<div data-slot="action" class="-mr-10">...</div>
</div>
// 7. Peer Element Control
<button className="peer" :data-active="isActive">Menu</button>
<div className="peer-data-[active=true]:bg-blue-500"/>
// 8. Named Group Focus
<div className="group/menu">
<button className="group-focus-within/menu:bg-blue-500"/>
</div>
// 9. Group Has Selectors
<div className="group/menu">
<div className="group-has-[[data-active=true]]/menu:bg-blue-500"/>
</div>
// 10. Variant Props
<button
data-variant={variant}
className="data-[variant=ghost]:border-blue-500"
/>
// 1 Když neobalíš objekt do reactive():
const filtersStorage = {
priceValue: computed({
get: () => selectedFilters.value.priceValue,
set: v => selectedFilters.value.priceValue = +v
})
}
// Pak filtersStorage.priceValue je přímo ComputedRef objekt. A ComputedRef se chová jako ref - musíš použít .value pro přístup k hodnotě:
// Bez reactive() wrapper
filtersStorage.priceValue.value // ✅ musíš použít .value
// 2
// Když obalíš objekt do reactive():
const filtersStorage = reactive({
priceValue: computed(...)
})
// S reactive() wrapper
filtersStorage.priceValue // ✅ .value se přidá automaticky
/*
Proč se to děje?
reactive() má speciální chování - automaticky unwrapuje refs na prvním levelu objektu
Tohle je záměrné chování pro lepší DX (developer experience)
Bez reactive() wrapper pracuješ přímo s ComputedRef objektem
*/