d2d-feedbackkit 0.1.1 → 0.1.2

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.
Files changed (2) hide show
  1. package/dist/vite-plugin.js +319 -0
  2. package/package.json +5 -4
@@ -0,0 +1,319 @@
1
+ // src/babel.ts
2
+ import path from "node:path";
3
+ import { parseExpression } from "@babel/parser";
4
+ function feedbackLocatorBabelPlugin(babel) {
5
+ const t = babel.types;
6
+ function ensureState(state) {
7
+ if (!state.feedbackLocator) {
8
+ throw new Error("Feedback Locator Babel state is not initialized");
9
+ }
10
+ return state.feedbackLocator;
11
+ }
12
+ function shouldSkip(state) {
13
+ return !state.filename || state.filename.includes("node_modules");
14
+ }
15
+ function enterComponent(state, name, loc) {
16
+ if (!name || !loc || !isComponentName(name)) {
17
+ return null;
18
+ }
19
+ const transformState = ensureState(state);
20
+ if (state.opts?.ignoreComponentNames?.includes(name)) {
21
+ return null;
22
+ }
23
+ const id = transformState.components.length;
24
+ transformState.components.push({ name, loc });
25
+ transformState.componentStack.push(id);
26
+ return id;
27
+ }
28
+ function exitComponent(state, id) {
29
+ if (id === null) {
30
+ return;
31
+ }
32
+ const transformState = ensureState(state);
33
+ const last = transformState.componentStack.at(-1);
34
+ if (last === id) {
35
+ transformState.componentStack.pop();
36
+ }
37
+ }
38
+ return {
39
+ name: "feedbackkit-source-locator-babel",
40
+ visitor: {
41
+ Program: {
42
+ enter(_programPath, state) {
43
+ if (shouldSkip(state)) {
44
+ return;
45
+ }
46
+ const projectRoot = normalizePath(state.opts?.projectRoot ?? state.cwd ?? process.cwd());
47
+ const filename = normalizePath(state.filename);
48
+ const filePath = toRepoRelativePath(filename, projectRoot);
49
+ const fileKey = filePath;
50
+ state.feedbackLocator = {
51
+ projectRoot,
52
+ filePath,
53
+ fileKey,
54
+ components: [],
55
+ expressions: [],
56
+ componentStack: []
57
+ };
58
+ },
59
+ exit(programPath, state) {
60
+ if (shouldSkip(state) || !state.feedbackLocator) {
61
+ return;
62
+ }
63
+ const transformState = state.feedbackLocator;
64
+ if (transformState.expressions.length === 0) {
65
+ return;
66
+ }
67
+ const data = {
68
+ filePath: transformState.filePath,
69
+ projectPath: transformState.projectRoot,
70
+ expressions: transformState.expressions,
71
+ components: transformState.components
72
+ };
73
+ const serializedData = JSON.stringify(data);
74
+ const serializedFileKey = JSON.stringify(transformState.fileKey);
75
+ const registerCode = `(() => {
76
+ if (typeof window !== "undefined") {
77
+ window.__FEEDBACK_LOCATOR_DATA__ = window.__FEEDBACK_LOCATOR_DATA__ || {};
78
+ window.__FEEDBACK_LOCATOR_DATA__[${serializedFileKey}] = ${serializedData};
79
+ }
80
+ })()`;
81
+ programPath.pushContainer("body", t.expressionStatement(parseExpression(registerCode)));
82
+ }
83
+ },
84
+ FunctionDeclaration: {
85
+ enter(pathNode, state) {
86
+ if (shouldSkip(state) || !state.feedbackLocator) {
87
+ return;
88
+ }
89
+ pathNode.setData("feedbackLocatorComponentId", enterComponent(state, pathNode.node.id?.name, pathNode.node.loc));
90
+ },
91
+ exit(pathNode, state) {
92
+ if (shouldSkip(state) || !state.feedbackLocator) {
93
+ return;
94
+ }
95
+ exitComponent(state, pathNode.getData("feedbackLocatorComponentId") ?? null);
96
+ }
97
+ },
98
+ VariableDeclarator: {
99
+ enter(pathNode, state) {
100
+ if (shouldSkip(state) || !state.feedbackLocator) {
101
+ return;
102
+ }
103
+ const initType = pathNode.node.init?.type;
104
+ const isFunction = initType === "ArrowFunctionExpression" || initType === "FunctionExpression";
105
+ const name = pathNode.node.id?.type === "Identifier" ? pathNode.node.id.name : null;
106
+ pathNode.setData("feedbackLocatorComponentId", isFunction ? enterComponent(state, name, pathNode.node.loc) : null);
107
+ },
108
+ exit(pathNode, state) {
109
+ if (shouldSkip(state) || !state.feedbackLocator) {
110
+ return;
111
+ }
112
+ exitComponent(state, pathNode.getData("feedbackLocatorComponentId") ?? null);
113
+ }
114
+ },
115
+ JSXElement(pathNode, state) {
116
+ if (shouldSkip(state) || !state.feedbackLocator || !pathNode.node.loc) {
117
+ return;
118
+ }
119
+ const openingElement = pathNode.node.openingElement;
120
+ const name = getJsxName(openingElement.name);
121
+ if (!isDomJsxName(openingElement.name)) {
122
+ return;
123
+ }
124
+ if (openingElement.attributes.some((attribute) => attribute.type === "JSXAttribute" && attribute.name?.name === "data-feedback-locator-id")) {
125
+ return;
126
+ }
127
+ const transformState = state.feedbackLocator;
128
+ const expressionId = transformState.expressions.length;
129
+ const componentId = transformState.componentStack.at(-1) ?? null;
130
+ const locatorId = `${transformState.fileKey}::${expressionId}`;
131
+ transformState.expressions.push({
132
+ name,
133
+ loc: pathNode.node.loc,
134
+ componentId
135
+ });
136
+ openingElement.attributes.push(t.jsxAttribute(t.jsxIdentifier("data-feedback-locator-id"), t.stringLiteral(locatorId)));
137
+ }
138
+ }
139
+ };
140
+ }
141
+ function getJsxName(node) {
142
+ if (node.type === "JSXIdentifier") {
143
+ return node.name;
144
+ }
145
+ if (node.type === "JSXMemberExpression") {
146
+ return `${getJsxName(node.object)}.${node.property.name}`;
147
+ }
148
+ if (node.type === "JSXNamespacedName") {
149
+ return `${node.namespace.name}.${node.name.name}`;
150
+ }
151
+ return "unknown";
152
+ }
153
+ function isDomJsxName(node) {
154
+ if (node.type !== "JSXIdentifier") {
155
+ return false;
156
+ }
157
+ return /^[a-z]/.test(node.name);
158
+ }
159
+ function isComponentName(name) {
160
+ return /^[A-Z]/.test(name);
161
+ }
162
+ function normalizePath(value) {
163
+ return value.split(path.sep).join("/");
164
+ }
165
+ function toRepoRelativePath(filename, projectRoot) {
166
+ const relative = normalizePath(path.relative(projectRoot, filename));
167
+ return relative.startsWith(".") ? filename : relative;
168
+ }
169
+ // src/source.ts
170
+ var feedbackLocatorDataAttribute = "data-feedback-locator-id";
171
+ var feedbackLocatorDataKey = "__FEEDBACK_LOCATOR_DATA__";
172
+ var locatorJsDataAttribute = "data-locatorjs-id";
173
+ function parseFeedbackLocatorId(locatorId) {
174
+ const separatorIndex = locatorId.lastIndexOf("::");
175
+ if (separatorIndex === -1) {
176
+ throw new Error("feedback locator id is malformed");
177
+ }
178
+ const fileKey = locatorId.slice(0, separatorIndex);
179
+ const expressionId = locatorId.slice(separatorIndex + 2);
180
+ if (!fileKey || !expressionId) {
181
+ throw new Error("feedback locator id is malformed");
182
+ }
183
+ return {
184
+ fileKey,
185
+ expressionIndex: Number(expressionId)
186
+ };
187
+ }
188
+ function getFeedbackLocatorSourceForElement(target) {
189
+ const found = target.closest(`[${feedbackLocatorDataAttribute}]`);
190
+ if (!(found instanceof HTMLElement)) {
191
+ return getLocatorJsSourceForElement(target);
192
+ }
193
+ const locatorId = found.dataset.feedbackLocatorId;
194
+ if (!locatorId || typeof window === "undefined") {
195
+ return getLocatorJsSourceForElement(target);
196
+ }
197
+ const { fileKey, expressionIndex } = parseFeedbackLocatorId(locatorId);
198
+ const fileData = window.__FEEDBACK_LOCATOR_DATA__?.[fileKey];
199
+ const expression = fileData?.expressions[expressionIndex];
200
+ if (!fileData || !expression) {
201
+ return getLocatorJsSourceForElement(found);
202
+ }
203
+ const component = getFeedbackLocatorComponent(fileData, expression.componentId);
204
+ const locatorJsComponent = component ? null : getLocatorJsComponentForElement(found);
205
+ const ancestorComponent = component || locatorJsComponent ? null : getAncestorComponentForElement(found);
206
+ const resolvedComponent = component ?? locatorJsComponent ?? ancestorComponent;
207
+ return {
208
+ locatorId,
209
+ filePath: fileData.filePath,
210
+ elementName: expression.name,
211
+ componentName: resolvedComponent?.name ?? null,
212
+ line: expression.loc.start.line,
213
+ column: expression.loc.start.column + 1,
214
+ componentLine: resolvedComponent?.line ?? null,
215
+ componentColumn: resolvedComponent?.column ?? null
216
+ };
217
+ }
218
+ function getLocatorJsSourceForElement(target) {
219
+ const found = target.closest(`[${locatorJsDataAttribute}]`);
220
+ if (!(found instanceof HTMLElement) || typeof window === "undefined") {
221
+ return null;
222
+ }
223
+ const locatorId = found.getAttribute(locatorJsDataAttribute);
224
+ if (!locatorId) {
225
+ return null;
226
+ }
227
+ const { fileKey, expressionIndex } = parseFeedbackLocatorId(locatorId);
228
+ const fileData = window.__LOCATOR_DATA__?.[fileKey];
229
+ const expression = fileData?.expressions?.[expressionIndex];
230
+ if (!fileData || !expression) {
231
+ return null;
232
+ }
233
+ const component = getLocatorJsComponentForElement(found);
234
+ return {
235
+ locatorId,
236
+ filePath: toLocatorJsFilePath(fileData, fileKey),
237
+ elementName: expression.name,
238
+ componentName: component?.name ?? null,
239
+ line: expression.loc.start.line,
240
+ column: expression.loc.start.column + 1,
241
+ componentLine: component?.line ?? null,
242
+ componentColumn: component?.column ?? null
243
+ };
244
+ }
245
+ function toLocatorJsFilePath(fileData, fileKey) {
246
+ const filePath = fileData.filePath ?? fileKey;
247
+ const projectPath = fileData.projectPath;
248
+ if (projectPath && filePath.startsWith(projectPath)) {
249
+ return filePath.slice(projectPath.length).replace(/^\//, "");
250
+ }
251
+ return filePath.replace(/^\//, "");
252
+ }
253
+ function getFeedbackLocatorComponent(fileData, componentId) {
254
+ if (componentId === null) {
255
+ return null;
256
+ }
257
+ const component = fileData.components[componentId];
258
+ if (!component) {
259
+ return null;
260
+ }
261
+ return {
262
+ name: component.name,
263
+ line: component.loc.start.line,
264
+ column: component.loc.start.column + 1
265
+ };
266
+ }
267
+ function getAncestorComponentForElement(element) {
268
+ let parent = element.parentElement;
269
+ while (parent) {
270
+ const locatorId = parent.getAttribute(feedbackLocatorDataAttribute);
271
+ if (locatorId) {
272
+ const component = getFeedbackLocatorComponentForLocatorId(locatorId);
273
+ if (component) {
274
+ return component;
275
+ }
276
+ }
277
+ parent = parent.parentElement;
278
+ }
279
+ return null;
280
+ }
281
+ function getFeedbackLocatorComponentForLocatorId(locatorId) {
282
+ const { fileKey, expressionIndex } = parseFeedbackLocatorId(locatorId);
283
+ const fileData = window.__FEEDBACK_LOCATOR_DATA__?.[fileKey];
284
+ const expression = fileData?.expressions[expressionIndex];
285
+ if (!fileData || !expression) {
286
+ return null;
287
+ }
288
+ return getFeedbackLocatorComponent(fileData, expression.componentId);
289
+ }
290
+ function getLocatorJsComponentForElement(element) {
291
+ const locatorJsId = element.getAttribute(locatorJsDataAttribute);
292
+ if (!locatorJsId) {
293
+ return null;
294
+ }
295
+ const { fileKey, expressionIndex } = parseFeedbackLocatorId(locatorJsId);
296
+ const fileData = window.__LOCATOR_DATA__?.[fileKey];
297
+ const expression = fileData?.expressions?.[expressionIndex];
298
+ const componentId = expression?.wrappingComponentId;
299
+ if (componentId === null || componentId === undefined) {
300
+ return null;
301
+ }
302
+ const component = fileData?.components?.[componentId];
303
+ if (!component) {
304
+ return null;
305
+ }
306
+ return {
307
+ name: component.name,
308
+ line: component.loc?.start.line ?? null,
309
+ column: component.loc?.start.column !== undefined ? component.loc.start.column + 1 : null
310
+ };
311
+ }
312
+ export {
313
+ parseFeedbackLocatorId,
314
+ getFeedbackLocatorSourceForElement,
315
+ feedbackLocatorDataKey,
316
+ feedbackLocatorDataAttribute,
317
+ feedbackLocatorBabelPlugin as feedbackKitBabelPlugin,
318
+ feedbackLocatorBabelPlugin as default
319
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "d2d-feedbackkit",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "SolidJS feedback widget, source locator, and integration helpers.",
6
6
  "license": "UNLICENSED",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "./vite-plugin": {
26
26
  "types": "./src/vite-plugin.ts",
27
- "default": "./src/vite-plugin.ts"
27
+ "default": "./dist/vite-plugin.js"
28
28
  },
29
29
  "./client": {
30
30
  "types": "./src/client.ts",
@@ -61,10 +61,11 @@
61
61
  "src/types.ts",
62
62
  "src/vite-plugin.ts",
63
63
  "src/web-component.tsx",
64
- "src/widget.tsx"
64
+ "src/widget.tsx",
65
+ "dist/vite-plugin.js"
65
66
  ],
66
67
  "scripts": {
67
- "build": "bun run type-check",
68
+ "build": "bun run type-check && bun build src/vite-plugin.ts --outfile dist/vite-plugin.js --target node --format esm --packages external",
68
69
  "clean": "rm -rf node_modules dist",
69
70
  "pack:dry-run": "npm pack --dry-run",
70
71
  "publish:public": "npm publish --access public",