diff --git a/.changeset/few-dolls-mix.md b/.changeset/few-dolls-mix.md
new file mode 100644
index 00000000000..0fc6b8e71e1
--- /dev/null
+++ b/.changeset/few-dolls-mix.md
@@ -0,0 +1,5 @@
+---
+'@module-federation/devtools': patch
+---
+
+feat(chrome-devtools): add inspector
diff --git a/packages/chrome-devtools/demos/inspector.css b/packages/chrome-devtools/demos/inspector.css
new file mode 100644
index 00000000000..32d2f249d15
--- /dev/null
+++ b/packages/chrome-devtools/demos/inspector.css
@@ -0,0 +1,123 @@
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family:
+ PingFang SC,
+ Hiragino Sans GB,
+ Microsoft YaHei,
+ Arial,
+ sans-serif;
+ background: linear-gradient(to bottom, transparent, #fff) #eceeef;
+}
+
+p {
+ margin: 0;
+}
+
+* {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ box-sizing: border-box;
+}
+
+.container-box {
+ min-height: 100vh;
+ max-width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding: 50px;
+}
+
+.landing-page {
+ padding: 20px;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+.header {
+ display: flex;
+ margin: 4rem 0 4rem;
+ align-items: center;
+ font-size: 4rem;
+ font-weight: 600;
+}
+
+.name {
+ color: #4ecaff;
+}
+
+.description {
+ text-align: center;
+ line-height: 1.5;
+ font-size: 1.3rem;
+ color: #1b3a42;
+ margin-bottom: 5rem;
+}
+
+.code {
+ background: #fafafa;
+ border-radius: 12px;
+ padding: 0.6rem 0.9rem;
+ font-size: 1.05rem;
+ font-family:
+ Menlo,
+ Monaco,
+ Lucida Console,
+ Liberation Mono,
+ DejaVu Sans Mono,
+ Bitstream Vera Sans Mono,
+ Courier New,
+ monospace;
+}
+
+.footer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 1100px;
+ margin-top: 3rem;
+}
+
+.card {
+ padding: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ height: 100px;
+ color: inherit;
+ text-decoration: none;
+ transition: 0.15s ease;
+ width: 45%;
+}
+
+.card:hover,
+.card:focus {
+ transform: scale(1.05);
+}
+
+.card h2 {
+ display: flex;
+ align-items: center;
+ font-size: 1.5rem;
+ margin: 0;
+ padding: 0;
+}
+
+.card p {
+ opacity: 0.6;
+ font-size: 0.9rem;
+ line-height: 1.5;
+ margin-top: 1rem;
+}
+
+.arrow-right {
+ width: 1.3rem;
+ margin-left: 0.5rem;
+ margin-top: 3px;
+}
diff --git a/packages/chrome-devtools/demos/inspector.tsx b/packages/chrome-devtools/demos/inspector.tsx
new file mode 100644
index 00000000000..e38b8d26632
--- /dev/null
+++ b/packages/chrome-devtools/demos/inspector.tsx
@@ -0,0 +1,159 @@
+import { Helmet } from '@modern-js/runtime/head';
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './inspector.css';
+import { wrapComponent } from '../src/utils/chrome/ComponentInspector';
+
+const Header = () => {
+ return (
+
+ Welcome to
+

+
Modern.js
+
+ );
+};
+
+const Footer = () => {
+ return (
+
+ );
+};
+
+const Description = () => {
+ return (
+
+ Get started by editing src/routes/page.tsx
+
+ );
+};
+
+function MyComponent() {
+ return Hello
;
+}
+console.log(222, MyComponent.name);
+
+class MyClassComponent extends React.Component {
+ render() {
+ return Hello
;
+ }
+}
+
+console.log(222, MyClassComponent.name);
+
+const Index = () => (
+
+
+
+
+
+
+ {wrapComponent({
+ react: React,
+ CustomComponent: Header,
+ componentName: Header.name,
+ mfName: 'provider1',
+ })}
+
+ {wrapComponent({
+ react: React,
+ CustomComponent: Description,
+ componentName: Description.name,
+ mfName: 'provider2',
+ versionOrEntry: '1.2.3',
+ })}
+
+ {wrapComponent({
+ react: React,
+ CustomComponent: Footer,
+ componentName: Footer.name,
+ mfName: 'provider3',
+ })}
+
+
+);
+
+export default Index;
+
+const rootEl = document.getElementById('root');
+if (rootEl) {
+ const root = ReactDOM.createRoot(rootEl);
+ root.render(
+
+
+ ,
+ );
+}
diff --git a/packages/chrome-devtools/manifest.json b/packages/chrome-devtools/manifest.json
index abfbdc85500..efd30ca0d7f 100644
--- a/packages/chrome-devtools/manifest.json
+++ b/packages/chrome-devtools/manifest.json
@@ -17,6 +17,7 @@
"static/js/post-message.js",
"static/js/post-message-start.js",
"static/js/fast-refresh.js",
+ "static/js/inspector-plugin.js",
"static/js/snapshot-plugin.js"
],
"matches": [""]
@@ -35,7 +36,11 @@
},
{
"matches": [""],
- "js": ["static/js/fast-refresh.js", "static/js/snapshot-plugin.js"],
+ "js": [
+ "static/js/fast-refresh.js",
+ "static/js/snapshot-plugin.js",
+ "static/js/inspector-plugin.js"
+ ],
"world": "MAIN",
"run_at": "document_start"
}
diff --git a/packages/chrome-devtools/modern.config.ts b/packages/chrome-devtools/modern.config.ts
index 4d13dcc9a50..2e8d98ac1ed 100644
--- a/packages/chrome-devtools/modern.config.ts
+++ b/packages/chrome-devtools/modern.config.ts
@@ -9,6 +9,8 @@ export default defineConfig({
},
},
output: {
+ injectStyles: process.env.INSPECTOR ? true : false,
+ cleanDistPath: process.env.INSPECTOR ? true : false,
disableInlineRuntimeChunk: true,
disableFilenameHash: true,
disableMinimize: true,
@@ -20,6 +22,16 @@ export default defineConfig({
},
tools: {
webpack: (config: Record) => {
+ if (process.env.INSPECTOR) {
+ config.entry = {
+ 'inspector-plugin': './src/utils/chrome/inspector-plugin.ts',
+ };
+ config.externals = {
+ react: '_mfReact',
+ };
+ return config;
+ }
+
if (process.env.E2ETEST) {
config.entry.worker = './src/worker/index.ts';
}
@@ -32,6 +44,10 @@ export default defineConfig({
'./src/utils/chrome/post-message-listener.ts';
config.entry['post-message-start'] =
'./src/utils/chrome/post-message-start.ts';
+
+ if (process.env.TEST_INSPECTOR) {
+ config.entry = './demos/inspector.tsx';
+ }
return config;
},
},
diff --git a/packages/chrome-devtools/modern.lib.config.ts b/packages/chrome-devtools/modern.lib.config.ts
index 66d0c3dd239..1a2f7dc4f78 100644
--- a/packages/chrome-devtools/modern.lib.config.ts
+++ b/packages/chrome-devtools/modern.lib.config.ts
@@ -6,5 +6,6 @@ export default defineConfig({
buildConfig: {
input: ['src', '!src/index.tsx'],
tsconfig: 'tsconfig.lib.json',
+ minify: false,
},
});
diff --git a/packages/chrome-devtools/package.json b/packages/chrome-devtools/package.json
index bb4915925f4..f6716261891 100644
--- a/packages/chrome-devtools/package.json
+++ b/packages/chrome-devtools/package.json
@@ -12,7 +12,8 @@
"storybook": "storybook dev -p 6006",
"reset": "npx rimraf ./**/node_modules",
"dev": "modern-app dev",
- "build": "modern-app build && node postpack.js",
+ "dev:inspector": "TEST_INSPECTOR=true modern-app dev",
+ "build": " INSPECTOR=true modern-app build && modern-app build && node postpack.js",
"build:debug": "DEBUG=true modern-app build && node postpack.js",
"build:lib": "rm -rf dist && modern-module build -c modern.lib.config.ts",
"release": "npm publish --tag canary",
@@ -61,7 +62,8 @@
"dagre": "^0.8.5",
"react": "~18.3.1",
"react-dom": "~18.3.1",
- "reactflow": "11.11.4"
+ "reactflow": "11.11.4",
+ "react-toastify": "11.0.5"
},
"devDependencies": {
"@modern-js-app/eslint-config": "2.54.6",
diff --git a/packages/chrome-devtools/project.json b/packages/chrome-devtools/project.json
index be8f4781833..378fe12705b 100644
--- a/packages/chrome-devtools/project.json
+++ b/packages/chrome-devtools/project.json
@@ -29,6 +29,18 @@
}
]
},
+ "dev:inspector": {
+ "executor": "nx:run-commands",
+ "options": {
+ "commands": ["npm run dev:inspector --prefix packages/chrome-devtools"]
+ },
+ "dependsOn": [
+ {
+ "target": "build",
+ "dependencies": true
+ }
+ ]
+ },
"test": {
"executor": "nx:run-commands",
"options": {
diff --git a/packages/chrome-devtools/src/component/Form/index.tsx b/packages/chrome-devtools/src/component/Form/index.tsx
index 0742d2cec03..28d4c94e7f2 100644
--- a/packages/chrome-devtools/src/component/Form/index.tsx
+++ b/packages/chrome-devtools/src/component/Form/index.tsx
@@ -44,7 +44,9 @@ interface FormProps {
setFormStatus: React.Dispatch>;
validateForm: any;
enableHMR: string;
+ enableInspector: string;
onHMRChange: (on: boolean) => void;
+ onInspectorChange: (on: boolean) => void;
}
const FormComponent = (props: FormProps & RootComponentProps) => {
const {
@@ -54,7 +56,9 @@ const FormComponent = (props: FormProps & RootComponentProps) => {
setFormStatus,
validateForm,
enableHMR,
+ enableInspector,
onHMRChange,
+ onInspectorChange,
versionList,
setVersionList,
getVersion,
@@ -185,6 +189,10 @@ const FormComponent = (props: FormProps & RootComponentProps) => {
onHMRChange(on);
};
+ const inspectorChange = (on: boolean) => {
+ onInspectorChange(on);
+ };
+
const onKeyChange = async (key: string, index: number) => {
const version = await getVersion?.(key);
if (version) {
@@ -229,6 +237,13 @@ const FormComponent = (props: FormProps & RootComponentProps) => {
onChange={hmrChange}
className={styles.switch}
/>
+
diff --git a/packages/chrome-devtools/src/component/Layout/index.tsx b/packages/chrome-devtools/src/component/Layout/index.tsx
index 3b1cec50fcf..5005cff6097 100644
--- a/packages/chrome-devtools/src/component/Layout/index.tsx
+++ b/packages/chrome-devtools/src/component/Layout/index.tsx
@@ -33,6 +33,8 @@ import {
__ENABLE_FAST_REFRESH__,
BROWSER_ENV_KEY,
__FEDERATION_DEVTOOLS__,
+ ENABLEINSPECTOR,
+ __ENABLE_INSPECTOR__,
} from '../../template/constant';
interface FormItemType {
key: string;
@@ -66,6 +68,7 @@ const Layout = (
const [snapshot, setSnapshot] = useState(moduleInfo);
const [form] = Form.useForm();
const [enableHMR, setEnalbeHMR] = useState('disable');
+ const [enableInspector, setEnalbeInspector] = useState('disable');
const { run } = useDebounceFn(
async (formData) => {
@@ -159,6 +162,12 @@ const Layout = (
onHMRChange(enable);
}
});
+ chrome.storage.sync.get([ENABLEINSPECTOR]).then((data) => {
+ const enable = data[ENABLEINSPECTOR];
+ if (typeof enable === 'boolean') {
+ onHMRChange(enable);
+ }
+ });
}, []);
useEffect(() => {
@@ -200,6 +209,19 @@ const Layout = (
injectScript(reloadPage, false);
};
+ const onInspectorChange = (on: boolean) => {
+ setEnalbeInspector(on ? 'enable' : 'disable');
+ chrome.storage.sync.set({
+ [ENABLEINSPECTOR]: on,
+ });
+ if (on) {
+ mergeStorage(__FEDERATION_DEVTOOLS__, __ENABLE_INSPECTOR__, on);
+ } else {
+ removeStorageKey(__FEDERATION_DEVTOOLS__, __ENABLE_INSPECTOR__);
+ }
+ injectScript(reloadPage, false);
+ };
+
return (
<>