@module-federation/bridge-vue3 2.3.3 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +5 -5
- package/dist/index.js +201 -216
- package/package.json +5 -11
- package/CHANGELOG.md +0 -911
- package/src/assets/vue.svg +0 -1
- package/src/components/HelloWorld.vue +0 -38
- package/src/create.ts +0 -83
- package/src/index.ts +0 -4
- package/src/provider.ts +0 -112
- package/src/remoteApp.tsx +0 -115
- package/src/routeUtils.ts +0 -238
- package/src/style.css +0 -79
- package/src/utils.ts +0 -3
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -41
- package/tsconfig.node.json +0 -11
- package/vite.config.ts +0 -30
package/src/assets/vue.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { ref } from 'vue';
|
|
3
|
-
|
|
4
|
-
defineProps<{ msg: string }>();
|
|
5
|
-
|
|
6
|
-
const count = ref(0);
|
|
7
|
-
</script>
|
|
8
|
-
|
|
9
|
-
<template>
|
|
10
|
-
<h1>{{ msg }}</h1>
|
|
11
|
-
|
|
12
|
-
<div class="card">
|
|
13
|
-
<button type="button" @click="count++">count is {{ count }}</button>
|
|
14
|
-
<p>
|
|
15
|
-
Edit
|
|
16
|
-
<code>components/HelloWorld.vue</code> to test HMR
|
|
17
|
-
</p>
|
|
18
|
-
</div>
|
|
19
|
-
|
|
20
|
-
<p>
|
|
21
|
-
Check out
|
|
22
|
-
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
|
23
|
-
>create-vue</a
|
|
24
|
-
>, the official Vue + Vite starter
|
|
25
|
-
</p>
|
|
26
|
-
<p>
|
|
27
|
-
Install
|
|
28
|
-
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
|
|
29
|
-
in your IDE for a better DX
|
|
30
|
-
</p>
|
|
31
|
-
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
|
32
|
-
</template>
|
|
33
|
-
|
|
34
|
-
<style scoped>
|
|
35
|
-
.read-the-docs {
|
|
36
|
-
color: #888;
|
|
37
|
-
}
|
|
38
|
-
</style>
|
package/src/create.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { type AsyncComponentOptions, defineAsyncComponent, h } from 'vue';
|
|
2
|
-
import { useRoute } from 'vue-router';
|
|
3
|
-
import RemoteApp from './remoteApp.jsx';
|
|
4
|
-
import { LoggerInstance } from './utils.js';
|
|
5
|
-
|
|
6
|
-
declare const __APP_VERSION__: string;
|
|
7
|
-
|
|
8
|
-
export function createRemoteAppComponent(info: {
|
|
9
|
-
loader: () => Promise<any>;
|
|
10
|
-
export?: string;
|
|
11
|
-
asyncComponentOptions?: Omit<AsyncComponentOptions, 'loader'>;
|
|
12
|
-
rootAttrs?: Record<string, unknown>;
|
|
13
|
-
memoryRoute?: { entryPath: string };
|
|
14
|
-
hashRoute?: boolean;
|
|
15
|
-
}) {
|
|
16
|
-
return defineAsyncComponent({
|
|
17
|
-
__APP_VERSION__,
|
|
18
|
-
...info.asyncComponentOptions,
|
|
19
|
-
//@ts-ignore
|
|
20
|
-
loader: async () => {
|
|
21
|
-
const route = useRoute();
|
|
22
|
-
|
|
23
|
-
let basename = '/';
|
|
24
|
-
const matchPath = route?.matched?.[0]?.path;
|
|
25
|
-
if (matchPath) {
|
|
26
|
-
if (matchPath.endsWith('/:pathMatch(.*)*')) {
|
|
27
|
-
basename = matchPath.replace('/:pathMatch(.*)*', '');
|
|
28
|
-
} else {
|
|
29
|
-
basename = route.matched[0].path;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const exportName = info?.export || 'default';
|
|
34
|
-
LoggerInstance.debug(
|
|
35
|
-
`createRemoteAppComponent LazyComponent create >>>`,
|
|
36
|
-
{
|
|
37
|
-
basename,
|
|
38
|
-
info,
|
|
39
|
-
},
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
const module: any = await info.loader();
|
|
43
|
-
const moduleName = module && module[Symbol.for('mf_module_id')];
|
|
44
|
-
const exportFn = module[exportName];
|
|
45
|
-
|
|
46
|
-
LoggerInstance.debug(
|
|
47
|
-
`createRemoteAppComponent LazyComponent loadRemote info >>>`,
|
|
48
|
-
{ moduleName, module, exportName, basename, route },
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
if (exportName in module && typeof exportFn === 'function') {
|
|
52
|
-
return {
|
|
53
|
-
render() {
|
|
54
|
-
return h(RemoteApp, {
|
|
55
|
-
moduleName,
|
|
56
|
-
providerInfo: exportFn,
|
|
57
|
-
basename,
|
|
58
|
-
rootAttrs: info.rootAttrs,
|
|
59
|
-
memoryRoute: info.memoryRoute,
|
|
60
|
-
hashRoute: info.hashRoute,
|
|
61
|
-
});
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
throw new Error('module not found');
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* @deprecated createRemoteComponent is deprecated, please use createRemoteAppComponent instead!
|
|
72
|
-
*/
|
|
73
|
-
export function createRemoteComponent(info: {
|
|
74
|
-
loader: () => Promise<any>;
|
|
75
|
-
export?: string;
|
|
76
|
-
asyncComponentOptions?: Omit<AsyncComponentOptions, 'loader'>;
|
|
77
|
-
rootAttrs?: Record<string, unknown>;
|
|
78
|
-
}) {
|
|
79
|
-
LoggerInstance.warn(
|
|
80
|
-
'createRemoteComponent is deprecated, please use createRemoteAppComponent instead!',
|
|
81
|
-
);
|
|
82
|
-
return createRemoteAppComponent(info);
|
|
83
|
-
}
|
package/src/index.ts
DELETED
package/src/provider.ts
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import * as Vue from 'vue';
|
|
2
|
-
import * as VueRouter from 'vue-router';
|
|
3
|
-
import { RenderFnParams } from '@module-federation/bridge-shared';
|
|
4
|
-
import { LoggerInstance } from './utils';
|
|
5
|
-
import { getInstance } from '@module-federation/runtime';
|
|
6
|
-
import { processRoutes } from './routeUtils';
|
|
7
|
-
|
|
8
|
-
declare const __APP_VERSION__: string;
|
|
9
|
-
|
|
10
|
-
type AddOptionsFnParams = {
|
|
11
|
-
app: Vue.App<Vue.Component>;
|
|
12
|
-
basename: RenderFnParams['basename'];
|
|
13
|
-
memoryRoute: RenderFnParams['memoryRoute'];
|
|
14
|
-
[key: string]: any;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type ProviderFnParams = {
|
|
18
|
-
rootComponent: Vue.Component;
|
|
19
|
-
appOptions: (params: AddOptionsFnParams) => {
|
|
20
|
-
router?: VueRouter.Router;
|
|
21
|
-
/** Called with the bridge's internal router after creation but before navigation.
|
|
22
|
-
* Use this to register global guards (beforeEach, afterEach, etc.) that would
|
|
23
|
-
* otherwise be lost when the bridge recreates the router. */
|
|
24
|
-
afterRouterCreate?: (router: VueRouter.Router) => void;
|
|
25
|
-
} | void;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export function createBridgeComponent(bridgeInfo: ProviderFnParams) {
|
|
29
|
-
const rootMap = new Map();
|
|
30
|
-
const instance = getInstance();
|
|
31
|
-
return () => {
|
|
32
|
-
return {
|
|
33
|
-
__APP_VERSION__,
|
|
34
|
-
async render(info: RenderFnParams) {
|
|
35
|
-
LoggerInstance.debug(`createBridgeComponent render Info`, info);
|
|
36
|
-
const {
|
|
37
|
-
moduleName,
|
|
38
|
-
dom,
|
|
39
|
-
basename,
|
|
40
|
-
memoryRoute,
|
|
41
|
-
hashRoute,
|
|
42
|
-
...propsInfo
|
|
43
|
-
} = info;
|
|
44
|
-
const app = Vue.createApp(bridgeInfo.rootComponent, propsInfo);
|
|
45
|
-
rootMap.set(dom, app);
|
|
46
|
-
|
|
47
|
-
const beforeBridgeRenderRes =
|
|
48
|
-
await instance?.bridgeHook?.lifecycle?.beforeBridgeRender?.emit(info);
|
|
49
|
-
|
|
50
|
-
const extraProps =
|
|
51
|
-
beforeBridgeRenderRes &&
|
|
52
|
-
typeof beforeBridgeRenderRes === 'object' &&
|
|
53
|
-
beforeBridgeRenderRes?.extraProps
|
|
54
|
-
? beforeBridgeRenderRes?.extraProps
|
|
55
|
-
: {};
|
|
56
|
-
|
|
57
|
-
const bridgeOptions = bridgeInfo.appOptions({
|
|
58
|
-
app,
|
|
59
|
-
basename,
|
|
60
|
-
memoryRoute,
|
|
61
|
-
hashRoute,
|
|
62
|
-
...propsInfo,
|
|
63
|
-
...extraProps,
|
|
64
|
-
});
|
|
65
|
-
if (bridgeOptions?.router) {
|
|
66
|
-
const { history, routes, patchRouter } = processRoutes({
|
|
67
|
-
router: bridgeOptions.router,
|
|
68
|
-
basename: info.basename,
|
|
69
|
-
memoryRoute: info.memoryRoute,
|
|
70
|
-
hashRoute: info.hashRoute,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const router = VueRouter.createRouter({
|
|
74
|
-
...bridgeOptions.router.options,
|
|
75
|
-
history,
|
|
76
|
-
routes,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
if (patchRouter) {
|
|
80
|
-
patchRouter(router);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (bridgeOptions.afterRouterCreate) {
|
|
84
|
-
bridgeOptions.afterRouterCreate(router);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
LoggerInstance.debug(`createBridgeComponent render router info>>>`, {
|
|
88
|
-
moduleName,
|
|
89
|
-
router,
|
|
90
|
-
});
|
|
91
|
-
// memory route Initializes the route
|
|
92
|
-
if (memoryRoute) {
|
|
93
|
-
await router.push(memoryRoute.entryPath);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
app.use(router);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
app.mount(dom);
|
|
100
|
-
instance?.bridgeHook?.lifecycle?.afterBridgeRender?.emit(info);
|
|
101
|
-
},
|
|
102
|
-
destroy(info: { dom: HTMLElement }) {
|
|
103
|
-
LoggerInstance.debug(`createBridgeComponent destroy Info`, info);
|
|
104
|
-
const root = rootMap.get(info?.dom);
|
|
105
|
-
|
|
106
|
-
instance?.bridgeHook?.lifecycle?.beforeBridgeDestroy?.emit(info);
|
|
107
|
-
root?.unmount();
|
|
108
|
-
instance?.bridgeHook?.lifecycle?.afterBridgeDestroy?.emit(info);
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
};
|
|
112
|
-
}
|
package/src/remoteApp.tsx
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ref,
|
|
3
|
-
onMounted,
|
|
4
|
-
onBeforeUnmount,
|
|
5
|
-
watch,
|
|
6
|
-
defineComponent,
|
|
7
|
-
useAttrs,
|
|
8
|
-
} from 'vue';
|
|
9
|
-
import { dispatchPopstateEnv } from '@module-federation/bridge-shared';
|
|
10
|
-
import { useRoute } from 'vue-router';
|
|
11
|
-
import { LoggerInstance } from './utils';
|
|
12
|
-
import { getInstance } from '@module-federation/runtime';
|
|
13
|
-
|
|
14
|
-
export default defineComponent({
|
|
15
|
-
name: 'RemoteApp',
|
|
16
|
-
props: {
|
|
17
|
-
moduleName: String,
|
|
18
|
-
basename: String,
|
|
19
|
-
memoryRoute: Object,
|
|
20
|
-
hashRoute: Boolean,
|
|
21
|
-
providerInfo: Function,
|
|
22
|
-
rootAttrs: Object,
|
|
23
|
-
},
|
|
24
|
-
inheritAttrs: false,
|
|
25
|
-
setup(props) {
|
|
26
|
-
const rootRef = ref(null);
|
|
27
|
-
const providerInfoRef = ref(null);
|
|
28
|
-
const pathname = ref('');
|
|
29
|
-
const route = useRoute();
|
|
30
|
-
const hostInstance = getInstance();
|
|
31
|
-
const componentAttrs = useAttrs();
|
|
32
|
-
|
|
33
|
-
const renderComponent = async () => {
|
|
34
|
-
const providerReturn = props.providerInfo?.();
|
|
35
|
-
providerInfoRef.value = providerReturn;
|
|
36
|
-
|
|
37
|
-
let renderProps = {
|
|
38
|
-
...componentAttrs,
|
|
39
|
-
moduleName: props.moduleName,
|
|
40
|
-
dom: rootRef.value,
|
|
41
|
-
basename: props.basename,
|
|
42
|
-
memoryRoute: props.memoryRoute,
|
|
43
|
-
hashRoute: props.hashRoute,
|
|
44
|
-
};
|
|
45
|
-
LoggerInstance.debug(
|
|
46
|
-
`createRemoteAppComponent LazyComponent render >>>`,
|
|
47
|
-
renderProps,
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
const beforeBridgeRenderRes =
|
|
51
|
-
(await hostInstance?.bridgeHook?.lifecycle?.beforeBridgeRender?.emit(
|
|
52
|
-
renderProps,
|
|
53
|
-
)) || {};
|
|
54
|
-
|
|
55
|
-
renderProps = { ...renderProps, ...beforeBridgeRenderRes.extraProps };
|
|
56
|
-
providerReturn.render(renderProps);
|
|
57
|
-
hostInstance?.bridgeHook?.lifecycle?.afterBridgeRender?.emit(renderProps);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const watchStopHandle = watch(
|
|
61
|
-
() => route?.path,
|
|
62
|
-
(newPath) => {
|
|
63
|
-
if (newPath !== route.path) {
|
|
64
|
-
renderComponent();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// dispatchPopstateEnv
|
|
68
|
-
if (pathname.value !== '' && pathname.value !== newPath) {
|
|
69
|
-
LoggerInstance.debug(
|
|
70
|
-
`createRemoteAppComponent dispatchPopstateEnv >>>`,
|
|
71
|
-
{
|
|
72
|
-
...props,
|
|
73
|
-
pathname: route.path,
|
|
74
|
-
},
|
|
75
|
-
);
|
|
76
|
-
dispatchPopstateEnv();
|
|
77
|
-
}
|
|
78
|
-
pathname.value = newPath;
|
|
79
|
-
},
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
onMounted(() => {
|
|
83
|
-
renderComponent();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
onBeforeUnmount(() => {
|
|
87
|
-
LoggerInstance.debug(
|
|
88
|
-
`createRemoteAppComponent LazyComponent destroy >>>`,
|
|
89
|
-
{
|
|
90
|
-
...props,
|
|
91
|
-
},
|
|
92
|
-
);
|
|
93
|
-
watchStopHandle();
|
|
94
|
-
|
|
95
|
-
hostInstance?.bridgeHook?.lifecycle?.beforeBridgeDestroy?.emit({
|
|
96
|
-
name: props.moduleName,
|
|
97
|
-
dom: rootRef.value,
|
|
98
|
-
basename: props.basename,
|
|
99
|
-
memoryRoute: props.memoryRoute,
|
|
100
|
-
hashRoute: props.hashRoute,
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
(providerInfoRef.value as any)?.destroy({ dom: rootRef.value });
|
|
104
|
-
hostInstance?.bridgeHook?.lifecycle?.afterBridgeDestroy?.emit({
|
|
105
|
-
name: props.moduleName,
|
|
106
|
-
dom: rootRef.value,
|
|
107
|
-
basename: props.basename,
|
|
108
|
-
memoryRoute: props.memoryRoute,
|
|
109
|
-
hashRoute: props.hashRoute,
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
return () => <div {...(props.rootAttrs || {})} ref={rootRef}></div>;
|
|
114
|
-
},
|
|
115
|
-
});
|
package/src/routeUtils.ts
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import * as VueRouter from 'vue-router';
|
|
2
|
-
|
|
3
|
-
export interface RouteProcessingOptions {
|
|
4
|
-
router: VueRouter.Router;
|
|
5
|
-
basename?: string;
|
|
6
|
-
memoryRoute?: boolean | { entryPath: string };
|
|
7
|
-
hashRoute?: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface RouteProcessingResult {
|
|
11
|
-
history: VueRouter.RouterHistory;
|
|
12
|
-
routes: VueRouter.RouteRecordNormalized[];
|
|
13
|
-
patchRouter?: (router: VueRouter.Router) => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Add basename prefix to all nested routes recursively
|
|
18
|
-
*
|
|
19
|
-
* @param routes - route configuration array
|
|
20
|
-
* @param basename - base path prefix
|
|
21
|
-
* @returns processed route configuration
|
|
22
|
-
*/
|
|
23
|
-
function addBasenameToNestedRoutes(
|
|
24
|
-
routes: VueRouter.RouteRecordNormalized[],
|
|
25
|
-
basename: string,
|
|
26
|
-
): VueRouter.RouteRecordNormalized[] {
|
|
27
|
-
/**
|
|
28
|
-
* Join two path segments, collapse multiple slashes, and optionally
|
|
29
|
-
* preserve a trailing slash that was present in the original value.
|
|
30
|
-
* A bare '/' root is never considered an intentional trailing slash.
|
|
31
|
-
*
|
|
32
|
-
* Relative paths (not starting with '/') are left untouched — Vue Router
|
|
33
|
-
* resolves them against the parent route, which already carries the basename.
|
|
34
|
-
*/
|
|
35
|
-
const prefixPath = (original: string): string => {
|
|
36
|
-
if (!original.startsWith('/')) return original;
|
|
37
|
-
|
|
38
|
-
const hasTrailingSlash = original.length > 1 && original.endsWith('/');
|
|
39
|
-
const normalized =
|
|
40
|
-
`${basename}/${original}`.replace(/\/+/g, '/').replace(/\/$/, '') || '/';
|
|
41
|
-
return hasTrailingSlash ? `${normalized}/` : normalized;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
return routes.map((route) => {
|
|
45
|
-
const updatedRoute: VueRouter.RouteRecordNormalized = {
|
|
46
|
-
...route,
|
|
47
|
-
path: prefixPath(route.path),
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// Prefix string redirects with basename
|
|
51
|
-
if (typeof route.redirect === 'string') {
|
|
52
|
-
updatedRoute.redirect = prefixPath(route.redirect);
|
|
53
|
-
} else if (
|
|
54
|
-
route.redirect &&
|
|
55
|
-
typeof route.redirect === 'object' &&
|
|
56
|
-
'path' in route.redirect &&
|
|
57
|
-
typeof route.redirect.path === 'string'
|
|
58
|
-
) {
|
|
59
|
-
updatedRoute.redirect = {
|
|
60
|
-
...route.redirect,
|
|
61
|
-
path: prefixPath(route.redirect.path),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Recursively process child routes
|
|
66
|
-
if (route.children && route.children.length > 0) {
|
|
67
|
-
updatedRoute.children = addBasenameToNestedRoutes(
|
|
68
|
-
route.children as VueRouter.RouteRecordNormalized[],
|
|
69
|
-
basename,
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return updatedRoute;
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Create a patch function that rewrites path-based navigations to include
|
|
79
|
-
* the basename prefix. This is needed because createWebHashHistory() does
|
|
80
|
-
* not accept a basename argument, so router.push('/foo') would bypass the
|
|
81
|
-
* prefixed route definitions.
|
|
82
|
-
*
|
|
83
|
-
* By patching push/replace/resolve *before* Vue Router resolves the
|
|
84
|
-
* location we also avoid the "No match found" console warning.
|
|
85
|
-
*/
|
|
86
|
-
function createHashBasenamePatch(
|
|
87
|
-
basename: string,
|
|
88
|
-
): (router: VueRouter.Router) => void {
|
|
89
|
-
const normalized = basename.replace(/\/+$/, '');
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Only absolute paths (starting with '/') that don't already carry the
|
|
93
|
-
* basename prefix need rewriting. Relative segments ('settings'),
|
|
94
|
-
* query-only ('?tab=1'), and hash-only ('#anchor') strings are resolved
|
|
95
|
-
* by Vue Router against the current route and must pass through untouched.
|
|
96
|
-
*/
|
|
97
|
-
const needsPrefix = (path: string): boolean =>
|
|
98
|
-
path.startsWith('/') &&
|
|
99
|
-
path !== normalized &&
|
|
100
|
-
!path.startsWith(normalized + '/');
|
|
101
|
-
|
|
102
|
-
const prefix = (path: string): string =>
|
|
103
|
-
`${normalized}${path}`.replace(/\/+/g, '/');
|
|
104
|
-
|
|
105
|
-
const rewrite = (
|
|
106
|
-
to: VueRouter.RouteLocationRaw,
|
|
107
|
-
): VueRouter.RouteLocationRaw => {
|
|
108
|
-
if (typeof to === 'string') {
|
|
109
|
-
return needsPrefix(to) ? prefix(to) : to;
|
|
110
|
-
}
|
|
111
|
-
if ('path' in to && typeof to.path === 'string' && needsPrefix(to.path)) {
|
|
112
|
-
return { ...to, path: prefix(to.path) };
|
|
113
|
-
}
|
|
114
|
-
return to;
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
return (router) => {
|
|
118
|
-
const originalPush = router.push.bind(router);
|
|
119
|
-
const originalReplace = router.replace.bind(router);
|
|
120
|
-
const originalResolve = router.resolve.bind(router);
|
|
121
|
-
|
|
122
|
-
router.push = (to: VueRouter.RouteLocationRaw) => originalPush(rewrite(to));
|
|
123
|
-
router.replace = (to: VueRouter.RouteLocationRaw) =>
|
|
124
|
-
originalReplace(rewrite(to));
|
|
125
|
-
router.resolve = ((to: VueRouter.RouteLocationRaw, ...rest: any[]) =>
|
|
126
|
-
originalResolve(rewrite(to), ...rest)) as typeof router.resolve;
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Route processing solution based on path analysis
|
|
132
|
-
*
|
|
133
|
-
* @param options - route processing options
|
|
134
|
-
* @returns processed history and routes
|
|
135
|
-
*/
|
|
136
|
-
export function processRoutes(
|
|
137
|
-
options: RouteProcessingOptions,
|
|
138
|
-
): RouteProcessingResult {
|
|
139
|
-
const { router, basename, memoryRoute, hashRoute } = options;
|
|
140
|
-
|
|
141
|
-
// Sort routes, try to process parent route first
|
|
142
|
-
const flatRoutes = router
|
|
143
|
-
.getRoutes()
|
|
144
|
-
.sort(
|
|
145
|
-
(a, b) =>
|
|
146
|
-
a.path.split('/').filter((p) => p).length -
|
|
147
|
-
b.path.split('/').filter((p) => p).length,
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
// Use Map/Set for O(1) lookup performance
|
|
151
|
-
// Store arrays because multiple routes can resolve to the same path
|
|
152
|
-
// (e.g. a parent and a default child with path: '')
|
|
153
|
-
const flatRoutesMap = new Map<string, VueRouter.RouteRecordNormalized[]>();
|
|
154
|
-
const processedRoutes = new Set<VueRouter.RouteRecordNormalized>();
|
|
155
|
-
|
|
156
|
-
flatRoutes.forEach((route) => {
|
|
157
|
-
const existing = flatRoutesMap.get(route.path) || [];
|
|
158
|
-
existing.push(route);
|
|
159
|
-
flatRoutesMap.set(route.path, existing);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Normalize path by removing double slashes and trailing slashes
|
|
164
|
-
*/
|
|
165
|
-
const normalizePath = (prefix: string, childPath: string): string => {
|
|
166
|
-
const fullPath = `${prefix}/${childPath}`;
|
|
167
|
-
return fullPath.replace(/\/+/g, '/').replace(/\/$/, '') || '/';
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const processChildren = (
|
|
171
|
-
route: VueRouter.RouteRecordNormalized,
|
|
172
|
-
prefix = '',
|
|
173
|
-
): VueRouter.RouteRecordNormalized => {
|
|
174
|
-
if (!route.children || route.children.length === 0) {
|
|
175
|
-
return route;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
for (let j = 0; j < route.children.length; j++) {
|
|
179
|
-
const child = route.children[j];
|
|
180
|
-
const fullPath = normalizePath(prefix, child.path);
|
|
181
|
-
const candidates = flatRoutesMap.get(fullPath) || [];
|
|
182
|
-
// Find a matching route that:
|
|
183
|
-
// 1. Hasn't been processed yet
|
|
184
|
-
// 2. Isn't the current parent route (avoids circular references when
|
|
185
|
-
// a child with path: '' resolves to the same absolute path)
|
|
186
|
-
// 3. Matches by name when the child definition specifies one
|
|
187
|
-
const childRoute = candidates.find(
|
|
188
|
-
(r) =>
|
|
189
|
-
!processedRoutes.has(r) &&
|
|
190
|
-
r !== route &&
|
|
191
|
-
(child.name == null || r.name === child.name),
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
if (childRoute) {
|
|
195
|
-
// Create a new optimized route object with relative path for nested routes
|
|
196
|
-
const relativeChildRoute: VueRouter.RouteRecordNormalized = {
|
|
197
|
-
...childRoute,
|
|
198
|
-
path: child.path, // Keep the original relative path from static route
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
route.children[j] = relativeChildRoute;
|
|
202
|
-
processedRoutes.add(childRoute);
|
|
203
|
-
|
|
204
|
-
processChildren(relativeChildRoute, fullPath);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return route;
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
// Reconstruct nested structure
|
|
212
|
-
let routes: VueRouter.RouteRecordNormalized[] = [];
|
|
213
|
-
|
|
214
|
-
for (const route of flatRoutes) {
|
|
215
|
-
if (!processedRoutes.has(route)) {
|
|
216
|
-
const processedRoute = processChildren(route, route.path);
|
|
217
|
-
processedRoutes.add(route);
|
|
218
|
-
routes.push(processedRoute);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
let history: VueRouter.RouterHistory;
|
|
223
|
-
let patchRouter: ((router: VueRouter.Router) => void) | undefined;
|
|
224
|
-
|
|
225
|
-
if (memoryRoute) {
|
|
226
|
-
history = VueRouter.createMemoryHistory(basename);
|
|
227
|
-
} else if (hashRoute) {
|
|
228
|
-
history = VueRouter.createWebHashHistory();
|
|
229
|
-
if (basename) {
|
|
230
|
-
routes = addBasenameToNestedRoutes(routes, basename);
|
|
231
|
-
patchRouter = createHashBasenamePatch(basename);
|
|
232
|
-
}
|
|
233
|
-
} else {
|
|
234
|
-
history = VueRouter.createWebHistory(basename);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return { history, routes, patchRouter };
|
|
238
|
-
}
|
package/src/style.css
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
:root {
|
|
2
|
-
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
3
|
-
line-height: 1.5;
|
|
4
|
-
font-weight: 400;
|
|
5
|
-
|
|
6
|
-
color-scheme: light dark;
|
|
7
|
-
color: rgba(255, 255, 255, 0.87);
|
|
8
|
-
background-color: #242424;
|
|
9
|
-
|
|
10
|
-
font-synthesis: none;
|
|
11
|
-
text-rendering: optimizeLegibility;
|
|
12
|
-
-webkit-font-smoothing: antialiased;
|
|
13
|
-
-moz-osx-font-smoothing: grayscale;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
a {
|
|
17
|
-
font-weight: 500;
|
|
18
|
-
color: #646cff;
|
|
19
|
-
text-decoration: inherit;
|
|
20
|
-
}
|
|
21
|
-
a:hover {
|
|
22
|
-
color: #535bf2;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
body {
|
|
26
|
-
margin: 0;
|
|
27
|
-
display: flex;
|
|
28
|
-
place-items: center;
|
|
29
|
-
min-width: 320px;
|
|
30
|
-
min-height: 100vh;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
h1 {
|
|
34
|
-
font-size: 3.2em;
|
|
35
|
-
line-height: 1.1;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
button {
|
|
39
|
-
border-radius: 8px;
|
|
40
|
-
border: 1px solid transparent;
|
|
41
|
-
padding: 0.6em 1.2em;
|
|
42
|
-
font-size: 1em;
|
|
43
|
-
font-weight: 500;
|
|
44
|
-
font-family: inherit;
|
|
45
|
-
background-color: #1a1a1a;
|
|
46
|
-
cursor: pointer;
|
|
47
|
-
transition: border-color 0.25s;
|
|
48
|
-
}
|
|
49
|
-
button:hover {
|
|
50
|
-
border-color: #646cff;
|
|
51
|
-
}
|
|
52
|
-
button:focus,
|
|
53
|
-
button:focus-visible {
|
|
54
|
-
outline: 4px auto -webkit-focus-ring-color;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.card {
|
|
58
|
-
padding: 2em;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
#app {
|
|
62
|
-
max-width: 1280px;
|
|
63
|
-
margin: 0 auto;
|
|
64
|
-
padding: 2rem;
|
|
65
|
-
text-align: center;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
@media (prefers-color-scheme: light) {
|
|
69
|
-
:root {
|
|
70
|
-
color: #213547;
|
|
71
|
-
background-color: #ffffff;
|
|
72
|
-
}
|
|
73
|
-
a:hover {
|
|
74
|
-
color: #747bff;
|
|
75
|
-
}
|
|
76
|
-
button {
|
|
77
|
-
background-color: #f9f9f9;
|
|
78
|
-
}
|
|
79
|
-
}
|
package/src/utils.ts
DELETED
package/src/vite-env.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|