@rsdoctor/components 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ import {
9
9
  ToolOutlined
10
10
  } from "@ant-design/icons";
11
11
  import { Manifest, SDK } from "@rsdoctor/types";
12
- import { Col, Menu, Grid, Typography } from "antd";
12
+ import { Col, Menu, Typography } from "antd";
13
13
  import { includes } from "lodash-es";
14
14
  import { useLocation, useNavigate } from "react-router-dom";
15
15
  import WebpackIcon from "../../assets/webpack.98604d64.svg";
@@ -22,7 +22,8 @@ import {
22
22
  LoaderFiles,
23
23
  PluginsAnalyze,
24
24
  ModuleResolve,
25
- LoaderTimeline
25
+ LoaderTimeline,
26
+ TreeShaking
26
27
  } from "../../pages";
27
28
  import { CompileName } from "./constants";
28
29
  const BuilderSwitchName = "builder-switcher";
@@ -39,7 +40,6 @@ const MenusBase = (props) => {
39
40
  transform: "translateY(-2px)"
40
41
  };
41
42
  const items = [];
42
- const { xxl } = Grid.useBreakpoint();
43
43
  console.log("enableRoutes: ", enableRoutes);
44
44
  if (includes(enableRoutes, Manifest.RsdoctorManifestClientRoutes.Overall)) {
45
45
  items.push({
@@ -110,6 +110,14 @@ const MenusBase = (props) => {
110
110
  label: t(BundleSize.name),
111
111
  key: BundleSize.route,
112
112
  icon: /* @__PURE__ */ jsx(FolderViewOutlined, { style: iconStyle })
113
+ },
114
+ includes(
115
+ enableRoutes,
116
+ Manifest.RsdoctorManifestClientRoutes.TreeShaking
117
+ ) && {
118
+ label: t(TreeShaking.name),
119
+ key: TreeShaking.route,
120
+ icon: /* @__PURE__ */ jsx(FolderViewOutlined, { style: iconStyle })
113
121
  }
114
122
  ].filter((e) => Boolean(e))
115
123
  });
@@ -129,7 +137,7 @@ const MenusBase = (props) => {
129
137
  height: Size.NavBarHeight,
130
138
  lineHeight: `${Size.NavBarHeight}px`,
131
139
  minWidth: 0,
132
- justifyContent: xxl ? "center" : "flex-end",
140
+ justifyContent: "flex-end",
133
141
  ...props.style
134
142
  },
135
143
  selectedKeys: [pathname === "/" ? OverallConstants.route : pathname]
@@ -357,7 +357,7 @@ const AssetDetail = ({ asset, chunks: includeChunks, modules: includeModules, mo
357
357
  description: /* @__PURE__ */ jsx(Typography.Text, { strong: true, children: `"${moduleKeyword}" can't match any modules` })
358
358
  }
359
359
  ) })
360
- ] }) : /* @__PURE__ */ jsx(Empty, { description: /* @__PURE__ */ jsx(Typography.Text, { strong: true, children: `"${asset.path}" don't has any modules` }) }),
360
+ ] }) : /* @__PURE__ */ jsx(Empty, { description: /* @__PURE__ */ jsx(Typography.Text, { strong: true, children: `"${asset.path}" doesn't have any modules` }) }),
361
361
  /* @__PURE__ */ jsx(
362
362
  ModuleGraphViewer,
363
363
  {
@@ -0,0 +1,3 @@
1
+ import { Client } from '@rsdoctor/types';
2
+ export declare const name = "TreeShaking";
3
+ export declare const route = Client.RsdoctorClientRoutes.TreeShaking;
@@ -0,0 +1,7 @@
1
+ import { Client } from "@rsdoctor/types";
2
+ const name = "TreeShaking";
3
+ const route = Client.RsdoctorClientRoutes.TreeShaking;
4
+ export {
5
+ name,
6
+ route
7
+ };
@@ -0,0 +1,12 @@
1
+ import type { Module, ModuleGraph, SourceRange } from '@rsdoctor/graph';
2
+ import { SDK } from '@rsdoctor/types';
3
+ import { SetEditorStatus } from './types';
4
+ export interface CodeEditorProps {
5
+ module: Module;
6
+ moduleGraph: ModuleGraph;
7
+ ranges: SourceRange[];
8
+ setEditorData: SetEditorStatus;
9
+ source: SDK.ModuleSource;
10
+ toLine?: number;
11
+ }
12
+ export declare function CodeEditor(props: CodeEditorProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,110 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState, useRef } from "react";
3
+ import path from "path-browserify";
4
+ import { Card, Space, Tooltip, Typography } from "antd";
5
+ import { InfoCircleOutlined, LoadingOutlined } from "@ant-design/icons";
6
+ import Editor from "@monaco-editor/react";
7
+ import { Range, editor } from "monaco-editor";
8
+ import { parseOpenTag } from "./open-tag";
9
+ import { getHoverMessageInModule } from "./utils";
10
+ import { getOriginalLanguage, getSelectionRange } from "../../utils";
11
+ import { DefaultEditorConfig } from "../../components/CodeViewer";
12
+ function CodeEditor(props) {
13
+ const { module, moduleGraph, ranges, toLine, setEditorData, source } = props;
14
+ const editorRef = useRef();
15
+ const oldRanges = useRef([]);
16
+ const oldHovers = useRef([]);
17
+ const oldToLine = useRef();
18
+ const changeModule = useRef(false);
19
+ const [content, setContent] = useState("");
20
+ useEffect(() => {
21
+ const { isPreferSource } = module;
22
+ changeModule.current = true;
23
+ setContent(
24
+ source.source || isPreferSource ? source.source : source.transformed
25
+ );
26
+ setTimeout(() => {
27
+ oldHovers.current = editorRef.current?.getModel()?.deltaDecorations(
28
+ oldHovers.current,
29
+ getHoverMessageInModule(module, moduleGraph)
30
+ ) ?? [];
31
+ }, 200);
32
+ }, [module, source]);
33
+ useEffect(() => {
34
+ function setRangeAndLine() {
35
+ const model = editorRef.current?.getModel();
36
+ if (!model) {
37
+ return;
38
+ }
39
+ oldRanges.current = model.deltaDecorations(
40
+ oldRanges.current,
41
+ ranges.map((arr) => {
42
+ return {
43
+ range: getSelectionRange(arr, Range),
44
+ options: {
45
+ stickiness: 1,
46
+ inlineClassName: "tree-shaking-statement-side-effect",
47
+ isWholeLine: false,
48
+ showIfCollapsed: true
49
+ }
50
+ };
51
+ })
52
+ );
53
+ if (editorRef.current && typeof toLine === "number" && oldToLine.current !== toLine) {
54
+ oldToLine.current = toLine;
55
+ editorRef.current.revealLine(toLine, editor.ScrollType.Smooth);
56
+ }
57
+ }
58
+ if (changeModule) {
59
+ setTimeout(setRangeAndLine, 300);
60
+ } else {
61
+ setRangeAndLine();
62
+ }
63
+ changeModule.current = false;
64
+ }, [ranges, toLine]);
65
+ useEffect(() => {
66
+ const openEditor = (event) => {
67
+ const query = parseOpenTag(event.target);
68
+ if (query) {
69
+ const module2 = moduleGraph.getModuleById(query.module);
70
+ if (module2) {
71
+ setEditorData(module2, [query.range], query.range.start.line);
72
+ }
73
+ }
74
+ };
75
+ document.body.addEventListener("click", openEditor);
76
+ return () => {
77
+ document.body.removeEventListener("click", openEditor);
78
+ };
79
+ }, []);
80
+ if (!module) {
81
+ return /* @__PURE__ */ jsx("div", { children: "请选择要查看的模块" });
82
+ }
83
+ const handleEditorDidMount = (editor2) => {
84
+ editorRef.current = editor2;
85
+ };
86
+ return /* @__PURE__ */ jsx(
87
+ Card,
88
+ {
89
+ title: /* @__PURE__ */ jsx(Tooltip, { title: module.path, children: /* @__PURE__ */ jsxs(Space, { children: [
90
+ /* @__PURE__ */ jsx(Typography.Text, { children: path.basename(module.path) }),
91
+ /* @__PURE__ */ jsx(InfoCircleOutlined, {})
92
+ ] }) }),
93
+ className: "tree-shaking-editor",
94
+ children: /* @__PURE__ */ jsx(
95
+ Editor,
96
+ {
97
+ theme: "vs-dark",
98
+ language: getOriginalLanguage(module.path),
99
+ value: content,
100
+ loading: /* @__PURE__ */ jsx(LoadingOutlined, { style: { fontSize: 30 } }),
101
+ options: DefaultEditorConfig,
102
+ onMount: handleEditorDidMount
103
+ }
104
+ )
105
+ }
106
+ );
107
+ }
108
+ export {
109
+ CodeEditor
110
+ };
@@ -0,0 +1,74 @@
1
+ .tree-shaking-page {
2
+ width: 100%;
3
+ height: 100%;
4
+ }
5
+ .tree-shaking-page .ant-card {
6
+ display: flex;
7
+ flex-direction: column;
8
+ }
9
+ .tree-shaking-page .tree-shaking-statement-side-effect {
10
+ text-decoration: underline;
11
+ background: #711f1c;
12
+ }
13
+ .tree-shaking-page .tree-shaking-statement-declaration-identifier {
14
+ text-decoration: underline;
15
+ background: rgb(44, 135, 236);
16
+ }
17
+ .tree-shaking-page .tree-shaking-files-box {
18
+ height: 700px;
19
+ }
20
+ .tree-shaking-page .tree-shaking-files {
21
+ overflow: auto;
22
+ height: 100%;
23
+ }
24
+ .tree-shaking-page .tree-shaking-editor {
25
+ flex-grow: 1;
26
+ height: 700px;
27
+ }
28
+ .tree-shaking-page .tree-shaking-editor .ant-card-body {
29
+ padding: 0;
30
+ }
31
+ .tree-shaking-page .tree-shaking-files-box,
32
+ .tree-shaking-page .tree-shaking-editor .ant-card-body {
33
+ width: auto;
34
+ height: calc(100% - 48px);
35
+ }
36
+ .tree-shaking-page .tree-shaking-export-table .ant-card-body {
37
+ padding: 0;
38
+ }
39
+ .tree-shaking-page .tree-shaking-export-table .ant-card-body .ant-divider {
40
+ margin: 24px;
41
+ }
42
+ .tree-shaking-page .tree-shaking-export-table .ant-table-wrapper .ant-table-tbody > tr:last-child > td {
43
+ border-bottom: none;
44
+ }
45
+ .tree-shaking-page .tree-shaking-export-table .ant-table-wrapper .ant-table-tbody > tr:last-child > td .ant-table-container table > thead > tr:first-child th {
46
+ border-top: 1px solid rgba(5, 5, 5, 0.06);
47
+ }
48
+ .tree-shaking-page .tree-shaking-export-table .ant-table-wrapper .ant-table-tbody > tr:last-child > td .ant-table-container table > thead > tr:first-child th th:first-child {
49
+ border-start-start-radius: 0;
50
+ }
51
+ .tree-shaking-page .tree-shaking-export-table .ant-table-wrapper .ant-table-tbody > tr:last-child > td .ant-table-container table > thead > tr:first-child th th:last-child {
52
+ border-start-end-radius: 0;
53
+ }
54
+ .tree-shaking-page .tree-shaking-side-effect-list {
55
+ display: flex;
56
+ flex-direction: column;
57
+ align-items: flex-start;
58
+ }
59
+ .tree-shaking-page .tree-shaking-side-effect-list .tree-shaking-side-effect-list-item {
60
+ overflow: hidden;
61
+ text-overflow: ellipsis;
62
+ white-space: nowrap;
63
+ }
64
+ .monaco-hover .markdown-hover.hover-row hr {
65
+ margin: 2px;
66
+ background-color: gray;
67
+ }
68
+ .monaco-hover .markdown-hover.hover-row div[data-code=tree-shaking-hover] ol {
69
+ margin: 4px 0 10px 0;
70
+ padding-left: 4px;
71
+ }
72
+ .monaco-hover .markdown-hover.hover-row div[data-code=tree-shaking-hover] ol pre {
73
+ margin: 2px 0;
74
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { SDK } from '@rsdoctor/types';
3
+ import './index.sass';
4
+ export * from './constants';
5
+ export declare const TreeShakingPage: React.FC<Omit<{
6
+ data: SDK.ModuleGraphData;
7
+ cwd: string;
8
+ }, "data" | "cwd">>;
@@ -0,0 +1,177 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { Card, Col, Row, Radio } from "antd";
4
+ import { isNumber } from "lodash-es";
5
+ import { FileSearchOutlined } from "@ant-design/icons";
6
+ import { SDK } from "@rsdoctor/types";
7
+ import path from "path-browserify";
8
+ import {
9
+ ConnectManifestData,
10
+ ServerAPIProvider
11
+ } from "../../components/Manifest";
12
+ import { Size } from "../../constants";
13
+ import { FileTree } from "../../components/FileTree";
14
+ import { KeywordInput } from "../../components/Form/keyword";
15
+ import { getTreeFilesDefaultExpandedKeys, useFileStructures } from "./utils";
16
+ import {
17
+ fetchManifest,
18
+ useModuleGraphInstanceByModuleGraph
19
+ } from "../../utils";
20
+ import { CodeEditor } from "./editor";
21
+ import { Space } from "./space";
22
+ import { TreeShakingTable } from "./table";
23
+ import "./index.css";
24
+ export * from "./constants";
25
+ const Component = ({
26
+ data,
27
+ cwd
28
+ }) => {
29
+ const moduleGraph = useModuleGraphInstanceByModuleGraph(data);
30
+ if (moduleGraph.size() === 0) {
31
+ return /* @__PURE__ */ jsx(Space, {});
32
+ }
33
+ const [searchInput, setSearchInput] = useState("");
34
+ const [toLine, setToLine] = useState(1);
35
+ const [ranges, setRanges] = useState([]);
36
+ const [tableKind, setTableKind] = useState("side-effect");
37
+ const filteredModules = useMemo(
38
+ () => moduleGraph.getModules().filter(
39
+ (item) => item.kind === SDK.ModuleKind.Normal && path.basename(item.path).includes(searchInput)
40
+ ),
41
+ [searchInput]
42
+ );
43
+ const entry = useMemo(
44
+ () => filteredModules.find((item) => item.isEntry),
45
+ []
46
+ );
47
+ const [selectedModule, setSelectedModule] = useState(
48
+ entry || filteredModules?.length && filteredModules?.[0]
49
+ );
50
+ const files = useFileStructures(
51
+ filteredModules,
52
+ moduleGraph,
53
+ searchInput,
54
+ selectedModule,
55
+ (file) => {
56
+ setEditorData(filteredModules.find((item) => item.path === file), []);
57
+ },
58
+ cwd
59
+ );
60
+ const setEditorData = (module, ranges2, line) => {
61
+ setSelectedModule(module);
62
+ setRanges(ranges2.slice());
63
+ if (isNumber(line)) {
64
+ setToLine(line);
65
+ }
66
+ };
67
+ return /* @__PURE__ */ jsxs(
68
+ Card,
69
+ {
70
+ title: "Tree Shaking Analysis",
71
+ bodyStyle: { paddingTop: 0 },
72
+ className: "tree-shaking-page",
73
+ children: [
74
+ /* @__PURE__ */ jsxs(
75
+ Row,
76
+ {
77
+ justify: "space-between",
78
+ align: "middle",
79
+ style: { marginBottom: Size.BasePadding, marginTop: Size.BasePadding },
80
+ children: [
81
+ /* @__PURE__ */ jsx(
82
+ KeywordInput,
83
+ {
84
+ icon: /* @__PURE__ */ jsx(FileSearchOutlined, {}),
85
+ width: 400,
86
+ label: "FileName",
87
+ placeholder: "search filename by keyword",
88
+ style: { width: "auto" },
89
+ onChange: (e) => setSearchInput(e)
90
+ }
91
+ ),
92
+ /* @__PURE__ */ jsxs(
93
+ Radio.Group,
94
+ {
95
+ value: tableKind,
96
+ onChange: ({ target }) => setTableKind(target.value),
97
+ children: [
98
+ /* @__PURE__ */ jsx(Radio.Button, { value: "side-effect", children: "SideEffects" }),
99
+ /* @__PURE__ */ jsx(Radio.Button, { value: "export", children: "Export Variables" })
100
+ ]
101
+ }
102
+ )
103
+ ]
104
+ }
105
+ ),
106
+ /* @__PURE__ */ jsxs(
107
+ Row,
108
+ {
109
+ justify: "space-between",
110
+ align: "top",
111
+ wrap: false,
112
+ gutter: [Size.BasePadding, Size.BasePadding],
113
+ children: [
114
+ /* @__PURE__ */ jsx(Col, { span: 7, children: /* @__PURE__ */ jsx(
115
+ Card,
116
+ {
117
+ title: `Total Files: ${filteredModules.length}`,
118
+ className: "tree-shaking-files-box",
119
+ children: /* @__PURE__ */ jsx(
120
+ FileTree,
121
+ {
122
+ className: "tree-shaking-files",
123
+ selectedKeys: selectedModule ? [selectedModule.path] : [],
124
+ defaultExpandedKeys: getTreeFilesDefaultExpandedKeys(files),
125
+ treeData: files,
126
+ expandAction: "click"
127
+ }
128
+ )
129
+ }
130
+ ) }),
131
+ /* @__PURE__ */ jsx(Col, { span: 9, children: /* @__PURE__ */ jsx(
132
+ ServerAPIProvider,
133
+ {
134
+ api: SDK.ServerAPI.API.GetModuleCodeByModuleId,
135
+ body: { moduleId: selectedModule.id },
136
+ children: (source) => {
137
+ return /* @__PURE__ */ jsx(
138
+ CodeEditor,
139
+ {
140
+ module: selectedModule,
141
+ moduleGraph,
142
+ ranges,
143
+ toLine,
144
+ setEditorData,
145
+ source
146
+ }
147
+ );
148
+ }
149
+ }
150
+ ) }),
151
+ /* @__PURE__ */ jsx(Col, { flex: 1, children: /* @__PURE__ */ jsx(
152
+ TreeShakingTable,
153
+ {
154
+ kind: tableKind,
155
+ setEditorData,
156
+ module: selectedModule,
157
+ moduleGraph
158
+ }
159
+ ) })
160
+ ]
161
+ }
162
+ )
163
+ ]
164
+ }
165
+ );
166
+ };
167
+ const TreeShakingPage = ConnectManifestData(
168
+ fetchManifest,
169
+ [
170
+ ["moduleGraph", "data"],
171
+ ["root", "cwd"]
172
+ ],
173
+ Component
174
+ );
175
+ export {
176
+ TreeShakingPage
177
+ };
@@ -0,0 +1,11 @@
1
+ import { SDK } from '@rsdoctor/types';
2
+ export declare enum AttributeKey {
3
+ Module = "data-module",
4
+ Range = "data-range"
5
+ }
6
+ export interface OpenTagData {
7
+ module: number;
8
+ range: SDK.SourceRange;
9
+ }
10
+ export declare function getOpenTagText(module: number, range: SDK.SourceRange, text: string): string;
11
+ export declare function parseOpenTag(dom: HTMLElement): OpenTagData | undefined;
@@ -0,0 +1,43 @@
1
+ var AttributeKey = /* @__PURE__ */ ((AttributeKey2) => {
2
+ AttributeKey2["Module"] = "data-module";
3
+ AttributeKey2["Range"] = "data-range";
4
+ return AttributeKey2;
5
+ })(AttributeKey || {});
6
+ function getOpenTagText(module, range, text) {
7
+ return `<a data-href="${"data-module" /* Module */}=${module}&${"data-range" /* Range */}=${encodeURIComponent(JSON.stringify(range))}">${text}</a>`;
8
+ }
9
+ function parseOpenTag(dom) {
10
+ if (dom.tagName.toLocaleLowerCase() !== "a") {
11
+ return;
12
+ }
13
+ const hrefString = dom.getAttribute("data-href") ?? "";
14
+ const result = {
15
+ module: -1,
16
+ range: {
17
+ start: {
18
+ line: 1,
19
+ column: 0
20
+ },
21
+ end: {
22
+ line: 1,
23
+ column: 0
24
+ }
25
+ }
26
+ };
27
+ for (const item of hrefString.split("&")) {
28
+ const [key, value] = item.split("=");
29
+ if (key === "data-module" /* Module */) {
30
+ result.module = Number.parseInt(value, 10);
31
+ continue;
32
+ }
33
+ if (key === "data-range" /* Range */) {
34
+ result.range = JSON.parse(decodeURIComponent(value));
35
+ }
36
+ }
37
+ return result;
38
+ }
39
+ export {
40
+ AttributeKey,
41
+ getOpenTagText,
42
+ parseOpenTag
43
+ };
@@ -0,0 +1 @@
1
+ export declare function Space(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Card, Empty } from "antd";
3
+ function Space() {
4
+ return /* @__PURE__ */ jsx(
5
+ Card,
6
+ {
7
+ title: "Tree Shaking Analysis",
8
+ bodyStyle: { paddingTop: 0 },
9
+ className: "tree-shaking-page",
10
+ children: /* @__PURE__ */ jsx(Empty, { style: { marginTop: 30 } })
11
+ }
12
+ );
13
+ }
14
+ export {
15
+ Space
16
+ };
@@ -0,0 +1,13 @@
1
+ import { Module, ModuleGraph } from '@rsdoctor/graph';
2
+ import React from 'react';
3
+ import type { TableKind, SetEditorStatus } from './types';
4
+ interface TableProps {
5
+ module: Module;
6
+ moduleGraph: ModuleGraph;
7
+ setEditorData: SetEditorStatus;
8
+ kind: TableKind;
9
+ }
10
+ export declare const SideEffectTable: React.FC<TableProps>;
11
+ export declare const ExportTable: React.FC<TableProps>;
12
+ export declare const TreeShakingTable: React.FC<TableProps>;
13
+ export {};
@@ -0,0 +1,319 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import {
3
+ Space,
4
+ Table,
5
+ Typography,
6
+ Divider,
7
+ Tooltip,
8
+ Button,
9
+ Input
10
+ } from "antd";
11
+ import { SearchOutlined } from "@ant-design/icons";
12
+ import React, { useMemo, useState, useRef } from "react";
13
+ import Highlighter from "react-highlight-words";
14
+ import { Card } from "../../components/Card";
15
+ import { ellipsisPath } from "./utils";
16
+ import { isDef } from "../../utils";
17
+ const tableHeight = 600;
18
+ function getDeclarationElement(val, setEditorData) {
19
+ if (!val) {
20
+ return /* @__PURE__ */ jsx("div", { children: "Can not find declaration." });
21
+ }
22
+ const { module } = val;
23
+ const range = module.isPreferSource ? val.position.source : val.position.transformed;
24
+ return /* @__PURE__ */ jsx(Tooltip, { title: `${module.path}, line ${range.start.line}`, children: /* @__PURE__ */ jsx(
25
+ Button,
26
+ {
27
+ type: "link",
28
+ onClick: () => setEditorData(module, [range], range.start.line),
29
+ children: "Move To"
30
+ }
31
+ ) });
32
+ }
33
+ function useSearchCell(dataIndex) {
34
+ const [searchText, setSearchText] = useState("");
35
+ const [searchedColumn, setSearchedColumn] = useState("");
36
+ const searchInput = useRef(null);
37
+ const handleSearch = (selectedKeys, confirm, dataIndex2) => {
38
+ confirm();
39
+ setSearchText(selectedKeys[0]);
40
+ setSearchedColumn(dataIndex2);
41
+ };
42
+ const handleReset = (clearFilters) => {
43
+ clearFilters();
44
+ setSearchText("");
45
+ };
46
+ return {
47
+ filterDropdown: ({
48
+ setSelectedKeys,
49
+ selectedKeys,
50
+ confirm,
51
+ clearFilters,
52
+ close
53
+ }) => /* @__PURE__ */ jsxs("div", { style: { padding: 8 }, onKeyDown: (e) => e.stopPropagation(), children: [
54
+ /* @__PURE__ */ jsx(
55
+ Input,
56
+ {
57
+ ref: searchInput,
58
+ placeholder: `Search ${dataIndex}`,
59
+ value: selectedKeys[0],
60
+ onChange: (e) => setSelectedKeys(e.target.value ? [e.target.value] : []),
61
+ onPressEnter: () => handleSearch(selectedKeys, confirm, dataIndex),
62
+ style: { marginBottom: 8, display: "block" }
63
+ }
64
+ ),
65
+ /* @__PURE__ */ jsxs(Space, { children: [
66
+ /* @__PURE__ */ jsx(
67
+ Button,
68
+ {
69
+ type: "primary",
70
+ onClick: () => handleSearch(selectedKeys, confirm, dataIndex),
71
+ icon: /* @__PURE__ */ jsx(SearchOutlined, {}),
72
+ size: "small",
73
+ style: { width: 90 },
74
+ children: "Search"
75
+ }
76
+ ),
77
+ /* @__PURE__ */ jsx(
78
+ Button,
79
+ {
80
+ onClick: () => clearFilters && handleReset(clearFilters),
81
+ size: "small",
82
+ style: { width: 90 },
83
+ children: "Reset"
84
+ }
85
+ ),
86
+ /* @__PURE__ */ jsx(Button, { type: "link", size: "small", onClick: close, children: "close" })
87
+ ] })
88
+ ] }),
89
+ filterIcon: (filtered) => /* @__PURE__ */ jsx(SearchOutlined, { style: { color: filtered ? "#1890ff" : void 0 } }),
90
+ onFilter: (value, record) => {
91
+ return record[dataIndex].toString().toLowerCase().includes(value.toLowerCase());
92
+ },
93
+ render: (text) => {
94
+ return searchedColumn === dataIndex ? /* @__PURE__ */ jsx(
95
+ Highlighter,
96
+ {
97
+ highlightStyle: { backgroundColor: "#ffc069", padding: 0 },
98
+ searchWords: [searchText],
99
+ autoEscape: true,
100
+ textToHighlight: text ? text.toString() : ""
101
+ }
102
+ ) : text;
103
+ }
104
+ };
105
+ }
106
+ const SideEffectTable = ({
107
+ module,
108
+ moduleGraph,
109
+ setEditorData
110
+ }) => {
111
+ const sideEffects = useMemo(
112
+ () => moduleGraph.getModuleGraphModule(module).getSideEffects(),
113
+ [module]
114
+ );
115
+ const dataSource = sideEffects.map(({ variable, name, identifier }, i) => {
116
+ return {
117
+ key: i,
118
+ name,
119
+ identifier: module.isPreferSource ? identifier.position.source : identifier.position.transformed,
120
+ declaration: variable?.identifier
121
+ };
122
+ }).sort((pre, next) => {
123
+ if (pre.name === next.name) {
124
+ return pre.identifier.start.line > next.identifier.start.line ? 1 : -1;
125
+ }
126
+ return pre.name > next.name ? 1 : -1;
127
+ }).reduce((ans, item) => {
128
+ const lastItem = ans[ans.length - 1];
129
+ if (!lastItem || lastItem.name !== item.name) {
130
+ ans.push(item);
131
+ return ans;
132
+ }
133
+ if (!lastItem.children) {
134
+ lastItem.children = [];
135
+ }
136
+ lastItem.children.push(item);
137
+ return ans;
138
+ }, []);
139
+ const columns = [
140
+ {
141
+ title: "Name",
142
+ dataIndex: "name",
143
+ key: "name",
144
+ align: "center",
145
+ ...useSearchCell("name")
146
+ },
147
+ {
148
+ title: "SideEffect",
149
+ dataIndex: "identifier",
150
+ key: "identifier",
151
+ align: "center",
152
+ render: (val) => {
153
+ return /* @__PURE__ */ jsx(Tooltip, { title: `Current File line ${val.start.line}`, children: /* @__PURE__ */ jsx(
154
+ Button,
155
+ {
156
+ type: "link",
157
+ onClick: () => setEditorData(module, [val], val.start.line),
158
+ children: "Move To"
159
+ }
160
+ ) });
161
+ }
162
+ },
163
+ {
164
+ title: "Declaration",
165
+ dataIndex: "declaration",
166
+ key: "declaration",
167
+ align: "center",
168
+ render: (val) => {
169
+ return getDeclarationElement(val, setEditorData);
170
+ }
171
+ }
172
+ ];
173
+ const titleInfo = /* @__PURE__ */ jsx(React.Fragment, { children: /* @__PURE__ */ jsxs(Space, { style: { fontWeight: 400 }, children: [
174
+ "Import",
175
+ /* @__PURE__ */ jsx(Typography.Text, { strong: true, children: dataSource.length }),
176
+ "variables",
177
+ /* @__PURE__ */ jsx(Divider, { type: "vertical" }),
178
+ /* @__PURE__ */ jsx(Typography.Text, { strong: true, children: sideEffects.length }),
179
+ "sideEffects"
180
+ ] }) });
181
+ return /* @__PURE__ */ jsx(
182
+ Card,
183
+ {
184
+ style: { marginBottom: 10 },
185
+ title: titleInfo,
186
+ className: "tree-shaking-export-table",
187
+ children: /* @__PURE__ */ jsx(
188
+ Table,
189
+ {
190
+ size: "small",
191
+ dataSource,
192
+ columns,
193
+ pagination: false,
194
+ scroll: { y: tableHeight }
195
+ }
196
+ )
197
+ }
198
+ );
199
+ };
200
+ const ExportTable = ({
201
+ module,
202
+ moduleGraph,
203
+ setEditorData
204
+ }) => {
205
+ const exportsData = useMemo(
206
+ () => moduleGraph.getModuleGraphModule(module).getExports(),
207
+ [module]
208
+ );
209
+ const allUnUsedExports = exportsData.filter(
210
+ (item) => item.getSideEffects().length === 0
211
+ );
212
+ const titleInfo = /* @__PURE__ */ jsxs(React.Fragment, { children: [
213
+ /* @__PURE__ */ jsxs(Space, { style: { fontWeight: 400 }, children: [
214
+ "Export",
215
+ /* @__PURE__ */ jsx(Typography.Text, { strong: true, children: exportsData.length })
216
+ ] }),
217
+ exportsData.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
218
+ /* @__PURE__ */ jsx(Divider, { type: "vertical" }),
219
+ /* @__PURE__ */ jsxs(Typography.Text, { style: { fontWeight: 400 }, code: true, children: [
220
+ /* @__PURE__ */ jsx(Typography.Text, { children: "Unused" }),
221
+ /* @__PURE__ */ jsx(Divider, { type: "vertical" }),
222
+ /* @__PURE__ */ jsx(Typography.Text, { strong: true, children: allUnUsedExports.length })
223
+ ] }),
224
+ /* @__PURE__ */ jsx(Divider, { type: "vertical" }),
225
+ /* @__PURE__ */ jsxs(Typography.Text, { style: { fontWeight: 400 }, code: true, children: [
226
+ /* @__PURE__ */ jsx(Typography.Text, { children: "Used" }),
227
+ /* @__PURE__ */ jsx(Divider, { type: "vertical" }),
228
+ /* @__PURE__ */ jsx(Typography.Text, { strong: true, children: exportsData.length - allUnUsedExports.length })
229
+ ] })
230
+ ] }) : ""
231
+ ] });
232
+ const dataSource = exportsData.map((item, i) => {
233
+ const { variable } = item;
234
+ return {
235
+ key: i,
236
+ name: item.name,
237
+ declaration: variable?.identifier,
238
+ used: item.getSideEffects().map(({ module: module2, identifier }) => {
239
+ const range = module2?.isPreferSource ? identifier.position.source : identifier.position.transformed;
240
+ if (!module2 || !range) {
241
+ return;
242
+ }
243
+ return {
244
+ module: module2,
245
+ range
246
+ };
247
+ }).filter(isDef)
248
+ };
249
+ });
250
+ const columns = [
251
+ {
252
+ title: "Name",
253
+ dataIndex: "name",
254
+ key: "name",
255
+ align: "center",
256
+ ...useSearchCell("name")
257
+ },
258
+ {
259
+ title: "Declaration",
260
+ dataIndex: "declaration",
261
+ key: "declaration",
262
+ align: "center",
263
+ render: (val) => {
264
+ return getDeclarationElement(val, setEditorData);
265
+ }
266
+ },
267
+ {
268
+ title: "Info",
269
+ dataIndex: "used",
270
+ key: "used",
271
+ align: "center",
272
+ sorter: (a, b) => a.used.length - b.used.length,
273
+ sortDirections: ["descend", "ascend"],
274
+ render: (val) => /* @__PURE__ */ jsxs("span", { children: [
275
+ "Used ",
276
+ val.length,
277
+ " times"
278
+ ] })
279
+ }
280
+ ];
281
+ const expandElement = (data) => {
282
+ return /* @__PURE__ */ jsxs("div", { className: "tree-shaking-side-effect-list", children: [
283
+ /* @__PURE__ */ jsx(Typography.Text, { children: "List of SideEffect:" }),
284
+ data.used.map(({ module: module2, range }) => /* @__PURE__ */ jsx(Tooltip, { title: `${module2.path}:${range.start.line}`, children: /* @__PURE__ */ jsx(
285
+ Button,
286
+ {
287
+ className: "tree-shaking-side-effect-list-item",
288
+ type: "link",
289
+ onClick: () => {
290
+ setEditorData(module2, [range], range.start.line);
291
+ },
292
+ children: `${ellipsisPath(module2.path)}:${range.start.line}`
293
+ }
294
+ ) }, module2.id))
295
+ ] });
296
+ };
297
+ return /* @__PURE__ */ jsx(Card, { title: titleInfo, className: "tree-shaking-export-table", children: /* @__PURE__ */ jsx(
298
+ Table,
299
+ {
300
+ size: "small",
301
+ dataSource,
302
+ columns,
303
+ pagination: false,
304
+ scroll: { y: tableHeight },
305
+ expandable: {
306
+ expandedRowRender: expandElement,
307
+ rowExpandable: (val) => val.used.length > 0
308
+ }
309
+ }
310
+ ) });
311
+ };
312
+ const TreeShakingTable = (props) => {
313
+ return props.kind === "side-effect" ? /* @__PURE__ */ jsx(SideEffectTable, { ...props }) : /* @__PURE__ */ jsx(ExportTable, { ...props });
314
+ };
315
+ export {
316
+ ExportTable,
317
+ SideEffectTable,
318
+ TreeShakingTable
319
+ };
@@ -0,0 +1,3 @@
1
+ import type { Module, SourceRange } from '@rsdoctor/graph';
2
+ export type TableKind = 'side-effect' | 'export';
3
+ export type SetEditorStatus = (module: Module, ranges: SourceRange[], line?: number) => void;
File without changes
@@ -0,0 +1,7 @@
1
+ import { Module, ModuleGraph, Statement } from '@rsdoctor/graph';
2
+ import { editor } from 'monaco-editor';
3
+ export declare function useFileStructures(modules: Module[], moduleGraph: ModuleGraph, searchInput: string, selectedModule: Module, onItemClick: (file: string) => void, cwd: string): import("../../utils").DataNode[];
4
+ export declare function getTreeFilesDefaultExpandedKeys(files: any[]): (string | number)[];
5
+ export declare function ellipsisPath(full: string): string;
6
+ export declare function getModulePositionString(statement: Statement, module: Module): string;
7
+ export declare function getHoverMessageInModule(module: Module, moduleGraph: ModuleGraph): editor.IModelDecoration[];
@@ -0,0 +1,147 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import path from "path-browserify";
4
+ import { escape, get } from "lodash-es";
5
+ import { Module } from "@rsdoctor/graph";
6
+ import { Tag, Space } from "antd";
7
+ import { Range } from "monaco-editor";
8
+ import { getOpenTagText } from "./open-tag";
9
+ import {
10
+ createFileStructures,
11
+ mapFileKey,
12
+ getSelectionRange
13
+ } from "../../utils";
14
+ import { Keyword } from "../../components/Keyword";
15
+ function useFileStructures(modules, moduleGraph, searchInput, selectedModule, onItemClick, cwd) {
16
+ const files = useMemo(
17
+ () => createFileStructures({
18
+ files: modules.map((item) => item.path),
19
+ cwd,
20
+ fileTitle(file, basename) {
21
+ const module = moduleGraph.getModules().find(
22
+ (item) => item.path === file && item.kind === Module.kind.Normal
23
+ );
24
+ const mgm = moduleGraph.getModuleGraphModule(module);
25
+ const infos = mgm.getExports();
26
+ const unused = infos.filter(
27
+ (info) => info.getSideEffects().length === 0
28
+ );
29
+ return /* @__PURE__ */ jsxs(Space, { style: { wordBreak: "break-all" }, children: [
30
+ /* @__PURE__ */ jsx(
31
+ Keyword,
32
+ {
33
+ text: basename,
34
+ keyword: file,
35
+ onClick: () => onItemClick(file)
36
+ }
37
+ ),
38
+ mgm.dynamic ? /* @__PURE__ */ jsx(Tag, { color: "cyan", children: "dynamic" }) : "",
39
+ unused.length > 0 ? /* @__PURE__ */ jsxs(Tag, { color: "green", children: [
40
+ unused.length,
41
+ " Unused"
42
+ ] }) : ""
43
+ ] });
44
+ }
45
+ }),
46
+ [searchInput, selectedModule]
47
+ );
48
+ return files;
49
+ }
50
+ function getTreeFilesDefaultExpandedKeys(files) {
51
+ return mapFileKey(files, 3, (node) => {
52
+ const resourcePath = get(node, "__RESOURCEPATH__");
53
+ const isNodeModules = resourcePath.indexOf("/node_modules/") > -1;
54
+ return !isNodeModules;
55
+ });
56
+ }
57
+ function ellipsisPath(full) {
58
+ let result = "";
59
+ let current = full;
60
+ for (let i = 0; i < 3; i++) {
61
+ result = result ? `${path.basename(current)}/${result}` : path.basename(current);
62
+ current = path.dirname(current);
63
+ }
64
+ return `...${result}`;
65
+ }
66
+ function getModulePositionString(statement, module) {
67
+ const maxLen = 30;
68
+ const { path: path2, isPreferSource } = module;
69
+ const start = isPreferSource ? statement.position.source.start : statement.position.transformed.start;
70
+ const suffix = `:${start.line ?? 1}:${start.column ?? 0}`;
71
+ return path2.length <= maxLen ? `${path2}${suffix}` : `...${path2.substring(path2.length - 30)}${suffix}`;
72
+ }
73
+ function getHoverMessageInModule(module, moduleGraph) {
74
+ const mgm = moduleGraph.getModuleGraphModule(module);
75
+ const exportLocals = mgm.getOwnExports();
76
+ const variables = exportLocals.map((item) => item.variable).filter((item) => Boolean(item));
77
+ if (exportLocals.length === 0 && variables.length === 0) {
78
+ return [];
79
+ }
80
+ function getVariableMessage(data) {
81
+ const exportData = data.getExportInfo();
82
+ if (!exportData) {
83
+ return "Can not find SideEffect info.";
84
+ }
85
+ const sideEffects = exportData.getSideEffects();
86
+ if (sideEffects.length === 0) {
87
+ return "Have no sideEffect.";
88
+ }
89
+ let content = `Used **${sideEffects.length}** times:
90
+
91
+ <div data-code="tree-shaking-hover">
92
+ <li>`;
93
+ for (const sideEffect of sideEffects) {
94
+ if (!sideEffect) {
95
+ continue;
96
+ }
97
+ const { identifier, module: module2 } = sideEffect;
98
+ if (!module2) {
99
+ continue;
100
+ }
101
+ const { id, isPreferSource: isPreferSource2 } = module2;
102
+ const lineCode = identifier.getLineCode();
103
+ content += `
104
+ <ol>
105
+ ${getOpenTagText(
106
+ id,
107
+ isPreferSource2 ? identifier.position.source : identifier.position.transformed,
108
+ getModulePositionString(identifier, module2)
109
+ )}
110
+ ${lineCode ? `<pre>${escape(lineCode)}</pre>` : ""}
111
+ </ol>`;
112
+ }
113
+ content += "\n</li></div>";
114
+ return content;
115
+ }
116
+ const { isPreferSource } = module;
117
+ const declarationHovers = variables.map((item) => {
118
+ const position = isPreferSource ? item.identifier.position.source : item.identifier.position.transformed;
119
+ const range = position && getSelectionRange(position, Range);
120
+ if (!position || !range) {
121
+ return;
122
+ }
123
+ return {
124
+ range,
125
+ options: {
126
+ stickiness: 1,
127
+ inlineClassName: "tree-shaking-statement-declaration-identifier",
128
+ isWholeLine: false,
129
+ showIfCollapsed: true,
130
+ hoverMessage: {
131
+ supportHtml: true,
132
+ supportThemeIcons: true,
133
+ isTrusted: true,
134
+ value: getVariableMessage(item)
135
+ }
136
+ }
137
+ };
138
+ }).filter((item) => Boolean(item));
139
+ return declarationHovers;
140
+ }
141
+ export {
142
+ ellipsisPath,
143
+ getHoverMessageInModule,
144
+ getModulePositionString,
145
+ getTreeFilesDefaultExpandedKeys,
146
+ useFileStructures
147
+ };
@@ -6,3 +6,4 @@ export * as LoaderFiles from './WebpackLoaders/Analysis';
6
6
  export * as PluginsAnalyze from './WebpackPlugins';
7
7
  export * as ModuleResolve from './ModuleResolve';
8
8
  export * as RuleIndex from './Resources/RuleIndex';
9
+ export * as TreeShaking from './TreeShaking';
@@ -6,6 +6,7 @@ import * as LoaderFiles from "./WebpackLoaders/Analysis";
6
6
  import * as PluginsAnalyze from "./WebpackPlugins";
7
7
  import * as ModuleResolve from "./ModuleResolve";
8
8
  import * as RuleIndex from "./Resources/RuleIndex";
9
+ import * as TreeShaking from "./TreeShaking";
9
10
  export {
10
11
  BundleSize,
11
12
  LoaderFiles,
@@ -14,5 +15,6 @@ export {
14
15
  ModuleResolve,
15
16
  Overall,
16
17
  PluginsAnalyze,
17
- RuleIndex
18
+ RuleIndex,
19
+ TreeShaking
18
20
  };
@@ -4,3 +4,4 @@ export declare function changeOrigin(origin: string): string;
4
4
  export declare function getSharingUrl(manifestCloudUrl: string): string;
5
5
  export declare function getDemoUrl(): string | null;
6
6
  export declare const getShortPath: (path: string) => string;
7
+ export declare function isDef<E = unknown>(data: E): data is NonNullable<E>;
package/dist/utils/url.js CHANGED
@@ -61,11 +61,15 @@ const getShortPath = (path) => {
61
61
  }
62
62
  return path;
63
63
  };
64
+ function isDef(data) {
65
+ return data !== void 0 && data !== null;
66
+ }
64
67
  export {
65
68
  changeOrigin,
66
69
  getDemoUrl,
67
70
  getManifestUrlFromUrlQuery,
68
71
  getSharingUrl,
69
72
  getShortPath,
73
+ isDef,
70
74
  isJsDataUrl
71
75
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsdoctor/components",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "main": "./dist/index.js",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
@@ -48,20 +48,24 @@
48
48
  "@monaco-editor/react": "4.6.0",
49
49
  "@types/lodash-es": "4.17.6",
50
50
  "@types/node": "^16",
51
+ "@types/path-browserify": "1.0.1",
51
52
  "@types/react": "^18",
53
+ "@types/react-highlight-words": "^0.16.7",
52
54
  "@types/url-parse": "1.4.8",
53
55
  "ansi-to-react": "6.1.6",
54
56
  "antd": "5.15.3",
55
57
  "axios": "^1.6.1",
56
58
  "dayjs": "1.11.6",
57
- "echarts": "^5.4.3",
59
+ "echarts": "^5.5.0",
58
60
  "echarts-for-react": "^3.0.2",
59
61
  "i18next": "22.0.4",
60
62
  "lodash-es": "4.17.21",
61
63
  "monaco-editor": "0.34.1",
64
+ "path-browserify": "1.0.1",
62
65
  "rc-dialog": "9.1.0",
63
66
  "rc-tree": "5.7.2",
64
67
  "react": "18.2.0",
68
+ "react-highlight-words": "^0.20.0",
65
69
  "react-hyper-tree": "0.3.12",
66
70
  "react-i18next": "12.0.0",
67
71
  "react-json-view": "1.21.3",
@@ -71,9 +75,9 @@
71
75
  "terser": "^5.26.0",
72
76
  "typescript": "^5.2.2",
73
77
  "url-parse": "1.5.10",
74
- "@rsdoctor/graph": "0.1.9",
75
- "@rsdoctor/types": "0.1.9",
76
- "@rsdoctor/utils": "0.1.9"
78
+ "@rsdoctor/graph": "0.2.0",
79
+ "@rsdoctor/types": "0.2.0",
80
+ "@rsdoctor/utils": "0.2.0"
77
81
  },
78
82
  "publishConfig": {
79
83
  "access": "public",