@dimina-kit/devtools 0.3.0 → 0.3.1-dev.20260513123738
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.
- package/dist/main/index.bundle.js +72 -23
- package/dist/main/services/default-adapter.js +1 -3
- package/dist/main/services/simulator/dir.d.ts +1 -1
- package/dist/main/services/simulator/dir.js +1 -4
- package/dist/main/services/simulator-storage/index.js +40 -1
- package/dist/main/utils/paths.d.ts +2 -0
- package/dist/main/utils/paths.js +27 -4
- package/dist/preload/instrumentation/wxml-extract.js +134 -3
- package/dist/preload/runtime/bridge.d.ts +3 -1
- package/dist/preload/runtime/bridge.js +55 -4
- package/dist/preload/shared/types.d.ts +2 -1
- package/dist/preload/windows/simulator.js +100 -7
- package/dist/renderer/assets/index-D1p6crtf.js +46 -0
- package/dist/renderer/assets/{input-VLotwer2.js → input-DxfYnzX_.js} +2 -2
- package/dist/renderer/assets/{ipc-transport-COKIC6Ny.js → ipc-transport-ClIvDcYY.js} +6 -6
- package/dist/renderer/assets/ipc-transport-EiWxu_hG.css +1 -0
- package/dist/renderer/assets/{popover-Dn0RwMLI.js → popover-CtXcBJtM.js} +2 -2
- package/dist/renderer/assets/{select-DzIsLaKX.js → select-DMfp49f1.js} +2 -2
- package/dist/renderer/assets/{settings-CD-dacWB.js → settings-DteGraEU.js} +2 -2
- package/dist/renderer/assets/{settings-api-BV9MPckG.js → settings-api-CY84UMpN.js} +2 -2
- package/dist/renderer/assets/{workbenchSettings-ClwgsvJL.js → workbenchSettings-DjELLnSJ.js} +2 -2
- package/dist/renderer/entries/main/index.html +5 -5
- package/dist/renderer/entries/popover/index.html +5 -5
- package/dist/renderer/entries/settings/index.html +5 -5
- package/dist/renderer/entries/workbench-settings/index.html +4 -4
- package/dist/shared/ipc-channels.d.ts +23 -0
- package/dist/shared/ipc-channels.js +5 -0
- package/package.json +2 -2
- package/dist/renderer/assets/index-D_HFRqZA.js +0 -46
- package/dist/renderer/assets/ipc-transport-6Gy3FXIL.css +0 -1
|
@@ -63,15 +63,31 @@ function setupCdpPort() {
|
|
|
63
63
|
|
|
64
64
|
// src/main/app/app.ts
|
|
65
65
|
import { app as app11, BrowserWindow as BrowserWindow3, nativeImage } from "electron";
|
|
66
|
-
import
|
|
66
|
+
import fs5 from "fs";
|
|
67
67
|
import path6 from "path";
|
|
68
68
|
|
|
69
69
|
// src/main/utils/paths.ts
|
|
70
|
+
import fs2 from "fs";
|
|
70
71
|
import path2 from "path";
|
|
71
72
|
import { fileURLToPath } from "url";
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
function resolveDevtoolsPackageRoot() {
|
|
74
|
+
let dir = path2.dirname(fileURLToPath(import.meta.url));
|
|
75
|
+
const root = path2.parse(dir).root;
|
|
76
|
+
while (dir !== root) {
|
|
77
|
+
const pkgPath = path2.join(dir, "package.json");
|
|
78
|
+
try {
|
|
79
|
+
const pkg2 = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
|
|
80
|
+
if (pkg2.name === "@dimina-kit/devtools") return dir;
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
dir = path2.dirname(dir);
|
|
84
|
+
}
|
|
85
|
+
throw new Error("@dimina-kit/devtools package root not found from " + fileURLToPath(import.meta.url));
|
|
86
|
+
}
|
|
87
|
+
var devtoolsPackageRoot = resolveDevtoolsPackageRoot();
|
|
88
|
+
var rendererDir = path2.join(devtoolsPackageRoot, "dist/renderer");
|
|
89
|
+
var defaultPreloadPath = path2.join(devtoolsPackageRoot, "dist/preload/windows/simulator.js");
|
|
90
|
+
var simulatorDir = path2.join(devtoolsPackageRoot, "dist/simulator");
|
|
75
91
|
|
|
76
92
|
// src/main/app/lifecycle.ts
|
|
77
93
|
import { app as app3, globalShortcut } from "electron";
|
|
@@ -197,10 +213,7 @@ function wireMainWindowEvents(win, state = {}) {
|
|
|
197
213
|
|
|
198
214
|
// src/main/services/default-adapter.ts
|
|
199
215
|
import path3 from "path";
|
|
200
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
201
216
|
import { app as app5 } from "electron";
|
|
202
|
-
var __dirname2 = path3.dirname(fileURLToPath2(import.meta.url));
|
|
203
|
-
var simulatorDir = path3.resolve(__dirname2, "../../simulator");
|
|
204
217
|
var defaultAdapter = {
|
|
205
218
|
async openProject(opts) {
|
|
206
219
|
const diminaKit = await import("@dimina-kit/devkit");
|
|
@@ -228,6 +241,10 @@ var SimulatorStorageChannel = {
|
|
|
228
241
|
GetSnapshot: "simulator:storage:snapshot",
|
|
229
242
|
Event: "simulator:storage:event"
|
|
230
243
|
};
|
|
244
|
+
var SimulatorElementChannel = {
|
|
245
|
+
Inspect: "simulator:element:inspect",
|
|
246
|
+
Clear: "simulator:element:clear"
|
|
247
|
+
};
|
|
231
248
|
var WorkbenchChannel = {
|
|
232
249
|
GetPanelConfig: "workbench:getPanelConfig",
|
|
233
250
|
GetApiNamespaces: "workbench:getApiNamespaces",
|
|
@@ -621,7 +638,7 @@ function createViewManager(ctx) {
|
|
|
621
638
|
// src/main/services/projects/project-repository.ts
|
|
622
639
|
import { app as app6 } from "electron";
|
|
623
640
|
import path5 from "path";
|
|
624
|
-
import
|
|
641
|
+
import fs3 from "fs";
|
|
625
642
|
|
|
626
643
|
// src/shared/constants.ts
|
|
627
644
|
var DEFAULT_SCENE = 1001;
|
|
@@ -671,13 +688,13 @@ function getProjectsFile() {
|
|
|
671
688
|
}
|
|
672
689
|
function load() {
|
|
673
690
|
try {
|
|
674
|
-
return JSON.parse(
|
|
691
|
+
return JSON.parse(fs3.readFileSync(getProjectsFile(), "utf-8"));
|
|
675
692
|
} catch {
|
|
676
693
|
return [];
|
|
677
694
|
}
|
|
678
695
|
}
|
|
679
696
|
function save(projects) {
|
|
680
|
-
|
|
697
|
+
fs3.writeFileSync(getProjectsFile(), JSON.stringify(projects, null, 2));
|
|
681
698
|
}
|
|
682
699
|
function listProjects() {
|
|
683
700
|
return load();
|
|
@@ -686,14 +703,14 @@ function validateProjectDir(dirPath) {
|
|
|
686
703
|
if (!dirPath) {
|
|
687
704
|
return "\u5C0F\u7A0B\u5E8F\u76EE\u5F55\u8DEF\u5F84\u4E3A\u7A7A\uFF0C\u8BF7\u9009\u62E9\u5305\u542B\u5C0F\u7A0B\u5E8F\u6E90\u7801\u7684\u76EE\u5F55";
|
|
688
705
|
}
|
|
689
|
-
if (!
|
|
706
|
+
if (!fs3.existsSync(dirPath)) {
|
|
690
707
|
return `\u5C0F\u7A0B\u5E8F\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${dirPath}`;
|
|
691
708
|
}
|
|
692
|
-
if (!
|
|
709
|
+
if (!fs3.existsSync(path5.join(dirPath, "app.json"))) {
|
|
693
710
|
const configPath = path5.join(dirPath, "project.config.json");
|
|
694
|
-
if (
|
|
711
|
+
if (fs3.existsSync(configPath)) {
|
|
695
712
|
try {
|
|
696
|
-
const cfg = JSON.parse(
|
|
713
|
+
const cfg = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
|
|
697
714
|
if (cfg.miniprogramRoot) {
|
|
698
715
|
const resolvedRoot = path5.resolve(dirPath, cfg.miniprogramRoot);
|
|
699
716
|
return `\u8BE5\u76EE\u5F55\u7F3A\u5C11 app.json\uFF0Cproject.config.json \u4E2D\u6307\u5B9A\u4E86 miniprogramRoot: "${cfg.miniprogramRoot}"\uFF0C\u8BF7\u5BFC\u5165 ${resolvedRoot}`;
|
|
@@ -711,8 +728,8 @@ function addProject(dirPath) {
|
|
|
711
728
|
let name = path5.basename(dirPath);
|
|
712
729
|
try {
|
|
713
730
|
const configPath = path5.join(dirPath, "project.config.json");
|
|
714
|
-
if (
|
|
715
|
-
const cfg = JSON.parse(
|
|
731
|
+
if (fs3.existsSync(configPath)) {
|
|
732
|
+
const cfg = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
|
|
716
733
|
if (cfg.projectname) name = cfg.projectname;
|
|
717
734
|
}
|
|
718
735
|
} catch (err) {
|
|
@@ -752,7 +769,7 @@ function getProjectPages(dirPath) {
|
|
|
752
769
|
const appJsonPath = path5.join(dirPath, "app.json");
|
|
753
770
|
try {
|
|
754
771
|
const appJson = JSON.parse(
|
|
755
|
-
|
|
772
|
+
fs3.readFileSync(appJsonPath, "utf-8")
|
|
756
773
|
);
|
|
757
774
|
return {
|
|
758
775
|
pages: appJson.pages || [],
|
|
@@ -776,7 +793,7 @@ function getProjectSettings(projectPath) {
|
|
|
776
793
|
}
|
|
777
794
|
try {
|
|
778
795
|
const configPath = path5.join(projectPath, "project.config.json");
|
|
779
|
-
const config = JSON.parse(
|
|
796
|
+
const config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
|
|
780
797
|
return {
|
|
781
798
|
uploadWithSourceMap: !!config.setting?.uploadWithSourceMap
|
|
782
799
|
};
|
|
@@ -789,14 +806,14 @@ function updateProjectSettings(projectPath, patch) {
|
|
|
789
806
|
const configPath = path5.join(projectPath, "project.config.json");
|
|
790
807
|
let config = {};
|
|
791
808
|
try {
|
|
792
|
-
config = JSON.parse(
|
|
809
|
+
config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
|
|
793
810
|
} catch {
|
|
794
811
|
}
|
|
795
812
|
const nextSetting = {
|
|
796
813
|
...config.setting ?? {},
|
|
797
814
|
...patch.uploadWithSourceMap === void 0 ? {} : { uploadWithSourceMap: patch.uploadWithSourceMap }
|
|
798
815
|
};
|
|
799
|
-
|
|
816
|
+
fs3.writeFileSync(
|
|
800
817
|
configPath,
|
|
801
818
|
JSON.stringify({ ...config, setting: nextSetting }, null, 2)
|
|
802
819
|
);
|
|
@@ -992,10 +1009,10 @@ function installAppMenu(ctx) {
|
|
|
992
1009
|
|
|
993
1010
|
// src/main/ipc/app.ts
|
|
994
1011
|
import { ipcMain } from "electron";
|
|
995
|
-
import
|
|
1012
|
+
import fs4 from "fs";
|
|
996
1013
|
function registerAppIpc(ctx) {
|
|
997
1014
|
ipcMain.handle(AppChannel.GetPreloadPath, () => {
|
|
998
|
-
return `file://${
|
|
1015
|
+
return `file://${fs4.realpathSync(ctx.preloadPath)}`;
|
|
999
1016
|
});
|
|
1000
1017
|
ipcMain.handle(AppChannel.GetBranding, async () => {
|
|
1001
1018
|
if (ctx.brandingProvider) return ctx.brandingProvider();
|
|
@@ -2373,6 +2390,36 @@ async function getSnapshot() {
|
|
|
2373
2390
|
return [];
|
|
2374
2391
|
}
|
|
2375
2392
|
}
|
|
2393
|
+
function findSimulatorWebContents() {
|
|
2394
|
+
for (const wc of wcStatic.getAllWebContents()) {
|
|
2395
|
+
if (isSimulatorWebview(wc)) return wc;
|
|
2396
|
+
}
|
|
2397
|
+
return null;
|
|
2398
|
+
}
|
|
2399
|
+
async function inspectElement(sid) {
|
|
2400
|
+
if (!sid) return null;
|
|
2401
|
+
const sim = findSimulatorWebContents();
|
|
2402
|
+
if (!sim) return null;
|
|
2403
|
+
try {
|
|
2404
|
+
const result = await sim.executeJavaScript(
|
|
2405
|
+
`window.__simulatorData && window.__simulatorData.highlightElement ? window.__simulatorData.highlightElement(${JSON.stringify(sid)}) : null`
|
|
2406
|
+
);
|
|
2407
|
+
return result ?? null;
|
|
2408
|
+
} catch (e) {
|
|
2409
|
+
console.warn("[simulator-element] inspect failed:", e.message);
|
|
2410
|
+
return null;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
async function clearElementInspection() {
|
|
2414
|
+
const sim = findSimulatorWebContents();
|
|
2415
|
+
if (!sim) return;
|
|
2416
|
+
try {
|
|
2417
|
+
await sim.executeJavaScript(
|
|
2418
|
+
"window.__simulatorData && window.__simulatorData.unhighlightElement && window.__simulatorData.unhighlightElement()"
|
|
2419
|
+
);
|
|
2420
|
+
} catch {
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2376
2423
|
function setupSimulatorStorage(host) {
|
|
2377
2424
|
hostWc = host;
|
|
2378
2425
|
for (const wc of wcStatic.getAllWebContents()) {
|
|
@@ -2391,6 +2438,8 @@ function setupSimulatorStorage(host) {
|
|
|
2391
2438
|
});
|
|
2392
2439
|
});
|
|
2393
2440
|
ipcMain10.handle(SimulatorStorageChannel.GetSnapshot, getSnapshot);
|
|
2441
|
+
ipcMain10.handle(SimulatorElementChannel.Inspect, (_event, sid) => inspectElement(sid));
|
|
2442
|
+
ipcMain10.handle(SimulatorElementChannel.Clear, clearElementInspection);
|
|
2394
2443
|
}
|
|
2395
2444
|
|
|
2396
2445
|
// src/main/services/update/update-manager.ts
|
|
@@ -2606,7 +2655,7 @@ function wireAppWindowEvents(config, instance) {
|
|
|
2606
2655
|
function enableDevRendererAutoReload(rendererDir2) {
|
|
2607
2656
|
if (!app11.isPackaged) {
|
|
2608
2657
|
let reloadTimer = null;
|
|
2609
|
-
|
|
2658
|
+
fs5.watch(rendererDir2, { recursive: true }, () => {
|
|
2610
2659
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
2611
2660
|
reloadTimer = setTimeout(() => {
|
|
2612
2661
|
for (const win of BrowserWindow3.getAllWindows()) {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
2
|
import { app } from 'electron';
|
|
4
|
-
|
|
5
|
-
const simulatorDir = path.resolve(__dirname, '../../simulator');
|
|
3
|
+
import { simulatorDir } from '../utils/paths.js';
|
|
6
4
|
export const defaultAdapter = {
|
|
7
5
|
async openProject(opts) {
|
|
8
6
|
const diminaKit = await import('@dimina-kit/devkit');
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { simulatorDir } from '../../utils/paths.js';
|
|
2
2
|
//# sourceMappingURL=dir.d.ts.map
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
4
|
-
export const simulatorDir = path.join(__dirname, '../../../simulator');
|
|
1
|
+
export { simulatorDir } from '../../utils/paths.js';
|
|
5
2
|
//# sourceMappingURL=dir.js.map
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* events stop flowing until they close it.
|
|
17
17
|
*/
|
|
18
18
|
import { app, ipcMain, webContents as wcStatic } from 'electron';
|
|
19
|
-
import { SimulatorStorageChannel, } from '../../../shared/ipc-channels.js';
|
|
19
|
+
import { SimulatorElementChannel, SimulatorStorageChannel, } from '../../../shared/ipc-channels.js';
|
|
20
20
|
let attachedWc = null;
|
|
21
21
|
let hostWc = null;
|
|
22
22
|
function isSimulatorWebview(wc) {
|
|
@@ -108,6 +108,43 @@ async function getSnapshot() {
|
|
|
108
108
|
return [];
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
+
// Element inspection delegates to the simulator preload's __simulatorData
|
|
112
|
+
// bridge via executeJavaScript. Looked up independently from `attachedWc` so
|
|
113
|
+
// it keeps working when the user opens Chrome DevTools and the storage
|
|
114
|
+
// debugger detaches (debugger.attach is mutually exclusive with F12).
|
|
115
|
+
function findSimulatorWebContents() {
|
|
116
|
+
for (const wc of wcStatic.getAllWebContents()) {
|
|
117
|
+
if (isSimulatorWebview(wc))
|
|
118
|
+
return wc;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
async function inspectElement(sid) {
|
|
123
|
+
if (!sid)
|
|
124
|
+
return null;
|
|
125
|
+
const sim = findSimulatorWebContents();
|
|
126
|
+
if (!sim)
|
|
127
|
+
return null;
|
|
128
|
+
try {
|
|
129
|
+
const result = (await sim.executeJavaScript(`window.__simulatorData && window.__simulatorData.highlightElement ? window.__simulatorData.highlightElement(${JSON.stringify(sid)}) : null`));
|
|
130
|
+
return result ?? null;
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
console.warn('[simulator-element] inspect failed:', e.message);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function clearElementInspection() {
|
|
138
|
+
const sim = findSimulatorWebContents();
|
|
139
|
+
if (!sim)
|
|
140
|
+
return;
|
|
141
|
+
try {
|
|
142
|
+
await sim.executeJavaScript('window.__simulatorData && window.__simulatorData.unhighlightElement && window.__simulatorData.unhighlightElement()');
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// best-effort
|
|
146
|
+
}
|
|
147
|
+
}
|
|
111
148
|
export function setupSimulatorStorage(host) {
|
|
112
149
|
hostWc = host;
|
|
113
150
|
// Attach to any simulator webview that already exists (project may have
|
|
@@ -132,5 +169,7 @@ export function setupSimulatorStorage(host) {
|
|
|
132
169
|
});
|
|
133
170
|
});
|
|
134
171
|
ipcMain.handle(SimulatorStorageChannel.GetSnapshot, getSnapshot);
|
|
172
|
+
ipcMain.handle(SimulatorElementChannel.Inspect, (_event, sid) => inspectElement(sid));
|
|
173
|
+
ipcMain.handle(SimulatorElementChannel.Clear, clearElementInspection);
|
|
135
174
|
}
|
|
136
175
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
export declare const devtoolsPackageRoot: string;
|
|
1
2
|
export declare const rendererDir: string;
|
|
2
3
|
export declare const defaultPreloadPath: string;
|
|
4
|
+
export declare const simulatorDir: string;
|
|
3
5
|
export declare function getRendererDir(): string;
|
|
4
6
|
export declare function getPreloadDir(): string;
|
|
5
7
|
export declare function getRendererHtml(filename: string): string;
|
package/dist/main/utils/paths.js
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
1
2
|
import path from 'path';
|
|
2
3
|
import { fileURLToPath } from 'url';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// Hardcoded relative jumps like '../../dist/renderer' break under the
|
|
5
|
+
// esbuild single-file bundle (dist/main/index.bundle.js): every inlined
|
|
6
|
+
// module ends up at the bundle's depth instead of its tsc location, so
|
|
7
|
+
// the math goes off by a directory. Walk up to this package's own
|
|
8
|
+
// package.json so the resolver works for tsc multi-file, the bundle,
|
|
9
|
+
// npm/pnpm node_modules, and asar packaging alike.
|
|
10
|
+
function resolveDevtoolsPackageRoot() {
|
|
11
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const root = path.parse(dir).root;
|
|
13
|
+
while (dir !== root) {
|
|
14
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
15
|
+
try {
|
|
16
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
17
|
+
if (pkg.name === '@dimina-kit/devtools')
|
|
18
|
+
return dir;
|
|
19
|
+
}
|
|
20
|
+
catch { }
|
|
21
|
+
dir = path.dirname(dir);
|
|
22
|
+
}
|
|
23
|
+
throw new Error('@dimina-kit/devtools package root not found from ' + fileURLToPath(import.meta.url));
|
|
24
|
+
}
|
|
25
|
+
export const devtoolsPackageRoot = resolveDevtoolsPackageRoot();
|
|
26
|
+
export const rendererDir = path.join(devtoolsPackageRoot, 'dist/renderer');
|
|
27
|
+
export const defaultPreloadPath = path.join(devtoolsPackageRoot, 'dist/preload/windows/simulator.js');
|
|
28
|
+
export const simulatorDir = path.join(devtoolsPackageRoot, 'dist/simulator');
|
|
6
29
|
export function getRendererDir() {
|
|
7
30
|
return rendererDir;
|
|
8
31
|
}
|
|
9
32
|
export function getPreloadDir() {
|
|
10
|
-
return path.join(
|
|
33
|
+
return path.join(devtoolsPackageRoot, 'dist/preload');
|
|
11
34
|
}
|
|
12
35
|
export function getRendererHtml(filename) {
|
|
13
36
|
return path.join(rendererDir, filename);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { registerSyntheticSid } from '../runtime/bridge.js';
|
|
1
2
|
const INTERNAL_CLASSES = new Set([
|
|
2
3
|
'dd-swiper-wrapper', 'dd-swiper-slides', 'dd-swiper-slide-frame', 'dd-swiper-dots',
|
|
3
4
|
'dd-picker-overlay', 'dd-picker-container', 'dd-picker-header', 'dd-picker-body',
|
|
@@ -24,10 +25,44 @@ function resolveTemplateNameFromParent(instance) {
|
|
|
24
25
|
}
|
|
25
26
|
return null;
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Convert a PascalCase / camelCase identifier to kebab-case. Matches the
|
|
30
|
+
* upstream `camelCaseToUnderscore` (dimina/fe/packages/common) so a name
|
|
31
|
+
* already normalized by `withInstall` round-trips identically.
|
|
32
|
+
* `View` -> `view`, `ScrollView` -> `scroll-view`, `CoverImage` -> `cover-image`.
|
|
33
|
+
*/
|
|
34
|
+
function pascalToKebab(name) {
|
|
35
|
+
return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Read the page's source path off Vue's provide/inject. dimina runtime.js 在
|
|
39
|
+
* dd-page 的 setup() 里调用 `provide('path', path)`,Vue 把它存到
|
|
40
|
+
* `instance.provides`。我们利用这个把页面节点的 tagName 从硬编码 `page`
|
|
41
|
+
* 升级为页面全路径(如 `pages/index/index`),对齐微信开发者工具。
|
|
42
|
+
*/
|
|
43
|
+
function resolvePagePath(instance) {
|
|
44
|
+
// dimina runtime 在 dd-page 的 setup 里 `instance.proxy.__page__ = true` 并
|
|
45
|
+
// `provide('path', path)`。两个标记同时具备才视为页面层级,避免 dd-page
|
|
46
|
+
// 的子节点继承 provides.path 后被误判(Vue 用 Object.create 链式 provides)。
|
|
47
|
+
const proxy = instance.proxy;
|
|
48
|
+
if (proxy?.__page__ !== true)
|
|
49
|
+
return null;
|
|
50
|
+
const provides = instance.provides;
|
|
51
|
+
const path = provides?.path;
|
|
52
|
+
if (typeof path !== 'string' || !path)
|
|
53
|
+
return null;
|
|
54
|
+
return path.startsWith('/') ? path.slice(1) : path;
|
|
55
|
+
}
|
|
27
56
|
function resolveTagName(instance) {
|
|
28
57
|
const type = instance.type;
|
|
29
58
|
if (!type)
|
|
30
59
|
return 'unknown';
|
|
60
|
+
// 页面层级优先:dd-page 没有 __tagName/__name,且 home 页等无 usingComponents
|
|
61
|
+
// 的页面 type.components 为 undefined,会落到 resolveTemplateNameFromParent
|
|
62
|
+
// 回到 'page'。在那之前直接用 provide('path') 的路径升级 tag 名。
|
|
63
|
+
const pagePath = resolvePagePath(instance);
|
|
64
|
+
if (pagePath)
|
|
65
|
+
return pagePath;
|
|
31
66
|
if (typeof type.__tagName === 'string')
|
|
32
67
|
return type.__tagName;
|
|
33
68
|
const name = (type.__name || type.name);
|
|
@@ -42,8 +77,16 @@ function resolveTagName(instance) {
|
|
|
42
77
|
return 'page';
|
|
43
78
|
if (name.startsWith('dd-'))
|
|
44
79
|
return name.slice(3);
|
|
80
|
+
// Reverse-map Dimina component names back to their miniprogram tag names.
|
|
81
|
+
// The installer (`withInstall`) sets `__tagName = camelCaseToUnderscore(__name)`,
|
|
82
|
+
// but in dev builds, custom registrations, or when the installer hasn't run,
|
|
83
|
+
// we may only see the raw `__name`/`name` (e.g. `View`, `ScrollView`, `DdButton`).
|
|
84
|
+
// Without this fallback the WXML panel would surface the upstream Vue name
|
|
85
|
+
// verbatim, defeating the panel's purpose of showing source-level tags.
|
|
45
86
|
if (name.startsWith('Dd') && name.length > 2)
|
|
46
|
-
return name.slice(2)
|
|
87
|
+
return pascalToKebab(name.slice(2));
|
|
88
|
+
if (/^[A-Z]/.test(name))
|
|
89
|
+
return pascalToKebab(name);
|
|
47
90
|
return name;
|
|
48
91
|
}
|
|
49
92
|
function extractProps(instance) {
|
|
@@ -79,7 +122,12 @@ function isInternalElement(vnode) {
|
|
|
79
122
|
function getElementSid(instance) {
|
|
80
123
|
const subTree = instance.subTree;
|
|
81
124
|
const el = subTree?.el;
|
|
82
|
-
|
|
125
|
+
if (!el?.getAttribute)
|
|
126
|
+
return undefined;
|
|
127
|
+
const sid = el.getAttribute('data-sid');
|
|
128
|
+
if (sid)
|
|
129
|
+
return sid;
|
|
130
|
+
return registerSyntheticSid(el);
|
|
83
131
|
}
|
|
84
132
|
function isTransparentComponent(instance, tagName) {
|
|
85
133
|
if (tagName === 'unknown')
|
|
@@ -97,9 +145,29 @@ function isTransparentComponent(instance, tagName) {
|
|
|
97
145
|
}
|
|
98
146
|
return false;
|
|
99
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* 检测 Vue 的 Comment vnode(`v-if`/`v-else`/`v-for` 占位锚点)。
|
|
150
|
+
* Vue 在条件不成立时会留下 `<!-- v-if -->` 这样的注释 vnode 用作 DOM 锚点。
|
|
151
|
+
* 这些 vnode 不是用户内容,必须从 wxml 树里剔除。
|
|
152
|
+
*
|
|
153
|
+
* - dev build:type 是 `Symbol('Comment')`,description = 'Comment' 或 'v-cmt'
|
|
154
|
+
* - prod build:description 可能丢失,但 children 一般是 'v-if'/'v-else' 等 marker
|
|
155
|
+
*/
|
|
156
|
+
function isCommentVNode(vnode) {
|
|
157
|
+
const type = vnode.type;
|
|
158
|
+
if (typeof type !== 'symbol')
|
|
159
|
+
return false;
|
|
160
|
+
const desc = type.description;
|
|
161
|
+
if (desc === 'Comment' || desc === 'v-cmt')
|
|
162
|
+
return true;
|
|
163
|
+
const children = typeof vnode.children === 'string' ? vnode.children : '';
|
|
164
|
+
return /^v-(if|else|else-if|for)$/.test(children);
|
|
165
|
+
}
|
|
100
166
|
function extractChildrenFromVNode(vnode, depth) {
|
|
101
167
|
if (!vnode || depth > 50)
|
|
102
168
|
return [];
|
|
169
|
+
if (isCommentVNode(vnode))
|
|
170
|
+
return [];
|
|
103
171
|
if (vnode.component) {
|
|
104
172
|
const result = walkInstance(vnode.component, depth + 1);
|
|
105
173
|
return result ? (Array.isArray(result) ? result : [result]) : [];
|
|
@@ -157,6 +225,69 @@ function extractChildrenFromVNode(vnode, depth) {
|
|
|
157
225
|
}
|
|
158
226
|
return result;
|
|
159
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Reverse-map Dimina's `<wrapper name="/components/foo/foo">` (used to host
|
|
230
|
+
* every user-defined component) back to the source-level tag the user wrote
|
|
231
|
+
* in WXML (e.g. `<foo>`).
|
|
232
|
+
*
|
|
233
|
+
* The wrapper carries the registered component path in its `name` Vue prop.
|
|
234
|
+
* By convention (also followed by miniprogram tooling), the registered key is
|
|
235
|
+
* the last path segment, optionally stripping a trailing `/index`. We can't
|
|
236
|
+
* see the page's `usingComponents` map from inside the Vue instance, so this
|
|
237
|
+
* convention-based recovery is best-effort: if the user picked a different
|
|
238
|
+
* registration key than the directory name (e.g. `usingComponents:
|
|
239
|
+
* { myCounter: '/components/counter/counter' }`), the panel will show
|
|
240
|
+
* `counter`, not `myCounter`.
|
|
241
|
+
*
|
|
242
|
+
* The path heuristic also resolves a name-collision risk: a user-written
|
|
243
|
+
* `<counter name="x">` would land in the same `attrs.name` slot as the
|
|
244
|
+
* wrapper-internal path. We only unwrap when the value looks like an absolute
|
|
245
|
+
* component path (leading `/`), which dimina always emits but a user would
|
|
246
|
+
* almost never type as a literal attr.
|
|
247
|
+
*/
|
|
248
|
+
function unwrapCustomComponent(node) {
|
|
249
|
+
if (node.tagName !== 'wrapper')
|
|
250
|
+
return node;
|
|
251
|
+
const path = node.attrs?.name;
|
|
252
|
+
if (typeof path !== 'string' || !path.startsWith('/'))
|
|
253
|
+
return node;
|
|
254
|
+
// 去掉前导 `/` 后保留原路径形式(保留所有斜杠,不做 kebab/dash 转换)。
|
|
255
|
+
// 仅当路径以 `/index` 结尾且剥离后仍至少剩一段时才剥(`/index` 单独存在则
|
|
256
|
+
// 退回 wrapper,避免出现空 tagName)。
|
|
257
|
+
const stripped = path.replace(/^\//, '');
|
|
258
|
+
const withoutIndex = stripped.endsWith('/index') ? stripped.slice(0, -'/index'.length) : stripped;
|
|
259
|
+
const recovered = withoutIndex || stripped;
|
|
260
|
+
if (!recovered || recovered === 'index')
|
|
261
|
+
return node;
|
|
262
|
+
const nextAttrs = {};
|
|
263
|
+
for (const [k, v] of Object.entries(node.attrs)) {
|
|
264
|
+
if (k === 'name')
|
|
265
|
+
continue;
|
|
266
|
+
nextAttrs[k] = v;
|
|
267
|
+
}
|
|
268
|
+
return { ...node, tagName: recovered, attrs: nextAttrs };
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* 把"用户授权"层级(页面 / 自定义组件,tagName 以路径形式呈现,含 `/`)
|
|
272
|
+
* 的 children 包一层合成 `#shadow-root`,对齐微信开发者工具:组件本身
|
|
273
|
+
* 与内部实现之间用 shadow-root 边界视觉分隔。
|
|
274
|
+
*
|
|
275
|
+
* 内置组件(view/text/button 等)和合成节点(#text/#fragment)tagName 都
|
|
276
|
+
* 不含 `/`,自然不会被包裹。children 为空也不插入(避免空壳)。重复调用
|
|
277
|
+
* 是幂等的:若 children[0] 已经是 #shadow-root 就跳过。
|
|
278
|
+
*/
|
|
279
|
+
function wrapInShadowRoot(node) {
|
|
280
|
+
if (!node.tagName.includes('/'))
|
|
281
|
+
return node;
|
|
282
|
+
if (node.children.length === 0)
|
|
283
|
+
return node;
|
|
284
|
+
if (node.children[0]?.tagName === '#shadow-root')
|
|
285
|
+
return node;
|
|
286
|
+
return {
|
|
287
|
+
...node,
|
|
288
|
+
children: [{ tagName: '#shadow-root', attrs: {}, children: node.children }],
|
|
289
|
+
};
|
|
290
|
+
}
|
|
160
291
|
export function walkInstance(instance, depth) {
|
|
161
292
|
if (depth > 50)
|
|
162
293
|
return null;
|
|
@@ -168,6 +299,6 @@ export function walkInstance(instance, depth) {
|
|
|
168
299
|
const sid = getElementSid(instance);
|
|
169
300
|
if (sid)
|
|
170
301
|
node.sid = sid;
|
|
171
|
-
return node;
|
|
302
|
+
return wrapInShadowRoot(unwrapCustomComponent(node));
|
|
172
303
|
}
|
|
173
304
|
//# sourceMappingURL=wxml-extract.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ElementInspection } from '../../shared/ipc-channels.js';
|
|
1
2
|
export interface WxmlNode {
|
|
2
3
|
tagName: string;
|
|
3
4
|
attrs: Record<string, string>;
|
|
@@ -20,7 +21,8 @@ interface SimulatorBridgeState {
|
|
|
20
21
|
wxml: Snapshot<WxmlNode | WxmlNode[] | null>;
|
|
21
22
|
refreshHandler: ((type: RefreshTarget) => void) | null;
|
|
22
23
|
}
|
|
23
|
-
declare function
|
|
24
|
+
export declare function registerSyntheticSid(el: HTMLElement): string;
|
|
25
|
+
declare function highlightElement(sid: string): ElementInspection | null;
|
|
24
26
|
declare function unhighlightElement(): void;
|
|
25
27
|
export { highlightElement, unhighlightElement };
|
|
26
28
|
export declare const simulatorBridge: SimulatorBridgeState;
|
|
@@ -7,6 +7,38 @@ const state = {
|
|
|
7
7
|
};
|
|
8
8
|
let highlightOverlay = null;
|
|
9
9
|
let exposedApi = null;
|
|
10
|
+
// 合成 sid 注册表:用 WeakMap 把元素 ↔ sid 双向绑定,避免在源 DOM 上写
|
|
11
|
+
// `data-*` 属性(提取本应只读,且属性形式会污染用户的快照/选择器)。
|
|
12
|
+
// elBySyntheticSid 为反向查找用 WeakRef,元素被 GC 后下次 lookup 自动清理。
|
|
13
|
+
const SYNTHETIC_SID_PREFIX = 'devtools-';
|
|
14
|
+
const syntheticSidByEl = new WeakMap();
|
|
15
|
+
const elBySyntheticSid = new Map();
|
|
16
|
+
let nextSyntheticSid = 1;
|
|
17
|
+
export function registerSyntheticSid(el) {
|
|
18
|
+
const existing = syntheticSidByEl.get(el);
|
|
19
|
+
if (existing)
|
|
20
|
+
return existing;
|
|
21
|
+
const synthetic = `${SYNTHETIC_SID_PREFIX}${nextSyntheticSid++}`;
|
|
22
|
+
syntheticSidByEl.set(el, synthetic);
|
|
23
|
+
elBySyntheticSid.set(synthetic, new WeakRef(el));
|
|
24
|
+
return synthetic;
|
|
25
|
+
}
|
|
26
|
+
function findElementBySid(doc, sid) {
|
|
27
|
+
if (sid.startsWith(SYNTHETIC_SID_PREFIX)) {
|
|
28
|
+
const ref = elBySyntheticSid.get(sid);
|
|
29
|
+
if (!ref)
|
|
30
|
+
return null;
|
|
31
|
+
const el = ref.deref();
|
|
32
|
+
if (!el || !el.isConnected) {
|
|
33
|
+
elBySyntheticSid.delete(sid);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
if (el.ownerDocument !== doc)
|
|
37
|
+
return null;
|
|
38
|
+
return el;
|
|
39
|
+
}
|
|
40
|
+
return doc.querySelector(`[data-sid="${CSS.escape(sid)}"]`);
|
|
41
|
+
}
|
|
10
42
|
function clone(value) {
|
|
11
43
|
try {
|
|
12
44
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -27,18 +59,20 @@ function ensureOverlay(doc) {
|
|
|
27
59
|
highlightOverlay.style.cssText =
|
|
28
60
|
'position:fixed;pointer-events:none;z-index:999999;' +
|
|
29
61
|
'border:2px solid #1a73e8;background:rgba(26,115,232,0.12);' +
|
|
30
|
-
'transition:all 0.1s ease;display:none;border-radius:2px;';
|
|
62
|
+
'transition:all 0.1s ease;display:none;border-radius:2px;box-sizing:border-box;';
|
|
31
63
|
doc.body.appendChild(highlightOverlay);
|
|
32
64
|
return highlightOverlay;
|
|
33
65
|
}
|
|
34
66
|
function highlightElement(sid) {
|
|
67
|
+
if (!sid)
|
|
68
|
+
return null;
|
|
35
69
|
const iframe = getPageIframe();
|
|
36
70
|
if (!iframe?.contentDocument)
|
|
37
|
-
return;
|
|
71
|
+
return null;
|
|
38
72
|
const doc = iframe.contentDocument;
|
|
39
|
-
const el = doc
|
|
73
|
+
const el = findElementBySid(doc, sid);
|
|
40
74
|
if (!el)
|
|
41
|
-
return;
|
|
75
|
+
return null;
|
|
42
76
|
const rect = el.getBoundingClientRect();
|
|
43
77
|
const overlay = ensureOverlay(doc);
|
|
44
78
|
overlay.style.left = `${rect.left}px`;
|
|
@@ -46,6 +80,23 @@ function highlightElement(sid) {
|
|
|
46
80
|
overlay.style.width = `${rect.width}px`;
|
|
47
81
|
overlay.style.height = `${rect.height}px`;
|
|
48
82
|
overlay.style.display = 'block';
|
|
83
|
+
const style = el.ownerDocument.defaultView?.getComputedStyle(el);
|
|
84
|
+
if (!style)
|
|
85
|
+
return null;
|
|
86
|
+
return {
|
|
87
|
+
sid,
|
|
88
|
+
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
89
|
+
style: {
|
|
90
|
+
display: style.display,
|
|
91
|
+
position: style.position,
|
|
92
|
+
boxSizing: style.boxSizing,
|
|
93
|
+
margin: style.margin,
|
|
94
|
+
padding: style.padding,
|
|
95
|
+
color: style.color,
|
|
96
|
+
backgroundColor: style.backgroundColor,
|
|
97
|
+
fontSize: style.fontSize,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
49
100
|
}
|
|
50
101
|
function unhighlightElement() {
|
|
51
102
|
if (highlightOverlay)
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* window by our preload/instrumentation code.
|
|
6
6
|
*/
|
|
7
7
|
import type { Snapshot, StorageSnapshot, WxmlNode } from '../runtime/bridge.js';
|
|
8
|
+
import type { ElementInspection } from '../../shared/ipc-channels.js';
|
|
8
9
|
/** The simulator data bridge exposed via contextBridge. */
|
|
9
10
|
export interface SimulatorDataBridge {
|
|
10
11
|
getAppdata: () => Record<string, unknown>;
|
|
@@ -16,7 +17,7 @@ export interface SimulatorDataBridge {
|
|
|
16
17
|
getWxmlSnapshot: () => Snapshot<WxmlNode | WxmlNode[] | null>;
|
|
17
18
|
getWxmlGen: () => number;
|
|
18
19
|
refresh: (type: 'wxml' | 'appdata' | 'storage') => void;
|
|
19
|
-
highlightElement: (sid: string) =>
|
|
20
|
+
highlightElement: (sid: string) => ElementInspection | null;
|
|
20
21
|
unhighlightElement: () => void;
|
|
21
22
|
}
|
|
22
23
|
/** Hook interface for app data interception. */
|