diff --git a/.changeset/fluffy-islands-unite.md b/.changeset/fluffy-islands-unite.md
new file mode 100644
index 00000000..2f68aef9
--- /dev/null
+++ b/.changeset/fluffy-islands-unite.md
@@ -0,0 +1,6 @@
+---
+'@rsdoctor/components': patch
+'@rsdoctor/client': patch
+---
+
+feat(platform): report platform add bundle size page
diff --git a/examples/modern-minimal/modern.config.ts b/examples/modern-minimal/modern.config.ts
new file mode 100644
index 00000000..31869622
--- /dev/null
+++ b/examples/modern-minimal/modern.config.ts
@@ -0,0 +1,27 @@
+import appTools, { defineConfig } from '@modern-js/app-tools';
+import { RsdoctorWebpackPlugin } from '@rsdoctor/webpack-plugin';
+
+const pluginName = 'Web Doctor';
+
+export default defineConfig({
+ source: {
+ entries: {
+ main: './src/index.ts',
+ },
+ },
+ plugins: [appTools()],
+ builderPlugins: [
+ {
+ name: pluginName,
+ setup(builder) {
+ builder.modifyWebpackChain((chain) => {
+ chain.plugin(pluginName).use(RsdoctorWebpackPlugin, [
+ {
+ disableClientServer: !process.env.ENABLE_CLIENT_SERVER,
+ },
+ ]);
+ });
+ },
+ },
+ ],
+});
diff --git a/examples/modern-minimal/package.json b/examples/modern-minimal/package.json
new file mode 100644
index 00000000..84e22c5f
--- /dev/null
+++ b/examples/modern-minimal/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "@example/doctor-modern-minimal",
+ "version": "0.0.1",
+ "description": "",
+ "files": [
+ "dist",
+ "src",
+ "package.json"
+ ],
+ "scripts": {
+ "start:analysis": "ENABLE_CLIENT_SERVER=true modern start",
+ "build:analysis": "ENABLE_CLIENT_SERVER=true modern build",
+ "build": "modern build"
+ },
+ "author": "",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/highlight": "7.18.6",
+ "chalk": "4.1.2",
+ "htmlparser2": "7.2.0"
+ },
+ "devDependencies": {
+ "@rsdoctor/webpack-plugin": "workspace:*",
+ "@modern-js/app-tools": "2.41.0",
+ "@types/node": "14.18.26",
+ "eslint": "8.22.0",
+ "ts-loader": "9.4.2",
+ "tslib": "2.4.1",
+ "typescript": "4.9.4"
+ }
+}
diff --git a/examples/modern-minimal/src/html.ts b/examples/modern-minimal/src/html.ts
new file mode 100644
index 00000000..ab1954d0
--- /dev/null
+++ b/examples/modern-minimal/src/html.ts
@@ -0,0 +1,15 @@
+import Parser from 'htmlparser2';
+
+export function getHtmlText(code: string) {
+ let context = '';
+
+ const parser = new Parser.Parser({
+ ontext(data) {
+ context += data;
+ },
+ });
+
+ parser.write(code);
+
+ return context;
+}
diff --git a/examples/modern-minimal/src/index.ts b/examples/modern-minimal/src/index.ts
new file mode 100644
index 00000000..2d5caaf8
--- /dev/null
+++ b/examples/modern-minimal/src/index.ts
@@ -0,0 +1,8 @@
+import { Instance } from 'chalk';
+import { highlight } from './utils';
+import { getHtmlText } from './html';
+
+const print = new Instance();
+
+print(getHtmlText('
Test Text
'));
+print(highlight('const abc = 123;'));
diff --git a/examples/modern-minimal/src/types.ts b/examples/modern-minimal/src/types.ts
new file mode 100644
index 00000000..8f4fb3ec
--- /dev/null
+++ b/examples/modern-minimal/src/types.ts
@@ -0,0 +1 @@
+declare module '@babel/highlight';
diff --git a/examples/modern-minimal/src/utils.ts b/examples/modern-minimal/src/utils.ts
new file mode 100644
index 00000000..a9ba4c1e
--- /dev/null
+++ b/examples/modern-minimal/src/utils.ts
@@ -0,0 +1,7 @@
+import highlight from '@babel/highlight';
+
+export { highlight };
+export const key1 = '123';
+export const key2 = '123';
+
+console.log(key2);
diff --git a/examples/modern-minimal/tsconfig.json b/examples/modern-minimal/tsconfig.json
new file mode 100644
index 00000000..d6a1f33f
--- /dev/null
+++ b/examples/modern-minimal/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "@rsdoctor/tsconfig/base",
+ "include": ["src", "webpack.config.ts"],
+ "compilerOptions": {
+ "outDir": "dist",
+ "baseUrl": ".",
+ "module": "ESNext"
+ }
+}
diff --git a/examples/webpack-minimal/src/index.ts b/examples/webpack-minimal/src/index.ts
index ee34841d..0d23e630 100644
--- a/examples/webpack-minimal/src/index.ts
+++ b/examples/webpack-minimal/src/index.ts
@@ -6,5 +6,5 @@ import { key6 } from './utils2';
const print = new Instance();
print(key6);
-print(getHtmlText('测试文本
'));
+print(getHtmlText('Test Text
'));
print(highlight?.('const abc = 123;'));
diff --git a/nx.json b/nx.json
index adb76fb0..2d4053d9 100644
--- a/nx.json
+++ b/nx.json
@@ -2,7 +2,7 @@
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"targetDefaults": {
"build": {
- "cache": false,
+ "cache": true,
"dependsOn": ["^build"],
"inputs": [
"{projectRoot}/src/**/*",
diff --git a/packages/client/modern.config.ts b/packages/client/modern.config.ts
index 7a2d7703..9219ee60 100644
--- a/packages/client/modern.config.ts
+++ b/packages/client/modern.config.ts
@@ -48,10 +48,7 @@ export default defineConfig<'webpack'>((env) => {
media: 'resource/media',
},
assetPrefix: IS_PRODUCTION
- ? // 此处不要修改!这里 production 的 publicPath 会和 sdk serve 的路径 以及 轻服务 部署的路径联动。
- // OFFICAL_PREVIEW_PUBLIC_PATH 是提供给 轻服务 部署后域名关系
- // "/" 是提供给 sdk 使用的
- OFFICAL_PREVIEW_PUBLIC_PATH?.replace(/\/resource$/, '') || '/'
+ ? OFFICAL_PREVIEW_PUBLIC_PATH?.replace(/\/resource$/, '') || '/'
: '/',
cleanDistPath: IS_PRODUCTION,
disableTsChecker: !IS_PRODUCTION,
@@ -62,11 +59,6 @@ export default defineConfig<'webpack'>((env) => {
strategy: 'custom',
splitChunks: {
cacheGroups: {
- shadow: {
- test: /node_modules\/@byted-shadow\/*/,
- name: 'shadow',
- chunks: 'all',
- },
react: {
test: /node_modules\/react-*/,
name: 'react',
diff --git a/packages/client/src/router.tsx b/packages/client/src/router.tsx
index 93270552..92914b80 100644
--- a/packages/client/src/router.tsx
+++ b/packages/client/src/router.tsx
@@ -1,15 +1,24 @@
import React from 'react';
import { Route, Routes } from 'react-router-dom';
-
-import { OverallPage } from '@rsdoctor/components/pages';
+import { Overall, BundleSize } from '@rsdoctor/components/pages';
export default function Router(): React.ReactElement {
+ const routes = [
+ /** bundle routes */
+ {
+ path: BundleSize.route,
+ element: ,
+ },
+ ].filter((e) => Boolean(e)) as { path: string; element: JSX.Element }[];
return (
- } />
- } />
+ } />
+ } />
+ {routes.map((e) => (
+
+ ))}
);
}
diff --git a/packages/components/src/components/Form/keyword.tsx b/packages/components/src/components/Form/keyword.tsx
new file mode 100644
index 00000000..f1c36f85
--- /dev/null
+++ b/packages/components/src/components/Form/keyword.tsx
@@ -0,0 +1,70 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Button, Input, InputRef, Typography } from 'antd';
+
+interface KeywordProps {
+ style?: React.CSSProperties;
+ labelStyle?: React.CSSProperties;
+ icon?: React.ReactNode;
+ label?: string;
+ placeholder?: string;
+ delay?: number;
+ className?: string;
+ width?: number;
+ onChange: (keyword: string) => void;
+}
+
+export const KeywordInput: React.FC = ({
+ icon: Icon,
+ label,
+ labelStyle: ls = {},
+ placeholder,
+ onChange,
+ style,
+ className,
+ width,
+ delay = 300,
+}) => {
+ const labelWidth = 120;
+ const [filename, setFilename] = useState('');
+ const labelStyle: React.CSSProperties = { width: labelWidth, ...ls };
+
+ const ref = useRef(null);
+
+ let timer: NodeJS.Timeout;
+
+ useEffect(() => {
+ onChange(filename);
+ }, [filename]);
+
+ return (
+
+ {label || Icon ? (
+
+ ) : null}
+ {
+ clearTimeout(timer);
+ const v = e.target.value.trim();
+ setTimeout(() => {
+ setFilename(v);
+ }, delay);
+ }}
+ />
+
+ );
+};
diff --git a/packages/components/src/components/Keyword/index.tsx b/packages/components/src/components/Keyword/index.tsx
new file mode 100644
index 00000000..be626fff
--- /dev/null
+++ b/packages/components/src/components/Keyword/index.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import { Typography } from 'antd';
+import { TextProps } from 'antd/es/typography/Text';
+
+export const Keyword: React.FC = ({ text, keyword, ...rest }) => {
+ if (!keyword) {
+ return {text};
+ }
+
+ const idx = text.indexOf(keyword);
+ if (idx === -1) {
+ return {text};
+ }
+
+ const els: (string | React.ReactNode)[] = [];
+
+ let str = text;
+
+ while (str.length > 0) {
+ const idx = str.indexOf(keyword);
+ if (idx > -1) {
+ if (idx !== 0) {
+ els.push(
+
+ {str.slice(0, idx)}
+ ,
+ );
+ }
+ els.push(
+
+ {keyword}
+ ,
+ );
+ str = str.slice(idx + keyword.length);
+ } else {
+ els.push(
+
+ {str}
+ ,
+ );
+ break;
+ }
+ }
+
+ return {els};
+};
diff --git a/packages/components/src/components/Layout/header.tsx b/packages/components/src/components/Layout/header.tsx
index 6ee8c4e9..3f1ccc0e 100644
--- a/packages/components/src/components/Layout/header.tsx
+++ b/packages/components/src/components/Layout/header.tsx
@@ -1,10 +1,8 @@
import { TranslationOutlined, UserOutlined } from '@ant-design/icons';
-import { Avatar, Button, Col, Dropdown, Input, Layout, Row, Select, Switch, Typography } from 'antd';
+import { Avatar, Col, Dropdown, Layout, Row, Switch, Typography } from 'antd';
import React from 'react';
-import { APILoaderMode4Dev, Language, Size, Theme } from '../../constants';
+import { Language, Size, Theme } from '../../constants';
import {
- getAPILoaderModeFromStorage,
- setAPILoaderModeToStorage,
useI18n,
useTheme
} from '../../utils';
@@ -63,28 +61,6 @@ export const Header: React.FC = () => {
wrap={false}
gutter={[Size.BasePadding / 3, 0]}
>
- {process.env.NODE_ENV === 'development' ? (
-
-
-
-
-
-
- ) : null}
diff --git a/packages/components/src/components/Layout/menus.tsx b/packages/components/src/components/Layout/menus.tsx
index db86a7f9..be3c35c6 100644
--- a/packages/components/src/components/Layout/menus.tsx
+++ b/packages/components/src/components/Layout/menus.tsx
@@ -1,5 +1,6 @@
import {
BarChartOutlined,
+ FolderViewOutlined,
MenuOutlined
} from '@ant-design/icons';
import { Manifest, SDK } from '@rsdoctor/types';
@@ -9,8 +10,9 @@ import React from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { Size } from '../../constants';
import * as OverallConstants from '../../pages/Overall/constants';
-import { useI18n } from '../../utils';
+import { useI18n, hasBundle } from '../../utils';
import { withServerAPI } from '../Manifest';
+import { BundleSize } from 'src/pages';
const BuilderSwitchName = 'builder-switcher';
@@ -21,9 +23,14 @@ const MenusBase: React.FC<{ style?: React.CSSProperties; routes: Manifest.Doctor
const { pathname } = useLocation();
const navigate = useNavigate();
const { routes: enableRoutes } = props;
+
const iconStyle: React.CSSProperties = {
fontSize: 16,
};
+ const customIconStyle: React.CSSProperties = {
+ ...iconStyle,
+ transform: 'translateY(-2px)',
+ };
const items: MenuProps['items'] = [];
@@ -43,6 +50,27 @@ const MenusBase: React.FC<{ style?: React.CSSProperties; routes: Manifest.Doctor
});
}
+ if (hasBundle(enableRoutes)) {
+ items.push({
+ label: t(BundleSize.name),
+ key: BundleSize.name,
+ icon: 📦,
+ children: [
+ includes(enableRoutes, Manifest.DoctorManifestClientRoutes.BundleSize) && {
+ label: t(BundleSize.name),
+ key: BundleSize.route,
+ icon: ,
+ },
+ // TODO: Tree shaking menu
+ // includes(enableRoutes, Manifest.DoctorManifestClientRoutes.TreeShaking) && {
+ // label: t(TreeShakingConstants.name),
+ // key: TreeShakingConstants.route,
+ // icon: ,
+ // },
+ ].filter((e) => Boolean(e)) as MenuProps['items'],
+ });
+ }
+
const MenuComponent = (