react-reinspect 0.1.2 → 0.1.3

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 CHANGED
@@ -72,6 +72,19 @@ export default defineConfig({
72
72
 
73
73
  Without this plugin, only manually wrapped components (`withReinspect`) are inspectable.
74
74
 
75
+ Next.js (webpack mode) can use:
76
+
77
+ ```ts
78
+ // next.config.ts
79
+ import { withReinspectAutoDiscover } from 'react-reinspect/next-plugin'
80
+
81
+ const nextConfig = {}
82
+
83
+ export default withReinspectAutoDiscover(nextConfig)
84
+ ```
85
+
86
+ If your Next dev server runs with Turbopack, switch to webpack mode for this transform plugin.
87
+
75
88
 
76
89
  ## Example
77
90
 
@@ -88,9 +101,12 @@ You are a senior React code agent, your task is to integrate react-reinspect to
88
101
  Do all of the following in one pass:
89
102
  1) Install `react-reinspect` using this repo's package manager.
90
103
  2) Turn it on in dev mode ONLY by wiring `ReinspectProvider` at app root.
91
- 3) Keep production safe (e.g.: `enabled: import.meta.env.DEV` or however we manage dev/prod in this repo.).
92
- 4) Run validation (build/tests if available) and fix any issues.
93
- 5) Output a concise summary with changed files and how to use it.
104
+ 3) If this app uses Vite, add `reinspectAutoDiscoverPlugin` from `react-reinspect/vite-plugin` in `vite.config.*`.
105
+ 4) If this app uses Next.js (webpack mode), wrap the Next config with `withReinspectAutoDiscover` from `react-reinspect/next-plugin`.
106
+ 5) If Next.js is running with Turbopack, either switch dev to webpack for auto-discovery transforms or fall back to manual wrapping (`withReinspect`).
107
+ 6) Keep production safe (e.g.: `enabled: import.meta.env.DEV` or however we manage dev/prod in this repo.).
108
+ 7) Run validation (build/tests if available) and fix any issues.
109
+ 8) Output a concise summary with changed files and how to use it.
94
110
 
95
111
  Use this example as the baseline:
96
112
 
@@ -108,6 +124,23 @@ export function AppProviders({ children }: { children: React.ReactNode }) {
108
124
  return <ReinspectProvider config={reinspectConfig}>{children}</ReinspectProvider>
109
125
  }
110
126
 
127
+ For auto-discovery setup, apply one of these depending on framework:
128
+
129
+ // Vite
130
+ import { defineConfig } from 'vite'
131
+ import react from '@vitejs/plugin-react'
132
+ import { reinspectAutoDiscoverPlugin } from 'react-reinspect/vite-plugin'
133
+
134
+ export default defineConfig({
135
+ plugins: [reinspectAutoDiscoverPlugin(), react()],
136
+ })
137
+
138
+ // Next.js (webpack mode)
139
+ import { withReinspectAutoDiscover } from 'react-reinspect/next-plugin'
140
+
141
+ const nextConfig = {}
142
+ export default withReinspectAutoDiscover(nextConfig)
143
+
111
144
  Output format I want from you:
112
145
  - What changed (bullet list)
113
146
  - Why this is safe in production
@@ -137,7 +170,34 @@ export function AppProviders({ children }: { children: React.ReactNode }) {
137
170
  }
138
171
  ```
139
172
 
140
- ### 2) Use it in the browser
173
+ ### 2) Enable auto-discovery transforms (pick your framework)
174
+
175
+ Vite:
176
+
177
+ ```ts
178
+ // vite.config.ts
179
+ import { defineConfig } from 'vite'
180
+ import react from '@vitejs/plugin-react'
181
+ import { reinspectAutoDiscoverPlugin } from 'react-reinspect/vite-plugin'
182
+
183
+ export default defineConfig({
184
+ plugins: [reinspectAutoDiscoverPlugin(), react()],
185
+ })
186
+ ```
187
+
188
+ Next.js (webpack mode):
189
+
190
+ ```ts
191
+ // next.config.ts
192
+ import { withReinspectAutoDiscover } from 'react-reinspect/next-plugin'
193
+
194
+ const nextConfig = {}
195
+ export default withReinspectAutoDiscover(nextConfig)
196
+ ```
197
+
198
+ If your Next dev server runs with Turbopack, switch to webpack mode for auto-discovery transforms.
199
+
200
+ ### 3) Use it in the browser
141
201
 
142
202
  - Click `Reinspect settings` button.
143
203
  - Right-click a wrapped component.
@@ -1 +1 @@
1
- {"version":3,"file":"reinspectAutoDiscoverPlugin.d.ts","sourceRoot":"","sources":["../../reinspectAutoDiscoverPlugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAElC,KAAK,iBAAiB,GAAG,aAAa,GAAG,aAAa,CAAA;AAgBtD,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;CAClB;AA4JD,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,iBAAiB,GACvB,2BAA2B,CAgP7B;AAED,wBAAgB,2BAA2B,IAAI,MAAM,CA2CpD"}
1
+ {"version":3,"file":"reinspectAutoDiscoverPlugin.d.ts","sourceRoot":"","sources":["../../reinspectAutoDiscoverPlugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAElC,KAAK,iBAAiB,GAAG,aAAa,GAAG,aAAa,CAAA;AAgBtD,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;CAClB;AAmQD,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,iBAAiB,GACvB,2BAA2B,CAwP7B;AAED,wBAAgB,2BAA2B,IAAI,MAAM,CA2CpD"}
@@ -47,19 +47,92 @@ function unwrapExpression(node) {
47
47
  }
48
48
  return currentNode;
49
49
  }
50
- function isMemoForwardRefCall(node) {
50
+ function isReactCreateElementCall(node) {
51
51
  const callee = unwrapExpression(node.callee);
52
52
  if (t.isIdentifier(callee)) {
53
- return callee.name === 'memo' || callee.name === 'forwardRef';
53
+ return callee.name === 'createElement';
54
54
  }
55
55
  if (t.isMemberExpression(callee) &&
56
56
  t.isIdentifier(callee.object) &&
57
57
  callee.object.name === 'React' &&
58
58
  t.isIdentifier(callee.property)) {
59
- return callee.property.name === 'memo' || callee.property.name === 'forwardRef';
59
+ return callee.property.name === 'createElement';
60
+ }
61
+ return false;
62
+ }
63
+ function isAstNode(value) {
64
+ return (typeof value === 'object' &&
65
+ value !== null &&
66
+ 'type' in value &&
67
+ typeof value.type === 'string');
68
+ }
69
+ function containsRenderableReactNode(node) {
70
+ if (!node) {
71
+ return false;
72
+ }
73
+ if (t.isJSXElement(node) || t.isJSXFragment(node)) {
74
+ return true;
75
+ }
76
+ if (t.isCallExpression(node) && isReactCreateElementCall(node)) {
77
+ return true;
78
+ }
79
+ for (const fieldValue of Object.values(node)) {
80
+ if (Array.isArray(fieldValue)) {
81
+ for (const value of fieldValue) {
82
+ if (isAstNode(value) && containsRenderableReactNode(value)) {
83
+ return true;
84
+ }
85
+ }
86
+ continue;
87
+ }
88
+ if (isAstNode(fieldValue) && containsRenderableReactNode(fieldValue)) {
89
+ return true;
90
+ }
91
+ }
92
+ return false;
93
+ }
94
+ function isComponentFunction(node) {
95
+ if (t.isBlockStatement(node.body)) {
96
+ return containsRenderableReactNode(node.body);
97
+ }
98
+ return containsRenderableReactNode(node.body);
99
+ }
100
+ function isMemoForwardRefCallee(callee) {
101
+ const expression = unwrapExpression(callee);
102
+ if (t.isIdentifier(expression)) {
103
+ return expression.name === 'memo' || expression.name === 'forwardRef';
104
+ }
105
+ if (t.isMemberExpression(expression) &&
106
+ t.isIdentifier(expression.object) &&
107
+ expression.object.name === 'React' &&
108
+ t.isIdentifier(expression.property)) {
109
+ return (expression.property.name === 'memo' ||
110
+ expression.property.name === 'forwardRef');
60
111
  }
61
112
  return false;
62
113
  }
114
+ function isMemoForwardRefCall(node) {
115
+ const callee = unwrapExpression(node.callee);
116
+ if (!isMemoForwardRefCallee(callee)) {
117
+ return false;
118
+ }
119
+ if (node.arguments.length === 0) {
120
+ return false;
121
+ }
122
+ const firstArgument = node.arguments[0];
123
+ if (!t.isExpression(firstArgument)) {
124
+ return false;
125
+ }
126
+ const componentCandidate = unwrapExpression(firstArgument);
127
+ if (t.isArrowFunctionExpression(componentCandidate) ||
128
+ t.isFunctionExpression(componentCandidate)) {
129
+ return isComponentFunction(componentCandidate);
130
+ }
131
+ if (t.isIdentifier(componentCandidate)) {
132
+ return isPascalCaseIdentifier(componentCandidate.name);
133
+ }
134
+ return true;
135
+ }
63
136
  function isWrappedInitializer(node) {
64
137
  const expression = unwrapExpression(node);
65
138
  if (!t.isCallExpression(expression)) {
@@ -72,7 +145,7 @@ function isWrappedInitializer(node) {
72
145
  function isComponentInitializer(node) {
73
146
  const expression = unwrapExpression(node);
74
147
  if (t.isArrowFunctionExpression(expression) || t.isFunctionExpression(expression)) {
75
- return true;
148
+ return isComponentFunction(expression);
76
149
  }
77
150
  if (t.isCallExpression(expression)) {
78
151
  return isMemoForwardRefCall(expression);
@@ -144,7 +217,8 @@ export function transformModuleForAutoDiscover(code, fileId, scope) {
144
217
  for (let index = 0; index < program.body.length; index += 1) {
145
218
  const statement = program.body[index];
146
219
  if (t.isFunctionDeclaration(statement) && statement.id) {
147
- if (isPascalCaseIdentifier(statement.id.name)) {
220
+ if (isPascalCaseIdentifier(statement.id.name) &&
221
+ isComponentFunction(statement)) {
148
222
  program.body.splice(index + 1, 0, createFunctionWrapAssignment(statement.id.name, scope, fallbackName));
149
223
  index += 1;
150
224
  modified = true;
@@ -155,7 +229,8 @@ export function transformModuleForAutoDiscover(code, fileId, scope) {
155
229
  statement.declaration &&
156
230
  t.isFunctionDeclaration(statement.declaration) &&
157
231
  statement.declaration.id &&
158
- isPascalCaseIdentifier(statement.declaration.id.name)) {
232
+ isPascalCaseIdentifier(statement.declaration.id.name) &&
233
+ isComponentFunction(statement.declaration)) {
159
234
  program.body.splice(index + 1, 0, createFunctionWrapAssignment(statement.declaration.id.name, scope, fallbackName));
160
235
  index += 1;
161
236
  modified = true;
@@ -200,7 +275,9 @@ export function transformModuleForAutoDiscover(code, fileId, scope) {
200
275
  }
201
276
  const declaration = statement.declaration;
202
277
  if (t.isFunctionDeclaration(declaration)) {
203
- if (declaration.id && isPascalCaseIdentifier(declaration.id.name)) {
278
+ if (declaration.id &&
279
+ isPascalCaseIdentifier(declaration.id.name) &&
280
+ isComponentFunction(declaration)) {
204
281
  const localName = declaration.id.name;
205
282
  const replacementStatements = [
206
283
  declaration,
@@ -212,7 +289,7 @@ export function transformModuleForAutoDiscover(code, fileId, scope) {
212
289
  modified = true;
213
290
  continue;
214
291
  }
215
- if (!declaration.id) {
292
+ if (!declaration.id && isComponentFunction(declaration)) {
216
293
  const localIdentifier = t.identifier(DEFAULT_EXPORT_LOCAL_IDENTIFIER);
217
294
  const wrappedValue = createAutoWrapCall(functionDeclarationToExpression(declaration), undefined, scope, fallbackName);
218
295
  const replacementStatements = [
@@ -0,0 +1,11 @@
1
+ interface ReinspectNextLoaderOptions {
2
+ includeThirdParty?: boolean;
3
+ }
4
+ interface LoaderContextLike {
5
+ resourcePath: string;
6
+ getOptions?: () => ReinspectNextLoaderOptions;
7
+ async?: () => ((error: Error | null, code?: string) => void) | undefined;
8
+ }
9
+ export default function reinspectNextAutoDiscoverLoader(this: LoaderContextLike, sourceCode: string): string | void;
10
+ export {};
11
+ //# sourceMappingURL=reinspectNextAutoDiscoverLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reinspectNextAutoDiscoverLoader.d.ts","sourceRoot":"","sources":["../../reinspectNextAutoDiscoverLoader.ts"],"names":[],"mappings":"AAQA,UAAU,0BAA0B;IAClC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,UAAU,iBAAiB;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,0BAA0B,CAAA;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;CACzE;AA4DD,MAAM,CAAC,OAAO,UAAU,+BAA+B,CACrD,IAAI,EAAE,iBAAiB,EACvB,UAAU,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CAyBf"}
@@ -0,0 +1,58 @@
1
+ import path from 'node:path';
2
+ import { shouldSkipThirdPartyModule, transformModuleForAutoDiscover, } from './reinspectAutoDiscoverPlugin';
3
+ const SUPPORTED_FILE_PATTERN = /\.[cm]?[jt]sx?$/;
4
+ function normalizeModuleId(id) {
5
+ return id.split(path.sep).join('/');
6
+ }
7
+ function isSupportedSourceFile(id) {
8
+ if (!SUPPORTED_FILE_PATTERN.test(id)) {
9
+ return false;
10
+ }
11
+ if (id.includes('/node_modules/react-reinspect/')) {
12
+ return false;
13
+ }
14
+ return true;
15
+ }
16
+ function resolveAutoDiscoverScope(normalizedId, includeThirdParty) {
17
+ const isThirdParty = normalizedId.includes('/node_modules/');
18
+ if (!isThirdParty) {
19
+ return 'first-party';
20
+ }
21
+ if (!includeThirdParty || shouldSkipThirdPartyModule(normalizedId)) {
22
+ return null;
23
+ }
24
+ return 'third-party';
25
+ }
26
+ function runTransform(sourceCode, normalizedId, options) {
27
+ if (!isSupportedSourceFile(normalizedId)) {
28
+ return sourceCode;
29
+ }
30
+ const includeThirdParty = Boolean(options.includeThirdParty);
31
+ const scope = resolveAutoDiscoverScope(normalizedId, includeThirdParty);
32
+ if (!scope) {
33
+ return sourceCode;
34
+ }
35
+ const transformedResult = transformModuleForAutoDiscover(sourceCode, normalizedId, scope);
36
+ return transformedResult.modified ? transformedResult.code : sourceCode;
37
+ }
38
+ export default function reinspectNextAutoDiscoverLoader(sourceCode) {
39
+ const options = this.getOptions?.() ?? {};
40
+ const normalizedId = normalizeModuleId(this.resourcePath ?? '');
41
+ const callback = this.async?.();
42
+ try {
43
+ const transformedCode = runTransform(sourceCode, normalizedId, options);
44
+ if (callback) {
45
+ callback(null, transformedCode);
46
+ return;
47
+ }
48
+ return transformedCode;
49
+ }
50
+ catch (error) {
51
+ const nextError = error instanceof Error ? error : new Error('Failed to transform module');
52
+ if (callback) {
53
+ callback(nextError);
54
+ return;
55
+ }
56
+ throw nextError;
57
+ }
58
+ }
@@ -0,0 +1,32 @@
1
+ export interface ReinspectNextPluginOptions {
2
+ includeThirdParty?: boolean;
3
+ enableInProduction?: boolean;
4
+ }
5
+ interface NextWebpackOptions {
6
+ dev: boolean;
7
+ }
8
+ interface NextWebpackUseEntry {
9
+ loader?: string;
10
+ options?: {
11
+ includeThirdParty?: boolean;
12
+ };
13
+ }
14
+ interface NextWebpackRule {
15
+ test?: RegExp;
16
+ enforce?: 'pre' | 'post';
17
+ exclude?: RegExp;
18
+ use?: NextWebpackUseEntry | NextWebpackUseEntry[];
19
+ }
20
+ interface NextWebpackModuleConfig {
21
+ rules?: NextWebpackRule[];
22
+ }
23
+ interface NextWebpackConfig {
24
+ module?: NextWebpackModuleConfig;
25
+ }
26
+ type NextWebpackFunction = (config: NextWebpackConfig, options: NextWebpackOptions) => NextWebpackConfig;
27
+ interface NextConfigLike {
28
+ webpack?: NextWebpackFunction;
29
+ }
30
+ export declare function withReinspectAutoDiscover(nextConfig?: NextConfigLike, pluginOptions?: ReinspectNextPluginOptions): NextConfigLike;
31
+ export {};
32
+ //# sourceMappingURL=reinspectNextPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reinspectNextPlugin.d.ts","sourceRoot":"","sources":["../../reinspectNextPlugin.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,0BAA0B;IACzC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,UAAU,kBAAkB;IAC1B,GAAG,EAAE,OAAO,CAAA;CACb;AAED,UAAU,mBAAmB;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE;QACR,iBAAiB,CAAC,EAAE,OAAO,CAAA;KAC5B,CAAA;CACF;AAED,UAAU,eAAe;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;IACxB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,EAAE,CAAA;CAClD;AAED,UAAU,uBAAuB;IAC/B,KAAK,CAAC,EAAE,eAAe,EAAE,CAAA;CAC1B;AAED,UAAU,iBAAiB;IACzB,MAAM,CAAC,EAAE,uBAAuB,CAAA;CACjC;AAED,KAAK,mBAAmB,GAAG,CACzB,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,kBAAkB,KACxB,iBAAiB,CAAA;AAEtB,UAAU,cAAc;IACtB,OAAO,CAAC,EAAE,mBAAmB,CAAA;CAC9B;AAqED,wBAAgB,yBAAyB,CACvC,UAAU,GAAE,cAAmB,EAC/B,aAAa,GAAE,0BAA+B,GAC7C,cAAc,CAqBhB"}
@@ -0,0 +1,70 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ function resolveNextLoaderPath() {
3
+ const loaderUrl = new URL('./reinspectNextAutoDiscoverLoader.js', import.meta.url);
4
+ if (loaderUrl.protocol === 'file:') {
5
+ return fileURLToPath(loaderUrl);
6
+ }
7
+ return loaderUrl.pathname;
8
+ }
9
+ function hasReinspectLoaderRule(rules) {
10
+ for (const rule of rules) {
11
+ const ruleUse = Array.isArray(rule.use)
12
+ ? rule.use
13
+ : rule.use
14
+ ? [rule.use]
15
+ : [];
16
+ if (ruleUse.some((entry) => entry.loader === resolveNextLoaderPath())) {
17
+ return true;
18
+ }
19
+ }
20
+ return false;
21
+ }
22
+ function addReinspectLoaderRule(config, options) {
23
+ const moduleConfig = config.module ?? {};
24
+ const rules = moduleConfig.rules ?? [];
25
+ if (hasReinspectLoaderRule(rules)) {
26
+ config.module = {
27
+ ...moduleConfig,
28
+ rules,
29
+ };
30
+ return config;
31
+ }
32
+ const nextLoaderPath = resolveNextLoaderPath();
33
+ const nextRules = [
34
+ {
35
+ test: /\.[cm]?[jt]sx?$/,
36
+ enforce: 'pre',
37
+ exclude: /node_modules/,
38
+ use: [
39
+ {
40
+ loader: nextLoaderPath,
41
+ options: {
42
+ includeThirdParty: Boolean(options.includeThirdParty),
43
+ },
44
+ },
45
+ ],
46
+ },
47
+ ...rules,
48
+ ];
49
+ config.module = {
50
+ ...moduleConfig,
51
+ rules: nextRules,
52
+ };
53
+ return config;
54
+ }
55
+ export function withReinspectAutoDiscover(nextConfig = {}, pluginOptions = {}) {
56
+ const originalWebpack = nextConfig.webpack;
57
+ return {
58
+ ...nextConfig,
59
+ webpack(config, options) {
60
+ const resolvedConfig = typeof originalWebpack === 'function'
61
+ ? originalWebpack(config, options) ?? config
62
+ : config;
63
+ const shouldEnable = options.dev || Boolean(pluginOptions.enableInProduction);
64
+ if (!shouldEnable) {
65
+ return resolvedConfig;
66
+ }
67
+ return addReinspectLoaderRule(resolvedConfig, pluginOptions);
68
+ },
69
+ };
70
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-reinspect",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Visual runtime inspector for React components with render counters and style overrides.",
5
5
  "keywords": [
6
6
  "react",
@@ -23,6 +23,10 @@
23
23
  "types": "./dist/plugin/reinspectAutoDiscoverPlugin.d.ts",
24
24
  "import": "./dist/plugin/reinspectAutoDiscoverPlugin.js"
25
25
  },
26
+ "./next-plugin": {
27
+ "types": "./dist/plugin/reinspectNextPlugin.d.ts",
28
+ "import": "./dist/plugin/reinspectNextPlugin.js"
29
+ },
26
30
  "./style.css": "./dist/lib/style.css"
27
31
  },
28
32
  "files": [