Quellcode durchsuchen

perf: optimize bootstrap modules to speed up first-screen loading (#5899)

优化首屏加载速度
Netfan vor 5 Monaten
Ursprung
Commit
329a176a5c

+ 54 - 25
apps/web-antd/src/adapter/component/index.ts

@@ -8,35 +8,64 @@ import type { Component } from 'vue';
 import type { BaseFormComponentType } from '@vben/common-ui';
 import type { Recordable } from '@vben/types';
 
-import { defineComponent, getCurrentInstance, h, ref } from 'vue';
+import {
+  defineAsyncComponent,
+  defineComponent,
+  getCurrentInstance,
+  h,
+  ref,
+} from 'vue';
 
 import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
 import { $t } from '@vben/locales';
 
-import {
-  AutoComplete,
-  Button,
-  Checkbox,
-  CheckboxGroup,
-  DatePicker,
-  Divider,
-  Input,
-  InputNumber,
-  InputPassword,
-  Mentions,
-  notification,
-  Radio,
-  RadioGroup,
-  RangePicker,
-  Rate,
-  Select,
-  Space,
-  Switch,
-  Textarea,
-  TimePicker,
-  TreeSelect,
-  Upload,
-} from 'ant-design-vue';
+import { notification } from 'ant-design-vue';
+
+const AutoComplete = defineAsyncComponent(
+  () => import('ant-design-vue/es/auto-complete'),
+);
+const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
+const Checkbox = defineAsyncComponent(
+  () => import('ant-design-vue/es/checkbox'),
+);
+const CheckboxGroup = defineAsyncComponent(() =>
+  import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
+);
+const DatePicker = defineAsyncComponent(
+  () => import('ant-design-vue/es/date-picker'),
+);
+const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
+const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
+const InputNumber = defineAsyncComponent(
+  () => import('ant-design-vue/es/input-number'),
+);
+const InputPassword = defineAsyncComponent(() =>
+  import('ant-design-vue/es/input').then((res) => res.InputPassword),
+);
+const Mentions = defineAsyncComponent(
+  () => import('ant-design-vue/es/mentions'),
+);
+const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
+const RadioGroup = defineAsyncComponent(() =>
+  import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
+);
+const RangePicker = defineAsyncComponent(() =>
+  import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
+);
+const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
+const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
+const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
+const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
+const Textarea = defineAsyncComponent(() =>
+  import('ant-design-vue/es/input').then((res) => res.Textarea),
+);
+const TimePicker = defineAsyncComponent(
+  () => import('ant-design-vue/es/time-picker'),
+);
+const TreeSelect = defineAsyncComponent(
+  () => import('ant-design-vue/es/tree-select'),
+);
+const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
 
 const withDefaultPlaceholder = <T extends Component>(
   component: T,

+ 3 - 2
apps/web-antd/src/bootstrap.ts

@@ -1,8 +1,7 @@
 import { createApp, watchEffect } from 'vue';
 
 import { registerAccessDirective } from '@vben/access';
-import { initTippy, registerLoadingDirective } from '@vben/common-ui';
-import { MotionPlugin } from '@vben/plugins/motion';
+import { registerLoadingDirective } from '@vben/common-ui/es/loading';
 import { preferences } from '@vben/preferences';
 import { initStores } from '@vben/stores';
 import '@vben/styles';
@@ -47,12 +46,14 @@ async function bootstrap(namespace: string) {
   registerAccessDirective(app);
 
   // 初始化 tippy
+  const { initTippy } = await import('@vben/common-ui/es/tippy');
   initTippy(app);
 
   // 配置路由及路由守卫
   app.use(router);
 
   // 配置Motion插件
+  const { MotionPlugin } = await import('@vben/plugins/motion');
   app.use(MotionPlugin);
 
   // 动态更新标题

+ 3 - 3
apps/web-antd/src/router/routes/core.ts

@@ -2,10 +2,10 @@ import type { RouteRecordRaw } from 'vue-router';
 
 import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
 
-import { AuthPageLayout, BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
-import Login from '#/views/_core/authentication/login.vue';
 
+const BasicLayout = () => import('#/layouts/basic.vue');
+const AuthPageLayout = () => import('#/layouts/auth.vue');
 /** 全局404页面 */
 const fallbackNotFoundRoute: RouteRecordRaw = {
   component: () => import('#/views/_core/fallback/not-found.vue'),
@@ -50,7 +50,7 @@ const coreRoutes: RouteRecordRaw[] = [
       {
         name: 'Login',
         path: 'login',
-        component: Login,
+        component: () => import('#/views/_core/authentication/login.vue'),
         meta: {
           title: $t('page.auth.login'),
         },

+ 111 - 21
apps/web-ele/src/adapter/component/index.ts

@@ -8,31 +8,121 @@ import type { Component } from 'vue';
 import type { BaseFormComponentType } from '@vben/common-ui';
 import type { Recordable } from '@vben/types';
 
-import { defineComponent, getCurrentInstance, h, ref } from 'vue';
+import {
+  defineAsyncComponent,
+  defineComponent,
+  getCurrentInstance,
+  h,
+  ref,
+} from 'vue';
 
 import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
 import { $t } from '@vben/locales';
 
-import {
-  ElButton,
-  ElCheckbox,
-  ElCheckboxButton,
-  ElCheckboxGroup,
-  ElDatePicker,
-  ElDivider,
-  ElInput,
-  ElInputNumber,
-  ElNotification,
-  ElRadio,
-  ElRadioButton,
-  ElRadioGroup,
-  ElSelectV2,
-  ElSpace,
-  ElSwitch,
-  ElTimePicker,
-  ElTreeSelect,
-  ElUpload,
-} from 'element-plus';
+import { ElNotification } from 'element-plus';
+
+const ElButton = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/button/index'),
+    import('element-plus/es/components/button/style/css'),
+  ]).then(([res]) => res.ElButton),
+);
+const ElCheckbox = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/checkbox/index'),
+    import('element-plus/es/components/checkbox/style/css'),
+  ]).then(([res]) => res.ElCheckbox),
+);
+const ElCheckboxButton = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/checkbox/index'),
+    import('element-plus/es/components/checkbox-button/style/css'),
+  ]).then(([res]) => res.ElCheckboxButton),
+);
+const ElCheckboxGroup = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/checkbox/index'),
+    import('element-plus/es/components/checkbox-group/style/css'),
+  ]).then(([res]) => res.ElCheckboxGroup),
+);
+const ElDatePicker = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/date-picker/index'),
+    import('element-plus/es/components/date-picker/style/css'),
+  ]).then(([res]) => res.ElDatePicker),
+);
+const ElDivider = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/divider/index'),
+    import('element-plus/es/components/divider/style/css'),
+  ]).then(([res]) => res.ElDivider),
+);
+const ElInput = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/input/index'),
+    import('element-plus/es/components/input/style/css'),
+  ]).then(([res]) => res.ElInput),
+);
+const ElInputNumber = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/input-number/index'),
+    import('element-plus/es/components/input-number/style/css'),
+  ]).then(([res]) => res.ElInputNumber),
+);
+const ElRadio = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/radio/index'),
+    import('element-plus/es/components/radio/style/css'),
+  ]).then(([res]) => res.ElRadio),
+);
+const ElRadioButton = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/radio/index'),
+    import('element-plus/es/components/radio-button/style/css'),
+  ]).then(([res]) => res.ElRadioButton),
+);
+const ElRadioGroup = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/radio/index'),
+    import('element-plus/es/components/radio-group/style/css'),
+  ]).then(([res]) => res.ElRadioGroup),
+);
+const ElSelectV2 = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/select-v2/index'),
+    import('element-plus/es/components/select-v2/style/css'),
+  ]).then(([res]) => res.ElSelectV2),
+);
+const ElSpace = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/space/index'),
+    import('element-plus/es/components/space/style/css'),
+  ]).then(([res]) => res.ElSpace),
+);
+const ElSwitch = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/switch/index'),
+    import('element-plus/es/components/switch/style/css'),
+  ]).then(([res]) => res.ElSwitch),
+);
+const ElTimePicker = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/time-picker/index'),
+    import('element-plus/es/components/time-picker/style/css'),
+  ]).then(([res]) => res.ElTimePicker),
+);
+const ElTreeSelect = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/tree-select/index'),
+    import('element-plus/es/components/tree-select/style/css'),
+  ]).then(([res]) => res.ElTreeSelect),
+);
+const ElUpload = defineAsyncComponent(() =>
+  Promise.all([
+    import('element-plus/es/components/upload/index'),
+    import('element-plus/es/components/upload/style/css'),
+  ]).then(([res]) => res.ElUpload),
+);
 
 const withDefaultPlaceholder = <T extends Component>(
   component: T,

+ 3 - 2
apps/web-ele/src/bootstrap.ts

@@ -1,8 +1,7 @@
 import { createApp, watchEffect } from 'vue';
 
 import { registerAccessDirective } from '@vben/access';
-import { initTippy, registerLoadingDirective } from '@vben/common-ui';
-import { MotionPlugin } from '@vben/plugins/motion';
+import { registerLoadingDirective } from '@vben/common-ui';
 import { preferences } from '@vben/preferences';
 import { initStores } from '@vben/stores';
 import '@vben/styles';
@@ -49,12 +48,14 @@ async function bootstrap(namespace: string) {
   registerAccessDirective(app);
 
   // 初始化 tippy
+  const { initTippy } = await import('@vben/common-ui/es/tippy');
   initTippy(app);
 
   // 配置路由及路由守卫
   app.use(router);
 
   // 配置Motion插件
+  const { MotionPlugin } = await import('@vben/plugins/motion');
   app.use(MotionPlugin);
 
   // 动态更新标题

+ 3 - 3
apps/web-ele/src/router/routes/core.ts

@@ -2,10 +2,10 @@ import type { RouteRecordRaw } from 'vue-router';
 
 import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
 
-import { AuthPageLayout, BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
-import Login from '#/views/_core/authentication/login.vue';
 
+const BasicLayout = () => import('#/layouts/basic.vue');
+const AuthPageLayout = () => import('#/layouts/auth.vue');
 /** 全局404页面 */
 const fallbackNotFoundRoute: RouteRecordRaw = {
   component: () => import('#/views/_core/fallback/not-found.vue'),
@@ -50,7 +50,7 @@ const coreRoutes: RouteRecordRaw[] = [
       {
         name: 'Login',
         path: 'login',
-        component: Login,
+        component: () => import('#/views/_core/authentication/login.vue'),
         meta: {
           title: $t('page.auth.login'),
         },

+ 56 - 20
apps/web-naive/src/adapter/component/index.ts

@@ -8,32 +8,68 @@ import type { Component } from 'vue';
 import type { BaseFormComponentType } from '@vben/common-ui';
 import type { Recordable } from '@vben/types';
 
-import { defineComponent, getCurrentInstance, h, ref } from 'vue';
+import {
+  defineAsyncComponent,
+  defineComponent,
+  getCurrentInstance,
+  h,
+  ref,
+} from 'vue';
 
 import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
 import { $t } from '@vben/locales';
 
-import {
-  NButton,
-  NCheckbox,
-  NCheckboxGroup,
-  NDatePicker,
-  NDivider,
-  NInput,
-  NInputNumber,
-  NRadio,
-  NRadioButton,
-  NRadioGroup,
-  NSelect,
-  NSpace,
-  NSwitch,
-  NTimePicker,
-  NTreeSelect,
-  NUpload,
-} from 'naive-ui';
-
 import { message } from '#/adapter/naive';
 
+const NButton = defineAsyncComponent(() =>
+  import('naive-ui/es/button').then((res) => res.NButton),
+);
+const NCheckbox = defineAsyncComponent(() =>
+  import('naive-ui/es/checkbox').then((res) => res.NCheckbox),
+);
+const NCheckboxGroup = defineAsyncComponent(() =>
+  import('naive-ui/es/checkbox').then((res) => res.NCheckboxGroup),
+);
+const NDatePicker = defineAsyncComponent(() =>
+  import('naive-ui/es/date-picker').then((res) => res.NDatePicker),
+);
+const NDivider = defineAsyncComponent(() =>
+  import('naive-ui/es/divider').then((res) => res.NDivider),
+);
+const NInput = defineAsyncComponent(() =>
+  import('naive-ui/es/input').then((res) => res.NInput),
+);
+const NInputNumber = defineAsyncComponent(() =>
+  import('naive-ui/es/input-number').then((res) => res.NInputNumber),
+);
+const NRadio = defineAsyncComponent(() =>
+  import('naive-ui/es/radio').then((res) => res.NRadio),
+);
+const NRadioButton = defineAsyncComponent(() =>
+  import('naive-ui/es/radio').then((res) => res.NRadioButton),
+);
+const NRadioGroup = defineAsyncComponent(() =>
+  import('naive-ui/es/radio').then((res) => res.NRadioGroup),
+);
+const NSelect = defineAsyncComponent(() =>
+  import('naive-ui/es/select').then((res) => res.NSelect),
+);
+const NSpace = defineAsyncComponent(() =>
+  import('naive-ui/es/space').then((res) => res.NSpace),
+);
+const NSwitch = defineAsyncComponent(() =>
+  import('naive-ui/es/switch').then((res) => res.NSwitch),
+);
+const NTimePicker = defineAsyncComponent(() =>
+  import('naive-ui/es/time-picker').then((res) => res.NTimePicker),
+);
+const NTreeSelect = defineAsyncComponent(() =>
+  import('naive-ui/es/tree-select').then((res) => res.NTreeSelect),
+);
+const NUpload = defineAsyncComponent(() =>
+  import('naive-ui/es/upload').then((res) => res.NUpload),
+);
+
 const withDefaultPlaceholder = <T extends Component>(
   component: T,
   type: 'input' | 'select',

+ 3 - 2
apps/web-naive/src/bootstrap.ts

@@ -1,8 +1,7 @@
 import { createApp, watchEffect } from 'vue';
 
 import { registerAccessDirective } from '@vben/access';
-import { initTippy, registerLoadingDirective } from '@vben/common-ui';
-import { MotionPlugin } from '@vben/plugins/motion';
+import { registerLoadingDirective } from '@vben/common-ui';
 import { preferences } from '@vben/preferences';
 import { initStores } from '@vben/stores';
 import '@vben/styles';
@@ -47,12 +46,14 @@ async function bootstrap(namespace: string) {
   registerAccessDirective(app);
 
   // 初始化 tippy
+  const { initTippy } = await import('@vben/common-ui/es/tippy');
   initTippy(app);
 
   // 配置路由及路由守卫
   app.use(router);
 
   // 配置Motion插件
+  const { MotionPlugin } = await import('@vben/plugins/motion');
   app.use(MotionPlugin);
 
   // 动态更新标题

+ 3 - 3
apps/web-naive/src/router/routes/core.ts

@@ -2,10 +2,10 @@ import type { RouteRecordRaw } from 'vue-router';
 
 import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
 
-import { AuthPageLayout, BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
-import Login from '#/views/_core/authentication/login.vue';
 
+const BasicLayout = () => import('#/layouts/basic.vue');
+const AuthPageLayout = () => import('#/layouts/auth.vue');
 /** 全局404页面 */
 const fallbackNotFoundRoute: RouteRecordRaw = {
   component: () => import('#/views/_core/fallback/not-found.vue'),
@@ -50,7 +50,7 @@ const coreRoutes: RouteRecordRaw[] = [
       {
         name: 'Login',
         path: 'login',
-        component: Login,
+        component: () => import('#/views/_core/authentication/login.vue'),
         meta: {
           title: $t('page.auth.login'),
         },

+ 8 - 0
packages/effects/common-ui/package.json

@@ -17,6 +17,14 @@
     ".": {
       "types": "./src/index.ts",
       "default": "./src/index.ts"
+    },
+    "./es/tippy": {
+      "types": "./src/components/tippy/index.ts",
+      "default": "./src/components/tippy/index.ts"
+    },
+    "./es/loading": {
+      "types": "./src/components/loading/index.ts",
+      "default": "./src/components/loading/index.ts"
     }
   },
   "dependencies": {

+ 54 - 25
playground/src/adapter/component/index.ts

@@ -8,35 +8,64 @@ import type { Component } from 'vue';
 import type { BaseFormComponentType } from '@vben/common-ui';
 import type { Recordable } from '@vben/types';
 
-import { defineComponent, getCurrentInstance, h, ref } from 'vue';
+import {
+  defineAsyncComponent,
+  defineComponent,
+  getCurrentInstance,
+  h,
+  ref,
+} from 'vue';
 
 import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
 import { $t } from '@vben/locales';
 
-import {
-  AutoComplete,
-  Button,
-  Checkbox,
-  CheckboxGroup,
-  DatePicker,
-  Divider,
-  Input,
-  InputNumber,
-  InputPassword,
-  Mentions,
-  notification,
-  Radio,
-  RadioGroup,
-  RangePicker,
-  Rate,
-  Select,
-  Space,
-  Switch,
-  Textarea,
-  TimePicker,
-  TreeSelect,
-  Upload,
-} from 'ant-design-vue';
+import { notification } from 'ant-design-vue';
+
+const AutoComplete = defineAsyncComponent(
+  () => import('ant-design-vue/es/auto-complete'),
+);
+const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
+const Checkbox = defineAsyncComponent(
+  () => import('ant-design-vue/es/checkbox'),
+);
+const CheckboxGroup = defineAsyncComponent(() =>
+  import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
+);
+const DatePicker = defineAsyncComponent(
+  () => import('ant-design-vue/es/date-picker'),
+);
+const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
+const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
+const InputNumber = defineAsyncComponent(
+  () => import('ant-design-vue/es/input-number'),
+);
+const InputPassword = defineAsyncComponent(() =>
+  import('ant-design-vue/es/input').then((res) => res.InputPassword),
+);
+const Mentions = defineAsyncComponent(
+  () => import('ant-design-vue/es/mentions'),
+);
+const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
+const RadioGroup = defineAsyncComponent(() =>
+  import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
+);
+const RangePicker = defineAsyncComponent(() =>
+  import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
+);
+const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
+const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
+const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
+const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
+const Textarea = defineAsyncComponent(() =>
+  import('ant-design-vue/es/input').then((res) => res.Textarea),
+);
+const TimePicker = defineAsyncComponent(
+  () => import('ant-design-vue/es/time-picker'),
+);
+const TreeSelect = defineAsyncComponent(
+  () => import('ant-design-vue/es/tree-select'),
+);
+const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
 
 const withDefaultPlaceholder = <T extends Component>(
   component: T,

+ 7 - 6
playground/src/bootstrap.ts

@@ -1,14 +1,12 @@
 import { createApp, watchEffect } from 'vue';
 
 import { registerAccessDirective } from '@vben/access';
-import { initTippy, registerLoadingDirective } from '@vben/common-ui';
-import { MotionPlugin } from '@vben/plugins/motion';
+import { registerLoadingDirective } from '@vben/common-ui';
 import { preferences } from '@vben/preferences';
 import { initStores } from '@vben/stores';
 import '@vben/styles';
 import '@vben/styles/antd';
 
-import { VueQueryPlugin } from '@tanstack/vue-query';
 import { useTitle } from '@vueuse/core';
 
 import { $t, setupI18n } from '#/locales';
@@ -21,13 +19,13 @@ async function bootstrap(namespace: string) {
   // 初始化组件适配器
   await initComponentAdapter();
 
-  // // 设置弹窗的默认配置
+  // 设置弹窗的默认配置
   // setDefaultModalProps({
   //   fullscreenButton: false,
   // });
-  // // 设置抽屉的默认配置
+  // 设置抽屉的默认配置
   // setDefaultDrawerProps({
-  //   // zIndex: 1020,
+  //   zIndex: 1020,
   // });
 
   const app = createApp(App);
@@ -48,15 +46,18 @@ async function bootstrap(namespace: string) {
   registerAccessDirective(app);
 
   // 初始化 tippy
+  const { initTippy } = await import('@vben/common-ui/es/tippy');
   initTippy(app);
 
   // 配置路由及路由守卫
   app.use(router);
 
   // 配置@tanstack/vue-query
+  const { VueQueryPlugin } = await import('@tanstack/vue-query');
   app.use(VueQueryPlugin);
 
   // 配置Motion插件
+  const { MotionPlugin } = await import('@vben/plugins/motion');
   app.use(MotionPlugin);
 
   // 动态更新标题

+ 3 - 3
playground/src/router/routes/core.ts

@@ -2,10 +2,10 @@ import type { RouteRecordRaw } from 'vue-router';
 
 import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
 
-import { AuthPageLayout, BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
-import Login from '#/views/_core/authentication/login.vue';
 
+const BasicLayout = () => import('#/layouts/basic.vue');
+const AuthPageLayout = () => import('#/layouts/auth.vue');
 /** 全局404页面 */
 const fallbackNotFoundRoute: RouteRecordRaw = {
   component: () => import('#/views/_core/fallback/not-found.vue'),
@@ -50,7 +50,7 @@ const coreRoutes: RouteRecordRaw[] = [
       {
         name: 'Login',
         path: 'login',
-        component: Login,
+        component: () => import('#/views/_core/authentication/login.vue'),
         meta: {
           title: $t('page.auth.login'),
         },