From 22012d17ba9ee2f419cf64dd607ac4cbe4f7bebf Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Mon, 24 Jun 2024 13:05:57 +0200 Subject: [PATCH] perf: make routes lazy Makes all routes lazy so their components don't get loaded with the initial load, but when navigating to the routes. --- packages/web-app-admin-settings/src/index.ts | 13 ++--- packages/web-app-draw-io/src/index.ts | 21 +++++--- packages/web-app-epub-reader/src/index.ts | 6 +-- packages/web-app-external/src/index.ts | 11 +++-- packages/web-app-files/src/index.ts | 48 ++++++++++--------- packages/web-app-ocm/src/index.ts | 10 ++-- packages/web-app-pdf-viewer/src/index.ts | 16 ++++--- packages/web-app-preview/src/App.vue | 2 +- packages/web-app-preview/src/index.ts | 20 ++++---- packages/web-app-search/src/index.ts | 30 +++++------- packages/web-app-text-editor/src/index.ts | 9 ++-- packages/web-app-webfinger/src/index.ts | 4 +- .../ComponentLoader/ComponentLoader.ts | 12 +++++ .../ComponentLoaderWrapper.vue | 38 +++++++++++++++ .../src/components/ComponentLoader/index.ts | 2 + packages/web-pkg/src/components/index.ts | 1 + packages/web-runtime/src/router/index.ts | 30 ++++++------ 17 files changed, 171 insertions(+), 102 deletions(-) create mode 100644 packages/web-pkg/src/components/ComponentLoader/ComponentLoader.ts create mode 100644 packages/web-pkg/src/components/ComponentLoader/ComponentLoaderWrapper.vue create mode 100644 packages/web-pkg/src/components/ComponentLoader/index.ts diff --git a/packages/web-app-admin-settings/src/index.ts b/packages/web-app-admin-settings/src/index.ts index 5dd5e8e3b57..d357f5d3be7 100644 --- a/packages/web-app-admin-settings/src/index.ts +++ b/packages/web-app-admin-settings/src/index.ts @@ -1,11 +1,8 @@ import translations from '../l10n/translations.json' -import General from './views/General.vue' -import Users from './views/Users.vue' -import Groups from './views/Groups.vue' -import Spaces from './views/Spaces.vue' import { Ability } from '@ownclouders/web-client' import { AppNavigationItem, + ComponentLoader, defineWebApplication, useAbility, useUserStore @@ -41,7 +38,7 @@ export const routes = ({ $ability }: { $ability: Ability }): RouteRecordRaw[] => { path: '/general', name: 'admin-settings-general', - component: General, + component: ComponentLoader(async () => (await import('./views/General.vue')).default), beforeEnter: (to, from, next) => { if (!$ability.can('read-all', 'Setting')) { next({ path: '/' }) @@ -56,7 +53,7 @@ export const routes = ({ $ability }: { $ability: Ability }): RouteRecordRaw[] => { path: '/users', name: 'admin-settings-users', - component: Users, + component: ComponentLoader(async () => (await import('./views/Users.vue')).default), beforeEnter: (to, from, next) => { if (!$ability.can('read-all', 'Account')) { next({ path: '/' }) @@ -71,7 +68,7 @@ export const routes = ({ $ability }: { $ability: Ability }): RouteRecordRaw[] => { path: '/groups', name: 'admin-settings-groups', - component: Groups, + component: ComponentLoader(async () => (await import('./views/Groups.vue')).default), beforeEnter: (to, from, next) => { if (!$ability.can('read-all', 'Group')) { next({ path: '/' }) @@ -86,7 +83,7 @@ export const routes = ({ $ability }: { $ability: Ability }): RouteRecordRaw[] => { path: '/spaces', name: 'admin-settings-spaces', - component: Spaces, + component: ComponentLoader(async () => (await import('./views/Spaces.vue')).default), beforeEnter: (to, from, next) => { if (!$ability.can('read-all', 'Drive')) { next({ path: '/' }) diff --git a/packages/web-app-draw-io/src/index.ts b/packages/web-app-draw-io/src/index.ts index d7b92e857d4..86e06688633 100644 --- a/packages/web-app-draw-io/src/index.ts +++ b/packages/web-app-draw-io/src/index.ts @@ -1,7 +1,11 @@ import { Resource } from '@ownclouders/web-client' -import { AppWrapperRoute, defineWebApplication, useUserStore } from '@ownclouders/web-pkg' +import { + AppWrapperRoute, + ComponentLoader, + defineWebApplication, + useUserStore +} from '@ownclouders/web-pkg' import translations from '../l10n/translations.json' -import App from './App.vue' import { useGettext } from 'vue3-gettext' const applicationId = 'draw-io' @@ -15,11 +19,14 @@ export default defineWebApplication({ { name: 'draw-io', path: '/:driveAliasAndItem(.*)?', - component: AppWrapperRoute(App, { - applicationId, - importResourceWithExtension(resource: Resource) { - return resource.extension === 'vsdx' ? 'drawio' : null - } + component: ComponentLoader(async () => { + const App = (await import('./App.vue')).default + return AppWrapperRoute(App, { + applicationId, + importResourceWithExtension(resource: Resource) { + return resource.extension === 'vsdx' ? 'drawio' : null + } + }) }), meta: { authContext: 'hybrid', diff --git a/packages/web-app-epub-reader/src/index.ts b/packages/web-app-epub-reader/src/index.ts index a9db472ba3f..bec37eff6ec 100644 --- a/packages/web-app-epub-reader/src/index.ts +++ b/packages/web-app-epub-reader/src/index.ts @@ -1,6 +1,6 @@ import { useGettext } from 'vue3-gettext' import translations from '../l10n/translations.json' -import { AppWrapperRoute, defineWebApplication } from '@ownclouders/web-pkg' +import { AppWrapperRoute, ComponentLoader, defineWebApplication } from '@ownclouders/web-pkg' export default defineWebApplication({ setup() { @@ -11,7 +11,7 @@ export default defineWebApplication({ const routes = [ { path: '/:driveAliasAndItem(.*)?', - component: async () => { + component: ComponentLoader(async () => { // lazy loading to avoid loading the epubjs package on page load const EpubReader = (await import('./App.vue')).default return AppWrapperRoute(EpubReader, { @@ -20,7 +20,7 @@ export default defineWebApplication({ responseType: 'blob' } }) - }, + }), name: 'epub-reader', meta: { authContext: 'hybrid', diff --git a/packages/web-app-external/src/index.ts b/packages/web-app-external/src/index.ts index d1b7a958ff3..fc8fd4556d3 100644 --- a/packages/web-app-external/src/index.ts +++ b/packages/web-app-external/src/index.ts @@ -4,10 +4,10 @@ import { useCapabilityStore, useAppsStore, useClientService, - useRequest + useRequest, + ComponentLoader } from '@ownclouders/web-pkg' import translations from '../l10n/translations.json' -import App from './App.vue' import { stringify } from 'qs' import { Resource, SpaceResource } from '@ownclouders/web-client' import { join } from 'path' @@ -23,8 +23,11 @@ const routes = [ { name: 'apps', path: '/:driveAliasAndItem(.*)?', - component: AppWrapperRoute(App, { - applicationId: appInfo.id + component: ComponentLoader(async () => { + const App = (await import('./App.vue')).default + return AppWrapperRoute(App, { + applicationId: appInfo.id + }) }), meta: { authContext: 'hybrid', diff --git a/packages/web-app-files/src/index.ts b/packages/web-app-files/src/index.ts index 2a7e8508f20..20e0995ca95 100644 --- a/packages/web-app-files/src/index.ts +++ b/packages/web-app-files/src/index.ts @@ -1,15 +1,7 @@ -import App from './App.vue' -import Favorites from './views/Favorites.vue' -import FilesDrop from './views/FilesDrop.vue' -import SharedWithMe from './views/shares/SharedWithMe.vue' -import SharedWithOthers from './views/shares/SharedWithOthers.vue' -import SharedViaLink from './views/shares/SharedViaLink.vue' -import SpaceDriveResolver from './views/spaces/DriveResolver.vue' -import SpaceProjects from './views/spaces/Projects.vue' -import TrashOverview from './views/trash/Overview.vue' import translations from '../l10n/translations.json' import { ApplicationInformation, + ComponentLoader, defineWebApplication, useCapabilityStore, useSpacesStore, @@ -18,9 +10,6 @@ import { import { extensions } from './extensions' import { buildRoutes } from '@ownclouders/web-pkg' import { AppNavigationItem } from '@ownclouders/web-pkg' - -// dirty: importing view from other extension within project -import SearchResults from '../../web-app-search/src/views/List.vue' import { isPersonalSpaceResource, isShareSpaceResource } from '@ownclouders/web-client' import { ComponentCustomProperties } from 'vue' import { extensionPoints } from './extensionPoints' @@ -140,21 +129,36 @@ export default defineWebApplication({ } }, routes: buildRoutes({ - App, - Favorites, - FilesDrop, - SearchResults, + App: ComponentLoader(async () => (await import('./App.vue')).default), + Favorites: ComponentLoader(async () => (await import('./views/Favorites.vue')).default), + FilesDrop: ComponentLoader(async () => (await import('./views/FilesDrop.vue')).default), + SearchResults: ComponentLoader( + // FIXME: import from another app + async () => (await import('../../web-app-search/src/views/List.vue')).default + ), Shares: { - SharedViaLink, - SharedWithMe, - SharedWithOthers + SharedViaLink: ComponentLoader( + async () => (await import('./views/shares/SharedViaLink.vue')).default + ), + SharedWithMe: ComponentLoader( + async () => (await import('./views/shares/SharedWithMe.vue')).default + ), + SharedWithOthers: ComponentLoader( + async () => (await import('./views/shares/SharedWithOthers.vue')).default + ) }, Spaces: { - DriveResolver: SpaceDriveResolver, - Projects: SpaceProjects + DriveResolver: ComponentLoader( + async () => (await import('./views/spaces/DriveResolver.vue')).default + ), + Projects: ComponentLoader( + async () => (await import('./views/spaces/Projects.vue')).default + ) }, Trash: { - Overview: TrashOverview + Overview: ComponentLoader( + async () => (await import('./views/trash/Overview.vue')).default + ) } }), navItems, diff --git a/packages/web-app-ocm/src/index.ts b/packages/web-app-ocm/src/index.ts index 16629c083e3..726521ec7b1 100644 --- a/packages/web-app-ocm/src/index.ts +++ b/packages/web-app-ocm/src/index.ts @@ -1,5 +1,9 @@ -import App from './views/App.vue' -import { defineWebApplication, useRouter, useUserStore } from '@ownclouders/web-pkg' +import { + ComponentLoader, + defineWebApplication, + useRouter, + useUserStore +} from '@ownclouders/web-pkg' import translations from '../l10n/translations.json' import { extensions } from './extensions' import { RouteRecordRaw } from 'vue-router' @@ -15,7 +19,7 @@ const routes: RouteRecordRaw[] = [ { path: '/invitations', name: 'ocm-app-invitations', - component: App, + component: ComponentLoader(async () => (await import('./views/App.vue')).default), meta: { patchCleanPath: true, title: 'Invitations' diff --git a/packages/web-app-pdf-viewer/src/index.ts b/packages/web-app-pdf-viewer/src/index.ts index 7743823cfb6..a21bb809f18 100644 --- a/packages/web-app-pdf-viewer/src/index.ts +++ b/packages/web-app-pdf-viewer/src/index.ts @@ -1,6 +1,5 @@ import translations from '../l10n/translations.json' -import { AppWrapperRoute } from '@ownclouders/web-pkg' -import PdfViewer from './App.vue' +import { AppWrapperRoute, ComponentLoader } from '@ownclouders/web-pkg' // just a dummy function to trick gettext tools function $gettext(msg: string) { @@ -10,11 +9,14 @@ function $gettext(msg: string) { const routes = [ { path: '/:driveAliasAndItem(.*)?', - component: AppWrapperRoute(PdfViewer, { - applicationId: 'pdf-viewer', - urlForResourceOptions: { - disposition: 'inline' - } + component: ComponentLoader(async () => { + const PdfViewer = (await import('./App.vue')).default + return AppWrapperRoute(PdfViewer, { + applicationId: 'pdf-viewer', + urlForResourceOptions: { + disposition: 'inline' + } + }) }), name: 'pdf-viewer', meta: { diff --git a/packages/web-app-preview/src/App.vue b/packages/web-app-preview/src/App.vue index 450e7cf541f..2f06889b88f 100644 --- a/packages/web-app-preview/src/App.vue +++ b/packages/web-app-preview/src/App.vue @@ -98,7 +98,7 @@ import { CachedFile } from './helpers/types' import { getMimeTypes } from './mimeTypes' import { PanzoomEventDetail } from '@panzoom/panzoom' -export const appId = 'preview' +const appId = 'preview' export default defineComponent({ name: 'Preview', diff --git a/packages/web-app-preview/src/index.ts b/packages/web-app-preview/src/index.ts index a2bf4bca8c9..722c5e047ae 100644 --- a/packages/web-app-preview/src/index.ts +++ b/packages/web-app-preview/src/index.ts @@ -1,23 +1,25 @@ -import { AppWrapperRoute, defineWebApplication } from '@ownclouders/web-pkg' +import { AppWrapperRoute, ComponentLoader, defineWebApplication } from '@ownclouders/web-pkg' import translations from '../l10n/translations.json' -import * as app from './App.vue' import { useGettext } from 'vue3-gettext' import { getMimeTypes } from './mimeTypes' -const { default: App, appId } = app - export default defineWebApplication({ setup({ applicationConfig }) { const { $gettext } = useGettext() + const appId = 'preview' + const routes = [ { path: '/:driveAliasAndItem(.*)?', - component: AppWrapperRoute(App, { - applicationId: appId, - urlForResourceOptions: { - disposition: 'inline' - } + component: ComponentLoader(async () => { + const App = (await import('./App.vue')).default + return AppWrapperRoute(App, { + applicationId: appId, + urlForResourceOptions: { + disposition: 'inline' + } + }) }), name: 'media', meta: { diff --git a/packages/web-app-search/src/index.ts b/packages/web-app-search/src/index.ts index c210a775f63..dd2bc93011d 100644 --- a/packages/web-app-search/src/index.ts +++ b/packages/web-app-search/src/index.ts @@ -1,38 +1,34 @@ -import App from './App.vue' -import List from './views/List.vue' // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import translations from '../l10n/translations.json' -import { ApplicationInformation, defineWebApplication } from '@ownclouders/web-pkg' +import { ApplicationInformation, ComponentLoader, defineWebApplication } from '@ownclouders/web-pkg' import { extensions } from './extensions' import { extensionPoints } from './extensionPoints' - -// just a dummy function to trick gettext tools -const $gettext = (msg: string) => { - return msg -} - -const appInfo: ApplicationInformation = { - name: $gettext('Search'), - id: 'search', - icon: 'folder', - isFileEditor: false -} +import { useGettext } from 'vue3-gettext' export default defineWebApplication({ setup() { + const { $gettext } = useGettext() + + const appInfo: ApplicationInformation = { + name: $gettext('Search'), + id: 'search', + icon: 'folder', + isFileEditor: false + } + return { appInfo, routes: [ { name: 'search', path: '/', - component: App, + component: ComponentLoader(async () => (await import('./App.vue')).default), children: [ { name: 'provider-list', path: 'list/:page?', - component: List, + component: ComponentLoader(async () => (await import('./views/List.vue')).default), meta: { authContext: 'user', contextQueryItems: ['term', 'provider'] diff --git a/packages/web-app-text-editor/src/index.ts b/packages/web-app-text-editor/src/index.ts index 659b6f8977e..1a4f97b2b75 100644 --- a/packages/web-app-text-editor/src/index.ts +++ b/packages/web-app-text-editor/src/index.ts @@ -1,9 +1,9 @@ import { useGettext } from 'vue3-gettext' import translations from '../l10n/translations.json' -import TextEditor from './App.vue' import { AppWrapperRoute, ApplicationFileExtension, + ComponentLoader, defineWebApplication, useUserStore } from '@ownclouders/web-pkg' @@ -81,8 +81,11 @@ export default defineWebApplication({ const routes = [ { path: '/:driveAliasAndItem(.*)?', - component: AppWrapperRoute(TextEditor, { - applicationId: appId + component: ComponentLoader(async () => { + const TextEditor = (await import('./App.vue')).default + return AppWrapperRoute(TextEditor, { + applicationId: appId + }) }), name: 'text-editor', meta: { diff --git a/packages/web-app-webfinger/src/index.ts b/packages/web-app-webfinger/src/index.ts index 54daa5dc62f..eefb383fbc3 100644 --- a/packages/web-app-webfinger/src/index.ts +++ b/packages/web-app-webfinger/src/index.ts @@ -1,5 +1,5 @@ +import { ComponentLoader } from '@ownclouders/web-pkg' import translations from '../l10n/translations.json' -import Resolve from './views/Resolve.vue' // just a dummy function to trick gettext tools function $gettext(msg: string) { @@ -27,7 +27,7 @@ const routes = () => [ { path: '/resolve', name: 'webfinger-resolve', - component: Resolve, + component: ComponentLoader(async () => (await import('./views/Resolve.vue')).default), meta: { authContext: 'idp', title: $gettext('Resolve destination'), diff --git a/packages/web-pkg/src/components/ComponentLoader/ComponentLoader.ts b/packages/web-pkg/src/components/ComponentLoader/ComponentLoader.ts new file mode 100644 index 00000000000..41aac250433 --- /dev/null +++ b/packages/web-pkg/src/components/ComponentLoader/ComponentLoader.ts @@ -0,0 +1,12 @@ +import { Component, defineComponent, h } from 'vue' +import ComponentLoaderWrapper from './ComponentLoaderWrapper.vue' + +export type LoadComponent = () => Promise + +export function ComponentLoader(loadComponent: LoadComponent) { + return defineComponent({ + render() { + return h(ComponentLoaderWrapper, { loadComponent }) + } + }) +} diff --git a/packages/web-pkg/src/components/ComponentLoader/ComponentLoaderWrapper.vue b/packages/web-pkg/src/components/ComponentLoader/ComponentLoaderWrapper.vue new file mode 100644 index 00000000000..a4758498098 --- /dev/null +++ b/packages/web-pkg/src/components/ComponentLoader/ComponentLoaderWrapper.vue @@ -0,0 +1,38 @@ + + + diff --git a/packages/web-pkg/src/components/ComponentLoader/index.ts b/packages/web-pkg/src/components/ComponentLoader/index.ts new file mode 100644 index 00000000000..08011a00824 --- /dev/null +++ b/packages/web-pkg/src/components/ComponentLoader/index.ts @@ -0,0 +1,2 @@ +export * from './ComponentLoader' +export { default as ComponentLoaderWrapper } from './ComponentLoaderWrapper.vue' diff --git a/packages/web-pkg/src/components/index.ts b/packages/web-pkg/src/components/index.ts index 6c9b791e93d..55a6191f826 100644 --- a/packages/web-pkg/src/components/index.ts +++ b/packages/web-pkg/src/components/index.ts @@ -6,6 +6,7 @@ export * from './Filters' export * from './Modals' export * from './SideBar' export * from './Search' +export * from './ComponentLoader' export * from './Spaces' export * from './TextEditor' diff --git a/packages/web-runtime/src/router/index.ts b/packages/web-runtime/src/router/index.ts index 310636a3ed8..1e0fadb0408 100644 --- a/packages/web-runtime/src/router/index.ts +++ b/packages/web-runtime/src/router/index.ts @@ -1,10 +1,4 @@ -import AccessDeniedPage from '../pages/accessDenied.vue' -import Account from '../pages/account.vue' -import LoginPage from '../pages/login.vue' -import LogoutPage from '../pages/logout.vue' import OidcCallbackPage from '../pages/oidcCallback.vue' -import ResolvePublicLinkPage from '../pages/resolvePublicLink.vue' -import ResolvePrivateLinkPage from '../pages/resolvePrivateLink.vue' import { setupAuthGuard } from './setupAuthGuard' import { patchRouter } from './patchCleanPath' import { @@ -13,10 +7,8 @@ import { createRouter, RouteLocationNormalizedLoaded } from 'vue-router' - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore import qs from 'qs' +import { ComponentLoader } from '@ownclouders/web-pkg' export * from './helpers' export { createRouter } from 'vue-router' @@ -31,13 +23,13 @@ const routes = [ { path: '/login', name: 'login', - component: LoginPage, + component: ComponentLoader(async () => (await import('../pages/login.vue')).default), meta: { title: $gettext('Login'), authContext: 'anonymous' } }, { path: '/logout', name: 'logout', - component: LogoutPage, + component: ComponentLoader(async () => (await import('../pages/logout.vue')).default), meta: { title: $gettext('Logout'), authContext: 'anonymous' } }, { @@ -55,31 +47,37 @@ const routes = [ { path: '/f/:fileId', name: 'resolvePrivateLink', - component: ResolvePrivateLinkPage, + component: ComponentLoader( + async () => (await import('../pages/resolvePrivateLink.vue')).default + ), meta: { title: $gettext('Private link'), authContext: 'user' } }, { path: '/s/:token/:driveAliasAndItem(.*)?', name: 'resolvePublicLink', - component: ResolvePublicLinkPage, + component: ComponentLoader( + async () => (await import('../pages/resolvePublicLink.vue')).default + ), meta: { title: $gettext('Public link'), authContext: 'anonymous' } }, { path: '/o/:token/:driveAliasAndItem(.*)?', name: 'resolvePublicOcmLink', - component: ResolvePublicLinkPage, + component: ComponentLoader( + async () => (await import('../pages/resolvePublicLink.vue')).default + ), meta: { title: $gettext('OCM link'), authContext: 'anonymous' } }, { path: '/access-denied', name: 'accessDenied', - component: AccessDeniedPage, + component: ComponentLoader(async () => (await import('../pages/accessDenied.vue')).default), meta: { title: $gettext('Access denied'), authContext: 'anonymous' } }, { path: '/account', name: 'account', - component: Account, + component: ComponentLoader(async () => (await import('../pages/account.vue')).default), meta: { title: $gettext('Account'), authContext: 'hybrid' } } ]