From 642621f44c4b3995f1d1db2017860409cfd91eac Mon Sep 17 00:00:00 2001 From: "Grigorii K. Shartsev" Date: Thu, 19 Jun 2025 17:04:50 +0200 Subject: [PATCH 1/2] chore(frontend): add useAsyncInit helper Signed-off-by: Grigorii K. Shartsev --- src/composables/useAsyncInit.ts | 70 +++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/composables/useAsyncInit.ts diff --git a/src/composables/useAsyncInit.ts b/src/composables/useAsyncInit.ts new file mode 100644 index 0000000000..3e77d58aa1 --- /dev/null +++ b/src/composables/useAsyncInit.ts @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { ShallowRef } from 'vue' + +import { shallowReadonly, shallowRef } from 'vue' + +type ResultContainer = { + result: Readonly> + isReady: ShallowRef + isLoading: ShallowRef + init: () => Promise +} + +/** + * Cache modules by loader to immediately return resolved module. + * Note: the loader itself is safe to be called multiple times. It is resolved by ESM/Bundler. + * But with a promise it requires at least one tick to resolve. + */ +const cache = new Map<() => Promise, ResultContainer>() + +/** + * Use lazy/async initialization. + * Allows to use something requiring asynchronous load/initialization with reactivity, such as dynamically imported modules or heavy initialized modules. + * Make sure that the same initiator function reference is used for the same initialization. + * @param initiator - Initialization function + * @param immediate - Whether to call the initiator immediately + */ +export function useAsyncInit(initiator: () => Promise, immediate: boolean = false): ResultContainer { + if (cache.has(initiator)) { + return cache.get(initiator) as ResultContainer + } + + // Use shallowRef to avoid unexpected deep reactivity for the result + const result = shallowRef(undefined) + const isReady = shallowRef(false) + const isLoading = shallowRef(false) + + /** + * Initialize + */ + async function init() { + // Avoid multiple initializations + if (isReady.value || isLoading.value) { + return + } + + isLoading.value = true + result.value = await initiator() + isLoading.value = false + isReady.value = true + } + + const resultContainer: ResultContainer = { + result: shallowReadonly(result), + isReady: shallowReadonly(isReady), + isLoading: shallowReadonly(isLoading), + init, + } + + cache.set(initiator, resultContainer) + + if (immediate) { + init() + } + + return resultContainer +} From 5e9e68a72ad6a2efb5b2d3c317ebecc397f7919c Mon Sep 17 00:00:00 2001 From: "Grigorii K. Shartsev" Date: Thu, 19 Jun 2025 17:10:19 +0200 Subject: [PATCH 2/2] perf(frontend): lazy load libphonenumber Signed-off-by: Grigorii K. Shartsev --- src/components/SelectPhoneNumber.vue | 16 ++++++++++---- src/composables/useLibphonenumber.ts | 33 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 src/composables/useLibphonenumber.ts diff --git a/src/components/SelectPhoneNumber.vue b/src/components/SelectPhoneNumber.vue index 79646672d6..9f4eeac8e5 100644 --- a/src/components/SelectPhoneNumber.vue +++ b/src/components/SelectPhoneNumber.vue @@ -22,11 +22,11 @@