jsx-loc-plugin 0.1.22

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/index.cjs ADDED
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ default: () => index_default,
34
+ withJsxLoc: () => withJsxLoc
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+ var import_path = __toESM(require("path"), 1);
38
+ var import_url = require("url");
39
+ var import_meta = {};
40
+ var computedDirname;
41
+ try {
42
+ computedDirname = import_path.default.dirname((0, import_url.fileURLToPath)(import_meta.url));
43
+ } catch {
44
+ computedDirname = typeof __dirname !== "undefined" ? __dirname : "";
45
+ }
46
+ function withJsxLoc(nextConfig = {}, pluginOptions = {}) {
47
+ const loaderPath = import_path.default.join(computedDirname, "loader.cjs");
48
+ console.log(`[jsx-loc-plugin] Plugin loaded, loader path: ${loaderPath}`);
49
+ const loaderObj = {
50
+ loader: loaderPath,
51
+ options: { ...pluginOptions }
52
+ };
53
+ const jsxRule = {
54
+ test: /\.(jsx|tsx)$/,
55
+ enforce: "pre",
56
+ use: [loaderObj],
57
+ exclude: pluginOptions.exclude || /node_modules/
58
+ };
59
+ if (pluginOptions.include) {
60
+ jsxRule.include = pluginOptions.include;
61
+ }
62
+ const tsxLoaderConfig = {
63
+ condition: {
64
+ all: [
65
+ { not: "foreign" },
66
+ // Only match paths that end with .tsx (not directories)
67
+ { path: /\/[^/]+\.tsx$/ }
68
+ ]
69
+ },
70
+ loaders: [
71
+ {
72
+ loader: loaderPath,
73
+ options: { ...pluginOptions }
74
+ }
75
+ ]
76
+ // as: "*.tsx",
77
+ };
78
+ const jsxLoaderConfig = {
79
+ condition: {
80
+ all: [
81
+ { not: "foreign" },
82
+ // Only match paths that end with .jsx (not directories)
83
+ { path: /\/[^/]+\.jsx$/ }
84
+ ]
85
+ },
86
+ loaders: [
87
+ {
88
+ loader: loaderPath,
89
+ options: { ...pluginOptions }
90
+ }
91
+ ]
92
+ // as: "*.jsx",
93
+ };
94
+ const turbopackRules = {
95
+ "*.tsx": tsxLoaderConfig,
96
+ "*.jsx": jsxLoaderConfig
97
+ };
98
+ const existingTurbopack = nextConfig.turbopack || {};
99
+ const existingRules = existingTurbopack.rules || {};
100
+ const enhancedConfig = {
101
+ ...nextConfig,
102
+ // Configure webpack
103
+ webpack(config, options) {
104
+ if (!config.module) {
105
+ config.module = { rules: [] };
106
+ }
107
+ if (!config.module.rules) {
108
+ config.module.rules = [];
109
+ }
110
+ config.module.rules.unshift(jsxRule);
111
+ if (typeof nextConfig.webpack === "function") {
112
+ return nextConfig.webpack(config, options);
113
+ }
114
+ return config;
115
+ },
116
+ // Configure Turbopack (Next.js 16+ top-level turbopack key)
117
+ turbopack: {
118
+ ...existingTurbopack,
119
+ rules: {
120
+ ...existingRules,
121
+ ...turbopackRules
122
+ }
123
+ }
124
+ };
125
+ return enhancedConfig;
126
+ }
127
+ var index_default = withJsxLoc;
128
+ // Annotate the CommonJS export names for ESM import in node:
129
+ 0 && (module.exports = {
130
+ withJsxLoc
131
+ });
@@ -0,0 +1,22 @@
1
+ import { NextConfig } from 'next';
2
+
3
+ /**
4
+ * Next.js plugin that adds data-loc attributes to JSX elements.
5
+ * Compatible with Next.js 16+ (supports both webpack and Turbopack)
6
+ */
7
+
8
+ interface JsxLocPluginOptions {
9
+ /** RegExp patterns to include (default: all .jsx/.tsx files) */
10
+ include?: RegExp | RegExp[];
11
+ /** RegExp patterns to exclude (default: /node_modules/) */
12
+ exclude?: RegExp | RegExp[];
13
+ }
14
+ /**
15
+ * Next.js plugin that adds data-loc attributes to JSX elements
16
+ * to help with component tracking and debugging.
17
+ *
18
+ * Supports both webpack and Turbopack configurations.
19
+ */
20
+ declare function withJsxLoc(nextConfig?: NextConfig, pluginOptions?: JsxLocPluginOptions): NextConfig;
21
+
22
+ export { type JsxLocPluginOptions, withJsxLoc as default, withJsxLoc };
@@ -0,0 +1,22 @@
1
+ import { NextConfig } from 'next';
2
+
3
+ /**
4
+ * Next.js plugin that adds data-loc attributes to JSX elements.
5
+ * Compatible with Next.js 16+ (supports both webpack and Turbopack)
6
+ */
7
+
8
+ interface JsxLocPluginOptions {
9
+ /** RegExp patterns to include (default: all .jsx/.tsx files) */
10
+ include?: RegExp | RegExp[];
11
+ /** RegExp patterns to exclude (default: /node_modules/) */
12
+ exclude?: RegExp | RegExp[];
13
+ }
14
+ /**
15
+ * Next.js plugin that adds data-loc attributes to JSX elements
16
+ * to help with component tracking and debugging.
17
+ *
18
+ * Supports both webpack and Turbopack configurations.
19
+ */
20
+ declare function withJsxLoc(nextConfig?: NextConfig, pluginOptions?: JsxLocPluginOptions): NextConfig;
21
+
22
+ export { type JsxLocPluginOptions, withJsxLoc as default, withJsxLoc };
package/dist/index.js ADDED
@@ -0,0 +1,95 @@
1
+ // src/index.ts
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ var computedDirname;
5
+ try {
6
+ computedDirname = path.dirname(fileURLToPath(import.meta.url));
7
+ } catch {
8
+ computedDirname = typeof __dirname !== "undefined" ? __dirname : "";
9
+ }
10
+ function withJsxLoc(nextConfig = {}, pluginOptions = {}) {
11
+ const loaderPath = path.join(computedDirname, "loader.cjs");
12
+ console.log(`[jsx-loc-plugin] Plugin loaded, loader path: ${loaderPath}`);
13
+ const loaderObj = {
14
+ loader: loaderPath,
15
+ options: { ...pluginOptions }
16
+ };
17
+ const jsxRule = {
18
+ test: /\.(jsx|tsx)$/,
19
+ enforce: "pre",
20
+ use: [loaderObj],
21
+ exclude: pluginOptions.exclude || /node_modules/
22
+ };
23
+ if (pluginOptions.include) {
24
+ jsxRule.include = pluginOptions.include;
25
+ }
26
+ const tsxLoaderConfig = {
27
+ condition: {
28
+ all: [
29
+ { not: "foreign" },
30
+ // Only match paths that end with .tsx (not directories)
31
+ { path: /\/[^/]+\.tsx$/ }
32
+ ]
33
+ },
34
+ loaders: [
35
+ {
36
+ loader: loaderPath,
37
+ options: { ...pluginOptions }
38
+ }
39
+ ]
40
+ // as: "*.tsx",
41
+ };
42
+ const jsxLoaderConfig = {
43
+ condition: {
44
+ all: [
45
+ { not: "foreign" },
46
+ // Only match paths that end with .jsx (not directories)
47
+ { path: /\/[^/]+\.jsx$/ }
48
+ ]
49
+ },
50
+ loaders: [
51
+ {
52
+ loader: loaderPath,
53
+ options: { ...pluginOptions }
54
+ }
55
+ ]
56
+ // as: "*.jsx",
57
+ };
58
+ const turbopackRules = {
59
+ "*.tsx": tsxLoaderConfig,
60
+ "*.jsx": jsxLoaderConfig
61
+ };
62
+ const existingTurbopack = nextConfig.turbopack || {};
63
+ const existingRules = existingTurbopack.rules || {};
64
+ const enhancedConfig = {
65
+ ...nextConfig,
66
+ // Configure webpack
67
+ webpack(config, options) {
68
+ if (!config.module) {
69
+ config.module = { rules: [] };
70
+ }
71
+ if (!config.module.rules) {
72
+ config.module.rules = [];
73
+ }
74
+ config.module.rules.unshift(jsxRule);
75
+ if (typeof nextConfig.webpack === "function") {
76
+ return nextConfig.webpack(config, options);
77
+ }
78
+ return config;
79
+ },
80
+ // Configure Turbopack (Next.js 16+ top-level turbopack key)
81
+ turbopack: {
82
+ ...existingTurbopack,
83
+ rules: {
84
+ ...existingRules,
85
+ ...turbopackRules
86
+ }
87
+ }
88
+ };
89
+ return enhancedConfig;
90
+ }
91
+ var index_default = withJsxLoc;
92
+ export {
93
+ index_default as default,
94
+ withJsxLoc
95
+ };
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/loader.ts
31
+ var loader_exports = {};
32
+ __export(loader_exports, {
33
+ default: () => loader_default
34
+ });
35
+ module.exports = __toCommonJS(loader_exports);
36
+
37
+ // src/transform.ts
38
+ var import_parser = require("@babel/parser");
39
+ var import_magic_string = __toESM(require("magic-string"), 1);
40
+ var VALID_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx"]);
41
+ var DEFAULT_PARSER_OPTIONS = {
42
+ sourceType: "module",
43
+ plugins: [
44
+ "jsx",
45
+ "typescript",
46
+ "decorators-legacy",
47
+ "classProperties",
48
+ "classPrivateProperties",
49
+ "classPrivateMethods",
50
+ "exportDefaultFrom",
51
+ "exportNamespaceFrom",
52
+ "asyncGenerators",
53
+ "functionBind",
54
+ "functionSent",
55
+ "dynamicImport",
56
+ "numericSeparator",
57
+ "optionalChaining",
58
+ "importMeta",
59
+ "bigInt",
60
+ "optionalCatchBinding",
61
+ "throwExpressions",
62
+ "nullishCoalescingOperator",
63
+ "topLevelAwait"
64
+ ],
65
+ errorRecovery: true
66
+ };
67
+ var SKIP_ELEMENTS = /* @__PURE__ */ new Set([
68
+ "Fragment",
69
+ "React.Fragment",
70
+ "Suspense",
71
+ "React.Suspense",
72
+ "StrictMode",
73
+ "React.StrictMode",
74
+ "Profiler",
75
+ "React.Profiler"
76
+ ]);
77
+ function shouldProcessFile(filePath) {
78
+ const ext = filePath.slice(filePath.lastIndexOf("."));
79
+ return VALID_EXTENSIONS.has(ext);
80
+ }
81
+ function getElementName(node) {
82
+ if (!node || !node.name) return null;
83
+ const name = node.name;
84
+ if (name.type === "JSXIdentifier") {
85
+ return name.name;
86
+ }
87
+ if (name.type === "JSXMemberExpression") {
88
+ const parts = [];
89
+ let current = name;
90
+ while (current) {
91
+ if (current.type === "JSXMemberExpression") {
92
+ parts.unshift(current.property.name);
93
+ current = current.object;
94
+ } else if (current.type === "JSXIdentifier") {
95
+ parts.unshift(current.name);
96
+ break;
97
+ } else {
98
+ break;
99
+ }
100
+ }
101
+ return parts.join(".");
102
+ }
103
+ if (name.type === "JSXNamespacedName") {
104
+ return `${name.namespace.name}:${name.name.name}`;
105
+ }
106
+ return null;
107
+ }
108
+ function shouldSkipElement(elementName) {
109
+ if (!elementName) return true;
110
+ return SKIP_ELEMENTS.has(elementName);
111
+ }
112
+ function hasDataLocAttribute(node) {
113
+ if (!node.attributes) return false;
114
+ return node.attributes.some(
115
+ (attr) => attr.type === "JSXAttribute" && attr.name?.type === "JSXIdentifier" && attr.name.name === "data-loc"
116
+ );
117
+ }
118
+ function findInsertionPoint(node, source) {
119
+ let insertPos = node.name.end;
120
+ if (node.typeParameters) {
121
+ insertPos = node.typeParameters.end;
122
+ }
123
+ if (node.attributes && node.attributes.length > 0) {
124
+ const firstAttr = node.attributes[0];
125
+ const nameEnd = node.typeParameters ? node.typeParameters.end : node.name.end;
126
+ let pos = nameEnd;
127
+ while (pos < firstAttr.start && /\s/.test(source[pos])) {
128
+ pos++;
129
+ }
130
+ insertPos = pos;
131
+ }
132
+ return insertPos;
133
+ }
134
+ function normalizeFilePath(filePath) {
135
+ return filePath.replace(/\\/g, "/");
136
+ }
137
+ function transformJsxCode(source, filePath, options = {}) {
138
+ const parserOptions = {
139
+ ...DEFAULT_PARSER_OPTIONS,
140
+ ...options.parserOptions
141
+ };
142
+ let ast;
143
+ try {
144
+ ast = (0, import_parser.parse)(source, parserOptions);
145
+ } catch (error) {
146
+ console.error(`[jsx-loc] Failed to parse ${filePath}:`, error);
147
+ return null;
148
+ }
149
+ const magicString = new import_magic_string.default(source);
150
+ const normalizedPath = normalizeFilePath(filePath);
151
+ let hasChanges = false;
152
+ function walk(node) {
153
+ if (!node || typeof node !== "object") return;
154
+ if (node.type === "JSXOpeningElement") {
155
+ const elementName = getElementName(node);
156
+ if (!shouldSkipElement(elementName) && !hasDataLocAttribute(node)) {
157
+ const loc = node.loc;
158
+ if (loc && loc.start) {
159
+ const line = loc.start.line;
160
+ const column = loc.start.column;
161
+ const locValue = `${normalizedPath}:${line}:${column}`;
162
+ const insertPos = findInsertionPoint(node, source);
163
+ magicString.appendLeft(insertPos, ` data-loc="${locValue}"`);
164
+ hasChanges = true;
165
+ }
166
+ }
167
+ }
168
+ for (const key of Object.keys(node)) {
169
+ const child = node[key];
170
+ if (Array.isArray(child)) {
171
+ for (const item of child) {
172
+ walk(item);
173
+ }
174
+ } else if (child && typeof child === "object" && child.type) {
175
+ walk(child);
176
+ }
177
+ }
178
+ }
179
+ if (ast.program && ast.program.body) {
180
+ for (const node of ast.program.body) {
181
+ walk(node);
182
+ }
183
+ }
184
+ if (!hasChanges) {
185
+ return null;
186
+ }
187
+ return {
188
+ code: magicString.toString(),
189
+ map: magicString.generateMap({
190
+ source: filePath,
191
+ file: filePath,
192
+ includeContent: true
193
+ })
194
+ };
195
+ }
196
+
197
+ // src/loader.ts
198
+ var import_fs = __toESM(require("fs"), 1);
199
+ function isFile(filePath) {
200
+ try {
201
+ const stat = import_fs.default.statSync(filePath);
202
+ return stat.isFile();
203
+ } catch {
204
+ return false;
205
+ }
206
+ }
207
+ async function jsxLocLoader(source) {
208
+ const callback = this.async();
209
+ console.log(`[jsx-loc-plugin] Processing: ${this.resourcePath}`);
210
+ if (!isFile(this.resourcePath)) {
211
+ console.log(`[jsx-loc-plugin] Skipping (not a file): ${this.resourcePath}`);
212
+ return callback(null, source);
213
+ }
214
+ if (!shouldProcessFile(this.resourcePath)) {
215
+ console.log(
216
+ `[jsx-loc-plugin] Skipping (shouldProcessFile=false): ${this.resourcePath}`
217
+ );
218
+ return callback(null, source);
219
+ }
220
+ try {
221
+ console.log(`[jsx-loc-plugin] resourcePath: ${this.resourcePath}`);
222
+ const result = transformJsxCode(source, this.resourcePath);
223
+ if (!result) {
224
+ console.log(
225
+ `[jsx-loc-plugin] No transform result for: ${this.resourcePath}`
226
+ );
227
+ return callback(null, source);
228
+ }
229
+ console.log(`[jsx-loc-plugin] Transformed: ${this.resourcePath}`);
230
+ callback(null, result.code, result.map);
231
+ } catch (error) {
232
+ console.error(
233
+ `[jsx-loc-plugin] Error processing file ${this.resourcePath}:`,
234
+ error
235
+ );
236
+ callback(null, source);
237
+ }
238
+ }
239
+ var loader_default = jsxLocLoader;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Webpack/Turbopack loader that transforms JSX to add data-loc attributes
3
+ */
4
+ interface LoaderContext {
5
+ async(): (err: Error | null, content?: string, map?: any) => void;
6
+ resourcePath: string;
7
+ }
8
+ declare function jsxLocLoader(this: LoaderContext, source: string): Promise<void>;
9
+
10
+ export { jsxLocLoader as default };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Webpack/Turbopack loader that transforms JSX to add data-loc attributes
3
+ */
4
+ interface LoaderContext {
5
+ async(): (err: Error | null, content?: string, map?: any) => void;
6
+ resourcePath: string;
7
+ }
8
+ declare function jsxLocLoader(this: LoaderContext, source: string): Promise<void>;
9
+
10
+ export { jsxLocLoader as default };
package/dist/loader.js ADDED
@@ -0,0 +1,206 @@
1
+ // src/transform.ts
2
+ import { parse } from "@babel/parser";
3
+ import MagicString from "magic-string";
4
+ var VALID_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx"]);
5
+ var DEFAULT_PARSER_OPTIONS = {
6
+ sourceType: "module",
7
+ plugins: [
8
+ "jsx",
9
+ "typescript",
10
+ "decorators-legacy",
11
+ "classProperties",
12
+ "classPrivateProperties",
13
+ "classPrivateMethods",
14
+ "exportDefaultFrom",
15
+ "exportNamespaceFrom",
16
+ "asyncGenerators",
17
+ "functionBind",
18
+ "functionSent",
19
+ "dynamicImport",
20
+ "numericSeparator",
21
+ "optionalChaining",
22
+ "importMeta",
23
+ "bigInt",
24
+ "optionalCatchBinding",
25
+ "throwExpressions",
26
+ "nullishCoalescingOperator",
27
+ "topLevelAwait"
28
+ ],
29
+ errorRecovery: true
30
+ };
31
+ var SKIP_ELEMENTS = /* @__PURE__ */ new Set([
32
+ "Fragment",
33
+ "React.Fragment",
34
+ "Suspense",
35
+ "React.Suspense",
36
+ "StrictMode",
37
+ "React.StrictMode",
38
+ "Profiler",
39
+ "React.Profiler"
40
+ ]);
41
+ function shouldProcessFile(filePath) {
42
+ const ext = filePath.slice(filePath.lastIndexOf("."));
43
+ return VALID_EXTENSIONS.has(ext);
44
+ }
45
+ function getElementName(node) {
46
+ if (!node || !node.name) return null;
47
+ const name = node.name;
48
+ if (name.type === "JSXIdentifier") {
49
+ return name.name;
50
+ }
51
+ if (name.type === "JSXMemberExpression") {
52
+ const parts = [];
53
+ let current = name;
54
+ while (current) {
55
+ if (current.type === "JSXMemberExpression") {
56
+ parts.unshift(current.property.name);
57
+ current = current.object;
58
+ } else if (current.type === "JSXIdentifier") {
59
+ parts.unshift(current.name);
60
+ break;
61
+ } else {
62
+ break;
63
+ }
64
+ }
65
+ return parts.join(".");
66
+ }
67
+ if (name.type === "JSXNamespacedName") {
68
+ return `${name.namespace.name}:${name.name.name}`;
69
+ }
70
+ return null;
71
+ }
72
+ function shouldSkipElement(elementName) {
73
+ if (!elementName) return true;
74
+ return SKIP_ELEMENTS.has(elementName);
75
+ }
76
+ function hasDataLocAttribute(node) {
77
+ if (!node.attributes) return false;
78
+ return node.attributes.some(
79
+ (attr) => attr.type === "JSXAttribute" && attr.name?.type === "JSXIdentifier" && attr.name.name === "data-loc"
80
+ );
81
+ }
82
+ function findInsertionPoint(node, source) {
83
+ let insertPos = node.name.end;
84
+ if (node.typeParameters) {
85
+ insertPos = node.typeParameters.end;
86
+ }
87
+ if (node.attributes && node.attributes.length > 0) {
88
+ const firstAttr = node.attributes[0];
89
+ const nameEnd = node.typeParameters ? node.typeParameters.end : node.name.end;
90
+ let pos = nameEnd;
91
+ while (pos < firstAttr.start && /\s/.test(source[pos])) {
92
+ pos++;
93
+ }
94
+ insertPos = pos;
95
+ }
96
+ return insertPos;
97
+ }
98
+ function normalizeFilePath(filePath) {
99
+ return filePath.replace(/\\/g, "/");
100
+ }
101
+ function transformJsxCode(source, filePath, options = {}) {
102
+ const parserOptions = {
103
+ ...DEFAULT_PARSER_OPTIONS,
104
+ ...options.parserOptions
105
+ };
106
+ let ast;
107
+ try {
108
+ ast = parse(source, parserOptions);
109
+ } catch (error) {
110
+ console.error(`[jsx-loc] Failed to parse ${filePath}:`, error);
111
+ return null;
112
+ }
113
+ const magicString = new MagicString(source);
114
+ const normalizedPath = normalizeFilePath(filePath);
115
+ let hasChanges = false;
116
+ function walk(node) {
117
+ if (!node || typeof node !== "object") return;
118
+ if (node.type === "JSXOpeningElement") {
119
+ const elementName = getElementName(node);
120
+ if (!shouldSkipElement(elementName) && !hasDataLocAttribute(node)) {
121
+ const loc = node.loc;
122
+ if (loc && loc.start) {
123
+ const line = loc.start.line;
124
+ const column = loc.start.column;
125
+ const locValue = `${normalizedPath}:${line}:${column}`;
126
+ const insertPos = findInsertionPoint(node, source);
127
+ magicString.appendLeft(insertPos, ` data-loc="${locValue}"`);
128
+ hasChanges = true;
129
+ }
130
+ }
131
+ }
132
+ for (const key of Object.keys(node)) {
133
+ const child = node[key];
134
+ if (Array.isArray(child)) {
135
+ for (const item of child) {
136
+ walk(item);
137
+ }
138
+ } else if (child && typeof child === "object" && child.type) {
139
+ walk(child);
140
+ }
141
+ }
142
+ }
143
+ if (ast.program && ast.program.body) {
144
+ for (const node of ast.program.body) {
145
+ walk(node);
146
+ }
147
+ }
148
+ if (!hasChanges) {
149
+ return null;
150
+ }
151
+ return {
152
+ code: magicString.toString(),
153
+ map: magicString.generateMap({
154
+ source: filePath,
155
+ file: filePath,
156
+ includeContent: true
157
+ })
158
+ };
159
+ }
160
+
161
+ // src/loader.ts
162
+ import fs from "fs";
163
+ function isFile(filePath) {
164
+ try {
165
+ const stat = fs.statSync(filePath);
166
+ return stat.isFile();
167
+ } catch {
168
+ return false;
169
+ }
170
+ }
171
+ async function jsxLocLoader(source) {
172
+ const callback = this.async();
173
+ console.log(`[jsx-loc-plugin] Processing: ${this.resourcePath}`);
174
+ if (!isFile(this.resourcePath)) {
175
+ console.log(`[jsx-loc-plugin] Skipping (not a file): ${this.resourcePath}`);
176
+ return callback(null, source);
177
+ }
178
+ if (!shouldProcessFile(this.resourcePath)) {
179
+ console.log(
180
+ `[jsx-loc-plugin] Skipping (shouldProcessFile=false): ${this.resourcePath}`
181
+ );
182
+ return callback(null, source);
183
+ }
184
+ try {
185
+ console.log(`[jsx-loc-plugin] resourcePath: ${this.resourcePath}`);
186
+ const result = transformJsxCode(source, this.resourcePath);
187
+ if (!result) {
188
+ console.log(
189
+ `[jsx-loc-plugin] No transform result for: ${this.resourcePath}`
190
+ );
191
+ return callback(null, source);
192
+ }
193
+ console.log(`[jsx-loc-plugin] Transformed: ${this.resourcePath}`);
194
+ callback(null, result.code, result.map);
195
+ } catch (error) {
196
+ console.error(
197
+ `[jsx-loc-plugin] Error processing file ${this.resourcePath}:`,
198
+ error
199
+ );
200
+ callback(null, source);
201
+ }
202
+ }
203
+ var loader_default = jsxLocLoader;
204
+ export {
205
+ loader_default as default
206
+ };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "jsx-loc-plugin",
3
+ "version": "0.1.22",
4
+ "description": "Next.js plugin that adds data-loc attributes to JSX elements for UILint inspection",
5
+ "author": "Peter Suggate",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/peter-suggate/uilint.git",
10
+ "directory": "packages/jsx-loc-plugin"
11
+ },
12
+ "homepage": "https://github.com/peter-suggate/uilint#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/peter-suggate/uilint/issues"
15
+ },
16
+ "keywords": [
17
+ "next",
18
+ "nextjs",
19
+ "jsx",
20
+ "uilint",
21
+ "plugin",
22
+ "webpack",
23
+ "turbopack"
24
+ ],
25
+ "type": "module",
26
+ "main": "./dist/index.cjs",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js",
33
+ "require": "./dist/index.cjs"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "dependencies": {
40
+ "@babel/parser": "^7.24.0",
41
+ "magic-string": "^0.30.10"
42
+ },
43
+ "devDependencies": {
44
+ "next": "^16.1.1",
45
+ "tsup": "^8.0.0",
46
+ "typescript": "^5.0.0"
47
+ },
48
+ "peerDependencies": {
49
+ "next": ">=14.0.0"
50
+ },
51
+ "scripts": {
52
+ "build": "tsup",
53
+ "dev": "tsup --watch"
54
+ }
55
+ }