Skip to content

Commit

Permalink
feat(devtools): static data source (#5840)
Browse files Browse the repository at this point in the history
  • Loading branch information
Asuka109 committed Jun 19, 2024
1 parent 413b27f commit bf1d59f
Show file tree
Hide file tree
Showing 50 changed files with 1,106 additions and 917 deletions.
3 changes: 2 additions & 1 deletion packages/devtools/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@
"typescript": "~5.0.4",
"ufo": "^1.3.0",
"@radix-ui/react-toast": "^1.1.5",
"valtio": "^1.11.1"
"valtio": "^1.11.1",
"flatted": "^3.2.9"
},
"dependencies": {
"cookie-es": "^1.0.0"
Expand Down
16 changes: 8 additions & 8 deletions packages/devtools/client/src/components/Devtools/Capsule.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { SetupClientParams } from '@modern-js/devtools-kit/runtime';
import { Flex, Theme } from '@radix-ui/themes';
import React, { useEffect, useState } from 'react';
import { useAsync, useEvent, useToggle } from 'react-use';
import { HiMiniCursorArrowRipple } from 'react-icons/hi2';
import { withQuery } from 'ufo';
import Visible from '../Visible';
import styles from './Capsule.module.scss';
import { FrameBox } from './FrameBox';
Expand All @@ -25,14 +23,16 @@ const parseDeepLink = (url = window.location) => {
return pathname;
};

export const DevtoolsCapsule: React.FC<SetupClientParams> = props => {
const logoSrc = props.def.assets.logo;
export interface DevtoolsCapsuleProps {
src: string;
logo: string;
}

export const DevtoolsCapsule: React.FC<DevtoolsCapsuleProps> = props => {
const deepLink = parseDeepLink();
const [showDevtools, toggleDevtools] = useToggle(Boolean(deepLink));
const [loadDevtools, setLoadDevtools] = useState(false);

const src = withQuery(props.endpoint, { src: props.dataSource });

const draggable = useStickyDraggable({ clamp: true });

const [appearance] = useThemeAppearance();
Expand Down Expand Up @@ -90,15 +90,15 @@ export const DevtoolsCapsule: React.FC<SetupClientParams> = props => {
<Visible when={showDevtools} keepAlive={true} load={loadDevtools}>
<div className={styles.container}>
<FrameBox
src={src}
src={props.src}
onClose={() => toggleDevtools(false)}
style={{ pointerEvents: draggable.isDragging ? 'none' : 'auto' }}
/>
</div>
</Visible>
<Flex className={styles.fab} {...draggable.props} align="center">
<DevtoolsCapsuleButton type="primary" onClick={toggleDevtools}>
<img className={styles.logo} src={logoSrc}></img>
<img className={styles.logo} src={props.logo} />
</DevtoolsCapsuleButton>
<DevtoolsCapsuleButton onClick={handleClickInspect}>
<HiMiniCursorArrowRipple />
Expand Down
9 changes: 4 additions & 5 deletions packages/devtools/client/src/components/Devtools/Puller.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import React, { useEffect } from 'react';
import { useNavigate } from '@modern-js/runtime/router';
import { useThrowable } from '@/utils';
import { $mountPoint } from '@/entries/client/routes/state';
import { $$globals, useGlobals } from '@/entries/client/globals';

let _intendPullUp = '';

$mountPoint.then(({ hooks }) => {
hooks.hookOnce('pullUp', async target => {
$$globals.then(({ mountPoint }) => {
mountPoint.hooks.hookOnce('pullUp', async target => {
_intendPullUp = target;
});
});

export const Puller: React.FC = () => {
const navigate = useNavigate();
const mountPoint = useThrowable($mountPoint);
const { mountPoint } = useGlobals();
useEffect(() => {
_intendPullUp && navigate(_intendPullUp);
mountPoint.hooks.hook('pullUp', async target => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { proxy, useSnapshot } from 'valtio';
import { Promisable } from 'type-fest';
import { LegacyRouteStats } from './LegacyRoute/Stats';
import { RemixRouteStats } from './RemixRoute/Stats';
import { $serverExported } from '@/entries/client/routes/state';
import { useGlobals } from '@/entries/client/globals';

export const $fileSystemRoutes = proxy<
Record<string, Promisable<FileSystemRoutes>>
Expand All @@ -23,14 +23,15 @@ export const ClientRouteStats: React.FC<ClientRouteStatsProps> = ({
if (!entryName) {
throw new TypeError('');
}
const { entrypoints } = useSnapshot($serverExported.framework).context;
const $globals = useGlobals();
const { entrypoints } = useSnapshot($globals.framework).context;
const entrypoint = _.find(entrypoints, { entryName });
if (!entrypoint) {
throw new TypeError(
`Can't found the entrypoint named ${JSON.stringify(route.entryName)}`,
);
}
const fileSystemRoutes = useSnapshot($serverExported.fileSystemRoutes);
const fileSystemRoutes = useSnapshot($globals.fileSystemRoutes);
const fileSystemRoute = fileSystemRoutes[entrypoint.entryName];

if (!entrypoint) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import type { ServerRoute } from '@modern-js/types';
import { Box, Flex, Strong, Text } from '@radix-ui/themes';
import { useSnapshot } from 'valtio';
import styles from './Stats.module.scss';
import { $serverExported } from '@/entries/client/routes/state';
import { useGlobals } from '@/entries/client/globals';

export interface EntryStatsProps {
route: ServerRoute;
}

export const EntryStats: React.FC<EntryStatsProps> = ({ route }) => {
const { entrypoints } = useSnapshot($serverExported.framework).context;
const $globals = useGlobals();
const { entrypoints } = useSnapshot($globals.framework).context;
const entrypoint =
route.entryName && _.find(entrypoints, { entryName: route.entryName });

Expand Down
180 changes: 180 additions & 0 deletions packages/devtools/client/src/entries/client/globals.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import {
ServerManifest,
MessagePortChannel,
ServerFunctions,
Tab,
ClientFunctions as ToServerFunctions,
WebSocketChannel,
applyOperation,
reviver,
} from '@modern-js/devtools-kit/runtime';
import { createBirpc } from 'birpc';
import { createHooks } from 'hookable';
import _ from 'lodash';
import {
HiMiniCircleStack,
HiOutlineAcademicCap,
HiOutlineAdjustmentsHorizontal,
HiOutlineCube,
HiOutlineHome,
HiOutlineRectangleGroup,
} from 'react-icons/hi2';
import { RiReactjsLine, RiShieldCrossLine } from 'react-icons/ri';
import { parseQuery, resolveURL } from 'ufo';
import { proxy, ref } from 'valtio';
import type {
MountPointFunctions,
ClientFunctions as ToMountPointFunctions,
} from '@/types/rpc';
import { use } from '@/utils';
import { PluginGlobals, setupPlugins } from '@/utils/pluggable';

const initializeMountPoint = async () => {
const channel = await MessagePortChannel.link(
window.parent,
'channel:connect:client',
);
const hooks = createHooks<ToMountPointFunctions>();
const definitions: ToMountPointFunctions = {
async pullUp(target) {
await hooks.callHook('pullUp', target);
},
};
const remote = createBirpc<MountPointFunctions, ToMountPointFunctions>(
definitions,
{ ...channel.handlers, timeout: 8_000 },
);
return { remote, hooks };
};

const initializeManifest = async (url: string) => {
const res = await fetch(url);
const json: ServerManifest = JSON.parse(await res.text(), reviver());
return json;
};

const initializeServer = async (url: string, state: ServerManifest) => {
const _url = new URL(url, location.href);
const socket = new window.WebSocket(_url);
const channel = await WebSocketChannel.link(socket);
const hooks = createHooks<ToServerFunctions>();
const definitions: ToServerFunctions = {
async refresh() {
location.reload();
},
async applyStateOperations(ops) {
for (const op of ops) {
applyOperation(state, op);
}
},
};
const remote = createBirpc<ServerFunctions, ToServerFunctions>(definitions, {
...channel.handlers,
timeout: 5000,
});
const server = { remote, hooks, url: _url };
return server;
};

const initializeTabs = async () => {
const globals = PluginGlobals.use();
const tabs: Tab[] = proxy([
{
name: 'overview',
title: 'Overview',
icon: <HiOutlineHome />,
view: { type: 'builtin', src: '/overview' },
},
{
name: 'config',
title: 'Config',
icon: <HiOutlineAdjustmentsHorizontal />,
view: { type: 'builtin', src: '/config' },
},
{
name: 'pages',
title: 'Pages',
icon: <HiOutlineRectangleGroup />,
view: { type: 'builtin', src: '/pages' },
},
{
name: 'react',
title: 'React',
icon: <RiReactjsLine />,
view: { type: 'builtin', src: '/react' },
},
{
name: 'context',
title: 'Context',
icon: <HiOutlineCube />,
view: { type: 'builtin', src: '/context' },
},
{
name: 'headers',
title: 'Header Modifier',
icon: <HiOutlineAcademicCap />,
view: { type: 'builtin', src: '/headers' },
},
{
name: 'doctor',
title: 'Doctor',
icon: <RiShieldCrossLine />,
view: { type: 'builtin', src: '/doctor' },
},
{
name: 'storage',
title: 'Storage',
icon: <HiMiniCircleStack />,
view: { type: 'builtin', src: '/storage/preset' },
},
]);
await globals.callHook('tab:list', tabs);
for (const tab of tabs) {
if (typeof tab.icon === 'object') {
tab.icon = ref(tab.icon);
}
if (tab.view.type === 'external') {
tab.view.component = ref(tab.view.component);
}
}
return tabs;
};

const initializeState = async (url: string) => {
const query = parseQuery(url);
const manifestUrl = _.castArray(query.src)[0] ?? resolveURL(url, 'manifest');

const $mountPoint = initializeMountPoint();
const $manifest = initializeManifest(manifestUrl);
const $server = $manifest.then(manifest =>
manifest.websocket
? initializeServer(manifest.websocket, proxy(manifest))
: null,
);
const $setupPlugins = $manifest.then(exported => {
const runtimePlugins = exported.context.def.plugins;
return setupPlugins(runtimePlugins);
});
const $tabs = $setupPlugins.then(initializeTabs);

const [mountPoint, exported, server, tabs] = await Promise.all([
$mountPoint,
$manifest,
$server,
$tabs,
]);

return proxy({
...exported,
version: process.env.PKG_VERSION,
tabs,
server: server ? ref(server) : null,
mountPoint: ref(mountPoint),
});
};

export const $$globals = initializeState(location.href);

export const useGlobals = () => use($$globals);

export type GlobalState = Awaited<typeof $$globals>;
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React from 'react';
import { useParams } from '@modern-js/runtime/router';
import { useSnapshot } from 'valtio';
import { $serverExported } from '../../state';
import { useGlobals } from '@/entries/client/globals';
import { ObjectInspector } from '@/components/ObjectInspector';

const Page: React.FC = () => {
const globals = useGlobals();
const configSet = {
framework: useSnapshot($serverExported.framework.config),
builder: useSnapshot($serverExported.builder.config),
bundler: useSnapshot($serverExported.bundler.configs),
framework: useSnapshot(globals.framework.config),
builder: useSnapshot(globals.builder.config),
bundler: useSnapshot(globals.bundler.configs),
} as const;
const { toolkit, type } = useParams() as {
toolkit: 'framework' | 'builder' | 'bundler';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from 'react';
import { useSnapshot } from 'valtio';
import { $serverExported } from '../../state';
import { useGlobals } from '@/entries/client/globals';
import { ObjectInspector } from '@/components/ObjectInspector';

const Page: React.FC = () => {
const { context } = useSnapshot($serverExported.framework);
const $globals = useGlobals();
const { context } = useSnapshot($globals.framework);
return <ObjectInspector data={context} sortObjectKeys={true} />;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from 'react';
import { useSnapshot } from 'valtio';
import { $serverExported } from '../../state';
import { useGlobals } from '@/entries/client/globals';
import { ObjectInspector } from '@/components/ObjectInspector';

const Page: React.FC = () => {
const { context } = useSnapshot($serverExported.framework);
const serverExported = useGlobals();
const { context } = useSnapshot(serverExported.framework);
return <ObjectInspector data={context} sortObjectKeys={true} />;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useRouteError } from '@modern-js/runtime/router';
import { Box, Link } from '@radix-ui/themes';
import { parseURL } from 'ufo';
import { useSnapshot } from 'valtio';
import { $serverExported } from '../state';
import { useGlobals } from '@/entries/client/globals';
import {
ErrorFallbackProps,
ErrorRouteHandler,
Expand All @@ -12,7 +12,8 @@ import { FeatureDisabled } from '@/components/Error/FeatureDisabled';

const Handler: FC<ErrorFallbackProps> = () => {
const error = useRouteError();
const { def } = useSnapshot($serverExported).context;
const serverExported = useGlobals();
const { def } = useSnapshot(serverExported).context;
const isStateError =
error && typeof error === 'object' && Object.keys(error).length === 0;
if (isStateError) {
Expand Down
Loading

0 comments on commit bf1d59f

Please sign in to comment.