trickle-observe 0.2.58 → 0.2.59
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/vite-plugin.js +1 -1
- package/dist-esm/vite-plugin.js +1 -1
- package/package.json +1 -1
- package/src/vite-plugin.test.ts +37 -0
- package/src/vite-plugin.ts +17 -1
package/dist/vite-plugin.js
CHANGED
|
@@ -747,7 +747,7 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
|
|
|
747
747
|
}
|
|
748
748
|
// Add React component render tracker if needed
|
|
749
749
|
if (bodyInsertions.length > 0) {
|
|
750
|
-
prefixLines.push(`if (!globalThis.__trickle_react_renders) { globalThis.__trickle_react_renders = new Map(); }`, `function __trickle_rc(name, line, props) {`, ` try {`, ` const key = ${JSON.stringify(filename)} + ':' + line;`, ` const count = (globalThis.__trickle_react_renders.get(key) || 0) + 1;`, ` globalThis.__trickle_react_renders.set(key, count);`, ` const dir = process.env.TRICKLE_LOCAL_DIR || __trickle_join(process.cwd(), '.trickle');`, ` try { __trickle_mkdirSync(dir, { recursive: true }); } catch(e) {}`, ` const f = __trickle_join(dir, 'variables.jsonl');`, ` const rec = { kind: 'react_render', file: ${JSON.stringify(filename)}, line: line, component: name, renderCount: count, timestamp: Date.now() / 1000 };`, ` if (props !== undefined && props !== null && typeof props === 'object') {`, ` try {`, ` const propKeys = Object.keys(props).filter(k => k !== 'children');`, ` const propSample = {};`, ` for (const k of propKeys.slice(0, 10)) {`, ` const v = props[k];`, ` const t = typeof v;`, ` if (t === 'string') propSample[k] = v.length > 40 ? v.slice(0, 40) + '...' : v;`, ` else if (t === 'number' || t === 'boolean') propSample[k] = v;`, ` else if (v === null || v === undefined) propSample[k] = v;`, ` else if (Array.isArray(v)) propSample[k] = '[' +
|
|
750
|
+
prefixLines.push(`if (!globalThis.__trickle_react_renders) { globalThis.__trickle_react_renders = new Map(); }`, `if (!globalThis.__trickle_react_prev_props) { globalThis.__trickle_react_prev_props = new Map(); }`, `function __trickle_rc(name, line, props) {`, ` try {`, ` const key = ${JSON.stringify(filename)} + ':' + line;`, ` const count = (globalThis.__trickle_react_renders.get(key) || 0) + 1;`, ` globalThis.__trickle_react_renders.set(key, count);`, ` const dir = process.env.TRICKLE_LOCAL_DIR || __trickle_join(process.cwd(), '.trickle');`, ` try { __trickle_mkdirSync(dir, { recursive: true }); } catch(e) {}`, ` const f = __trickle_join(dir, 'variables.jsonl');`, ` const rec = { kind: 'react_render', file: ${JSON.stringify(filename)}, line: line, component: name, renderCount: count, timestamp: Date.now() / 1000 };`, ` if (props !== undefined && props !== null && typeof props === 'object') {`, ` try {`, ` const propKeys = Object.keys(props).filter(k => k !== 'children');`, ` const propSample = {};`, ` for (const k of propKeys.slice(0, 10)) {`, ` const v = props[k];`, ` const t = typeof v;`, ` if (t === 'string') propSample[k] = v.length > 40 ? v.slice(0, 40) + '...' : v;`, ` else if (t === 'number' || t === 'boolean') propSample[k] = v;`, ` else if (v === null || v === undefined) propSample[k] = v;`, ` else if (Array.isArray(v)) propSample[k] = '[arr:' + v.length + ']';`, ` else if (t === 'function') propSample[k] = '[fn]';`, ` else propSample[k] = '[object]';`, ` }`, ` rec.props = propSample;`, ` rec.propKeys = propKeys;`, ` // Detect which props changed vs previous render`, ` const prevProps = globalThis.__trickle_react_prev_props.get(key);`, ` if (prevProps && count > 1) {`, ` const changedProps = [];`, ` const allKeys = new Set([...Object.keys(prevProps), ...Object.keys(propSample)]);`, ` for (const k of allKeys) {`, ` const prev = prevProps[k];`, ` const curr = propSample[k];`, ` if (String(prev) !== String(curr)) {`, ` changedProps.push({ key: k, from: prev, to: curr });`, ` }`, ` }`, ` if (changedProps.length > 0) rec.changedProps = changedProps;`, ` }`, ` globalThis.__trickle_react_prev_props.set(key, propSample);`, ` } catch(e2) {}`, ` }`, ` __trickle_appendFileSync(f, JSON.stringify(rec) + '\\n');`, ` } catch(e) {}`, `}`);
|
|
751
751
|
}
|
|
752
752
|
// Add React hook tracker if needed
|
|
753
753
|
if (hookInsertions.length > 0) {
|
package/dist-esm/vite-plugin.js
CHANGED
|
@@ -741,7 +741,7 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
|
|
|
741
741
|
}
|
|
742
742
|
// Add React component render tracker if needed
|
|
743
743
|
if (bodyInsertions.length > 0) {
|
|
744
|
-
prefixLines.push(`if (!globalThis.__trickle_react_renders) { globalThis.__trickle_react_renders = new Map(); }`, `function __trickle_rc(name, line, props) {`, ` try {`, ` const key = ${JSON.stringify(filename)} + ':' + line;`, ` const count = (globalThis.__trickle_react_renders.get(key) || 0) + 1;`, ` globalThis.__trickle_react_renders.set(key, count);`, ` const dir = process.env.TRICKLE_LOCAL_DIR || __trickle_join(process.cwd(), '.trickle');`, ` try { __trickle_mkdirSync(dir, { recursive: true }); } catch(e) {}`, ` const f = __trickle_join(dir, 'variables.jsonl');`, ` const rec = { kind: 'react_render', file: ${JSON.stringify(filename)}, line: line, component: name, renderCount: count, timestamp: Date.now() / 1000 };`, ` if (props !== undefined && props !== null && typeof props === 'object') {`, ` try {`, ` const propKeys = Object.keys(props).filter(k => k !== 'children');`, ` const propSample = {};`, ` for (const k of propKeys.slice(0, 10)) {`, ` const v = props[k];`, ` const t = typeof v;`, ` if (t === 'string') propSample[k] = v.length > 40 ? v.slice(0, 40) + '...' : v;`, ` else if (t === 'number' || t === 'boolean') propSample[k] = v;`, ` else if (v === null || v === undefined) propSample[k] = v;`, ` else if (Array.isArray(v)) propSample[k] = '[' +
|
|
744
|
+
prefixLines.push(`if (!globalThis.__trickle_react_renders) { globalThis.__trickle_react_renders = new Map(); }`, `if (!globalThis.__trickle_react_prev_props) { globalThis.__trickle_react_prev_props = new Map(); }`, `function __trickle_rc(name, line, props) {`, ` try {`, ` const key = ${JSON.stringify(filename)} + ':' + line;`, ` const count = (globalThis.__trickle_react_renders.get(key) || 0) + 1;`, ` globalThis.__trickle_react_renders.set(key, count);`, ` const dir = process.env.TRICKLE_LOCAL_DIR || __trickle_join(process.cwd(), '.trickle');`, ` try { __trickle_mkdirSync(dir, { recursive: true }); } catch(e) {}`, ` const f = __trickle_join(dir, 'variables.jsonl');`, ` const rec = { kind: 'react_render', file: ${JSON.stringify(filename)}, line: line, component: name, renderCount: count, timestamp: Date.now() / 1000 };`, ` if (props !== undefined && props !== null && typeof props === 'object') {`, ` try {`, ` const propKeys = Object.keys(props).filter(k => k !== 'children');`, ` const propSample = {};`, ` for (const k of propKeys.slice(0, 10)) {`, ` const v = props[k];`, ` const t = typeof v;`, ` if (t === 'string') propSample[k] = v.length > 40 ? v.slice(0, 40) + '...' : v;`, ` else if (t === 'number' || t === 'boolean') propSample[k] = v;`, ` else if (v === null || v === undefined) propSample[k] = v;`, ` else if (Array.isArray(v)) propSample[k] = '[arr:' + v.length + ']';`, ` else if (t === 'function') propSample[k] = '[fn]';`, ` else propSample[k] = '[object]';`, ` }`, ` rec.props = propSample;`, ` rec.propKeys = propKeys;`, ` // Detect which props changed vs previous render`, ` const prevProps = globalThis.__trickle_react_prev_props.get(key);`, ` if (prevProps && count > 1) {`, ` const changedProps = [];`, ` const allKeys = new Set([...Object.keys(prevProps), ...Object.keys(propSample)]);`, ` for (const k of allKeys) {`, ` const prev = prevProps[k];`, ` const curr = propSample[k];`, ` if (String(prev) !== String(curr)) {`, ` changedProps.push({ key: k, from: prev, to: curr });`, ` }`, ` }`, ` if (changedProps.length > 0) rec.changedProps = changedProps;`, ` }`, ` globalThis.__trickle_react_prev_props.set(key, propSample);`, ` } catch(e2) {}`, ` }`, ` __trickle_appendFileSync(f, JSON.stringify(rec) + '\\n');`, ` } catch(e) {}`, `}`);
|
|
745
745
|
}
|
|
746
746
|
// Add React hook tracker if needed
|
|
747
747
|
if (hookInsertions.length > 0) {
|
package/package.json
CHANGED
package/src/vite-plugin.test.ts
CHANGED
|
@@ -202,6 +202,43 @@ describe('Correct function body brace detection', () => {
|
|
|
202
202
|
});
|
|
203
203
|
});
|
|
204
204
|
|
|
205
|
+
// ── Re-render cause detection ─────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
describe('Re-render cause detection', () => {
|
|
208
|
+
it('emits changedProps tracking code in transformed output', () => {
|
|
209
|
+
const code = `function Card({ count, label }) { return null; }`;
|
|
210
|
+
const out = transformTsx(code);
|
|
211
|
+
assert.ok(out, 'should transform');
|
|
212
|
+
// Should include the prev_props map and comparison logic
|
|
213
|
+
assert.ok(out!.includes('__trickle_react_prev_props'), 'should initialize prev_props map');
|
|
214
|
+
assert.ok(out!.includes('changedProps'), 'should include changedProps detection');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('includes prev props comparison logic in __trickle_rc', () => {
|
|
218
|
+
const code = `function UserCard({ name, age }) { return null; }`;
|
|
219
|
+
const out = transformTsx(code);
|
|
220
|
+
assert.ok(out, 'should transform');
|
|
221
|
+
assert.ok(out!.includes('prevProps'), 'should reference prevProps');
|
|
222
|
+
assert.ok(out!.includes('globalThis.__trickle_react_prev_props.set'), 'should store current props as prev');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('stores previous props keyed by component file+line', () => {
|
|
226
|
+
const code = `function Button({ disabled, onClick }) { return null; }`;
|
|
227
|
+
const out = transformTsx(code);
|
|
228
|
+
assert.ok(out, 'should transform');
|
|
229
|
+
// The key used for prev_props storage should be the same as the render key
|
|
230
|
+
assert.ok(out!.includes('globalThis.__trickle_react_prev_props.get(key)'), 'should retrieve prev props by key');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('does not emit prev_props tracking in .ts files', () => {
|
|
234
|
+
const code = `function helper({ x, y }) { return x + y; }`;
|
|
235
|
+
const out = transformTs(code);
|
|
236
|
+
if (out) {
|
|
237
|
+
assert.ok(!out.includes('__trickle_react_prev_props'), 'should NOT track prev props in .ts files');
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
205
242
|
// ── React hook observability ──────────────────────────────────────────────────
|
|
206
243
|
|
|
207
244
|
describe('React hook observability', () => {
|
package/src/vite-plugin.ts
CHANGED
|
@@ -799,6 +799,7 @@ function transformEsmSource(
|
|
|
799
799
|
if (bodyInsertions.length > 0) {
|
|
800
800
|
prefixLines.push(
|
|
801
801
|
`if (!globalThis.__trickle_react_renders) { globalThis.__trickle_react_renders = new Map(); }`,
|
|
802
|
+
`if (!globalThis.__trickle_react_prev_props) { globalThis.__trickle_react_prev_props = new Map(); }`,
|
|
802
803
|
`function __trickle_rc(name, line, props) {`,
|
|
803
804
|
` try {`,
|
|
804
805
|
` const key = ${JSON.stringify(filename)} + ':' + line;`,
|
|
@@ -818,12 +819,27 @@ function transformEsmSource(
|
|
|
818
819
|
` if (t === 'string') propSample[k] = v.length > 40 ? v.slice(0, 40) + '...' : v;`,
|
|
819
820
|
` else if (t === 'number' || t === 'boolean') propSample[k] = v;`,
|
|
820
821
|
` else if (v === null || v === undefined) propSample[k] = v;`,
|
|
821
|
-
` else if (Array.isArray(v)) propSample[k] = '[' +
|
|
822
|
+
` else if (Array.isArray(v)) propSample[k] = '[arr:' + v.length + ']';`,
|
|
822
823
|
` else if (t === 'function') propSample[k] = '[fn]';`,
|
|
823
824
|
` else propSample[k] = '[object]';`,
|
|
824
825
|
` }`,
|
|
825
826
|
` rec.props = propSample;`,
|
|
826
827
|
` rec.propKeys = propKeys;`,
|
|
828
|
+
` // Detect which props changed vs previous render`,
|
|
829
|
+
` const prevProps = globalThis.__trickle_react_prev_props.get(key);`,
|
|
830
|
+
` if (prevProps && count > 1) {`,
|
|
831
|
+
` const changedProps = [];`,
|
|
832
|
+
` const allKeys = new Set([...Object.keys(prevProps), ...Object.keys(propSample)]);`,
|
|
833
|
+
` for (const k of allKeys) {`,
|
|
834
|
+
` const prev = prevProps[k];`,
|
|
835
|
+
` const curr = propSample[k];`,
|
|
836
|
+
` if (String(prev) !== String(curr)) {`,
|
|
837
|
+
` changedProps.push({ key: k, from: prev, to: curr });`,
|
|
838
|
+
` }`,
|
|
839
|
+
` }`,
|
|
840
|
+
` if (changedProps.length > 0) rec.changedProps = changedProps;`,
|
|
841
|
+
` }`,
|
|
842
|
+
` globalThis.__trickle_react_prev_props.set(key, propSample);`,
|
|
827
843
|
` } catch(e2) {}`,
|
|
828
844
|
` }`,
|
|
829
845
|
` __trickle_appendFileSync(f, JSON.stringify(rec) + '\\n');`,
|