tarojs-plugin-chucker 1.0.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.
- package/README.md +155 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +206 -0
- package/dist/loader.d.ts +2 -0
- package/dist/loader.js +14 -0
- package/dist/runtime/components/Chucker.d.ts +2 -0
- package/dist/runtime/components/Chucker.js +529 -0
- package/dist/runtime/components/icons.d.ts +13 -0
- package/dist/runtime/components/icons.js +61 -0
- package/dist/runtime/hooks/useVirtual.d.ts +71 -0
- package/dist/runtime/hooks/useVirtual.js +298 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/index.js +64 -0
- package/dist/runtime/interceptor.d.ts +15 -0
- package/dist/runtime/interceptor.js +491 -0
- package/dist/runtime/page.d.ts +2 -0
- package/dist/runtime/page.js +11 -0
- package/dist/runtime/store.d.ts +95 -0
- package/dist/runtime/store.js +172 -0
- package/dist/runtime/utils.d.ts +2 -0
- package/dist/runtime/utils.js +50 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# tarojs-plugin-chucker
|
|
2
|
+
|
|
3
|
+
A Chucker-like debugger plugin for Taro JS miniapps. It intercepts, logs, and displays network requests and native plugin calls (`wx.invokeNativePlugin`) directly in your miniapp during Runtime.
|
|
4
|
+
|
|
5
|
+
It automatically injects a floating trigger button into every page at build-time, which opens a dedicated full-screen debugger console.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- 🛰️ **API Request Interception**: Automatically hooks into `Taro.request`, `Taro.uploadFile`, and `Taro.downloadFile`.
|
|
12
|
+
- 🔌 **WeChat Native Plugins Tracker**: Intercepts `wx.invokeNativePlugin` parameters, callbacks, and Promise resolutions.
|
|
13
|
+
- 🛠️ **Build-time Injection**: Automatically injects the WXML floating debugger button and layout styling onto every compiled page, and registers `/pages/chucker/index` in-memory.
|
|
14
|
+
- 📋 **Copy to cURL**: Formats network calls into standard shell cURL commands and copies them to the clipboard with one tap.
|
|
15
|
+
- 🔍 **Interactive Console**: Beautiful dark-mode UI overlay with tabs (Overview, Request, Response), search, filters (All, Network, Native), and JSON pretty-printing.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Install the package via `pnpm`, `yarn`, or `npm`:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add tarojs-plugin-chucker --save-dev
|
|
25
|
+
# or
|
|
26
|
+
yarn add tarojs-plugin-chucker --dev
|
|
27
|
+
# or
|
|
28
|
+
npm install tarojs-plugin-chucker --save-dev
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Configuration
|
|
34
|
+
|
|
35
|
+
### 1. Register Compile-Time Plugin
|
|
36
|
+
|
|
37
|
+
Add `tarojs-plugin-chucker` to the `plugins` array in your Taro project configuration:
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
// config/index.js
|
|
41
|
+
const config = {
|
|
42
|
+
// ...
|
|
43
|
+
plugins: [
|
|
44
|
+
[
|
|
45
|
+
"tarojs-plugin-chucker",
|
|
46
|
+
{
|
|
47
|
+
// By default, only enabled in development. Set true to force enable.
|
|
48
|
+
enabled: process.env.NODE_ENV === "development",
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Dedicated Debugger Page
|
|
56
|
+
|
|
57
|
+
To navigate to the debugger manually (e.g., from custom menus or gestures):
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import Taro from "@tarojs/taro";
|
|
61
|
+
|
|
62
|
+
Taro.navigateTo({ url: "/pages/chucker/index" });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Custom Logging
|
|
68
|
+
|
|
69
|
+
Beyond the automatic network interception, you can log **any custom event** into the Chucker console using the `chuckerStore` API. All custom entries appear alongside network logs in the same debugger UI.
|
|
70
|
+
|
|
71
|
+
### Import
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { chuckerStore } from "tarojs-plugin-chucker/runtime";
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### One-shot logging — `chuckerStore.log()`
|
|
78
|
+
|
|
79
|
+
Use `log()` to record a complete event in a single call. An `id` and `startTime` are auto-generated if omitted.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Log a WebSocket message
|
|
83
|
+
chuckerStore.log({
|
|
84
|
+
type: "websocket",
|
|
85
|
+
method: "MESSAGE",
|
|
86
|
+
url: "wss://example.com/ws",
|
|
87
|
+
requestData: { event: "ping" },
|
|
88
|
+
status: "success",
|
|
89
|
+
responseData: { event: "pong" },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Log a custom analytics event
|
|
93
|
+
chuckerStore.log({
|
|
94
|
+
type: "analytics",
|
|
95
|
+
method: "TRACK",
|
|
96
|
+
url: "page_view",
|
|
97
|
+
requestData: { page: "/home", userId: "abc123" },
|
|
98
|
+
status: "sent",
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Async tracking — `startTracking()` / `completeTracking()`
|
|
103
|
+
|
|
104
|
+
For operations that have a measurable duration (e.g., GraphQL queries, RPC calls), use the tracking pair. Duration is calculated automatically.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Start tracking a GraphQL mutation
|
|
108
|
+
const id = chuckerStore.startTracking({
|
|
109
|
+
type: "graphql",
|
|
110
|
+
method: "MUTATION",
|
|
111
|
+
url: "https://api.example.com/graphql",
|
|
112
|
+
requestData: { query: "mutation { createUser(name: \"Alice\") { id } }" },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ... later when the response arrives
|
|
116
|
+
chuckerStore.completeTracking(id, {
|
|
117
|
+
status: 200,
|
|
118
|
+
responseData: { data: { createUser: { id: 1 } } },
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// You can also provide an explicit duration (in ms)
|
|
122
|
+
chuckerStore.completeTracking(id, {
|
|
123
|
+
status: 200,
|
|
124
|
+
responseData: result,
|
|
125
|
+
duration: 350,
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### API Reference
|
|
130
|
+
|
|
131
|
+
| Method | Returns | Description |
|
|
132
|
+
|--------|---------|-------------|
|
|
133
|
+
| `chuckerStore.log(input)` | `string` (id) | Add a complete log entry in one call |
|
|
134
|
+
| `chuckerStore.startTracking(input)` | `string` (id) | Begin tracking an async operation |
|
|
135
|
+
| `chuckerStore.completeTracking(id, result?)` | `void` | Finalize a tracked operation |
|
|
136
|
+
| `chuckerStore.getLogs()` | `ChuckerLog[]` | Get all current log entries |
|
|
137
|
+
| `chuckerStore.clear()` | `void` | Clear all log entries |
|
|
138
|
+
| `chuckerStore.subscribe(listener)` | `() => void` | Subscribe to log changes, returns unsubscribe function |
|
|
139
|
+
|
|
140
|
+
### `CustomLogInput` fields
|
|
141
|
+
|
|
142
|
+
| Field | Type | Required | Default |
|
|
143
|
+
|-------|------|----------|---------|
|
|
144
|
+
| `type` | `"network" \| "native" \| string` | ✅ | — |
|
|
145
|
+
| `method` | `string` | ✅ | — |
|
|
146
|
+
| `url` | `string` | ✅ | — |
|
|
147
|
+
| `id` | `string` | ❌ | Auto-generated (`usr_xxxxx`) |
|
|
148
|
+
| `startTime` | `number` | ❌ | `Date.now()` |
|
|
149
|
+
| `requestHeaders` | `Record<string, string>` | ❌ | — |
|
|
150
|
+
| `requestData` | `any` | ❌ | — |
|
|
151
|
+
| `status` | `string \| number` | ❌ | — |
|
|
152
|
+
| `responseHeaders` | `Record<string, string>` | ❌ | — |
|
|
153
|
+
| `responseData` | `any` | ❌ | — |
|
|
154
|
+
| `error` | `string` | ❌ | — |
|
|
155
|
+
| `duration` | `number` | ❌ | — |
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const path = __importStar(require("path"));
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
exports.default = (ctx, options = {}) => {
|
|
39
|
+
ctx.modifyAppConfig(({ appConfig }) => {
|
|
40
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
41
|
+
const enabled = options.enabled !== undefined ? options.enabled : isDev;
|
|
42
|
+
if (!enabled)
|
|
43
|
+
return;
|
|
44
|
+
if (appConfig && appConfig.pages) {
|
|
45
|
+
if (!appConfig.pages.includes("pages/chucker/index")) {
|
|
46
|
+
appConfig.pages.push("pages/chucker/index");
|
|
47
|
+
console.log('[Taro Chucker] Dynamically registered "pages/chucker/index" page');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
ctx.modifyWebpackChain(({ chain }) => {
|
|
52
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
53
|
+
const enabled = options.enabled !== undefined ? options.enabled : isDev;
|
|
54
|
+
chain.plugin("definePlugin").tap((args) => {
|
|
55
|
+
if (args && args[0]) {
|
|
56
|
+
args[0]["process.env.CHUCKER_ENABLED"] = JSON.stringify(enabled);
|
|
57
|
+
}
|
|
58
|
+
return args;
|
|
59
|
+
});
|
|
60
|
+
if (enabled) {
|
|
61
|
+
// Find the actual app entry file under sourcePath
|
|
62
|
+
const sourcePath = ctx.paths.sourcePath;
|
|
63
|
+
const entryFiles = ["app.ts", "app.tsx", "app.js", "app.jsx"];
|
|
64
|
+
let appEntryFile = "";
|
|
65
|
+
for (const file of entryFiles) {
|
|
66
|
+
const fullPath = path.join(sourcePath, file);
|
|
67
|
+
if (fs.existsSync(fullPath)) {
|
|
68
|
+
appEntryFile = fullPath;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Setup Webpack aliases to virtualize pages/chucker/index page resolution
|
|
73
|
+
const userPagePathWin = path.join(ctx.paths.sourcePath, "pages", "chucker", "index");
|
|
74
|
+
const userPagePathUnix = userPagePathWin.replace(/\\/g, "/");
|
|
75
|
+
const templatePath = path.resolve(__dirname, "runtime", "page.js");
|
|
76
|
+
chain.resolve.alias
|
|
77
|
+
.set("pages/chucker/index", templatePath)
|
|
78
|
+
.set(userPagePathWin, templatePath)
|
|
79
|
+
.set(userPagePathWin + ".tsx", templatePath)
|
|
80
|
+
.set(userPagePathWin + ".ts", templatePath)
|
|
81
|
+
.set(userPagePathWin + ".js", templatePath)
|
|
82
|
+
.set(userPagePathWin + ".jsx", templatePath)
|
|
83
|
+
.set(userPagePathUnix, templatePath)
|
|
84
|
+
.set(userPagePathUnix + ".tsx", templatePath)
|
|
85
|
+
.set(userPagePathUnix + ".ts", templatePath)
|
|
86
|
+
.set(userPagePathUnix + ".js", templatePath)
|
|
87
|
+
.set(userPagePathUnix + ".jsx", templatePath);
|
|
88
|
+
// Force resolve React, Taro, and Taro components from the host project's node_modules
|
|
89
|
+
// to prevent duplicate library instances (e.g. invalid hook call with useState).
|
|
90
|
+
const hostNodeModules = ctx.paths.nodeModulesPath || path.resolve(ctx.paths.appPath, "node_modules");
|
|
91
|
+
if (hostNodeModules) {
|
|
92
|
+
const reactPath = path.resolve(hostNodeModules, "react");
|
|
93
|
+
if (fs.existsSync(reactPath)) {
|
|
94
|
+
chain.resolve.alias.set("react", reactPath);
|
|
95
|
+
}
|
|
96
|
+
const taroPath = path.resolve(hostNodeModules, "@tarojs/taro");
|
|
97
|
+
if (fs.existsSync(taroPath)) {
|
|
98
|
+
chain.resolve.alias.set("@tarojs/taro", taroPath);
|
|
99
|
+
}
|
|
100
|
+
const componentsPath = path.resolve(hostNodeModules, "@tarojs/components");
|
|
101
|
+
if (fs.existsSync(componentsPath)) {
|
|
102
|
+
chain.resolve.alias.set("@tarojs/components", componentsPath);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
chain.module
|
|
106
|
+
.rule("chucker-app-injector")
|
|
107
|
+
.test((resourcePath) => {
|
|
108
|
+
if (!resourcePath || !appEntryFile)
|
|
109
|
+
return false;
|
|
110
|
+
const absoluteResource = path.resolve(resourcePath);
|
|
111
|
+
const normalizedResource = absoluteResource.replace(/\\/g, "/");
|
|
112
|
+
const normalizedEntryPath = appEntryFile.replace(/\\/g, "/");
|
|
113
|
+
const resPathNoExt = normalizedResource.replace(/\.[a-zA-Z0-9]+$/, "");
|
|
114
|
+
const entryPathNoExt = normalizedEntryPath.replace(/\.[a-zA-Z0-9]+$/, "");
|
|
115
|
+
const isJsOrTs = /\.(ts|tsx|js|jsx)$/.test(resourcePath);
|
|
116
|
+
const isMatch = resPathNoExt === entryPathNoExt && isJsOrTs;
|
|
117
|
+
if (normalizedResource.includes("app.") || isMatch) {
|
|
118
|
+
console.log(`[Taro Chucker] Matching: ${normalizedResource} vs ${normalizedEntryPath} -> ${isMatch}`);
|
|
119
|
+
}
|
|
120
|
+
return isMatch;
|
|
121
|
+
})
|
|
122
|
+
.use("chucker-loader")
|
|
123
|
+
.loader(path.resolve(__dirname, "loader.js"))
|
|
124
|
+
.end();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
ctx.modifyBuildAssets(({ assets }) => {
|
|
128
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
129
|
+
const enabled = options.enabled !== undefined ? options.enabled : isDev;
|
|
130
|
+
if (!enabled)
|
|
131
|
+
return;
|
|
132
|
+
Object.keys(assets).forEach((filename) => {
|
|
133
|
+
// 1. Inject page configuration dynamically into pages/chucker/index.json
|
|
134
|
+
if (filename === "pages/chucker/index.json") {
|
|
135
|
+
const asset = assets[filename];
|
|
136
|
+
const originalSource = asset.source();
|
|
137
|
+
const content = typeof originalSource === "string" ? originalSource : originalSource.toString();
|
|
138
|
+
try {
|
|
139
|
+
const config = JSON.parse(content);
|
|
140
|
+
config.navigationBarTitleText = "Chucker";
|
|
141
|
+
config.navigationBarBackgroundColor = "#121212";
|
|
142
|
+
config.navigationBarTextStyle = "white";
|
|
143
|
+
config.backgroundColor = "#121212";
|
|
144
|
+
const newContent = JSON.stringify(config);
|
|
145
|
+
assets[filename] = {
|
|
146
|
+
source: () => newContent,
|
|
147
|
+
size: () => newContent.length,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
console.error("🚀 [Taro Chucker] Failed to modify chucker page config:", err);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// 2. Inject WXML float button to all page templates (except Chucker page itself)
|
|
155
|
+
const isPageTemplate = filename.startsWith("pages/") &&
|
|
156
|
+
/\.(wxml|axml|ttml|swan|qml)$/.test(filename) &&
|
|
157
|
+
!filename.includes("pages/chucker/");
|
|
158
|
+
if (isPageTemplate) {
|
|
159
|
+
const asset = assets[filename];
|
|
160
|
+
const originalSource = asset.source();
|
|
161
|
+
const content = typeof originalSource === "string" ? originalSource : originalSource.toString();
|
|
162
|
+
if (!content.includes("chucker-float-btn")) {
|
|
163
|
+
const wxmlSnippet = '\n<view class="chucker-float-btn" bindtap="chuckerTap">C</view>\n';
|
|
164
|
+
const newContent = content + wxmlSnippet;
|
|
165
|
+
assets[filename] = {
|
|
166
|
+
source: () => newContent,
|
|
167
|
+
size: () => newContent.length,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// 2. Inject styles to main style sheets (app.wxss / app.acss / etc.)
|
|
172
|
+
const isAppStylesheet = /^app\.(wxss|acss|css|ttss|qss)$/.test(filename);
|
|
173
|
+
if (isAppStylesheet) {
|
|
174
|
+
const asset = assets[filename];
|
|
175
|
+
const originalSource = asset.source();
|
|
176
|
+
const content = typeof originalSource === "string" ? originalSource : originalSource.toString();
|
|
177
|
+
if (!content.includes(".chucker-float-btn")) {
|
|
178
|
+
const styles = `
|
|
179
|
+
.chucker-float-btn {
|
|
180
|
+
position: fixed !important;
|
|
181
|
+
bottom: calc(env(safe-area-inset-bottom) + 135rpx) !important;
|
|
182
|
+
right: 15px !important;
|
|
183
|
+
width: 46px !important;
|
|
184
|
+
height: 46px !important;
|
|
185
|
+
border-radius: 23px !important;
|
|
186
|
+
color: white;
|
|
187
|
+
background-color: rgba(30, 30, 30, 0.85) !important;
|
|
188
|
+
border: 1px solid rgba(255, 255, 255, 0.15) !important;
|
|
189
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3) !important;
|
|
190
|
+
display: flex !important;
|
|
191
|
+
align-items: center !important;
|
|
192
|
+
justify-content: center !important;
|
|
193
|
+
z-index: 99999 !important;
|
|
194
|
+
font-size: 20px !important;
|
|
195
|
+
}
|
|
196
|
+
`;
|
|
197
|
+
const newContent = content + styles;
|
|
198
|
+
assets[filename] = {
|
|
199
|
+
source: () => newContent,
|
|
200
|
+
size: () => newContent.length,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
};
|
package/dist/loader.d.ts
ADDED
package/dist/loader.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
function chuckerAppLoader(source) {
|
|
3
|
+
// Only inject if in dev/build or check for custom environment variables if needed
|
|
4
|
+
const injection = `
|
|
5
|
+
import { initChucker } from 'tarojs-plugin-chucker/runtime';
|
|
6
|
+
try {
|
|
7
|
+
initChucker();
|
|
8
|
+
} catch (e) {
|
|
9
|
+
console.error('Chucker auto-initialization error:', e);
|
|
10
|
+
}
|
|
11
|
+
`;
|
|
12
|
+
return injection + source;
|
|
13
|
+
}
|
|
14
|
+
module.exports = chuckerAppLoader;
|