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 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` | ❌ | — |
@@ -0,0 +1,6 @@
1
+ import { IPluginContext } from "@tarojs/service";
2
+ export interface ChuckerPluginOptions {
3
+ enabled?: boolean;
4
+ }
5
+ declare const _default: (ctx: IPluginContext, options?: ChuckerPluginOptions) => void;
6
+ export default _default;
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
+ };
@@ -0,0 +1,2 @@
1
+ declare function chuckerAppLoader(this: any, source: string): string;
2
+ export = chuckerAppLoader;
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;
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare const Chucker: React.FC;