Skip to content

Commit

Permalink
feat(devtools/client): switch to hash router (#5876)
Browse files Browse the repository at this point in the history
  • Loading branch information
Asuka109 authored Jul 10, 2024
1 parent 35cddd7 commit 7002e03
Show file tree
Hide file tree
Showing 27 changed files with 346 additions and 248 deletions.
36 changes: 33 additions & 3 deletions packages/devtools/client/modern.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from 'path';
import { appTools, defineConfig } from '@modern-js/app-tools';
import { nanoid } from '@modern-js/utils';
import { DistPathConfig } from '@rsbuild/core';
import { ROUTE_BASENAME } from '@modern-js/devtools-kit/runtime';
import { ServiceWorkerCompilerPlugin } from './plugins/ServiceWorkerCompilerPlugin';
import packageMeta from './package.json';
Expand All @@ -15,16 +16,44 @@ if (process.env.NODE_ENV === 'production') {
globalVars.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { isDisabled: true };
}

const assetPrefix = process.env.ASSET_PREFIX || ROUTE_BASENAME;
const distPathTypes = [
'root',
'js',
'jsAsync',
'css',
'cssAsync',
'svg',
'font',
'html',
'wasm',
'image',
'media',
'server',
'worker',
] as const;
const distPath: DistPathConfig = { html: 'static/html' };
for (const type of distPathTypes) {
const varName = `DIST_PATH_${type.toUpperCase()}`;
const value = process.env[varName];
value && (distPath[type] = value);
}
console.log('Build @modern-js/devtools-client with asset prefix:', assetPrefix);
console.log(
'Build @modern-js/devtools-client with dist path config:',
JSON.stringify(distPath, null, 2),
);

// https://modernjs.dev/en/configure/app/usage
export default defineConfig<'rspack'>({
runtime: {
router: {
basename: ROUTE_BASENAME,
supportHtml5History: false,
},
},
dev: {
assetPrefix: ROUTE_BASENAME,
port: 8780,
assetPrefix: 'http://localhost:8780',
},
source: {
mainEntryName: 'client',
Expand All @@ -47,7 +76,8 @@ export default defineConfig<'rspack'>({
},
},
output: {
assetPrefix: ROUTE_BASENAME,
distPath,
assetPrefix,
disableInlineRuntimeChunk: true,
disableSourceMap: process.env.NODE_ENV === 'production',
},
Expand Down
68 changes: 58 additions & 10 deletions packages/devtools/client/src/entries/client/globals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,20 @@ import {
HiOutlineRectangleGroup,
} from 'react-icons/hi2';
import { RiReactjsLine, RiShieldCrossLine } from 'react-icons/ri';
import { parseQuery, resolveURL } from 'ufo';
import {
createBridge,
createStore,
initialize,
} from 'react-devtools-inline/frontend';
import { parseQuery, parseURL, 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';
import { WallAgent } from '@/utils/react-devtools';

const initializeMountPoint = async () => {
const channel = await MessagePortChannel.link(
Expand All @@ -47,10 +53,24 @@ const initializeMountPoint = async () => {
return { remote, hooks };
};

class InitializeManifestError extends Error {
constructor(cause?: unknown) {
super();
this.name = 'InitializeManifestError';
this.cause = cause;
this.message = 'Failed to initialize manifest of DevTools';
}
}

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

const initializeServer = async (url: string, state: ServerManifest) => {
Expand Down Expand Up @@ -140,8 +160,29 @@ const initializeTabs = async () => {
return tabs;
};

const initializeReactDevtools = async (
mountPoint: Awaited<ReturnType<typeof initializeMountPoint>>,
) => {
const wallAgent = new WallAgent();
const unreg = wallAgent.bindRemote(
mountPoint.remote,
'sendReactDevtoolsData',
);

await mountPoint.remote.activateReactDevtools();
const bridge = createBridge(window.parent, wallAgent);
// const _shutdownBridge = bridge.shutdown;
// Workaround for https://github.com/facebook/react/blob/77ec61885fb19607cdd116a6790095afa40b5a94/packages/react-devtools-shared/src/devtools/views/DevTools.js#L258-L267
bridge.shutdown = () => null;

const store = createStore(bridge);
const Renderer = initialize(window.parent, { bridge, store });

return { store, bridge, wallAgent, unreg, Renderer };
};

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

const $mountPoint = initializeMountPoint();
Expand All @@ -156,20 +197,27 @@ const initializeState = async (url: string) => {
return setupPlugins(runtimePlugins);
});
const $tabs = $setupPlugins.then(initializeTabs);
const $reactDevtools = $mountPoint.then(initializeReactDevtools);

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

return proxy({
...exported,
version: process.env.PKG_VERSION,
tabs,
server: server ? ref(server) : null,
mountPoint: ref(mountPoint),
react: {
devtools: {
store: ref(reactDevtools.store),
bridge: ref(reactDevtools.bridge),
wallAgent: ref(reactDevtools.wallAgent),
unreg: ref(reactDevtools.unreg),
Renderer: ref(reactDevtools.Renderer),
},
},
});
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { useLocation, useNavigate, useParams } from '@modern-js/runtime/router';
import { Box, useThemeContext } from '@radix-ui/themes';
import React, { useEffect, useMemo } from 'react';
import {
initialize,
createBridge,
createStore,
} from 'react-devtools-inline/frontend';
import { wallAgent } from '../state';
import React, { useEffect } from 'react';
import { useSnapshot } from 'valtio';
import { useGlobals } from '@/entries/client/globals';

const Page: React.FC = () => {
Expand All @@ -16,7 +11,7 @@ const Page: React.FC = () => {
const navigate = useNavigate();
const browserTheme = ctx.appearance === 'light' ? 'light' : 'dark';

const { mountPoint } = useGlobals();
const { mountPoint, react } = useSnapshot(useGlobals());

useEffect(() => {
mountPoint.remote.activateReactDevtools();
Expand All @@ -26,33 +21,24 @@ const Page: React.FC = () => {
// 检查URL的hash部分
if (location.hash === '#inspecting') {
const startInspecting = () => {
wallAgent.send('startInspectingNative', null);
react.devtools.wallAgent.send('startInspectingNative', null);
navigate(location.pathname, { replace: true });
};
if (wallAgent.status === 'active') {
if (react.devtools.wallAgent.status === 'active') {
startInspecting();
} else {
wallAgent.hookOnce('active', startInspecting);
react.devtools.wallAgent.hookOnce('active', startInspecting);
}
}
}, [location, navigate]);

const InnerView = useMemo(() => {
const bridge = createBridge(window.parent, wallAgent);
const store = createStore(bridge);
const ret = initialize(window.parent, { bridge, store });
return ret;
}, []);

return (
<Box width="100%" height="100%" pt="5">
{InnerView && (
<InnerView
browserTheme={browserTheme}
overrideTab={params.tab === 'profiler' ? 'profiler' : 'components'}
hideSettings={false}
/>
)}
<react.devtools.Renderer
browserTheme={browserTheme}
overrideTab={params.tab === 'profiler' ? 'profiler' : 'components'}
hideSettings={false}
/>
</Box>
);
};
Expand Down

This file was deleted.

1 change: 1 addition & 0 deletions packages/devtools/client/src/entries/mount/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const App: FC = () => {
if (!manifest) throw new TypeError('Devtools manifest is not found');

const clientSrc = manifest.client;
if (!clientSrc) throw new TypeError('Devtools client source is not found');
const manifestSrc = manifest.source;
const frameBoxUrl = new URL(clientSrc, location.href);
if (manifestSrc) {
Expand Down
1 change: 0 additions & 1 deletion packages/devtools/client/src/entries/mount/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import './react-devtools-backend';
import './state';
import { createRoot } from 'react-dom/client';
import styles from './index.module.scss';
Expand Down

This file was deleted.

24 changes: 16 additions & 8 deletions packages/devtools/client/src/utils/react-devtools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,23 @@ export class WallAgent extends Hookable<WallAgentHooks> implements Wall {

bindRemote(remote: BirpcReturn<object, object>, methodName: string) {
const _remote = remote as any;
_remote.$functions[methodName] = (
event: ReactDevtoolsWallEvent,
...rest: unknown[]
) => {

const handler = (event: ReactDevtoolsWallEvent, ...rest: unknown[]) => {
this.callHook('receive', event, ...rest);
};
this.hook('send', (...args) => {
_remote[methodName](...args);
});
return this;
_remote.$functions[methodName] = handler;

const listener: ReactDevtoolsWallListener = (event, ...rest) => {
_remote[methodName](event, ...rest);
};
this.hook('send', listener);

const unreg = () => {
if (_remote.$functions[methodName] === handler) {
delete _remote.$functions[methodName];
}
this.removeHook('send', listener);
};
return unreg;
}
}
6 changes: 4 additions & 2 deletions packages/devtools/client/tests/utils/react-devtools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ describe('WallAgent', () => {
]);
const remote1 = createBirpc({}, channel1);
const remote2 = createBirpc({}, channel2);
const wall1 = new WallAgent().bindRemote(remote1, 'sendRemote');
const wall2 = new WallAgent().bindRemote(remote2, 'sendRemote');
const wall1 = new WallAgent()
wall1.bindRemote(remote1, 'sendRemote');
const wall2 = new WallAgent()
wall2.bindRemote(remote2, 'sendRemote');

const fn = jest.fn();
wall1.listen(fn);
Expand Down
6 changes: 4 additions & 2 deletions packages/devtools/kit/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ export interface ServerManifest extends ExportedServerState {
/** WebSocket endpoint for live connection. */
websocket?: string;
/** Client endpoint for interactive panel. */
client: string;
client?: string;
/** Route assets. */
routeAssets: Record<string, RouteAsset>;
routeAssets: string | Record<string, RouteAsset>;
/** Semver of @modern-js/plugin-devtools. */
version: string;
}
4 changes: 3 additions & 1 deletion packages/devtools/plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@
"ufo": "^1.3.0",
"valtio": "^1.11.1",
"ws": "^8.13.0",
"hookable": "^5.5.3"
"hookable": "^5.5.3",
"react-devtools-inline": "^4.28.5"
},
"devDependencies": {
"@modern-js/app-tools": "workspace:*",
Expand All @@ -78,6 +79,7 @@
"@swc/helpers": "0.5.3",
"@types/node": "^14",
"@types/ws": "^8.5.5",
"@types/react-devtools-inline": "^4.24.8",
"type-fest": "^4.1.0",
"typescript": "^5",
"@rsbuild/core": "0.7.10"
Expand Down
Loading

0 comments on commit 7002e03

Please sign in to comment.