Skip to content

Commit d7f4178

Browse files
fix: hmr experience (#1708)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 96d8520 commit d7f4178

22 files changed

+316
-282
lines changed

demo/starter/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
"@slidev/theme-default": "^0.25.0",
1313
"@slidev/theme-seriph": "^0.25.0",
1414
"nodemon": "^3.1.4",
15-
"vue": "^3.4.31"
15+
"vue": "^3.4.32"
1616
}
1717
}

demo/vue-runner/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
"@slidev/cli": "workspace:*",
1111
"@slidev/theme-default": "^0.25.0",
1212
"@slidev/theme-seriph": "^0.25.0",
13-
"@vue/compiler-sfc": "^3.4.31",
13+
"@vue/compiler-sfc": "^3.4.32",
1414
"nodemon": "^3.1.4",
15-
"vue": "^3.4.31"
15+
"vue": "^3.4.32"
1616
}
1717
}

packages/client/composables/useClicks.ts

+14-17
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { clamp, sum } from '@antfu/utils'
22
import type { ClicksContext, NormalizedRangeClickValue, NormalizedSingleClickValue, RawAtValue, RawSingleAtValue, SlideRoute } from '@slidev/types'
33
import type { Ref } from 'vue'
4-
import { computed, ref, shallowReactive } from 'vue'
5-
import { routeForceRefresh } from '../logic/route'
4+
import { computed, onMounted, onUnmounted, ref, shallowReactive } from 'vue'
65

76
export function normalizeSingleAtValue(at: RawSingleAtValue): NormalizedSingleClickValue {
87
if (at === false || at === 'false')
@@ -54,17 +53,19 @@ export function createClicksContextBase(
5453
get isMounted() {
5554
return isMounted.value
5655
},
57-
onMounted: () => {
58-
isMounted.value = true
59-
// Convert maxMap to reactive
60-
maxMap = shallowReactive(maxMap)
61-
// Make sure the query is not greater than the total
62-
context.current = current.value
63-
},
64-
onUnmounted: () => {
65-
isMounted.value = false
66-
relativeSizeMap = new Map()
67-
maxMap = new Map()
56+
setup() {
57+
onMounted(() => {
58+
isMounted.value = true
59+
// Convert maxMap to reactive
60+
maxMap = shallowReactive(maxMap)
61+
// Make sure the query is not greater than the total
62+
context.current = current.value
63+
})
64+
onUnmounted(() => {
65+
isMounted.value = false
66+
relativeSizeMap = new Map()
67+
maxMap = new Map()
68+
})
6869
},
6970
calculateSince(rawAt, size = 1) {
7071
const at = normalizeSingleAtValue(rawAt)
@@ -144,13 +145,9 @@ export function createClicksContextBase(
144145
maxMap.delete(el)
145146
},
146147
get currentOffset() {
147-
// eslint-disable-next-line ts/no-unused-expressions
148-
routeForceRefresh.value
149148
return sum(...relativeSizeMap.values())
150149
},
151150
get total() {
152-
// eslint-disable-next-line ts/no-unused-expressions
153-
routeForceRefresh.value
154151
return clicksTotalOverrides
155152
?? (isMounted.value
156153
? Math.max(0, ...maxMap.values())

packages/client/env.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { computed, ref } from 'vue'
1+
import { computed } from 'vue'
22
import { objectMap } from '@antfu/utils'
33
import configs from '#slidev/configs'
44

55
export { configs }
66

77
export const mode = __DEV__ ? 'dev' : 'build'
88

9-
export const slideAspect = ref(configs.aspectRatio ?? (16 / 9))
10-
export const slideWidth = ref(configs.canvasWidth ?? 980)
9+
export const slideAspect = computed(() => configs.aspectRatio)
10+
export const slideWidth = computed(() => configs.canvasWidth)
1111

1212
// To honor the aspect ratio more as possible, we need to approximate the height to the next integer.
1313
// Doing this, we will prevent on print, to create an additional empty white page after each page.

packages/client/internals/PrintSlideClick.vue

-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ provideLocal(injectionSlidevContext, reactive({
3939
<GlobalBottom />
4040

4141
<SlideWrapper
42-
:is="route.component!"
4342
:clicks-context="nav.clicksContext.value"
4443
:class="getSlideClass(route)"
4544
:route="route"

packages/client/internals/QuickOverview.vue

+1-7
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,6 @@ watchEffect(() => {
9898
// Watch rowCount, make sure up and down shortcut work correctly.
9999
overviewRowCount.value = rowCount.value
100100
})
101-
102-
const activeSlidesLoaded = ref(false)
103-
setTimeout(() => {
104-
activeSlidesLoaded.value = true
105-
}, 3000)
106101
</script>
107102

108103
<template>
@@ -113,8 +108,7 @@ setTimeout(() => {
113108
leave-to-class="opacity-0 scale-102 !backdrop-blur-0px"
114109
>
115110
<div
116-
v-if="showOverview || activeSlidesLoaded"
117-
v-show="showOverview"
111+
v-if="showOverview"
118112
class="fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)] z-20 bg-main !bg-opacity-75 p-16 py-20 overflow-y-auto backdrop-blur-5px"
119113
@click="close"
120114
>

packages/client/internals/SlideWrapper.vue

+2-16
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
<script setup lang="ts">
2-
import { computed, defineAsyncComponent, defineComponent, h, ref, toRef } from 'vue'
2+
import { computed, ref, toRef } from 'vue'
33
import type { CSSProperties, PropType } from 'vue'
44
import { provideLocal } from '@vueuse/core'
55
import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'
66
import { injectionClicksContext, injectionCurrentPage, injectionFrontmatter, injectionRenderContext, injectionRoute, injectionSlideZoom } from '../constants'
77
import { getSlideClass } from '../utils'
88
import { configs } from '../env'
9-
import SlideLoading from './SlideLoading.vue'
109
import { SlideBottom, SlideTop } from '#slidev/global-layers'
1110
1211
const props = defineProps({
@@ -47,19 +46,6 @@ const style = computed<CSSProperties>(() => ({
4746
...zoomStyle.value,
4847
'user-select': configs.selectable ? undefined : 'none',
4948
}))
50-
51-
const SlideComponent = computed(() => props.route && defineAsyncComponent({
52-
loader: async () => {
53-
const component = await props.route.component()
54-
return defineComponent({
55-
mounted: props.clicksContext?.onMounted,
56-
unmounted: props.clicksContext?.onUnmounted,
57-
render: () => h(component.default),
58-
})
59-
},
60-
delay: 300,
61-
loadingComponent: SlideLoading,
62-
}))
6349
</script>
6450

6551
<template>
@@ -69,7 +55,7 @@ const SlideComponent = computed(() => props.route && defineAsyncComponent({
6955
:style="style"
7056
>
7157
<SlideBottom />
72-
<SlideComponent />
58+
<component :is="props.route.component" />
7359
<SlideTop />
7460
</div>
7561
</template>

packages/client/internals/SlidesShow.vue

+32-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
2-
import { TransitionGroup, computed, shallowRef, watch } from 'vue'
2+
import { TransitionGroup, computed, shallowRef, watchEffect } from 'vue'
33
import { recomputeAllPoppers } from 'floating-vue'
4+
import type { SlideRoute } from '@slidev/types'
45
import { useNav } from '../composables/useNav'
56
import { useViewTransition } from '../composables/useViewTransition'
67
import { skipTransition } from '../logic/hmr'
@@ -19,20 +20,34 @@ const {
1920
currentSlideRoute,
2021
currentTransition,
2122
getPrimaryClicks,
23+
prevRoute,
2224
nextRoute,
2325
slides,
2426
isPrintMode,
2527
isPrintWithClicks,
2628
clicksDirection,
2729
} = useNav()
2830
29-
// preload next route
30-
watch(currentSlideRoute, () => {
31-
if (currentSlideRoute.value?.meta && currentSlideRoute.value.meta.preload !== false)
32-
currentSlideRoute.value.meta.__preloaded = true
33-
if (nextRoute.value?.meta && nextRoute.value.meta.preload !== false)
34-
nextRoute.value.meta.__preloaded = true
35-
}, { immediate: true })
31+
function preloadRoute(route: SlideRoute) {
32+
if (route.meta.preload !== false) {
33+
route.meta.__preloaded = true
34+
route.load()
35+
}
36+
}
37+
// preload current, prev and next slides
38+
watchEffect(() => {
39+
preloadRoute(currentSlideRoute.value)
40+
preloadRoute(prevRoute.value)
41+
preloadRoute(nextRoute.value)
42+
})
43+
// preload all slides after 3s
44+
watchEffect((onCleanup) => {
45+
const routes = slides.value
46+
const timeout = setTimeout(() => {
47+
routes.forEach(preloadRoute)
48+
}, 3000)
49+
onCleanup(() => clearTimeout(timeout))
50+
})
3651
3752
const hasViewTransition = useViewTransition()
3853
@@ -67,14 +82,15 @@ function onAfterLeave() {
6782
}"
6883
@after-leave="onAfterLeave"
6984
>
70-
<SlideWrapper
71-
v-for="route of loadedRoutes"
72-
v-show="route === currentSlideRoute"
73-
:key="route.no"
74-
:clicks-context="isPrintMode && !isPrintWithClicks ? createFixedClicks(route, CLICKS_MAX) : getPrimaryClicks(route)"
75-
:route="route"
76-
:render-context="renderContext"
77-
/>
85+
<template v-for="route of loadedRoutes" :key="route.no">
86+
<SlideWrapper
87+
v-if="Math.abs(route.no - currentSlideRoute.no) <= 20"
88+
v-show="route === currentSlideRoute"
89+
:clicks-context="isPrintMode && !isPrintWithClicks ? createFixedClicks(route, CLICKS_MAX) : getPrimaryClicks(route)"
90+
:route="route"
91+
:render-context="renderContext"
92+
/>
93+
</template>
7894
</component>
7995

8096
<DragControl v-if="activeDragElement" :data="activeDragElement" />

packages/client/logic/route.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { WritableComputedRef } from 'vue'
2-
import { computed, nextTick, ref, unref } from 'vue'
2+
import { computed, nextTick, unref } from 'vue'
33
import { useRouter } from 'vue-router'
44

55
export function useRouteQuery<T extends string | string[]>(
@@ -35,6 +35,3 @@ export function useRouteQuery<T extends string | string[]>(
3535
},
3636
})
3737
}
38-
39-
// force update collected elements when the route is fully resolved
40-
export const routeForceRefresh = ref(0)

packages/client/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"shiki-magic-move": "^0.4.2",
5858
"typescript": "^5.5.3",
5959
"unocss": "^0.61.3",
60-
"vue": "^3.4.31",
60+
"vue": "^3.4.32",
6161
"vue-router": "^4.4.0",
6262
"yaml": "^2.4.5"
6363
},

packages/client/pages/play.vue

+15-12
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,21 @@ registerShortcuts()
3737
if (__SLIDEV_FEATURE_WAKE_LOCK__)
3838
useWakeLock()
3939
40-
useStyleTag(computed(() => `
41-
vite-error-overlay {
42-
--width: calc(100vw - ${isEditorVertical.value ? 0 : editorWidth.value}px);
43-
--height: calc(100vh - ${isEditorVertical.value ? editorHeight.value : 0}px);
44-
position: fixed;
45-
left: 0;
46-
top: 0;
47-
width: calc(var(--width) / var(--slidev-slide-scale));
48-
height: calc(var(--height) / var(--slidev-slide-scale));
49-
transform-origin: top left;
50-
transform: scale(var(--slidev-slide-scale));
51-
}`))
40+
if (import.meta.hot) {
41+
useStyleTag(computed(() => `
42+
vite-error-overlay {
43+
--width: calc(100vw - ${isEditorVertical.value ? 0 : editorWidth.value}px);
44+
--height: calc(100vh - ${isEditorVertical.value ? editorHeight.value : 0}px);
45+
position: fixed;
46+
left: 0;
47+
top: 0;
48+
width: calc(var(--width) / var(--slidev-slide-scale));
49+
height: calc(var(--height) / var(--slidev-slide-scale));
50+
transform-origin: top left;
51+
transform: scale(var(--slidev-slide-scale));
52+
}`,
53+
))
54+
}
5255
5356
const persistNav = computed(() => isScreenVertical.value || showEditor.value)
5457

packages/client/pages/presenter.vue

+6-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const nextFrame = computed(() => {
5656
if (clicksContext.value.current < clicksContext.value.total)
5757
return [currentSlideRoute.value!, clicksContext.value.current + 1] as const
5858
else if (hasNext.value)
59-
return [nextRoute.value!, 0] as const
59+
return [nextRoute.value, 0] as const
6060
else
6161
return null
6262
})
@@ -135,6 +135,11 @@ onMounted(() => {
135135
render-context="previewNext"
136136
/>
137137
</SlideContainer>
138+
<div v-else class="h-full flex justify-center items-center">
139+
<div class="text-gray-500">
140+
End of the presentation
141+
</div>
142+
</div>
138143
<div class="absolute left-0 top-0 bg-main border-b border-r border-main px2 py1 op50 text-sm">
139144
Next
140145
</div>

packages/client/setup/main.ts

-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import type { AppContext } from '@slidev/types'
22
import TwoSlashFloatingVue from '@shikijs/vitepress-twoslash/client'
33
import type { App } from 'vue'
4-
import { nextTick } from 'vue'
54
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
65
import { createHead } from '@unhead/vue'
7-
import { routeForceRefresh } from '../logic/route'
86
import { createVClickDirectives } from '../modules/v-click'
97
import { createVMarkDirective } from '../modules/v-mark'
108
import { createVDragDirective } from '../modules/v-drag'
@@ -43,13 +41,6 @@ export default async function setupMain(app: App) {
4341
router,
4442
}
4543

46-
nextTick(() => {
47-
router.afterEach(async () => {
48-
await nextTick()
49-
routeForceRefresh.value += 1
50-
})
51-
})
52-
5344
for (const setup of setups)
5445
await setup(context)
5546
}

packages/create-app/template/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
"@slidev/cli": "^0.49.16",
1212
"@slidev/theme-default": "latest",
1313
"@slidev/theme-seriph": "latest",
14-
"vue": "^3.4.31"
14+
"vue": "^3.4.32"
1515
}
1616
}

0 commit comments

Comments
 (0)