@vibexdotnew/inspector 0.0.8 → 0.0.9

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.
@@ -0,0 +1,461 @@
1
+
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.default = componentTagger;
5
+ const parser_1 = require("@babel/parser");
6
+ const magic_string_1 = require("magic-string");
7
+ const estree_walker_1 = require("estree-walker");
8
+ const path = require("path");
9
+ /* ───────────────────────────────────────────── Blacklists */
10
+ const threeFiberElems = [
11
+ "object3D",
12
+ "audioListener",
13
+ "positionalAudio",
14
+ "mesh",
15
+ "batchedMesh",
16
+ "instancedMesh",
17
+ "scene",
18
+ "sprite",
19
+ "lOD",
20
+ "skinnedMesh",
21
+ "skeleton",
22
+ "bone",
23
+ "lineSegments",
24
+ "lineLoop",
25
+ "points",
26
+ "group",
27
+ "camera",
28
+ "perspectiveCamera",
29
+ "orthographicCamera",
30
+ "cubeCamera",
31
+ "arrayCamera",
32
+ "instancedBufferGeometry",
33
+ "bufferGeometry",
34
+ "boxBufferGeometry",
35
+ "circleBufferGeometry",
36
+ "coneBufferGeometry",
37
+ "cylinderBufferGeometry",
38
+ "dodecahedronBufferGeometry",
39
+ "extrudeBufferGeometry",
40
+ "icosahedronBufferGeometry",
41
+ "latheBufferGeometry",
42
+ "octahedronBufferGeometry",
43
+ "planeBufferGeometry",
44
+ "polyhedronBufferGeometry",
45
+ "ringBufferGeometry",
46
+ "shapeBufferGeometry",
47
+ "sphereBufferGeometry",
48
+ "tetrahedronBufferGeometry",
49
+ "torusBufferGeometry",
50
+ "torusKnotBufferGeometry",
51
+ "tubeBufferGeometry",
52
+ "wireframeGeometry",
53
+ "tetrahedronGeometry",
54
+ "octahedronGeometry",
55
+ "icosahedronGeometry",
56
+ "dodecahedronGeometry",
57
+ "polyhedronGeometry",
58
+ "tubeGeometry",
59
+ "torusKnotGeometry",
60
+ "torusGeometry",
61
+ "sphereGeometry",
62
+ "ringGeometry",
63
+ "planeGeometry",
64
+ "latheGeometry",
65
+ "shapeGeometry",
66
+ "extrudeGeometry",
67
+ "edgesGeometry",
68
+ "coneGeometry",
69
+ "cylinderGeometry",
70
+ "circleGeometry",
71
+ "boxGeometry",
72
+ "capsuleGeometry",
73
+ "material",
74
+ "shadowMaterial",
75
+ "spriteMaterial",
76
+ "rawShaderMaterial",
77
+ "shaderMaterial",
78
+ "pointsMaterial",
79
+ "meshPhysicalMaterial",
80
+ "meshStandardMaterial",
81
+ "meshPhongMaterial",
82
+ "meshToonMaterial",
83
+ "meshNormalMaterial",
84
+ "meshLambertMaterial",
85
+ "meshDepthMaterial",
86
+ "meshDistanceMaterial",
87
+ "meshBasicMaterial",
88
+ "meshMatcapMaterial",
89
+ "lineDashedMaterial",
90
+ "lineBasicMaterial",
91
+ "primitive",
92
+ "light",
93
+ "spotLightShadow",
94
+ "spotLight",
95
+ "pointLight",
96
+ "rectAreaLight",
97
+ "hemisphereLight",
98
+ "directionalLightShadow",
99
+ "directionalLight",
100
+ "ambientLight",
101
+ "lightShadow",
102
+ "ambientLightProbe",
103
+ "hemisphereLightProbe",
104
+ "lightProbe",
105
+ "spotLightHelper",
106
+ "skeletonHelper",
107
+ "pointLightHelper",
108
+ "hemisphereLightHelper",
109
+ "gridHelper",
110
+ "polarGridHelper",
111
+ "directionalLightHelper",
112
+ "cameraHelper",
113
+ "boxHelper",
114
+ "box3Helper",
115
+ "planeHelper",
116
+ "arrowHelper",
117
+ "axesHelper",
118
+ "texture",
119
+ "videoTexture",
120
+ "dataTexture",
121
+ "dataTexture3D",
122
+ "compressedTexture",
123
+ "cubeTexture",
124
+ "canvasTexture",
125
+ "depthTexture",
126
+ "raycaster",
127
+ "vector2",
128
+ "vector3",
129
+ "vector4",
130
+ "euler",
131
+ "matrix3",
132
+ "matrix4",
133
+ "quaternion",
134
+ "bufferAttribute",
135
+ "float16BufferAttribute",
136
+ "float32BufferAttribute",
137
+ "float64BufferAttribute",
138
+ "int8BufferAttribute",
139
+ "int16BufferAttribute",
140
+ "int32BufferAttribute",
141
+ "uint8BufferAttribute",
142
+ "uint16BufferAttribute",
143
+ "uint32BufferAttribute",
144
+ "instancedBufferAttribute",
145
+ "color",
146
+ "fog",
147
+ "fogExp2",
148
+ "shape",
149
+ "colorShiftMaterial"
150
+ ];
151
+ const dreiElems = [
152
+ "AsciiRenderer",
153
+ "Billboard",
154
+ "Clone",
155
+ "ComputedAttribute",
156
+ "Decal",
157
+ "Edges",
158
+ "Effects",
159
+ "GradientTexture",
160
+ "MarchingCubes",
161
+ "Outlines",
162
+ "PositionalAudio",
163
+ "Sampler",
164
+ "ScreenSizer",
165
+ "ScreenSpace",
166
+ "Splat",
167
+ "Svg",
168
+ "Text",
169
+ "Text3D",
170
+ "Trail",
171
+ "CubeCamera",
172
+ "OrthographicCamera",
173
+ "PerspectiveCamera",
174
+ "CameraControls",
175
+ "FaceControls",
176
+ "KeyboardControls",
177
+ "MotionPathControls",
178
+ "PresentationControls",
179
+ "ScrollControls",
180
+ "DragControls",
181
+ "GizmoHelper",
182
+ "Grid",
183
+ "Helper",
184
+ "PivotControls",
185
+ "TransformControls",
186
+ "CubeTexture",
187
+ "Fbx",
188
+ "Gltf",
189
+ "Ktx2",
190
+ "Loader",
191
+ "Progress",
192
+ "ScreenVideoTexture",
193
+ "Texture",
194
+ "TrailTexture",
195
+ "VideoTexture",
196
+ "WebcamVideoTexture",
197
+ "CycleRaycast",
198
+ "DetectGPU",
199
+ "Example",
200
+ "FaceLandmarker",
201
+ "Fbo",
202
+ "Html",
203
+ "Select",
204
+ "SpriteAnimator",
205
+ "StatsGl",
206
+ "Stats",
207
+ "Trail",
208
+ "Wireframe",
209
+ "CurveModifier",
210
+ "AdaptiveDpr",
211
+ "AdaptiveEvents",
212
+ "BakeShadows",
213
+ "Bvh",
214
+ "Detailed",
215
+ "Instances",
216
+ "Merged",
217
+ "meshBounds",
218
+ "PerformanceMonitor",
219
+ "Points",
220
+ "Preload",
221
+ "Segments",
222
+ "Fisheye",
223
+ "Hud",
224
+ "Mask",
225
+ "MeshPortalMaterial",
226
+ "RenderCubeTexture",
227
+ "RenderTexture",
228
+ "View",
229
+ "MeshDiscardMaterial",
230
+ "MeshDistortMaterial",
231
+ "MeshReflectorMaterial",
232
+ "MeshRefractionMaterial",
233
+ "MeshTransmissionMaterial",
234
+ "MeshWobbleMaterial",
235
+ "PointMaterial",
236
+ "shaderMaterial",
237
+ "SoftShadows",
238
+ "CatmullRomLine",
239
+ "CubicBezierLine",
240
+ "Facemesh",
241
+ "Line",
242
+ "Mesh",
243
+ "QuadraticBezierLine",
244
+ "RoundedBox",
245
+ "ScreenQuad",
246
+ "AccumulativeShadows",
247
+ "Backdrop",
248
+ "BBAnchor",
249
+ "Bounds",
250
+ "CameraShake",
251
+ "Caustics",
252
+ "Center",
253
+ "Cloud",
254
+ "ContactShadows",
255
+ "Environment",
256
+ "Float",
257
+ "Lightformer",
258
+ "MatcapTexture",
259
+ "NormalTexture",
260
+ "RandomizedLight",
261
+ "Resize",
262
+ "ShadowAlpha",
263
+ "Shadow",
264
+ "Sky",
265
+ "Sparkles",
266
+ "SpotLightShadow",
267
+ "SpotLight",
268
+ "Stage",
269
+ "Stars",
270
+ "OrbitControls"
271
+ ];
272
+ const shouldTag = (name) => !threeFiberElems.includes(name) && !dreiElems.includes(name);
273
+ // ➕ Collect aliases of the Next.js <Image> component so we can reliably tag it even if it was renamed.
274
+ const isNextImageAlias = (aliases, name) => aliases.has(name);
275
+ const extractLiteralValue = (node) => {
276
+ if (!node)
277
+ return undefined;
278
+ switch (node.type) {
279
+ case 'StringLiteral':
280
+ return node.value;
281
+ case 'NumericLiteral':
282
+ return node.value;
283
+ case 'BooleanLiteral':
284
+ return node.value;
285
+ case 'ObjectExpression':
286
+ const obj = {};
287
+ for (const prop of node.properties) {
288
+ if (prop.type === 'ObjectProperty' && !prop.computed) {
289
+ const key = prop.key.type === 'Identifier' ? prop.key.name : prop.key.value;
290
+ obj[key] = extractLiteralValue(prop.value);
291
+ }
292
+ }
293
+ return obj;
294
+ case 'ArrayExpression':
295
+ return node.elements.map((el) => extractLiteralValue(el));
296
+ default:
297
+ return undefined;
298
+ }
299
+ };
300
+ const findVariableDeclarations = (ast) => {
301
+ const variables = new Map();
302
+ (0, estree_walker_1.walk)(ast, {
303
+ enter(node) {
304
+ var _a;
305
+ // Handle const/let/var declarations
306
+ if (node.type === 'VariableDeclaration') {
307
+ for (const declarator of node.declarations) {
308
+ if (declarator.id.type === 'Identifier' && declarator.init) {
309
+ const varName = declarator.id.name;
310
+ const value = extractLiteralValue(declarator.init);
311
+ variables.set(varName, {
312
+ name: varName,
313
+ type: Array.isArray(value) ? 'array' : typeof value === 'object' ? 'object' : 'primitive',
314
+ value,
315
+ arrayItems: Array.isArray(value) ? value : undefined,
316
+ loc: (_a = declarator.loc) === null || _a === void 0 ? void 0 : _a.start
317
+ });
318
+ }
319
+ }
320
+ }
321
+ }
322
+ });
323
+ return variables;
324
+ };
325
+ const findMapContext = (node, variables) => {
326
+ var _a, _b, _c, _d, _e, _f, _g;
327
+ // Walk up the tree to find if this JSX element is inside a map call
328
+ let current = node;
329
+ let depth = 0;
330
+ const maxDepth = 10; // Prevent infinite loops
331
+ while (current && depth < maxDepth) {
332
+ if (current.type === 'CallExpression' &&
333
+ ((_a = current.callee) === null || _a === void 0 ? void 0 : _a.type) === 'MemberExpression' &&
334
+ ((_c = (_b = current.callee) === null || _b === void 0 ? void 0 : _b.property) === null || _c === void 0 ? void 0 : _c.name) === 'map') {
335
+ // Found a .map() call, check if it's on a known array
336
+ const arrayName = (_d = current.callee.object) === null || _d === void 0 ? void 0 : _d.name;
337
+ const mapCallback = (_e = current.arguments) === null || _e === void 0 ? void 0 : _e[0];
338
+ if (arrayName && (mapCallback === null || mapCallback === void 0 ? void 0 : mapCallback.type) === 'ArrowFunctionExpression') {
339
+ const itemParam = (_f = mapCallback.params) === null || _f === void 0 ? void 0 : _f[0];
340
+ const indexParam = (_g = mapCallback.params) === null || _g === void 0 ? void 0 : _g[1];
341
+ if ((itemParam === null || itemParam === void 0 ? void 0 : itemParam.type) === 'Identifier') {
342
+ const varInfo = variables.get(arrayName);
343
+ return {
344
+ arrayName,
345
+ itemVarName: itemParam.name,
346
+ arrayItems: varInfo === null || varInfo === void 0 ? void 0 : varInfo.arrayItems,
347
+ arrayLoc: varInfo === null || varInfo === void 0 ? void 0 : varInfo.loc,
348
+ indexVarName: (indexParam === null || indexParam === void 0 ? void 0 : indexParam.type) === 'Identifier' ? indexParam.name : undefined
349
+ };
350
+ }
351
+ }
352
+ }
353
+ current = current.parent;
354
+ depth++;
355
+ }
356
+ return null;
357
+ };
358
+ const getSemanticName = (node, mapContext, imageAliases) => {
359
+ const getName = () => {
360
+ if (node.name.type === 'JSXIdentifier')
361
+ return node.name.name;
362
+ if (node.name.type === 'JSXMemberExpression')
363
+ return `${node.name.object.name}.${node.name.property.name}`;
364
+ return null;
365
+ };
366
+ const tagName = getName();
367
+ if (!tagName)
368
+ return null;
369
+ // For Next.js Image components, always return 'img' so the name is a valid HTML tag.
370
+ if (isNextImageAlias(imageAliases, tagName)) {
371
+ return 'img';
372
+ }
373
+ return isNextImageAlias(imageAliases, tagName) ? 'img' : tagName;
374
+ };
375
+ /* ───────────────────────────────────────────── Loader */
376
+ function componentTagger(src, map) {
377
+ const done = this.async();
378
+ try {
379
+ if (/node_modules/.test(this.resourcePath))
380
+ return done(null, src, map);
381
+ const ast = (0, parser_1.parse)(src, {
382
+ sourceType: 'module',
383
+ plugins: ['jsx', 'typescript'],
384
+ });
385
+ const ms = new magic_string_1.default(src);
386
+ const rel = path.relative(process.cwd(), this.resourcePath);
387
+ let mutated = false;
388
+ // Add parent references to AST nodes for upward traversal (non-enumerable to avoid infinite recursion)
389
+ (0, estree_walker_1.walk)(ast, {
390
+ enter(node, parent) {
391
+ if (parent && !Object.prototype.hasOwnProperty.call(node, 'parent')) {
392
+ Object.defineProperty(node, 'parent', { value: parent, enumerable: false });
393
+ }
394
+ }
395
+ });
396
+ // 0️⃣ Collect variable declarations first
397
+ const variables = findVariableDeclarations(ast);
398
+ // 1️⃣ Gather local identifiers that reference `next/image`.
399
+ const imageAliases = new Set();
400
+ (0, estree_walker_1.walk)(ast, {
401
+ enter(node) {
402
+ if (node.type === 'ImportDeclaration' &&
403
+ node.source.value === 'next/image') {
404
+ for (const spec of node.specifiers) {
405
+ imageAliases.add(spec.local.name);
406
+ }
407
+ }
408
+ },
409
+ });
410
+ // 2️⃣ Inject attributes with enhanced semantic context.
411
+ (0, estree_walker_1.walk)(ast, {
412
+ enter(node) {
413
+ var _a;
414
+ if (node.type !== 'JSXOpeningElement')
415
+ return;
416
+ const mapContext = findMapContext(node, variables);
417
+ const semanticName = getSemanticName(node, mapContext, imageAliases);
418
+ if (!semanticName ||
419
+ ['Fragment', 'React.Fragment'].includes(semanticName) ||
420
+ (!isNextImageAlias(imageAliases, semanticName.split('-')[0]) &&
421
+ !shouldTag(semanticName)))
422
+ return;
423
+ const { line, column } = node.loc.start;
424
+ let orchidsId = `${rel}:${line}:${column}`;
425
+ // Enhance the ID with context if we have map information
426
+ if (mapContext) {
427
+ orchidsId += `@${mapContext.arrayName}`;
428
+ }
429
+ // 🔍 Append referenced variable locations for simple identifier references in props
430
+ (_a = node.attributes) === null || _a === void 0 ? void 0 : _a.forEach((attr) => {
431
+ var _a, _b;
432
+ if (attr.type === 'JSXAttribute' &&
433
+ ((_a = attr.value) === null || _a === void 0 ? void 0 : _a.type) === 'JSXExpressionContainer' &&
434
+ ((_b = attr.value.expression) === null || _b === void 0 ? void 0 : _b.type) === 'Identifier') {
435
+ const refName = attr.value.expression.name;
436
+ const varInfo = variables.get(refName);
437
+ if (varInfo) {
438
+ orchidsId += `@${refName}`;
439
+ }
440
+ }
441
+ });
442
+ // 📍 If inside a map context and we have an index variable, inject data-map-index
443
+ if (mapContext === null || mapContext === void 0 ? void 0 : mapContext.indexVarName) {
444
+ ms.appendLeft(node.name.end, ` data-map-index={${mapContext.indexVarName}}`);
445
+ }
446
+ ms.appendLeft(node.name.end, ` data-orchids-id="${orchidsId}" data-orchids-name="${semanticName}"`);
447
+ mutated = true;
448
+ },
449
+ });
450
+ if (!mutated)
451
+ return done(null, src, map);
452
+ const out = ms.toString();
453
+ const outMap = ms.generateMap({ hires: true });
454
+ /* Turbopack expects the sourcemap as a JSON *string*. */
455
+ done(null, out, JSON.stringify(outMap));
456
+ }
457
+ catch (err) {
458
+ done(err);
459
+ }
460
+ }
461
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibexdotnew/inspector",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Visual inspection and element tagging for Next.js and Expo applications",
5
5
  "author": "Vibex",
6
6
  "license": "MIT",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "exports": {
29
29
  "./package.json": "./package.json",
30
- "./loader.js": "./next/element-tagger.cjs",
30
+ "./loader.js": "./next/element-tagger.js",
31
31
  "./transformer.js": "./expo/metro-transformer.cjs",
32
32
  "./next": {
33
33
  "types": "./dist/next/index.d.ts",
@@ -1,372 +0,0 @@
1
- "use strict";
2
-
3
- /**
4
- * Vibex Element Tagger - Turbopack/Webpack Loader
5
- *
6
- * Transforms JSX to inject location attributes for visual editing.
7
- * Injects: data-vibex-loc="filepath:line:col" data-vibex-name="tagName"
8
- *
9
- * SAFE: Never throws errors - returns original source on any failure
10
- */
11
-
12
- // ─────────────────────────────────────────────────────────────────────────────
13
- // Safe imports with fallbacks
14
- // ─────────────────────────────────────────────────────────────────────────────
15
-
16
- let parse, MagicString, walk;
17
-
18
- try {
19
- parse = require("@babel/parser").parse;
20
- } catch {
21
- parse = null;
22
- }
23
-
24
- try {
25
- MagicString = require("magic-string");
26
- } catch {
27
- MagicString = null;
28
- }
29
-
30
- // estree-walker v3 is ESM-only, try multiple import strategies
31
- try {
32
- walk = require("estree-walker").walk;
33
- } catch {
34
- try {
35
- // Try the sync version
36
- const mod = require("estree-walker/src/sync.js");
37
- walk = mod.walk || mod.default?.walk;
38
- } catch {
39
- walk = null;
40
- }
41
- }
42
-
43
- const path = require("path");
44
- const fs = require("fs");
45
-
46
- // ─────────────────────────────────────────────────────────────────────────────
47
- // Element Exclusion Lists
48
- // ─────────────────────────────────────────────────────────────────────────────
49
-
50
- const EXCLUDED_ELEMENTS = new Set([
51
- // React Three Fiber primitives
52
- "primitive", "mesh", "group", "scene", "object3D",
53
- "camera", "perspectiveCamera", "orthographicCamera", "cubeCamera", "arrayCamera",
54
- "instancedMesh", "batchedMesh", "skinnedMesh", "sprite", "lOD",
55
- "lineSegments", "lineLoop", "points", "skeleton", "bone",
56
- "bufferGeometry", "instancedBufferGeometry",
57
- "boxGeometry", "sphereGeometry", "planeGeometry", "cylinderGeometry",
58
- "coneGeometry", "torusGeometry", "torusKnotGeometry", "ringGeometry",
59
- "circleGeometry", "tubeGeometry", "extrudeGeometry", "latheGeometry",
60
- "shapeGeometry", "polyhedronGeometry", "icosahedronGeometry",
61
- "octahedronGeometry", "tetrahedronGeometry", "dodecahedronGeometry",
62
- "capsuleGeometry", "edgesGeometry", "wireframeGeometry",
63
- "material", "meshBasicMaterial", "meshStandardMaterial", "meshPhongMaterial",
64
- "meshLambertMaterial", "meshToonMaterial", "meshNormalMaterial",
65
- "meshPhysicalMaterial", "meshDepthMaterial", "meshDistanceMaterial",
66
- "meshMatcapMaterial", "shaderMaterial", "rawShaderMaterial",
67
- "pointsMaterial", "lineBasicMaterial", "lineDashedMaterial",
68
- "spriteMaterial", "shadowMaterial",
69
- "light", "ambientLight", "directionalLight", "pointLight", "spotLight",
70
- "hemisphereLight", "rectAreaLight", "lightProbe",
71
- "texture", "videoTexture", "canvasTexture", "cubeTexture",
72
- "dataTexture", "compressedTexture", "depthTexture",
73
- "vector2", "vector3", "vector4", "euler", "quaternion",
74
- "matrix3", "matrix4", "color", "raycaster", "fog", "fogExp2",
75
- // Drei components
76
- "Billboard", "Text", "Text3D", "Html", "Trail", "Decal", "Edges", "Outlines",
77
- "PerspectiveCamera", "OrthographicCamera", "CubeCamera",
78
- "OrbitControls", "CameraControls", "TransformControls", "PivotControls",
79
- "Environment", "Sky", "Stars", "Cloud", "Sparkles", "Float", "Stage",
80
- "RoundedBox", "Line", "Points", "Instances", "Merged",
81
- "MeshReflectorMaterial", "MeshTransmissionMaterial", "shaderMaterial",
82
- ]);
83
-
84
- // ─────────────────────────────────────────────────────────────────────────────
85
- // Simple AST Walker (fallback if estree-walker fails)
86
- // ─────────────────────────────────────────────────────────────────────────────
87
-
88
- function simpleWalk(node, callbacks, parent = null) {
89
- if (!node || typeof node !== "object") return;
90
-
91
- // Add parent reference
92
- if (parent && !Object.prototype.hasOwnProperty.call(node, "parent")) {
93
- Object.defineProperty(node, "parent", {
94
- value: parent,
95
- enumerable: false,
96
- writable: true,
97
- });
98
- }
99
-
100
- if (callbacks.enter) {
101
- callbacks.enter(node, parent);
102
- }
103
-
104
- // Walk children
105
- for (const key of Object.keys(node)) {
106
- if (key === "parent") continue;
107
- const child = node[key];
108
- if (Array.isArray(child)) {
109
- for (const item of child) {
110
- if (item && typeof item === "object" && item.type) {
111
- simpleWalk(item, callbacks, node);
112
- }
113
- }
114
- } else if (child && typeof child === "object" && child.type) {
115
- simpleWalk(child, callbacks, node);
116
- }
117
- }
118
- }
119
-
120
- // Use estree-walker if available, otherwise use simple walker
121
- const walkAst = walk || simpleWalk;
122
-
123
- // ─────────────────────────────────────────────────────────────────────────────
124
- // Helpers
125
- // ─────────────────────────────────────────────────────────────────────────────
126
-
127
- /**
128
- * Quick check if source likely contains JSX
129
- */
130
- function hasJSX(source) {
131
- // Look for JSX patterns: <Component, <div, etc.
132
- return /<[A-Za-z]/.test(source);
133
- }
134
-
135
- /**
136
- * Check if element should receive location attributes
137
- */
138
- function shouldInjectAttrs(tagName) {
139
- if (!tagName) return false;
140
- if (tagName === "Fragment" || tagName === "React.Fragment") return false;
141
- if (EXCLUDED_ELEMENTS.has(tagName)) return false;
142
- return true;
143
- }
144
-
145
- /**
146
- * Extract tag name from JSX opening element
147
- */
148
- function getTagName(node) {
149
- try {
150
- const { name } = node;
151
- if (name.type === "JSXIdentifier") {
152
- return name.name;
153
- }
154
- if (name.type === "JSXMemberExpression") {
155
- return `${name.object.name}.${name.property.name}`;
156
- }
157
- } catch {
158
- return null;
159
- }
160
- return null;
161
- }
162
-
163
- /**
164
- * Detect if JSX element is inside a .map() call
165
- */
166
- function detectMapContext(node) {
167
- try {
168
- let current = node;
169
- let depth = 0;
170
-
171
- while (current && depth < 15) {
172
- if (
173
- current.type === "CallExpression" &&
174
- current.callee?.type === "MemberExpression" &&
175
- current.callee.property?.name === "map"
176
- ) {
177
- const arrayRef = current.callee.object?.name;
178
- const callback = current.arguments?.[0];
179
-
180
- if (arrayRef && callback?.type === "ArrowFunctionExpression") {
181
- const indexArg = callback.params?.[1];
182
- return {
183
- arrayName: arrayRef,
184
- indexName: indexArg?.type === "Identifier" ? indexArg.name : null,
185
- };
186
- }
187
- }
188
- current = current.parent;
189
- depth++;
190
- }
191
- } catch {
192
- // Ignore errors
193
- }
194
- return null;
195
- }
196
-
197
- /**
198
- * Collect Next.js Image import aliases
199
- */
200
- function collectImageAliases(ast) {
201
- const aliases = new Set();
202
-
203
- try {
204
- walkAst(ast, {
205
- enter(node) {
206
- if (
207
- node.type === "ImportDeclaration" &&
208
- node.source?.value === "next/image"
209
- ) {
210
- for (const spec of node.specifiers || []) {
211
- if (spec.local?.name) {
212
- aliases.add(spec.local.name);
213
- }
214
- }
215
- }
216
- },
217
- });
218
- } catch {
219
- // Ignore errors
220
- }
221
-
222
- return aliases;
223
- }
224
-
225
- // ─────────────────────────────────────────────────────────────────────────────
226
- // Main Loader
227
- // ─────────────────────────────────────────────────────────────────────────────
228
-
229
- /**
230
- * Vibex Element Tagger Loader
231
- * SAFE: Returns original source on any error
232
- */
233
- function elementTagger(source, sourceMap) {
234
- const callback = this.async();
235
-
236
- // Early exit: missing dependencies
237
- if (!parse || !MagicString) {
238
- return callback(null, source, sourceMap);
239
- }
240
-
241
- // Early exit: no resource path or it's a directory
242
- if (!this.resourcePath) {
243
- return callback(null, source, sourceMap);
244
- }
245
-
246
- // Early exit: path doesn't look like a file (no extension)
247
- const ext = path.extname(this.resourcePath);
248
- if (!ext || !['.tsx', '.jsx', '.ts', '.js'].includes(ext)) {
249
- return callback(null, source, sourceMap);
250
- }
251
-
252
- // Early exit: check if it's actually a file (not a directory)
253
- try {
254
- const stat = fs.statSync(this.resourcePath);
255
- if (!stat.isFile()) {
256
- return callback(null, source, sourceMap);
257
- }
258
- } catch {
259
- // If stat fails, still try to process (might be virtual file)
260
- }
261
-
262
- // Early exit: node_modules
263
- if (/node_modules/.test(this.resourcePath)) {
264
- return callback(null, source, sourceMap);
265
- }
266
-
267
- // Early exit: no JSX detected
268
- if (!hasJSX(source)) {
269
- return callback(null, source, sourceMap);
270
- }
271
-
272
- try {
273
- // Parse source
274
- const ast = parse(source, {
275
- sourceType: "module",
276
- plugins: ["jsx", "typescript"],
277
- errorRecovery: true, // Don't fail on syntax errors
278
- });
279
-
280
- if (!ast || !ast.program) {
281
- return callback(null, source, sourceMap);
282
- }
283
-
284
- const magicStr = new MagicString(source);
285
- const relativePath = path.relative(process.cwd(), this.resourcePath);
286
- const imageAliases = collectImageAliases(ast);
287
- let hasChanges = false;
288
-
289
- // Add parent references
290
- walkAst(ast.program, {
291
- enter() { }, // Just to trigger parent ref addition
292
- });
293
-
294
- // Process JSX elements
295
- walkAst(ast.program, {
296
- enter(node) {
297
- try {
298
- if (node.type !== "JSXOpeningElement") return;
299
-
300
- let tagName = getTagName(node);
301
- if (!tagName) return;
302
-
303
- // Check if already has data-vibex-loc
304
- const hasAttr = node.attributes?.some(
305
- (attr) =>
306
- attr.type === "JSXAttribute" &&
307
- attr.name?.name === "data-vibex-loc"
308
- );
309
- if (hasAttr) return;
310
-
311
- // Normalize Next.js Image to img
312
- const isNextImage = imageAliases.has(tagName.split(".")[0]);
313
- const displayName = isNextImage ? "img" : tagName;
314
-
315
- // Check if we should tag this element
316
- if (!shouldInjectAttrs(tagName) && !isNextImage) return;
317
-
318
- // Ensure we have location info
319
- if (!node.loc?.start || !node.name?.end) return;
320
-
321
- // Build location identifier
322
- const { line, column } = node.loc.start;
323
- let locId = `${relativePath}:${line}:${column}`;
324
-
325
- // Add iteration context if inside .map()
326
- const mapCtx = detectMapContext(node);
327
- if (mapCtx?.arrayName) {
328
- locId += `#${mapCtx.arrayName}`;
329
- }
330
-
331
- // Inject data-vibex-iter for map index if available
332
- if (mapCtx?.indexName) {
333
- magicStr.appendLeft(
334
- node.name.end,
335
- ` data-vibex-iter=${mapCtx.indexName}`
336
- );
337
- }
338
-
339
- // Inject location and name attributes
340
- magicStr.appendLeft(
341
- node.name.end,
342
- ` data-vibex-loc="${locId}" data-vibex-name="${displayName}"`
343
- );
344
-
345
- hasChanges = true;
346
- } catch {
347
- // Ignore errors for individual nodes
348
- }
349
- },
350
- });
351
-
352
- // Return original if no changes
353
- if (!hasChanges) {
354
- return callback(null, source, sourceMap);
355
- }
356
-
357
- // Generate output
358
- const output = magicStr.toString();
359
- const outputMap = magicStr.generateMap({ hires: true });
360
-
361
- // Turbopack expects sourcemap as JSON string
362
- callback(null, output, JSON.stringify(outputMap));
363
-
364
- } catch (error) {
365
- // On ANY error, return original source unchanged
366
- console.warn("[element-tagger] Skipping file due to error:", this.resourcePath);
367
- callback(null, source, sourceMap);
368
- }
369
- }
370
-
371
- module.exports = elementTagger;
372
- module.exports.default = elementTagger;