From b7109a722020549d623892ab25f173d495b40e05 Mon Sep 17 00:00:00 2001 From: Asuka109 Date: Wed, 24 Jan 2024 14:41:46 +0800 Subject: [PATCH] feat(devtools): refactor mounting logic and inject options to client (#5297) --- .changeset/wicked-bikes-breathe.md | 6 +++ packages/devtools/client/exports/.eslintrc.js | 13 ----- packages/devtools/client/exports/mount.mjs | 40 -------------- packages/devtools/client/modern.config.ts | 1 + packages/devtools/client/package.json | 2 +- .../client/src/entries/mount/index.tsx | 53 ++++++++++--------- packages/devtools/plugin/src/cli.ts | 51 ++++++++++++++---- 7 files changed, 78 insertions(+), 88 deletions(-) create mode 100644 .changeset/wicked-bikes-breathe.md delete mode 100644 packages/devtools/client/exports/.eslintrc.js delete mode 100644 packages/devtools/client/exports/mount.mjs diff --git a/.changeset/wicked-bikes-breathe.md b/.changeset/wicked-bikes-breathe.md new file mode 100644 index 000000000000..5911ad710978 --- /dev/null +++ b/.changeset/wicked-bikes-breathe.md @@ -0,0 +1,6 @@ +--- +'@modern-js/devtools-client': patch +'@modern-js/plugin-devtools': patch +--- + +refactor(devtools): mounting logic and inject options to client diff --git a/packages/devtools/client/exports/.eslintrc.js b/packages/devtools/client/exports/.eslintrc.js deleted file mode 100644 index 4a96c063fb6f..000000000000 --- a/packages/devtools/client/exports/.eslintrc.js +++ /dev/null @@ -1,13 +0,0 @@ -/** @type {import('eslint').Linter.Config} */ -// eslint-disable-next-line import/no-commonjs -module.exports = { - root: true, - extends: ['@modern-js-app'], - parserOptions: { - tsconfigRootDir: __dirname, - project: ['../tsconfig.json'], - }, - rules: { - curly: 'off', - }, -}; diff --git a/packages/devtools/client/exports/mount.mjs b/packages/devtools/client/exports/mount.mjs deleted file mode 100644 index dd1d460b2d4b..000000000000 --- a/packages/devtools/client/exports/mount.mjs +++ /dev/null @@ -1,40 +0,0 @@ -import routesManifest from '../dist/routes-manifest.json'; - -try { - const container = document.createElement('div'); - container.className = '_modern_js_devtools_container'; - document.body.appendChild(container); - - const shadow = container.attachShadow({ mode: 'closed' }); - - for (const asset of routesManifest.routeAssets.mount.assets) { - if (asset.endsWith('.js')) { - const el = document.createElement('script'); - el.src = asset; - shadow.appendChild(el); - } else if (asset.endsWith('.css')) { - const el = document.createElement('link'); - el.href = asset; - el.rel = 'stylesheet'; - shadow.appendChild(el); - } else { - console.warn(new Error(`Can't resolve unknown asset tag: ${asset}`)); - } - } - - const app = document.createElement('div'); - app.className = '_modern_js_devtools_mountpoint theme-register'; - const appGlobalExport = '_modern_js_devtools_app'; - window[appGlobalExport] ||= {}; - Object.assign(window[appGlobalExport], { - container: app, - // eslint-disable-next-line no-undef - resourceQuery: __resourceQuery, - }); - shadow.appendChild(app); -} catch (err) { - const e = new Error('Failed to execute mount point of DevTools.'); - e.cause = err; - console.error(e); - console.error(err); -} diff --git a/packages/devtools/client/modern.config.ts b/packages/devtools/client/modern.config.ts index dda996372365..0dcccb97a22b 100644 --- a/packages/devtools/client/modern.config.ts +++ b/packages/devtools/client/modern.config.ts @@ -33,6 +33,7 @@ export default defineConfig<'rspack'>({ 'process.env.VERSION': packageMeta.version, 'process.env.PKG_VERSION': packageMeta.version, 'process.env.DEVTOOLS_MARK': nanoid(), + __REACT_DEVTOOLS_GLOBAL_HOOK__: { isDisabled: true }, }, alias: { // Trick to fix: Modern.js won't recognize experimental react as react@18. diff --git a/packages/devtools/client/package.json b/packages/devtools/client/package.json index d8ca8f3afd30..1ca9947692b1 100644 --- a/packages/devtools/client/package.json +++ b/packages/devtools/client/package.json @@ -22,7 +22,7 @@ "main": "./dist/html/client/index.html", "exports": { ".": "./dist/html/client/index.html", - "./mount": "./exports/mount.mjs", + "./manifest": "./dist/routes-manifest.json", "./sw-proxy": "./dist/public/sw-proxy.js", "./package.json": "./package.json" }, diff --git a/packages/devtools/client/src/entries/mount/index.tsx b/packages/devtools/client/src/entries/mount/index.tsx index 15b8554e69ae..32fb0a687c99 100644 --- a/packages/devtools/client/src/entries/mount/index.tsx +++ b/packages/devtools/client/src/entries/mount/index.tsx @@ -1,33 +1,38 @@ import './react-devtools-backend'; import './state'; import { createRoot } from 'react-dom/client'; -import { parseQuery } from 'ufo'; -import _ from 'lodash'; import { SetupClientParams } from '@modern-js/devtools-kit'; import styles from './index.module.scss'; import { DevtoolsActionButton } from '@/components/Devtools/Action'; -// @ts-expect-error -const { container, resourceQuery } = window._modern_js_devtools_app as { - container: HTMLDivElement; - resourceQuery: string; -}; -container.classList.add(styles.container); -const root = createRoot(container); -const parsed = parseQuery(resourceQuery); -if ( - !_.isString(parsed.dataSource) || - !_.isString(parsed.def) || - !_.isString(parsed.endpoint) -) { - throw new TypeError( - `Failed to parse client definitions of devtools: ${resourceQuery}`, - ); +declare global { + interface Window { + __MODERN_JS_DEVTOOLS_OPTIONS__: SetupClientParams; + } } -const options: SetupClientParams = { - dataSource: parsed.dataSource, - endpoint: parsed.endpoint, - def: JSON.parse(parsed.def), -}; -root.render(); +document.addEventListener('DOMContentLoaded', () => { + const outer = document.createElement('div'); + outer.className = '_modern_js_devtools_container'; + document.body.appendChild(outer); + + const shadow = outer.attachShadow({ mode: 'closed' }); + + const template = document.getElementById('_modern_js_devtools_styles'); + if (!(template instanceof HTMLTemplateElement)) { + throw new Error('template not found'); + } + shadow.appendChild(template.content); + + const container = document.createElement('div'); + container.classList.add( + '_modern_js_devtools_mountpoint', + 'theme-register', + styles.container, + ); + shadow.appendChild(container); + + const options = window.__MODERN_JS_DEVTOOLS_OPTIONS__; + const root = createRoot(container); + root.render(); +}); diff --git a/packages/devtools/plugin/src/cli.ts b/packages/devtools/plugin/src/cli.ts index 11983132d09f..b031b5241888 100644 --- a/packages/devtools/plugin/src/cli.ts +++ b/packages/devtools/plugin/src/cli.ts @@ -1,11 +1,11 @@ import http from 'http'; import path from 'path'; +import assert from 'assert'; import { ProxyDetail } from '@modern-js/types'; import { getPort, logger } from '@modern-js/utils'; import createServeMiddleware from 'serve-static'; import type { AppTools, CliPlugin } from '@modern-js/app-tools'; import { ROUTE_BASENAME } from '@modern-js/devtools-kit'; -import { withQuery } from 'ufo'; import { DevtoolsPluginOptions, DevtoolsPluginInlineOptions, @@ -58,24 +58,55 @@ export const devtoolsPlugin = ( }); logger.info(`${ctx.def.name.formalName} Devtools is enabled`); - const runtimeEntry = require.resolve( - '@modern-js/devtools-client/mount', - ); - const swProxyEntry = require.resolve( '@modern-js/devtools-client/sw-proxy', ); + // Inject options to client. + const serializedOptions = JSON.stringify(ctx); + const tags: AppTools['normalizedConfig']['html']['tags'] = [ + { + tag: 'script', + children: `window.__MODERN_JS_DEVTOOLS_OPTIONS__ = ${serializedOptions};`, + head: true, + append: false, + }, + ]; + + const styles: string[] = []; + const manifest = require('@modern-js/devtools-client/manifest'); + // Inject JavaScript chunks to client. + for (const src of manifest.routeAssets.mount.assets) { + assert(typeof src === 'string'); + if (src.endsWith('.js')) { + tags.push({ + tag: 'script', + attrs: { src }, + head: true, + append: false, + }); + } else if (src.endsWith('.css')) { + styles.push(src); + } + } + // Inject CSS chunks to client inside of template to avoid polluting global. + tags.push({ + tag: 'template', + attrs: { id: '_modern_js_devtools_styles' }, + append: true, + head: false, + children: styles + .map(src => ``) + .join(''), + }); + return { builderPlugins: [rpc.builderPlugin], - source: { - preEntry: [withQuery(runtimeEntry, ctx)], - include: [runtimeEntry], - }, + source: {}, output: { copy: [{ from: swProxyEntry, to: 'public' }], }, - html: {}, + html: { tags }, tools: { devServer: { proxy: {