@modern-js/devtools-client 2.34.0 → 2.35.1
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/.turbo/turbo-build.log +135 -20
- package/CHANGELOG.md +20 -0
- package/build.sh +5 -0
- package/dist/html/main/index.html +5 -0
- package/dist/modern.config.json +31 -11
- package/dist/nestedRoutes.json +1 -1
- package/dist/route.json +3 -3
- package/dist/routes-manifest.json +198 -16
- package/dist/static/css/198.76c8c1d9.css +51 -0
- package/dist/static/css/main.9bc38ddc.css +1 -0
- package/dist/static/js/198.9a4a2f6b.js +1 -0
- package/dist/static/js/198.9a4a2f6b.js.LICENSE.txt +39 -0
- package/dist/static/js/198.9a4a2f6b.js.map +1 -0
- package/dist/static/js/async/config/builder/resolved/page.83919c8c.js +1 -0
- package/dist/static/js/async/config/builder/resolved/page.83919c8c.js.map +1 -0
- package/dist/static/js/async/config/builder/transformed/page.cdfad512.js +1 -0
- package/dist/static/js/async/config/builder/transformed/page.cdfad512.js.map +1 -0
- package/dist/static/js/async/config/bundler/resolved/page.c2911ed1.js +1 -0
- package/dist/static/js/async/config/bundler/resolved/page.c2911ed1.js.map +1 -0
- package/dist/static/js/async/config/bundler/transformed/page.268e1037.js +1 -0
- package/dist/static/js/async/config/bundler/transformed/page.268e1037.js.map +1 -0
- package/dist/static/js/async/config/framework/resolved/page.d5729166.js +1 -0
- package/dist/static/js/async/config/framework/resolved/page.d5729166.js.map +1 -0
- package/dist/static/js/async/config/framework/transformed/page.240ab8d3.js +1 -0
- package/dist/static/js/async/config/framework/transformed/page.240ab8d3.js.map +1 -0
- package/dist/static/js/async/config/layout.272e8d6b.js +1 -0
- package/dist/static/js/async/config/layout.272e8d6b.js.map +1 -0
- package/dist/static/js/async/config/page.40d352df.js +1 -0
- package/dist/static/js/async/config/page.40d352df.js.map +1 -0
- package/dist/static/js/async/context/builder/page.62f2bc66.js +1 -0
- package/dist/static/js/async/context/builder/page.62f2bc66.js.map +1 -0
- package/dist/static/js/async/context/framework/page.052a6c77.js +1 -0
- package/dist/static/js/async/context/framework/page.052a6c77.js.map +1 -0
- package/dist/static/js/async/context/layout.06edb4f8.js +1 -0
- package/dist/static/js/async/context/layout.06edb4f8.js.map +1 -0
- package/dist/static/js/async/context/page.94cbdf00.js +1 -0
- package/dist/static/js/async/context/page.94cbdf00.js.map +1 -0
- package/dist/static/js/async/overview/page.1d1e2eae.js +1 -0
- package/dist/static/js/async/overview/page.1d1e2eae.js.map +1 -0
- package/dist/static/js/async/page.ae98c3d4.js +1 -0
- package/dist/static/js/async/page.ae98c3d4.js.map +1 -0
- package/dist/static/js/async/pages/layout.27df0fc6.js +1 -0
- package/dist/static/js/async/pages/layout.27df0fc6.js.map +1 -0
- package/dist/static/js/async/pages/page.d8425bcf.js +1 -0
- package/dist/static/js/async/pages/page.d8425bcf.js.map +1 -0
- package/dist/static/js/lib-lodash.d4590abd.js +1 -0
- package/dist/static/js/lib-lodash.d4590abd.js.map +1 -0
- package/dist/static/js/lib-polyfill.6e913595.js +1 -0
- package/dist/static/js/lib-polyfill.6e913595.js.map +1 -0
- package/dist/static/js/lib-react.e7e80806.js +1 -0
- package/dist/static/js/{lib-react.be1fdbe9.js.LICENSE.txt → lib-react.e7e80806.js.LICENSE.txt} +11 -1
- package/dist/static/js/lib-react.e7e80806.js.map +1 -0
- package/dist/static/js/lib-router.ee136613.js +1 -0
- package/dist/static/js/{lib-router.7b48a0f0.js.LICENSE.txt → lib-router.ee136613.js.LICENSE.txt} +3 -3
- package/dist/static/js/lib-router.ee136613.js.map +1 -0
- package/dist/static/js/main.7e1a3442.js +1 -0
- package/dist/static/js/main.7e1a3442.js.map +1 -0
- package/modern.config.ts +42 -3
- package/package.json +28 -8
- package/scripts/build-redirects.js +32 -0
- package/scripts/deploy-netlify.js +0 -0
- package/src/.eslintrc.js +4 -0
- package/src/components/Breadcrumbs.tsx +43 -0
- package/src/components/ObjectInspector.tsx +37 -0
- package/src/constants.tsx +34 -0
- package/src/routes/RootTabs.tsx +148 -0
- package/src/routes/config/builder/resolved/page.tsx +17 -0
- package/src/routes/config/builder/transformed/page.tsx +17 -0
- package/src/routes/config/bundler/resolved/page.tsx +17 -0
- package/src/routes/config/bundler/transformed/page.tsx +17 -0
- package/src/routes/config/framework/resolved/page.tsx +17 -0
- package/src/routes/config/framework/transformed/page.tsx +17 -0
- package/src/routes/config/layout.tsx +68 -0
- package/src/routes/config/page.loader.ts +7 -0
- package/src/routes/config/page.tsx +1 -0
- package/src/routes/context/builder/page.tsx +12 -0
- package/src/routes/context/framework/page.tsx +14 -0
- package/src/routes/context/layout.tsx +38 -0
- package/src/routes/context/page.loader.ts +7 -0
- package/src/routes/context/page.tsx +1 -0
- package/src/routes/layout.css +3 -0
- package/src/routes/layout.tsx +25 -0
- package/src/routes/overview/heading.svg +12 -0
- package/src/routes/overview/page.tsx +122 -0
- package/src/routes/page.loader.ts +7 -0
- package/src/routes/page.tsx +1 -0
- package/src/routes/pages/BaseRoute.tsx +87 -0
- package/src/routes/pages/EntryRoute/ClientRouteStats/ClientRouteStats.tsx +42 -0
- package/src/routes/pages/EntryRoute/ClientRouteStats/LegacyRouteStats.tsx +6 -0
- package/src/routes/pages/EntryRoute/ClientRouteStats/RemixRoute.tsx +85 -0
- package/src/routes/pages/EntryRoute/ClientRouteStats/RemixRouteStats.tsx +44 -0
- package/src/routes/pages/EntryRoute/ClientRouteStats/index.ts +1 -0
- package/src/routes/pages/EntryRoute/EntryRoute.tsx +25 -0
- package/src/routes/pages/EntryRoute/EntryStats.tsx +44 -0
- package/src/routes/pages/EntryRoute/MatchRemixRouteContext.ts +6 -0
- package/src/routes/pages/EntryRoute/index.ts +1 -0
- package/src/routes/pages/MatchUrl.tsx +3 -0
- package/src/routes/pages/ServerRoute.tsx +21 -0
- package/src/routes/pages/UnknownRoute.tsx +20 -0
- package/src/routes/pages/layout.tsx +13 -0
- package/src/routes/pages/page.tsx +54 -0
- package/src/rpc/index.ts +36 -0
- package/src/stores/index.tsx +141 -0
- package/src/types/index.ts +62 -0
- package/src/utils/context.tsx +22 -0
- package/src/utils/hooks.ts +23 -0
- package/src/utils/index.ts +1 -0
- package/dist/html/devtools/index.html +0 -5
- package/dist/static/css/async/devtools_page.215ea1d3.css +0 -1
- package/dist/static/js/async/devtools_page.8337a013.js +0 -1
- package/dist/static/js/async/devtools_page.8337a013.js.map +0 -1
- package/dist/static/js/devtools.56bf0fe9.js +0 -1
- package/dist/static/js/devtools.56bf0fe9.js.map +0 -1
- package/dist/static/js/lib-polyfill.db4711e8.js +0 -1
- package/dist/static/js/lib-polyfill.db4711e8.js.map +0 -1
- package/dist/static/js/lib-react.be1fdbe9.js +0 -1
- package/dist/static/js/lib-react.be1fdbe9.js.map +0 -1
- package/dist/static/js/lib-router.7b48a0f0.js +0 -1
- package/dist/static/js/lib-router.7b48a0f0.js.map +0 -1
- package/dist/static/js/vendors~node_modules_pnpm_loadable_component_5_15_3_react_18_2_0_node_modules_loadable_compon~2b20fe.2a50a754.js +0 -1
- package/dist/static/js/vendors~node_modules_pnpm_loadable_component_5_15_3_react_18_2_0_node_modules_loadable_compon~2b20fe.2a50a754.js.LICENSE.txt +0 -24
- package/dist/static/js/vendors~node_modules_pnpm_loadable_component_5_15_3_react_18_2_0_node_modules_loadable_compon~2b20fe.2a50a754.js.map +0 -1
- package/src/devtools/routes/index.css +0 -115
- package/src/devtools/routes/layout.tsx +0 -9
- package/src/devtools/routes/page.tsx +0 -19
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Card, Flex, Text } from '@radix-ui/themes';
|
|
3
|
+
import {
|
|
4
|
+
ArchiveIcon,
|
|
5
|
+
CubeIcon,
|
|
6
|
+
MixIcon,
|
|
7
|
+
GlobeIcon,
|
|
8
|
+
} from '@radix-ui/react-icons';
|
|
9
|
+
import { useSnapshot } from 'valtio';
|
|
10
|
+
import styled from '@emotion/styled';
|
|
11
|
+
import srcHeading from './heading.svg';
|
|
12
|
+
import { useStore } from '@/stores';
|
|
13
|
+
|
|
14
|
+
const BUNDLER_PACKAGE_NAMES = {
|
|
15
|
+
webpack: 'webpack',
|
|
16
|
+
rspack: '@rspack/core',
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
const Page: React.FC = () => {
|
|
20
|
+
const $store = useStore();
|
|
21
|
+
const store = useSnapshot($store);
|
|
22
|
+
const { toolsType } = store.framework.context;
|
|
23
|
+
if (toolsType !== 'app-tools') {
|
|
24
|
+
throw Error();
|
|
25
|
+
}
|
|
26
|
+
const toolsPackage = store.packages.appTools;
|
|
27
|
+
const toolsPackageVer = store.dependencies[toolsPackage]!;
|
|
28
|
+
|
|
29
|
+
const { bundlerType } = store.builder.context;
|
|
30
|
+
const bundlerVer = store.dependencies[BUNDLER_PACKAGE_NAMES[bundlerType]];
|
|
31
|
+
|
|
32
|
+
const numFrameworkPlugin = store.framework.config.transformed.plugins.length;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Flex direction="column" align="center">
|
|
36
|
+
<Flex gap="2">
|
|
37
|
+
<img src={store.assets.logo} />
|
|
38
|
+
<LogoHeading src={srcHeading} />
|
|
39
|
+
</Flex>
|
|
40
|
+
<Description>
|
|
41
|
+
{store.name.formalName} DevTools v{store.version}
|
|
42
|
+
</Description>
|
|
43
|
+
<Flex wrap="wrap" gap="3" mt="7" width="100%" justify="center">
|
|
44
|
+
<Box>
|
|
45
|
+
<Indicator icon={<ArchiveIcon width="36" height="36" color="gray" />}>
|
|
46
|
+
Compiled with {(store.compileTimeCost / 1000).toFixed(2)}s
|
|
47
|
+
</Indicator>
|
|
48
|
+
</Box>
|
|
49
|
+
<Box>
|
|
50
|
+
<Indicator
|
|
51
|
+
title="Tools"
|
|
52
|
+
icon={<CubeIcon width="36" height="36" color="gray" />}
|
|
53
|
+
>
|
|
54
|
+
<Text color="gray" size="2">
|
|
55
|
+
<Box>
|
|
56
|
+
{toolsType}@{toolsPackageVer}
|
|
57
|
+
</Box>
|
|
58
|
+
<Box>
|
|
59
|
+
{bundlerType}@{bundlerVer}
|
|
60
|
+
</Box>
|
|
61
|
+
<Box>react@{store.dependencies.react}</Box>
|
|
62
|
+
</Text>
|
|
63
|
+
</Indicator>
|
|
64
|
+
</Box>
|
|
65
|
+
<Box>
|
|
66
|
+
<Indicator
|
|
67
|
+
title="Plugin"
|
|
68
|
+
icon={<MixIcon width="36" height="36" color="gray" />}
|
|
69
|
+
>
|
|
70
|
+
<Box>
|
|
71
|
+
<Text size="2" color="gray">
|
|
72
|
+
{numFrameworkPlugin} framework plugins
|
|
73
|
+
</Text>
|
|
74
|
+
</Box>
|
|
75
|
+
<Box>
|
|
76
|
+
<Text size="2" color="gray">
|
|
77
|
+
{store.framework.context.plugins.length} builder plugins
|
|
78
|
+
</Text>
|
|
79
|
+
</Box>
|
|
80
|
+
</Indicator>
|
|
81
|
+
</Box>
|
|
82
|
+
<Box>
|
|
83
|
+
<Indicator icon={<GlobeIcon width="36" height="36" color="gray" />}>
|
|
84
|
+
{store.framework.context.serverRoutes.length} Endpoints
|
|
85
|
+
</Indicator>
|
|
86
|
+
</Box>
|
|
87
|
+
</Flex>
|
|
88
|
+
</Flex>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default Page;
|
|
93
|
+
|
|
94
|
+
interface IndicatorProps extends React.PropsWithChildren {
|
|
95
|
+
title?: string;
|
|
96
|
+
icon: React.ReactElement;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const Indicator: React.FC<IndicatorProps> = ({ title, icon, children }) => {
|
|
100
|
+
return (
|
|
101
|
+
<Card>
|
|
102
|
+
<Flex gap="4" p="2">
|
|
103
|
+
<Flex align="center" direction="column" gap="1">
|
|
104
|
+
{icon}
|
|
105
|
+
<Text size="1" color="gray">
|
|
106
|
+
{title ?? children}
|
|
107
|
+
</Text>
|
|
108
|
+
</Flex>
|
|
109
|
+
{title && <Box>{children}</Box>}
|
|
110
|
+
</Flex>
|
|
111
|
+
</Card>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const Description = styled(Text)({
|
|
116
|
+
fontSize: 'var(--font-size-1)',
|
|
117
|
+
color: 'var(--gray-7)',
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const LogoHeading = styled.img({
|
|
121
|
+
width: '10rem',
|
|
122
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default null;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React, { useContext, useState } from 'react';
|
|
2
|
+
import type { ServerRoute as IServerRoute } from '@modern-js/types';
|
|
3
|
+
import * as Collapsible from '@radix-ui/react-collapsible';
|
|
4
|
+
import { Box, Card, Text } from '@radix-ui/themes';
|
|
5
|
+
import { CaretSortIcon } from '@radix-ui/react-icons';
|
|
6
|
+
import { parseURL } from 'ufo';
|
|
7
|
+
import styled from '@emotion/styled';
|
|
8
|
+
import { MatchUrlContext } from './MatchUrl';
|
|
9
|
+
|
|
10
|
+
export interface BaseRouteProps extends React.PropsWithChildren {
|
|
11
|
+
badge: React.ReactElement;
|
|
12
|
+
route: IServerRoute;
|
|
13
|
+
title: string;
|
|
14
|
+
open?: boolean;
|
|
15
|
+
onOpenChange?: (open: boolean) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const BaseRoute: React.FC<BaseRouteProps> = ({
|
|
19
|
+
badge,
|
|
20
|
+
route,
|
|
21
|
+
title,
|
|
22
|
+
children,
|
|
23
|
+
open,
|
|
24
|
+
onOpenChange,
|
|
25
|
+
}) => {
|
|
26
|
+
const url = useContext(MatchUrlContext);
|
|
27
|
+
const [_open, _setOpen] = useState(false);
|
|
28
|
+
const isMatching = Boolean(url);
|
|
29
|
+
const { pathname } = parseURL(url);
|
|
30
|
+
const isMatched =
|
|
31
|
+
pathname === route.urlPath || pathname.startsWith(`${route.urlPath}/`);
|
|
32
|
+
const isOpen = isMatched || (open ?? _open);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Container>
|
|
36
|
+
<Collapsible.Root open={isOpen} onOpenChange={onOpenChange ?? _setOpen}>
|
|
37
|
+
<CollapsibleTrigger>
|
|
38
|
+
{badge}
|
|
39
|
+
<UrlText data-miss-matched={isMatching && !isMatched}>
|
|
40
|
+
{title}
|
|
41
|
+
</UrlText>
|
|
42
|
+
<Box grow="1" />
|
|
43
|
+
<CollapsedMark open={isOpen}>
|
|
44
|
+
<CaretSortIcon />
|
|
45
|
+
</CollapsedMark>
|
|
46
|
+
</CollapsibleTrigger>
|
|
47
|
+
<CollapsibleContent>{children}</CollapsibleContent>
|
|
48
|
+
</Collapsible.Root>
|
|
49
|
+
</Container>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const Container = styled(Card)({
|
|
54
|
+
backgroundColor: '#181818',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const CollapsibleTrigger = styled(Collapsible.Trigger)({
|
|
58
|
+
all: 'unset',
|
|
59
|
+
width: '100%',
|
|
60
|
+
display: 'flex',
|
|
61
|
+
gap: 'var(--space-2)',
|
|
62
|
+
alignItems: 'center',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const CollapsibleContent = styled(Collapsible.Content)({
|
|
66
|
+
paddingTop: 'var(--space-2)',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const UrlText = styled(Text)({
|
|
70
|
+
color: 'var(--gray-12)',
|
|
71
|
+
fontSize: 'var(--font-size-2)',
|
|
72
|
+
transition: 'color 200ms',
|
|
73
|
+
'&[data-miss-matched="true"]': {
|
|
74
|
+
color: 'var(--gray-10)',
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const CollapsedMark = styled(Box)<{ open?: boolean }>(props => ({
|
|
79
|
+
backgroundColor: props.open ? 'var(--gray-4)' : 'transparent',
|
|
80
|
+
height: 'var(--font-size-5)',
|
|
81
|
+
width: 'var(--font-size-5)',
|
|
82
|
+
borderRadius: 'var(--radius-2)',
|
|
83
|
+
display: 'flex',
|
|
84
|
+
justifyContent: 'center',
|
|
85
|
+
alignItems: 'center',
|
|
86
|
+
transition: 'background-color 100ms',
|
|
87
|
+
}));
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { FileSystemRoutes } from '@modern-js/devtools-kit';
|
|
2
|
+
import type { RouteLegacy, ServerRoute } from '@modern-js/types';
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { useSnapshot } from 'valtio';
|
|
6
|
+
import { LegacyRouteStats } from './LegacyRouteStats';
|
|
7
|
+
import { RemixRouteStats } from './RemixRouteStats';
|
|
8
|
+
import { useStore } from '@/stores';
|
|
9
|
+
|
|
10
|
+
export interface ClientRouteStatsProps {
|
|
11
|
+
route: ServerRoute;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ClientRouteStats: React.FC<ClientRouteStatsProps> = ({
|
|
15
|
+
route,
|
|
16
|
+
}) => {
|
|
17
|
+
const $store = useStore();
|
|
18
|
+
const store = useSnapshot($store);
|
|
19
|
+
const { entrypoints } = store.framework.context;
|
|
20
|
+
const entrypoint =
|
|
21
|
+
route.entryName && _.find(entrypoints, { entryName: route.entryName });
|
|
22
|
+
|
|
23
|
+
if (!entrypoint) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Can't found the entrypoint named ${JSON.stringify(route.entryName)}`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const fileSystemRoute =
|
|
30
|
+
store.framework.fileSystemRoutes[entrypoint.entryName];
|
|
31
|
+
|
|
32
|
+
if (isLegacyRoutes(fileSystemRoute as any)) {
|
|
33
|
+
return <LegacyRouteStats />;
|
|
34
|
+
} else {
|
|
35
|
+
return (
|
|
36
|
+
<RemixRouteStats remixRoutes={fileSystemRoute as any} route={route} />
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const isLegacyRoutes = (routes: FileSystemRoutes): routes is RouteLegacy[] =>
|
|
42
|
+
routes[0] && !('type' in routes[0]);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React, { useContext, useRef } from 'react';
|
|
2
|
+
import { RouteObject } from '@modern-js/runtime/router';
|
|
3
|
+
import { Box, Flex, Link, Code } from '@radix-ui/themes';
|
|
4
|
+
import styled from '@emotion/styled';
|
|
5
|
+
import _ from 'lodash';
|
|
6
|
+
import { withoutTrailingSlash } from 'ufo';
|
|
7
|
+
import { useHoverDirty } from 'react-use';
|
|
8
|
+
import { MatchRemixRouteContext } from '../MatchRemixRouteContext';
|
|
9
|
+
|
|
10
|
+
export interface RemixRouteProps {
|
|
11
|
+
route: RouteObject | RouteObject[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const RemixRoute: React.FC<RemixRouteProps> = ({ route }) => {
|
|
15
|
+
const routes = _.castArray(route);
|
|
16
|
+
const curr = _.last(routes)!;
|
|
17
|
+
const componentFile =
|
|
18
|
+
'_component' in curr && _.isString(curr._component)
|
|
19
|
+
? curr._component
|
|
20
|
+
: null;
|
|
21
|
+
const displayPath = routes
|
|
22
|
+
.map(r => r.path && withoutTrailingSlash(r.path))
|
|
23
|
+
.filter(_.isString)
|
|
24
|
+
.join('/');
|
|
25
|
+
const isIndex = curr.index ?? false;
|
|
26
|
+
const isRoot = displayPath === '/';
|
|
27
|
+
const matched = useContext(MatchRemixRouteContext);
|
|
28
|
+
const isMatching = matched.length > 0;
|
|
29
|
+
const isMatched = _.find(matched, { route: { id: curr.id } });
|
|
30
|
+
|
|
31
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
32
|
+
const hovered = useHoverDirty(ref);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Box ref={ref}>
|
|
36
|
+
<Flex gap="2" align="center" mb={curr.children && '1'}>
|
|
37
|
+
<EndpointContainer data-miss-matched={isMatching && !isMatched}>
|
|
38
|
+
{!isRoot && (
|
|
39
|
+
<EndpointTag data-compose={isIndex && 'head'}>
|
|
40
|
+
{displayPath}
|
|
41
|
+
</EndpointTag>
|
|
42
|
+
)}
|
|
43
|
+
{isIndex && (
|
|
44
|
+
<EndpointTag color="purple" data-compose={!isRoot && 'tail'}>
|
|
45
|
+
/(index)
|
|
46
|
+
</EndpointTag>
|
|
47
|
+
)}
|
|
48
|
+
</EndpointContainer>
|
|
49
|
+
{hovered && componentFile && <ShyLink>{componentFile}</ShyLink>}
|
|
50
|
+
</Flex>
|
|
51
|
+
<Flex direction="column" gap="1">
|
|
52
|
+
{curr.children?.map(route => (
|
|
53
|
+
<RemixRoute key={route.id} route={routes.concat(route)} />
|
|
54
|
+
))}
|
|
55
|
+
</Flex>
|
|
56
|
+
</Box>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const EndpointTag = styled(Code)({
|
|
61
|
+
fontSize: 'var(--font-size-2)',
|
|
62
|
+
'&[data-compose="head"]': {
|
|
63
|
+
paddingRight: '0',
|
|
64
|
+
borderTopRightRadius: '0',
|
|
65
|
+
borderBottomRightRadius: '0',
|
|
66
|
+
},
|
|
67
|
+
'&[data-compose="tail"]': {
|
|
68
|
+
paddingLeft: '0',
|
|
69
|
+
borderTopLeftRadius: '0',
|
|
70
|
+
borderBottomLeftRadius: '0',
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const ShyLink = styled(Link)({
|
|
75
|
+
color: 'var(--gray-9)',
|
|
76
|
+
fontSize: 'var(--font-size-1)',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const EndpointContainer = styled(Box)({
|
|
80
|
+
display: 'flex',
|
|
81
|
+
transition: 'opacity 200ms',
|
|
82
|
+
'&[data-miss-matched="true"]': {
|
|
83
|
+
opacity: '0.5',
|
|
84
|
+
},
|
|
85
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RouteMatch,
|
|
3
|
+
RouteObject,
|
|
4
|
+
matchRoutes as matchRemixRoutes,
|
|
5
|
+
} from '@modern-js/runtime/router';
|
|
6
|
+
import type { ServerRoute } from '@modern-js/types';
|
|
7
|
+
import { Flex } from '@radix-ui/themes';
|
|
8
|
+
import React, { useContext, useMemo } from 'react';
|
|
9
|
+
import { MatchUrlContext } from '../../MatchUrl';
|
|
10
|
+
import { MatchRemixRouteContext } from '../MatchRemixRouteContext';
|
|
11
|
+
import { RemixRoute } from './RemixRoute';
|
|
12
|
+
|
|
13
|
+
export interface RemixRouteStatsProps {
|
|
14
|
+
remixRoutes: RouteObject[];
|
|
15
|
+
route: ServerRoute;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const RemixRouteStats: React.FC<RemixRouteStatsProps> = ({
|
|
19
|
+
remixRoutes,
|
|
20
|
+
route,
|
|
21
|
+
}) => {
|
|
22
|
+
const testingUrl = useContext(MatchUrlContext);
|
|
23
|
+
const matchedRoutes = useMemo(() => {
|
|
24
|
+
if (!testingUrl || !remixRoutes) return [];
|
|
25
|
+
const location = testingUrl.replace(route.urlPath, '');
|
|
26
|
+
const matched = matchRemixRoutes(remixRoutes, location) ?? [];
|
|
27
|
+
return matched as RouteMatch<string, RouteObject>[];
|
|
28
|
+
}, [remixRoutes, testingUrl]);
|
|
29
|
+
|
|
30
|
+
if (!remixRoutes.length) return null;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<MatchRemixRouteContext.Provider value={matchedRoutes}>
|
|
34
|
+
<Flex gap="2" direction="column">
|
|
35
|
+
{remixRoutes.map(r => (
|
|
36
|
+
<RemixRoute
|
|
37
|
+
key={r.id}
|
|
38
|
+
route={{ ...r, path: route.urlPath + (r.path ?? '') }}
|
|
39
|
+
/>
|
|
40
|
+
))}
|
|
41
|
+
</Flex>
|
|
42
|
+
</MatchRemixRouteContext.Provider>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ClientRouteStats';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Badge, Flex } from '@radix-ui/themes';
|
|
3
|
+
import { ServerRoute } from '@modern-js/types';
|
|
4
|
+
import { BaseRoute } from '../BaseRoute';
|
|
5
|
+
import { EntryStats } from './EntryStats';
|
|
6
|
+
import { ClientRouteStats } from './ClientRouteStats';
|
|
7
|
+
|
|
8
|
+
export interface EntryRouteProps {
|
|
9
|
+
route: ServerRoute;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const EntryRoute: React.FC<EntryRouteProps> = ({ route }) => {
|
|
13
|
+
return (
|
|
14
|
+
<BaseRoute badge={badge} route={route} title={route.urlPath}>
|
|
15
|
+
<Flex direction="column" gap="2">
|
|
16
|
+
<EntryStats route={route} />
|
|
17
|
+
<ClientRouteStats route={route} />
|
|
18
|
+
</Flex>
|
|
19
|
+
</BaseRoute>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default EntryRoute;
|
|
24
|
+
|
|
25
|
+
const badge = <Badge color="cyan">Entry</Badge>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import { useSnapshot } from 'valtio';
|
|
4
|
+
import type { ServerRoute } from '@modern-js/types';
|
|
5
|
+
import { Box, Card, Flex, Strong, Text } from '@radix-ui/themes';
|
|
6
|
+
import styled from '@emotion/styled';
|
|
7
|
+
import { useStore } from '@/stores';
|
|
8
|
+
|
|
9
|
+
export interface EntryStatsProps {
|
|
10
|
+
route: ServerRoute;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const EntryStats: React.FC<EntryStatsProps> = ({ route }) => {
|
|
14
|
+
const $store = useStore();
|
|
15
|
+
const store = useSnapshot($store);
|
|
16
|
+
const { entrypoints } = store.framework.context;
|
|
17
|
+
const entrypoint =
|
|
18
|
+
route.entryName && _.find(entrypoints, { entryName: route.entryName });
|
|
19
|
+
|
|
20
|
+
if (!entrypoint) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Can't found the entrypoint named ${JSON.stringify(route.entryName)}`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Stats>
|
|
28
|
+
<Box>
|
|
29
|
+
<Flex gap="1">
|
|
30
|
+
<Strong>Directory: </Strong>
|
|
31
|
+
<Text>{entrypoint.absoluteEntryDir}</Text>
|
|
32
|
+
</Flex>
|
|
33
|
+
<Flex gap="1">
|
|
34
|
+
<Strong>Entry:</Strong>
|
|
35
|
+
<Text>{entrypoint.entry}</Text>
|
|
36
|
+
</Flex>
|
|
37
|
+
</Box>
|
|
38
|
+
</Stats>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const Stats = styled(Card)({
|
|
43
|
+
fontSize: 'var(--font-size-1)',
|
|
44
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './EntryRoute';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ServerRoute as IServerRoute } from '@modern-js/types';
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
import EntryRoute from './EntryRoute/EntryRoute';
|
|
5
|
+
import UnknownRoute from './UnknownRoute';
|
|
6
|
+
|
|
7
|
+
export interface ServerRouteProps {
|
|
8
|
+
route: IServerRoute;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ServerRoute: React.FC<ServerRouteProps> = ({ route }) => {
|
|
12
|
+
return dispatchServerRoute(route);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const dispatchServerRoute = (route: IServerRoute) => {
|
|
16
|
+
if (_.isString(route.entryName)) {
|
|
17
|
+
return <EntryRoute route={route} />;
|
|
18
|
+
} else {
|
|
19
|
+
return <UnknownRoute route={route} />;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Badge, Card } from '@radix-ui/themes';
|
|
3
|
+
import { ServerRoute } from '@modern-js/types';
|
|
4
|
+
import { BaseRoute } from './BaseRoute';
|
|
5
|
+
|
|
6
|
+
export interface UnknownRouteProps {
|
|
7
|
+
route: ServerRoute;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const UnknownRoute: React.FC<UnknownRouteProps> = ({ route }) => {
|
|
11
|
+
return (
|
|
12
|
+
<BaseRoute badge={badge} route={route} title={route.urlPath}>
|
|
13
|
+
<Card>Unknown route type.</Card>
|
|
14
|
+
</BaseRoute>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default UnknownRoute;
|
|
19
|
+
|
|
20
|
+
const badge = <Badge color="gray">Unknown</Badge>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Outlet } from '@modern-js/runtime/router';
|
|
2
|
+
import { ScrollArea } from '@radix-ui/themes';
|
|
3
|
+
import { Suspense } from 'react';
|
|
4
|
+
|
|
5
|
+
export default function Layout() {
|
|
6
|
+
return (
|
|
7
|
+
<ScrollArea scrollbars="both">
|
|
8
|
+
<Suspense fallback={'loading...'}>
|
|
9
|
+
<Outlet />
|
|
10
|
+
</Suspense>
|
|
11
|
+
</ScrollArea>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import styled from '@emotion/styled';
|
|
2
|
+
import { Box, Text, TextField } from '@radix-ui/themes';
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { useSnapshot } from 'valtio';
|
|
5
|
+
import { MatchUrlContext } from './MatchUrl';
|
|
6
|
+
import { ServerRoute } from './ServerRoute';
|
|
7
|
+
import { useStore } from '@/stores';
|
|
8
|
+
|
|
9
|
+
const Page: React.FC = () => {
|
|
10
|
+
const $store = useStore();
|
|
11
|
+
const store = useSnapshot($store);
|
|
12
|
+
const { serverRoutes } = store.framework.context;
|
|
13
|
+
|
|
14
|
+
const [testingUrl, setTestingUrl] = useState<string>('');
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<MatchUrlContext.Provider value={testingUrl}>
|
|
18
|
+
<Container>
|
|
19
|
+
<Box>
|
|
20
|
+
<TextField.Root>
|
|
21
|
+
<TextField.Slot>
|
|
22
|
+
<Text size="2">test:</Text>
|
|
23
|
+
</TextField.Slot>
|
|
24
|
+
<TextField.Input
|
|
25
|
+
placeholder="/foo?bar#baz"
|
|
26
|
+
onChange={e => setTestingUrl(e.target.value)}
|
|
27
|
+
type="search"
|
|
28
|
+
autoComplete="false"
|
|
29
|
+
autoCapitalize="false"
|
|
30
|
+
autoCorrect="false"
|
|
31
|
+
/>
|
|
32
|
+
</TextField.Root>
|
|
33
|
+
</Box>
|
|
34
|
+
<Box height="2" />
|
|
35
|
+
<RoutesContainer>
|
|
36
|
+
{serverRoutes.map(route => (
|
|
37
|
+
<ServerRoute key={route.entryPath} route={route} />
|
|
38
|
+
))}
|
|
39
|
+
</RoutesContainer>
|
|
40
|
+
</Container>
|
|
41
|
+
</MatchUrlContext.Provider>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default Page;
|
|
46
|
+
|
|
47
|
+
const Container = styled(Box)({});
|
|
48
|
+
|
|
49
|
+
const RoutesContainer = styled(Box)({
|
|
50
|
+
display: 'flex',
|
|
51
|
+
flexDirection: 'column',
|
|
52
|
+
gap: 'var(--space-2)',
|
|
53
|
+
justifyContent: 'space-between',
|
|
54
|
+
});
|
package/src/rpc/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ClientFunctions, ServerFunctions } from '@modern-js/devtools-kit';
|
|
2
|
+
import { createBirpc } from 'birpc';
|
|
3
|
+
import { StoreContextValue } from '@/types';
|
|
4
|
+
|
|
5
|
+
export interface SetupOptions {
|
|
6
|
+
url: string;
|
|
7
|
+
$store: StoreContextValue;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const setupServerConnection = async (options: SetupOptions) => {
|
|
11
|
+
const { url, $store } = options;
|
|
12
|
+
const ws = new window.WebSocket(url);
|
|
13
|
+
|
|
14
|
+
const server = createBirpc<ServerFunctions, ClientFunctions>(
|
|
15
|
+
{
|
|
16
|
+
refresh: () => location.reload(),
|
|
17
|
+
updateFileSystemRoutes({ entrypoint, routes }) {
|
|
18
|
+
$store.framework.fileSystemRoutes[entrypoint.entryName] = routes;
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
post: data => ws.send(data),
|
|
23
|
+
on: cb => (ws.onmessage = cb),
|
|
24
|
+
serialize: v => JSON.stringify(v),
|
|
25
|
+
deserialize: v => JSON.parse(v.data.toString()),
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
await new Promise<void>((resolve, reject) => {
|
|
30
|
+
ws.onopen = () => resolve();
|
|
31
|
+
ws.onerror = () =>
|
|
32
|
+
reject(new Error(`Failed connect to WebSocket server: ${url}`));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return { server };
|
|
36
|
+
};
|