@tanstack/devtools-vite 0.2.4 → 0.2.6

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.
@@ -18,9 +18,8 @@ export type EditorConfig = {
18
18
  open: (path: string, lineNumber: string | undefined, columnNumber?: string) => Promise<void>;
19
19
  };
20
20
  export declare const DEFAULT_EDITOR_CONFIG: EditorConfig;
21
- export declare const handleOpenSource: ({ data, openInEditor, appDir, }: {
21
+ export declare const handleOpenSource: ({ data, openInEditor, }: {
22
22
  data: OpenSourceData;
23
- appDir: string;
24
23
  openInEditor: EditorConfig["open"];
25
24
  }) => Promise<void>;
26
25
  export {};
@@ -1,54 +1,21 @@
1
- import { normalizePath } from "vite";
2
- import { checkPath } from "./utils.js";
3
1
  const DEFAULT_EDITOR_CONFIG = {
4
2
  name: "VSCode",
5
3
  open: async (path, lineNumber, columnNumber) => {
6
4
  const { exec } = await import("node:child_process");
7
5
  exec(
8
- `code -g "${normalizePath(path).replaceAll("$", "\\$")}${lineNumber ? `:${lineNumber}` : ""}${columnNumber ? `:${columnNumber}` : ""}"`
6
+ `code -g "${path.replaceAll("$", "\\$")}${lineNumber ? `:${lineNumber}` : ""}${columnNumber ? `:${columnNumber}` : ""}"`
9
7
  );
10
8
  }
11
9
  };
12
10
  const handleOpenSource = async ({
13
11
  data,
14
- openInEditor,
15
- appDir
12
+ openInEditor
16
13
  }) => {
17
- const { source, line, routeID } = data.data;
14
+ const { source, line, column } = data.data;
18
15
  const lineNum = line ? `${line}` : void 0;
19
- const fs = await import("node:fs");
20
- const path = await import("node:path");
16
+ const columnNum = column ? `${column}` : void 0;
21
17
  if (source) {
22
- return openInEditor(source, lineNum);
23
- }
24
- if (routeID) {
25
- const routePath = path.join(appDir, routeID);
26
- const checkedPath = await checkPath(routePath);
27
- if (!checkedPath) return;
28
- const { type, validPath } = checkedPath;
29
- const reactExtensions = ["tsx", "jsx"];
30
- const allExtensions = ["ts", "js", ...reactExtensions];
31
- const isRoot = routeID === "root";
32
- const findFileByExtension = (prefix, filePaths) => {
33
- const file = filePaths.find(
34
- (file2) => allExtensions.some((ext) => file2 === `${prefix}.${ext}`)
35
- );
36
- return file;
37
- };
38
- if (isRoot) {
39
- if (!fs.existsSync(appDir)) return;
40
- const filesInReactRouterPath = fs.readdirSync(appDir);
41
- const rootFile = findFileByExtension("root", filesInReactRouterPath);
42
- rootFile && openInEditor(path.join(appDir, rootFile), lineNum);
43
- return;
44
- }
45
- if (type === "directory") {
46
- const filesInFolderRoute = fs.readdirSync(validPath);
47
- const routeFile = findFileByExtension("route", filesInFolderRoute);
48
- routeFile && openInEditor(path.join(appDir, routeID, routeFile), lineNum);
49
- return;
50
- }
51
- return openInEditor(validPath, lineNum);
18
+ return openInEditor(source, lineNum, columnNum);
52
19
  }
53
20
  };
54
21
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"editor.js","sources":["../../src/editor.ts"],"sourcesContent":["import { normalizePath } from 'vite'\nimport { checkPath } from './utils.js'\n\ntype OpenSourceData = {\n type: 'open-source'\n data: {\n /** The source file to open */\n source?: string\n /** The react router route ID, usually discovered via the hook useMatches */\n routeID?: string\n /** The line number in the source file */\n line?: number\n /** The column number in the source file */\n column?: number\n }\n}\n\nexport type EditorConfig = {\n /** The name of the editor, used for debugging purposes */\n name: string\n /** Callback to open a file in the editor */\n open: (\n path: string,\n lineNumber: string | undefined,\n columnNumber?: string,\n ) => Promise<void>\n}\n\nexport const DEFAULT_EDITOR_CONFIG: EditorConfig = {\n name: 'VSCode',\n open: async (path, lineNumber, columnNumber) => {\n const { exec } = await import('node:child_process')\n exec(\n `code -g \"${normalizePath(path).replaceAll('$', '\\\\$')}${lineNumber ? `:${lineNumber}` : ''}${columnNumber ? `:${columnNumber}` : ''}\"`,\n )\n },\n}\n\nexport const handleOpenSource = async ({\n data,\n openInEditor,\n appDir,\n}: {\n data: OpenSourceData\n appDir: string\n openInEditor: EditorConfig['open']\n}) => {\n const { source, line, routeID } = data.data\n const lineNum = line ? `${line}` : undefined\n const fs = await import('node:fs')\n const path = await import('node:path')\n if (source) {\n return openInEditor(source, lineNum)\n }\n\n if (routeID) {\n const routePath = path.join(appDir, routeID)\n const checkedPath = await checkPath(routePath)\n\n if (!checkedPath) return\n const { type, validPath } = checkedPath\n\n const reactExtensions = ['tsx', 'jsx']\n const allExtensions = ['ts', 'js', ...reactExtensions]\n const isRoot = routeID === 'root'\n const findFileByExtension = (prefix: string, filePaths: Array<string>) => {\n const file = filePaths.find((file) =>\n allExtensions.some((ext) => file === `${prefix}.${ext}`),\n )\n return file\n }\n\n if (isRoot) {\n if (!fs.existsSync(appDir)) return\n const filesInReactRouterPath = fs.readdirSync(appDir)\n const rootFile = findFileByExtension('root', filesInReactRouterPath)\n rootFile && openInEditor(path.join(appDir, rootFile), lineNum)\n return\n }\n\n // If its not the root route, then we find the file or folder in the routes folder\n // We know that the route ID is in the form of \"routes/contact\" or \"routes/user.profile\" when is not root\n // so the ID already contains the \"routes\" segment, so we just need to find the file or folder in the routes folder\n if (type === 'directory') {\n const filesInFolderRoute = fs.readdirSync(validPath)\n const routeFile = findFileByExtension('route', filesInFolderRoute)\n routeFile && openInEditor(path.join(appDir, routeID, routeFile), lineNum)\n return\n }\n return openInEditor(validPath, lineNum)\n }\n}\n"],"names":["file"],"mappings":";;AA4BO,MAAM,wBAAsC;AAAA,EACjD,MAAM;AAAA,EACN,MAAM,OAAO,MAAM,YAAY,iBAAiB;AAC9C,UAAM,EAAE,KAAA,IAAS,MAAM,OAAO,oBAAoB;AAClD;AAAA,MACE,YAAY,cAAc,IAAI,EAAE,WAAW,KAAK,KAAK,CAAC,GAAG,aAAa,IAAI,UAAU,KAAK,EAAE,GAAG,eAAe,IAAI,YAAY,KAAK,EAAE;AAAA,IAAA;AAAA,EAExI;AACF;AAEO,MAAM,mBAAmB,OAAO;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AACF,MAIM;AACJ,QAAM,EAAE,QAAQ,MAAM,QAAA,IAAY,KAAK;AACvC,QAAM,UAAU,OAAO,GAAG,IAAI,KAAK;AACnC,QAAM,KAAK,MAAM,OAAO,SAAS;AACjC,QAAM,OAAO,MAAM,OAAO,WAAW;AACrC,MAAI,QAAQ;AACV,WAAO,aAAa,QAAQ,OAAO;AAAA,EACrC;AAEA,MAAI,SAAS;AACX,UAAM,YAAY,KAAK,KAAK,QAAQ,OAAO;AAC3C,UAAM,cAAc,MAAM,UAAU,SAAS;AAE7C,QAAI,CAAC,YAAa;AAClB,UAAM,EAAE,MAAM,UAAA,IAAc;AAE5B,UAAM,kBAAkB,CAAC,OAAO,KAAK;AACrC,UAAM,gBAAgB,CAAC,MAAM,MAAM,GAAG,eAAe;AACrD,UAAM,SAAS,YAAY;AAC3B,UAAM,sBAAsB,CAAC,QAAgB,cAA6B;AACxE,YAAM,OAAO,UAAU;AAAA,QAAK,CAACA,UAC3B,cAAc,KAAK,CAAC,QAAQA,UAAS,GAAG,MAAM,IAAI,GAAG,EAAE;AAAA,MAAA;AAEzD,aAAO;AAAA,IACT;AAEA,QAAI,QAAQ;AACV,UAAI,CAAC,GAAG,WAAW,MAAM,EAAG;AAC5B,YAAM,yBAAyB,GAAG,YAAY,MAAM;AACpD,YAAM,WAAW,oBAAoB,QAAQ,sBAAsB;AACnE,kBAAY,aAAa,KAAK,KAAK,QAAQ,QAAQ,GAAG,OAAO;AAC7D;AAAA,IACF;AAKA,QAAI,SAAS,aAAa;AACxB,YAAM,qBAAqB,GAAG,YAAY,SAAS;AACnD,YAAM,YAAY,oBAAoB,SAAS,kBAAkB;AACjE,mBAAa,aAAa,KAAK,KAAK,QAAQ,SAAS,SAAS,GAAG,OAAO;AACxE;AAAA,IACF;AACA,WAAO,aAAa,WAAW,OAAO;AAAA,EACxC;AACF;"}
1
+ {"version":3,"file":"editor.js","sources":["../../src/editor.ts"],"sourcesContent":["type OpenSourceData = {\n type: 'open-source'\n data: {\n /** The source file to open */\n source?: string\n /** The react router route ID, usually discovered via the hook useMatches */\n routeID?: string\n /** The line number in the source file */\n line?: number\n /** The column number in the source file */\n column?: number\n }\n}\n\nexport type EditorConfig = {\n /** The name of the editor, used for debugging purposes */\n name: string\n /** Callback to open a file in the editor */\n open: (\n path: string,\n lineNumber: string | undefined,\n columnNumber?: string,\n ) => Promise<void>\n}\n\nexport const DEFAULT_EDITOR_CONFIG: EditorConfig = {\n name: 'VSCode',\n open: async (path, lineNumber, columnNumber) => {\n const { exec } = await import('node:child_process')\n exec(\n `code -g \"${path.replaceAll('$', '\\\\$')}${lineNumber ? `:${lineNumber}` : ''}${columnNumber ? `:${columnNumber}` : ''}\"`,\n )\n },\n}\n\nexport const handleOpenSource = async ({\n data,\n openInEditor,\n}: {\n data: OpenSourceData\n openInEditor: EditorConfig['open']\n}) => {\n const { source, line, column } = data.data\n const lineNum = line ? `${line}` : undefined\n const columnNum = column ? `${column}` : undefined\n if (source) {\n return openInEditor(source, lineNum, columnNum)\n }\n}\n"],"names":[],"mappings":"AAyBO,MAAM,wBAAsC;AAAA,EACjD,MAAM;AAAA,EACN,MAAM,OAAO,MAAM,YAAY,iBAAiB;AAC9C,UAAM,EAAE,KAAA,IAAS,MAAM,OAAO,oBAAoB;AAClD;AAAA,MACE,YAAY,KAAK,WAAW,KAAK,KAAK,CAAC,GAAG,aAAa,IAAI,UAAU,KAAK,EAAE,GAAG,eAAe,IAAI,YAAY,KAAK,EAAE;AAAA,IAAA;AAAA,EAEzH;AACF;AAEO,MAAM,mBAAmB,OAAO;AAAA,EACrC;AAAA,EACA;AACF,MAGM;AACJ,QAAM,EAAE,QAAQ,MAAM,OAAA,IAAW,KAAK;AACtC,QAAM,UAAU,OAAO,GAAG,IAAI,KAAK;AACnC,QAAM,YAAY,SAAS,GAAG,MAAM,KAAK;AACzC,MAAI,QAAQ;AACV,WAAO,aAAa,QAAQ,SAAS,SAAS;AAAA,EAChD;AACF;"}
@@ -2,21 +2,141 @@ import { normalizePath } from "vite";
2
2
  import { gen, trav } from "./babel.js";
3
3
  import { parse } from "@babel/parser";
4
4
  import * as t from "@babel/types";
5
+ const getPropsNameFromFunctionDeclaration = (functionDeclaration) => {
6
+ let propsName = null;
7
+ if (functionDeclaration.type === "FunctionExpression") {
8
+ const firstArgument = functionDeclaration.params[0];
9
+ if (firstArgument && firstArgument.type === "Identifier") {
10
+ propsName = firstArgument.name;
11
+ }
12
+ if (firstArgument && firstArgument.type === "ObjectPattern") {
13
+ firstArgument.properties.forEach((prop) => {
14
+ if (prop.type === "RestElement" && prop.argument.type === "Identifier") {
15
+ propsName = prop.argument.name;
16
+ }
17
+ });
18
+ }
19
+ return propsName;
20
+ }
21
+ if (functionDeclaration.type === "ArrowFunctionExpression") {
22
+ const firstArgument = functionDeclaration.params[0];
23
+ if (firstArgument && firstArgument.type === "Identifier") {
24
+ propsName = firstArgument.name;
25
+ }
26
+ if (firstArgument && firstArgument.type === "ObjectPattern") {
27
+ firstArgument.properties.forEach((prop) => {
28
+ if (prop.type === "RestElement" && prop.argument.type === "Identifier") {
29
+ propsName = prop.argument.name;
30
+ }
31
+ });
32
+ }
33
+ return propsName;
34
+ }
35
+ if (functionDeclaration.type === "FunctionDeclaration") {
36
+ const firstArgument = functionDeclaration.params[0];
37
+ if (firstArgument && firstArgument.type === "Identifier") {
38
+ propsName = firstArgument.name;
39
+ }
40
+ if (firstArgument && firstArgument.type === "ObjectPattern") {
41
+ firstArgument.properties.forEach((prop) => {
42
+ if (prop.type === "RestElement" && prop.argument.type === "Identifier") {
43
+ propsName = prop.argument.name;
44
+ }
45
+ });
46
+ }
47
+ return propsName;
48
+ }
49
+ if (functionDeclaration.init?.type === "ArrowFunctionExpression" || functionDeclaration.init?.type === "FunctionExpression") {
50
+ const firstArgument = functionDeclaration.init.params[0];
51
+ if (firstArgument && firstArgument.type === "Identifier") {
52
+ propsName = firstArgument.name;
53
+ }
54
+ if (firstArgument && firstArgument.type === "ObjectPattern") {
55
+ firstArgument.properties.forEach((prop) => {
56
+ if (prop.type === "RestElement" && prop.argument.type === "Identifier") {
57
+ propsName = prop.argument.name;
58
+ }
59
+ });
60
+ }
61
+ }
62
+ return propsName;
63
+ };
64
+ const transformJSX = (element, propsName, file) => {
65
+ const loc = element.node.loc;
66
+ if (!loc) return;
67
+ const line = loc.start.line;
68
+ const column = loc.start.column;
69
+ const hasDataSource = element.node.attributes.some(
70
+ (attr) => attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && attr.name.name === "data-tsd-source"
71
+ );
72
+ const hasSpread = element.node.attributes.some(
73
+ (attr) => attr.type === "JSXSpreadAttribute" && attr.argument.type === "Identifier" && attr.argument.name === propsName
74
+ );
75
+ if (hasSpread || hasDataSource) {
76
+ return;
77
+ }
78
+ element.node.attributes.push(
79
+ t.jsxAttribute(
80
+ t.jsxIdentifier("data-tsd-source"),
81
+ t.stringLiteral(`${file}:${line}:${column}`)
82
+ )
83
+ );
84
+ return true;
85
+ };
5
86
  const transform = (ast, file) => {
6
87
  let didTransform = false;
7
88
  trav(ast, {
8
- JSXOpeningElement(path) {
9
- const loc = path.node.loc;
10
- if (!loc) return;
11
- const line = loc.start.line;
12
- const column = loc.start.column;
13
- path.node.attributes.push(
14
- t.jsxAttribute(
15
- t.jsxIdentifier("data-tsd-source"),
16
- t.stringLiteral(`${file}:${line}:${column}`)
17
- )
89
+ FunctionDeclaration(functionDeclaration) {
90
+ const propsName = getPropsNameFromFunctionDeclaration(
91
+ functionDeclaration.node
18
92
  );
19
- didTransform = true;
93
+ functionDeclaration.traverse({
94
+ JSXOpeningElement(element) {
95
+ const transformed = transformJSX(element, propsName, file);
96
+ if (transformed) {
97
+ didTransform = true;
98
+ }
99
+ }
100
+ });
101
+ },
102
+ ArrowFunctionExpression(path) {
103
+ const propsName = getPropsNameFromFunctionDeclaration(path.node);
104
+ path.traverse({
105
+ JSXOpeningElement(element) {
106
+ const transformed = transformJSX(element, propsName, file);
107
+ if (transformed) {
108
+ didTransform = true;
109
+ }
110
+ }
111
+ });
112
+ },
113
+ FunctionExpression(path) {
114
+ const propsName = getPropsNameFromFunctionDeclaration(path.node);
115
+ path.traverse({
116
+ JSXOpeningElement(element) {
117
+ const transformed = transformJSX(element, propsName, file);
118
+ if (transformed) {
119
+ didTransform = true;
120
+ }
121
+ }
122
+ });
123
+ },
124
+ VariableDeclaration(path) {
125
+ const functionDeclaration = path.node.declarations.find((decl) => {
126
+ return decl.init?.type === "ArrowFunctionExpression" || decl.init?.type === "FunctionExpression";
127
+ });
128
+ if (!functionDeclaration) {
129
+ return;
130
+ }
131
+ const propsName = getPropsNameFromFunctionDeclaration(functionDeclaration);
132
+ path.traverse({
133
+ JSXOpeningElement(element) {
134
+ const transformed = transformJSX(element, propsName, file);
135
+ if (transformed) {
136
+ didTransform = true;
137
+ }
138
+ }
139
+ });
20
140
  }
21
141
  });
22
142
  return didTransform;
@@ -1 +1 @@
1
- {"version":3,"file":"inject-source.js","sources":["../../src/inject-source.ts"],"sourcesContent":["import { normalizePath } from 'vite'\nimport { gen, parse, t, trav } from './babel'\nimport type { types as Babel } from '@babel/core'\nimport type { ParseResult } from '@babel/parser'\n\nconst transform = (ast: ParseResult<Babel.File>, file: string) => {\n let didTransform = false\n trav(ast, {\n JSXOpeningElement(path) {\n const loc = path.node.loc\n if (!loc) return\n const line = loc.start.line\n const column = loc.start.column\n\n // Inject data-source as a string: \"<file>:<line>:<column>\"\n path.node.attributes.push(\n t.jsxAttribute(\n t.jsxIdentifier('data-tsd-source'),\n t.stringLiteral(`${file}:${line}:${column}`),\n ),\n )\n\n didTransform = true\n },\n })\n\n return didTransform\n}\n\nexport function addSourceToJsx(code: string, id: string) {\n const [filePath] = id.split('?')\n // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain\n const location = filePath?.replace(normalizePath(process.cwd()), '')!\n\n try {\n const ast = parse(code, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n })\n const didTransform = transform(ast, location)\n if (!didTransform) {\n return { code }\n }\n return gen(ast, {\n sourceMaps: true,\n filename: id,\n sourceFileName: filePath,\n })\n } catch (e) {\n return { code }\n }\n}\n"],"names":[],"mappings":";;;;AAKA,MAAM,YAAY,CAAC,KAA8B,SAAiB;AAChE,MAAI,eAAe;AACnB,OAAK,KAAK;AAAA,IACR,kBAAkB,MAAM;AACtB,YAAM,MAAM,KAAK,KAAK;AACtB,UAAI,CAAC,IAAK;AACV,YAAM,OAAO,IAAI,MAAM;AACvB,YAAM,SAAS,IAAI,MAAM;AAGzB,WAAK,KAAK,WAAW;AAAA,QACnB,EAAE;AAAA,UACA,EAAE,cAAc,iBAAiB;AAAA,UACjC,EAAE,cAAc,GAAG,IAAI,IAAI,IAAI,IAAI,MAAM,EAAE;AAAA,QAAA;AAAA,MAC7C;AAGF,qBAAe;AAAA,IACjB;AAAA,EAAA,CACD;AAED,SAAO;AACT;AAEO,SAAS,eAAe,MAAc,IAAY;AACvD,QAAM,CAAC,QAAQ,IAAI,GAAG,MAAM,GAAG;AAE/B,QAAM,WAAW,UAAU,QAAQ,cAAc,QAAQ,IAAA,CAAK,GAAG,EAAE;AAEnE,MAAI;AACF,UAAM,MAAM,MAAM,MAAM;AAAA,MACtB,YAAY;AAAA,MACZ,SAAS,CAAC,OAAO,YAAY;AAAA,IAAA,CAC9B;AACD,UAAM,eAAe,UAAU,KAAK,QAAQ;AAC5C,QAAI,CAAC,cAAc;AACjB,aAAO,EAAE,KAAA;AAAA,IACX;AACA,WAAO,IAAI,KAAK;AAAA,MACd,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,gBAAgB;AAAA,IAAA,CACjB;AAAA,EACH,SAAS,GAAG;AACV,WAAO,EAAE,KAAA;AAAA,EACX;AACF;"}
1
+ {"version":3,"file":"inject-source.js","sources":["../../src/inject-source.ts"],"sourcesContent":["import { normalizePath } from 'vite'\nimport { gen, parse, t, trav } from './babel'\nimport type { types as Babel, NodePath } from '@babel/core'\nimport type { ParseResult } from '@babel/parser'\n\nconst getPropsNameFromFunctionDeclaration = (\n functionDeclaration:\n | t.VariableDeclarator\n | t.FunctionExpression\n | t.FunctionDeclaration\n | t.ArrowFunctionExpression,\n) => {\n let propsName: string | null = null\n\n if (functionDeclaration.type === 'FunctionExpression') {\n const firstArgument = functionDeclaration.params[0]\n // handles (props) => {}\n if (firstArgument && firstArgument.type === 'Identifier') {\n propsName = firstArgument.name\n }\n // handles ({ ...props }) => {}\n if (firstArgument && firstArgument.type === 'ObjectPattern') {\n firstArgument.properties.forEach((prop) => {\n if (\n prop.type === 'RestElement' &&\n prop.argument.type === 'Identifier'\n ) {\n propsName = prop.argument.name\n }\n })\n }\n return propsName\n }\n if (functionDeclaration.type === 'ArrowFunctionExpression') {\n const firstArgument = functionDeclaration.params[0]\n // handles (props) => {}\n if (firstArgument && firstArgument.type === 'Identifier') {\n propsName = firstArgument.name\n }\n // handles ({ ...props }) => {}\n if (firstArgument && firstArgument.type === 'ObjectPattern') {\n firstArgument.properties.forEach((prop) => {\n if (\n prop.type === 'RestElement' &&\n prop.argument.type === 'Identifier'\n ) {\n propsName = prop.argument.name\n }\n })\n }\n return propsName\n }\n if (functionDeclaration.type === 'FunctionDeclaration') {\n const firstArgument = functionDeclaration.params[0]\n // handles (props) => {}\n if (firstArgument && firstArgument.type === 'Identifier') {\n propsName = firstArgument.name\n }\n // handles ({ ...props }) => {}\n if (firstArgument && firstArgument.type === 'ObjectPattern') {\n firstArgument.properties.forEach((prop) => {\n if (\n prop.type === 'RestElement' &&\n prop.argument.type === 'Identifier'\n ) {\n propsName = prop.argument.name\n }\n })\n }\n return propsName\n }\n // Arrow function case\n if (\n functionDeclaration.init?.type === 'ArrowFunctionExpression' ||\n functionDeclaration.init?.type === 'FunctionExpression'\n ) {\n const firstArgument = functionDeclaration.init.params[0]\n // handles (props) => {}\n if (firstArgument && firstArgument.type === 'Identifier') {\n propsName = firstArgument.name\n }\n // handles ({ ...props }) => {}\n if (firstArgument && firstArgument.type === 'ObjectPattern') {\n firstArgument.properties.forEach((prop) => {\n if (\n prop.type === 'RestElement' &&\n prop.argument.type === 'Identifier'\n ) {\n propsName = prop.argument.name\n }\n })\n }\n }\n return propsName\n}\n\nconst transformJSX = (\n element: NodePath<t.JSXOpeningElement>,\n propsName: string | null,\n file: string,\n) => {\n const loc = element.node.loc\n if (!loc) return\n const line = loc.start.line\n const column = loc.start.column\n\n const hasDataSource = element.node.attributes.some(\n (attr) =>\n attr.type === 'JSXAttribute' &&\n attr.name.type === 'JSXIdentifier' &&\n attr.name.name === 'data-tsd-source',\n )\n // Check if props are spread\n const hasSpread = element.node.attributes.some(\n (attr) =>\n attr.type === 'JSXSpreadAttribute' &&\n attr.argument.type === 'Identifier' &&\n attr.argument.name === propsName,\n )\n\n if (hasSpread || hasDataSource) {\n // Do not inject if props are spread\n return\n }\n\n // Inject data-source as a string: \"<file>:<line>:<column>\"\n element.node.attributes.push(\n t.jsxAttribute(\n t.jsxIdentifier('data-tsd-source'),\n t.stringLiteral(`${file}:${line}:${column}`),\n ),\n )\n\n return true\n}\n\nconst transform = (ast: ParseResult<Babel.File>, file: string) => {\n let didTransform = false\n\n trav(ast, {\n FunctionDeclaration(functionDeclaration) {\n const propsName = getPropsNameFromFunctionDeclaration(\n functionDeclaration.node,\n )\n functionDeclaration.traverse({\n JSXOpeningElement(element) {\n const transformed = transformJSX(element, propsName, file)\n if (transformed) {\n didTransform = true\n }\n },\n })\n },\n ArrowFunctionExpression(path) {\n const propsName = getPropsNameFromFunctionDeclaration(path.node)\n path.traverse({\n JSXOpeningElement(element) {\n const transformed = transformJSX(element, propsName, file)\n if (transformed) {\n didTransform = true\n }\n },\n })\n },\n FunctionExpression(path) {\n const propsName = getPropsNameFromFunctionDeclaration(path.node)\n path.traverse({\n JSXOpeningElement(element) {\n const transformed = transformJSX(element, propsName, file)\n if (transformed) {\n didTransform = true\n }\n },\n })\n },\n VariableDeclaration(path) {\n const functionDeclaration = path.node.declarations.find((decl) => {\n return (\n decl.init?.type === 'ArrowFunctionExpression' ||\n decl.init?.type === 'FunctionExpression'\n )\n })\n if (!functionDeclaration) {\n return\n }\n const propsName = getPropsNameFromFunctionDeclaration(functionDeclaration)\n\n path.traverse({\n JSXOpeningElement(element) {\n const transformed = transformJSX(element, propsName, file)\n if (transformed) {\n didTransform = true\n }\n },\n })\n },\n })\n\n return didTransform\n}\n\nexport function addSourceToJsx(code: string, id: string) {\n const [filePath] = id.split('?')\n // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain\n const location = filePath?.replace(normalizePath(process.cwd()), '')!\n\n try {\n const ast = parse(code, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n })\n const didTransform = transform(ast, location)\n if (!didTransform) {\n return { code }\n }\n return gen(ast, {\n sourceMaps: true,\n filename: id,\n sourceFileName: filePath,\n })\n } catch (e) {\n return { code }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -2,8 +2,6 @@ import { EditorConfig } from './editor.js';
2
2
  import { ServerEventBusConfig } from '@tanstack/devtools-event-bus/server';
3
3
  import { Plugin } from 'vite';
4
4
  export type TanStackDevtoolsViteConfig = {
5
- /** The directory where the react router app is located. Defaults to the "./src" relative to where vite.config is being defined. */
6
- appDir?: string;
7
5
  /**
8
6
  * Configuration for the editor integration. Defaults to opening in VS code
9
7
  */
@@ -22,6 +20,16 @@ export type TanStackDevtoolsViteConfig = {
22
20
  */
23
21
  enabled: boolean;
24
22
  };
23
+ /**
24
+ * Configuration for source injection.
25
+ */
26
+ injectSource: {
27
+ /**
28
+ * Whether to enable source injection via data-tsd-source.
29
+ * @default true
30
+ */
31
+ enabled: boolean;
32
+ };
25
33
  };
26
34
  export declare const defineDevtoolsConfig: (config: TanStackDevtoolsViteConfig) => TanStackDevtoolsViteConfig;
27
35
  export declare const devtools: (args?: TanStackDevtoolsViteConfig) => Array<Plugin>;
@@ -7,15 +7,15 @@ import { addSourceToJsx } from "./inject-source.js";
7
7
  const defineDevtoolsConfig = (config) => config;
8
8
  const devtools = (args) => {
9
9
  let port = 5173;
10
- const appDir = args?.appDir || "./src";
11
10
  const enhancedLogsConfig = args?.enhancedLogs ?? { enabled: true };
11
+ const injectSourceConfig = args?.injectSource ?? { enabled: true };
12
12
  const bus = new ServerEventBus(args?.eventBusConfig);
13
13
  return [
14
14
  {
15
15
  enforce: "pre",
16
16
  name: "@tanstack/devtools:inject-source",
17
17
  apply(config) {
18
- return config.mode === "development";
18
+ return config.mode === "development" && injectSourceConfig.enabled;
19
19
  },
20
20
  transform(code, id) {
21
21
  if (id.includes("node_modules") || id.includes("?raw") || id.includes("dist") || id.includes("build"))
@@ -44,11 +44,11 @@ const devtools = (args) => {
44
44
  port = server.config.server.port;
45
45
  });
46
46
  const editor = args?.editor ?? DEFAULT_EDITOR_CONFIG;
47
- const openInEditor = async (path, lineNum) => {
47
+ const openInEditor = async (path, lineNum, columnNum) => {
48
48
  if (!path) {
49
49
  return;
50
50
  }
51
- await editor.open(path, lineNum);
51
+ await editor.open(path, lineNum, columnNum);
52
52
  };
53
53
  server.middlewares.use(
54
54
  (req, res, next) => handleDevToolsViteRequest(req, res, next, (parsedData) => {
@@ -56,8 +56,7 @@ const devtools = (args) => {
56
56
  if (routine === "open-source") {
57
57
  return handleOpenSource({
58
58
  data: { type: data.type, data },
59
- openInEditor,
60
- appDir
59
+ openInEditor
61
60
  });
62
61
  }
63
62
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sources":["../../src/plugin.ts"],"sourcesContent":["import { normalizePath } from 'vite'\nimport chalk from 'chalk'\nimport { ServerEventBus } from '@tanstack/devtools-event-bus/server'\nimport { handleDevToolsViteRequest } from './utils'\nimport { DEFAULT_EDITOR_CONFIG, handleOpenSource } from './editor'\nimport { addSourceToJsx } from './inject-source'\nimport type { EditorConfig } from './editor'\nimport type { ServerEventBusConfig } from '@tanstack/devtools-event-bus/server'\nimport type { Plugin } from 'vite'\n\nexport type TanStackDevtoolsViteConfig = {\n /** The directory where the react router app is located. Defaults to the \"./src\" relative to where vite.config is being defined. */\n appDir?: string\n /**\n * Configuration for the editor integration. Defaults to opening in VS code\n */\n editor?: EditorConfig\n /**\n * The configuration options for the server event bus\n */\n eventBusConfig?: ServerEventBusConfig\n /**\n * Configuration for enhanced logging.\n */\n enhancedLogs?: {\n /**\n * Whether to enable enhanced logging.\n * @default true\n */\n enabled: boolean\n }\n}\n\nexport const defineDevtoolsConfig = (config: TanStackDevtoolsViteConfig) =>\n config\n\nexport const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {\n let port = 5173\n const appDir = args?.appDir || './src'\n const enhancedLogsConfig = args?.enhancedLogs ?? { enabled: true }\n const bus = new ServerEventBus(args?.eventBusConfig)\n\n return [\n {\n enforce: 'pre',\n name: '@tanstack/devtools:inject-source',\n apply(config) {\n return config.mode === 'development'\n },\n transform(code, id) {\n if (\n id.includes('node_modules') ||\n id.includes('?raw') ||\n id.includes('dist') ||\n id.includes('build')\n )\n return code\n\n return addSourceToJsx(code, id)\n },\n },\n {\n enforce: 'pre',\n name: '@tanstack/devtools:custom-server',\n apply(config) {\n // Custom server is only needed in development for piping events to the client\n return config.mode === 'development'\n },\n configureServer(server) {\n bus.start()\n server.middlewares.use((req, _res, next) => {\n if (req.socket.localPort && req.socket.localPort !== port) {\n port = req.socket.localPort\n }\n next()\n })\n if (server.config.server.port) {\n port = server.config.server.port\n }\n\n server.httpServer?.on('listening', () => {\n port = server.config.server.port\n })\n\n const editor = args?.editor ?? DEFAULT_EDITOR_CONFIG\n const openInEditor = async (\n path: string | undefined,\n lineNum: string | undefined,\n ) => {\n if (!path) {\n return\n }\n await editor.open(path, lineNum)\n }\n server.middlewares.use((req, res, next) =>\n handleDevToolsViteRequest(req, res, next, (parsedData) => {\n const { data, routine } = parsedData\n if (routine === 'open-source') {\n return handleOpenSource({\n data: { type: data.type, data },\n openInEditor,\n appDir,\n })\n }\n return\n }),\n )\n },\n transform(code) {\n if (code.includes('__TSD_PORT__')) {\n code = code.replace('__TSD_PORT__', String(port))\n }\n return code\n },\n },\n {\n name: '@tanstack/devtools:better-console-logs',\n enforce: 'pre',\n apply(config) {\n return config.mode === 'development' && enhancedLogsConfig.enabled\n },\n transform(code, id) {\n // Ignore anything external\n if (\n id.includes('node_modules') ||\n id.includes('?raw') ||\n id.includes('dist') ||\n id.includes('build')\n )\n return code\n\n if (!code.includes('console.')) {\n return code\n }\n const lines = code.split('\\n')\n return lines\n .map((line, lineNumber) => {\n if (\n line.trim().startsWith('//') ||\n line.trim().startsWith('/**') ||\n line.trim().startsWith('*')\n ) {\n return line\n }\n // Do not add for arrow functions or return statements\n if (\n line.replaceAll(' ', '').includes('=>console.') ||\n line.includes('return console.')\n ) {\n return line\n }\n\n const column = line.indexOf('console.')\n const location = `${id.replace(normalizePath(process.cwd()), '')}:${lineNumber + 1}:${column + 1}`\n const logMessage = `'${chalk.magenta('LOG')} ${chalk.blueBright(`${location} - http://localhost:${port}/__tsd/open-source?source=${encodeURIComponent(id.replace(normalizePath(process.cwd()), ''))}&line=${lineNumber + 1}&column=${column + 1}`)}\\\\n → '`\n if (line.includes('console.log(')) {\n const newLine = `console.log(${logMessage},`\n return line.replace('console.log(', newLine)\n }\n if (line.includes('console.error(')) {\n const newLine = `console.error(${logMessage},`\n return line.replace('console.error(', newLine)\n }\n return line\n })\n .join('\\n')\n },\n },\n ]\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sources":["../../src/plugin.ts"],"sourcesContent":["import { normalizePath } from 'vite'\nimport chalk from 'chalk'\nimport { ServerEventBus } from '@tanstack/devtools-event-bus/server'\nimport { handleDevToolsViteRequest } from './utils'\nimport { DEFAULT_EDITOR_CONFIG, handleOpenSource } from './editor'\nimport { addSourceToJsx } from './inject-source'\nimport type { EditorConfig } from './editor'\nimport type { ServerEventBusConfig } from '@tanstack/devtools-event-bus/server'\nimport type { Plugin } from 'vite'\n\nexport type TanStackDevtoolsViteConfig = {\n /**\n * Configuration for the editor integration. Defaults to opening in VS code\n */\n editor?: EditorConfig\n /**\n * The configuration options for the server event bus\n */\n eventBusConfig?: ServerEventBusConfig\n /**\n * Configuration for enhanced logging.\n */\n enhancedLogs?: {\n /**\n * Whether to enable enhanced logging.\n * @default true\n */\n enabled: boolean\n }\n /**\n * Configuration for source injection.\n */\n injectSource: {\n /**\n * Whether to enable source injection via data-tsd-source.\n * @default true\n */\n enabled: boolean\n }\n}\n\nexport const defineDevtoolsConfig = (config: TanStackDevtoolsViteConfig) =>\n config\n\nexport const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {\n let port = 5173\n const enhancedLogsConfig = args?.enhancedLogs ?? { enabled: true }\n const injectSourceConfig = args?.injectSource ?? { enabled: true }\n const bus = new ServerEventBus(args?.eventBusConfig)\n\n return [\n {\n enforce: 'pre',\n name: '@tanstack/devtools:inject-source',\n apply(config) {\n return config.mode === 'development' && injectSourceConfig.enabled\n },\n transform(code, id) {\n if (\n id.includes('node_modules') ||\n id.includes('?raw') ||\n id.includes('dist') ||\n id.includes('build')\n )\n return code\n\n return addSourceToJsx(code, id)\n },\n },\n {\n enforce: 'pre',\n name: '@tanstack/devtools:custom-server',\n apply(config) {\n // Custom server is only needed in development for piping events to the client\n return config.mode === 'development'\n },\n configureServer(server) {\n bus.start()\n server.middlewares.use((req, _res, next) => {\n if (req.socket.localPort && req.socket.localPort !== port) {\n port = req.socket.localPort\n }\n next()\n })\n if (server.config.server.port) {\n port = server.config.server.port\n }\n\n server.httpServer?.on('listening', () => {\n port = server.config.server.port\n })\n\n const editor = args?.editor ?? DEFAULT_EDITOR_CONFIG\n const openInEditor: EditorConfig['open'] = async (\n path,\n lineNum,\n columnNum,\n ) => {\n if (!path) {\n return\n }\n await editor.open(path, lineNum, columnNum)\n }\n server.middlewares.use((req, res, next) =>\n handleDevToolsViteRequest(req, res, next, (parsedData) => {\n const { data, routine } = parsedData\n if (routine === 'open-source') {\n return handleOpenSource({\n data: { type: data.type, data },\n openInEditor,\n })\n }\n return\n }),\n )\n },\n transform(code) {\n if (code.includes('__TSD_PORT__')) {\n code = code.replace('__TSD_PORT__', String(port))\n }\n return code\n },\n },\n {\n name: '@tanstack/devtools:better-console-logs',\n enforce: 'pre',\n apply(config) {\n return config.mode === 'development' && enhancedLogsConfig.enabled\n },\n transform(code, id) {\n // Ignore anything external\n if (\n id.includes('node_modules') ||\n id.includes('?raw') ||\n id.includes('dist') ||\n id.includes('build')\n )\n return code\n\n if (!code.includes('console.')) {\n return code\n }\n const lines = code.split('\\n')\n return lines\n .map((line, lineNumber) => {\n if (\n line.trim().startsWith('//') ||\n line.trim().startsWith('/**') ||\n line.trim().startsWith('*')\n ) {\n return line\n }\n // Do not add for arrow functions or return statements\n if (\n line.replaceAll(' ', '').includes('=>console.') ||\n line.includes('return console.')\n ) {\n return line\n }\n\n const column = line.indexOf('console.')\n const location = `${id.replace(normalizePath(process.cwd()), '')}:${lineNumber + 1}:${column + 1}`\n const logMessage = `'${chalk.magenta('LOG')} ${chalk.blueBright(`${location} - http://localhost:${port}/__tsd/open-source?source=${encodeURIComponent(id.replace(normalizePath(process.cwd()), ''))}&line=${lineNumber + 1}&column=${column + 1}`)}\\\\n → '`\n if (line.includes('console.log(')) {\n const newLine = `console.log(${logMessage},`\n return line.replace('console.log(', newLine)\n }\n if (line.includes('console.error(')) {\n const newLine = `console.error(${logMessage},`\n return line.replace('console.error(', newLine)\n }\n return line\n })\n .join('\\n')\n },\n },\n ]\n}\n"],"names":[],"mappings":"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"}
@@ -1,10 +1,3 @@
1
1
  import { Connect } from 'vite';
2
2
  import { IncomingMessage, ServerResponse } from 'node:http';
3
3
  export declare const handleDevToolsViteRequest: (req: Connect.IncomingMessage, res: ServerResponse<IncomingMessage>, next: Connect.NextFunction, cb: (data: any) => void) => void;
4
- export declare function checkPath(routePath: string, extensions?: string[]): Promise<{
5
- readonly validPath: string;
6
- readonly type: "directory";
7
- } | {
8
- readonly validPath: string;
9
- readonly type: "file";
10
- } | null>;
package/dist/esm/utils.js CHANGED
@@ -3,13 +3,15 @@ const handleDevToolsViteRequest = (req, res, next, cb) => {
3
3
  if (req.url?.includes("__tsd/open-source")) {
4
4
  const searchParams = new URLSearchParams(req.url.split("?")[1]);
5
5
  const source = searchParams.get("source");
6
- const line = searchParams.get("line");
7
- const column = searchParams.get("column");
6
+ if (!source) {
7
+ return;
8
+ }
9
+ const [file, line, column] = source.split(":");
8
10
  cb({
9
11
  type: "open-source",
10
12
  routine: "open-source",
11
13
  data: {
12
- source: source ? normalizePath(`${process.cwd()}/${source}`) : void 0,
14
+ source: file ? normalizePath(`${process.cwd()}/${file}`) : void 0,
13
15
  line,
14
16
  column
15
17
  }
@@ -36,21 +38,7 @@ const handleDevToolsViteRequest = (req, res, next, cb) => {
36
38
  res.write("OK");
37
39
  });
38
40
  };
39
- async function checkPath(routePath, extensions = [".tsx", ".jsx", ".ts", ".js"]) {
40
- const fs = await import("node:fs");
41
- if (fs.existsSync(routePath) && fs.lstatSync(routePath).isDirectory()) {
42
- return { validPath: routePath, type: "directory" };
43
- }
44
- for (const ext of extensions) {
45
- const filePath = `${routePath}${ext}`;
46
- if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
47
- return { validPath: filePath, type: "file" };
48
- }
49
- }
50
- return null;
51
- }
52
41
  export {
53
- checkPath,
54
42
  handleDevToolsViteRequest
55
43
  };
56
44
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import { normalizePath } from 'vite'\nimport type { Connect } from 'vite'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nexport const handleDevToolsViteRequest = (\n req: Connect.IncomingMessage,\n res: ServerResponse<IncomingMessage>,\n next: Connect.NextFunction,\n cb: (data: any) => void,\n) => {\n if (req.url?.includes('__tsd/open-source')) {\n const searchParams = new URLSearchParams(req.url.split('?')[1])\n const source = searchParams.get('source')\n const line = searchParams.get('line')\n const column = searchParams.get('column')\n cb({\n type: 'open-source',\n routine: 'open-source',\n data: {\n source: source\n ? normalizePath(`${process.cwd()}/${source}`)\n : undefined,\n line,\n column,\n },\n })\n res.setHeader('Content-Type', 'text/html')\n res.write(`<script> window.close(); </script>`)\n res.end()\n return\n }\n if (!req.url?.includes('__tsd')) {\n return next()\n }\n\n const chunks: Array<any> = []\n req.on('data', (chunk) => {\n chunks.push(chunk)\n })\n req.on('end', () => {\n const dataToParse = Buffer.concat(chunks)\n try {\n const parsedData = JSON.parse(dataToParse.toString())\n cb(parsedData)\n } catch (e) {}\n res.write('OK')\n })\n}\n\nexport async function checkPath(\n routePath: string,\n extensions = ['.tsx', '.jsx', '.ts', '.js'],\n) {\n const fs = await import('node:fs')\n // Check if the path exists as a directory\n if (fs.existsSync(routePath) && fs.lstatSync(routePath).isDirectory()) {\n return { validPath: routePath, type: 'directory' } as const\n }\n\n // Check if the path exists as a file with one of the given extensions\n for (const ext of extensions) {\n const filePath = `${routePath}${ext}`\n if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {\n return { validPath: filePath, type: 'file' } as const\n }\n }\n\n // If neither a file nor a directory is found\n return null\n}\n"],"names":[],"mappings":";AAIO,MAAM,4BAA4B,CACvC,KACA,KACA,MACA,OACG;AACH,MAAI,IAAI,KAAK,SAAS,mBAAmB,GAAG;AAC1C,UAAM,eAAe,IAAI,gBAAgB,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,CAAC;AAC9D,UAAM,SAAS,aAAa,IAAI,QAAQ;AACxC,UAAM,OAAO,aAAa,IAAI,MAAM;AACpC,UAAM,SAAS,aAAa,IAAI,QAAQ;AACxC,OAAG;AAAA,MACD,MAAM;AAAA,MACN,SAAS;AAAA,MACT,MAAM;AAAA,QACJ,QAAQ,SACJ,cAAc,GAAG,QAAQ,KAAK,IAAI,MAAM,EAAE,IAC1C;AAAA,QACJ;AAAA,QACA;AAAA,MAAA;AAAA,IACF,CACD;AACD,QAAI,UAAU,gBAAgB,WAAW;AACzC,QAAI,MAAM,qCAAoC;AAC9C,QAAI,IAAA;AACJ;AAAA,EACF;AACA,MAAI,CAAC,IAAI,KAAK,SAAS,OAAO,GAAG;AAC/B,WAAO,KAAA;AAAA,EACT;AAEA,QAAM,SAAqB,CAAA;AAC3B,MAAI,GAAG,QAAQ,CAAC,UAAU;AACxB,WAAO,KAAK,KAAK;AAAA,EACnB,CAAC;AACD,MAAI,GAAG,OAAO,MAAM;AAClB,UAAM,cAAc,OAAO,OAAO,MAAM;AACxC,QAAI;AACF,YAAM,aAAa,KAAK,MAAM,YAAY,UAAU;AACpD,SAAG,UAAU;AAAA,IACf,SAAS,GAAG;AAAA,IAAC;AACb,QAAI,MAAM,IAAI;AAAA,EAChB,CAAC;AACH;AAEA,eAAsB,UACpB,WACA,aAAa,CAAC,QAAQ,QAAQ,OAAO,KAAK,GAC1C;AACA,QAAM,KAAK,MAAM,OAAO,SAAS;AAEjC,MAAI,GAAG,WAAW,SAAS,KAAK,GAAG,UAAU,SAAS,EAAE,eAAe;AACrE,WAAO,EAAE,WAAW,WAAW,MAAM,YAAA;AAAA,EACvC;AAGA,aAAW,OAAO,YAAY;AAC5B,UAAM,WAAW,GAAG,SAAS,GAAG,GAAG;AACnC,QAAI,GAAG,WAAW,QAAQ,KAAK,GAAG,UAAU,QAAQ,EAAE,UAAU;AAC9D,aAAO,EAAE,WAAW,UAAU,MAAM,OAAA;AAAA,IACtC;AAAA,EACF;AAGA,SAAO;AACT;"}
1
+ {"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import { normalizePath } from 'vite'\nimport type { Connect } from 'vite'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nexport const handleDevToolsViteRequest = (\n req: Connect.IncomingMessage,\n res: ServerResponse<IncomingMessage>,\n next: Connect.NextFunction,\n cb: (data: any) => void,\n) => {\n if (req.url?.includes('__tsd/open-source')) {\n const searchParams = new URLSearchParams(req.url.split('?')[1])\n const source = searchParams.get('source')\n if (!source) {\n return\n }\n const [file, line, column] = source.split(':')\n\n cb({\n type: 'open-source',\n routine: 'open-source',\n data: {\n source: file ? normalizePath(`${process.cwd()}/${file}`) : undefined,\n line,\n column,\n },\n })\n res.setHeader('Content-Type', 'text/html')\n res.write(`<script> window.close(); </script>`)\n res.end()\n return\n }\n if (!req.url?.includes('__tsd')) {\n return next()\n }\n\n const chunks: Array<any> = []\n req.on('data', (chunk) => {\n chunks.push(chunk)\n })\n req.on('end', () => {\n const dataToParse = Buffer.concat(chunks)\n try {\n const parsedData = JSON.parse(dataToParse.toString())\n cb(parsedData)\n } catch (e) {}\n res.write('OK')\n })\n}\n"],"names":[],"mappings":";AAIO,MAAM,4BAA4B,CACvC,KACA,KACA,MACA,OACG;AACH,MAAI,IAAI,KAAK,SAAS,mBAAmB,GAAG;AAC1C,UAAM,eAAe,IAAI,gBAAgB,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,CAAC;AAC9D,UAAM,SAAS,aAAa,IAAI,QAAQ;AACxC,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AACA,UAAM,CAAC,MAAM,MAAM,MAAM,IAAI,OAAO,MAAM,GAAG;AAE7C,OAAG;AAAA,MACD,MAAM;AAAA,MACN,SAAS;AAAA,MACT,MAAM;AAAA,QACJ,QAAQ,OAAO,cAAc,GAAG,QAAQ,KAAK,IAAI,IAAI,EAAE,IAAI;AAAA,QAC3D;AAAA,QACA;AAAA,MAAA;AAAA,IACF,CACD;AACD,QAAI,UAAU,gBAAgB,WAAW;AACzC,QAAI,MAAM,qCAAoC;AAC9C,QAAI,IAAA;AACJ;AAAA,EACF;AACA,MAAI,CAAC,IAAI,KAAK,SAAS,OAAO,GAAG;AAC/B,WAAO,KAAA;AAAA,EACT;AAEA,QAAM,SAAqB,CAAA;AAC3B,MAAI,GAAG,QAAQ,CAAC,UAAU;AACxB,WAAO,KAAK,KAAK;AAAA,EACnB,CAAC;AACD,MAAI,GAAG,OAAO,MAAM;AAClB,UAAM,cAAc,OAAO,OAAO,MAAM;AACxC,QAAI;AACF,YAAM,aAAa,KAAK,MAAM,YAAY,UAAU;AACpD,SAAG,UAAU;AAAA,IACf,SAAS,GAAG;AAAA,IAAC;AACb,QAAI,MAAM,IAAI;AAAA,EAChB,CAAC;AACH;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/devtools-vite",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "TanStack Vite plugin used to enhance the core devtools with additional functionalities",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -52,7 +52,8 @@
52
52
  "devDependencies": {
53
53
  "@types/babel__core": "^7.20.5",
54
54
  "@types/babel__generator": "^7.27.0",
55
- "@types/babel__traverse": "^7.28.0"
55
+ "@types/babel__traverse": "^7.28.0",
56
+ "happy-dom": "^18.0.1"
56
57
  },
57
58
  "scripts": {
58
59
  "clean": "premove ./build ./dist",
package/src/editor.ts CHANGED
@@ -1,6 +1,3 @@
1
- import { normalizePath } from 'vite'
2
- import { checkPath } from './utils.js'
3
-
4
1
  type OpenSourceData = {
5
2
  type: 'open-source'
6
3
  data: {
@@ -31,7 +28,7 @@ export const DEFAULT_EDITOR_CONFIG: EditorConfig = {
31
28
  open: async (path, lineNumber, columnNumber) => {
32
29
  const { exec } = await import('node:child_process')
33
30
  exec(
34
- `code -g "${normalizePath(path).replaceAll('$', '\\$')}${lineNumber ? `:${lineNumber}` : ''}${columnNumber ? `:${columnNumber}` : ''}"`,
31
+ `code -g "${path.replaceAll('$', '\\$')}${lineNumber ? `:${lineNumber}` : ''}${columnNumber ? `:${columnNumber}` : ''}"`,
35
32
  )
36
33
  },
37
34
  }
@@ -39,54 +36,14 @@ export const DEFAULT_EDITOR_CONFIG: EditorConfig = {
39
36
  export const handleOpenSource = async ({
40
37
  data,
41
38
  openInEditor,
42
- appDir,
43
39
  }: {
44
40
  data: OpenSourceData
45
- appDir: string
46
41
  openInEditor: EditorConfig['open']
47
42
  }) => {
48
- const { source, line, routeID } = data.data
43
+ const { source, line, column } = data.data
49
44
  const lineNum = line ? `${line}` : undefined
50
- const fs = await import('node:fs')
51
- const path = await import('node:path')
45
+ const columnNum = column ? `${column}` : undefined
52
46
  if (source) {
53
- return openInEditor(source, lineNum)
54
- }
55
-
56
- if (routeID) {
57
- const routePath = path.join(appDir, routeID)
58
- const checkedPath = await checkPath(routePath)
59
-
60
- if (!checkedPath) return
61
- const { type, validPath } = checkedPath
62
-
63
- const reactExtensions = ['tsx', 'jsx']
64
- const allExtensions = ['ts', 'js', ...reactExtensions]
65
- const isRoot = routeID === 'root'
66
- const findFileByExtension = (prefix: string, filePaths: Array<string>) => {
67
- const file = filePaths.find((file) =>
68
- allExtensions.some((ext) => file === `${prefix}.${ext}`),
69
- )
70
- return file
71
- }
72
-
73
- if (isRoot) {
74
- if (!fs.existsSync(appDir)) return
75
- const filesInReactRouterPath = fs.readdirSync(appDir)
76
- const rootFile = findFileByExtension('root', filesInReactRouterPath)
77
- rootFile && openInEditor(path.join(appDir, rootFile), lineNum)
78
- return
79
- }
80
-
81
- // If its not the root route, then we find the file or folder in the routes folder
82
- // We know that the route ID is in the form of "routes/contact" or "routes/user.profile" when is not root
83
- // so the ID already contains the "routes" segment, so we just need to find the file or folder in the routes folder
84
- if (type === 'directory') {
85
- const filesInFolderRoute = fs.readdirSync(validPath)
86
- const routeFile = findFileByExtension('route', filesInFolderRoute)
87
- routeFile && openInEditor(path.join(appDir, routeID, routeFile), lineNum)
88
- return
89
- }
90
- return openInEditor(validPath, lineNum)
47
+ return openInEditor(source, lineNum, columnNum)
91
48
  }
92
49
  }
@@ -1,26 +1,198 @@
1
1
  import { normalizePath } from 'vite'
2
2
  import { gen, parse, t, trav } from './babel'
3
- import type { types as Babel } from '@babel/core'
3
+ import type { types as Babel, NodePath } from '@babel/core'
4
4
  import type { ParseResult } from '@babel/parser'
5
5
 
6
+ const getPropsNameFromFunctionDeclaration = (
7
+ functionDeclaration:
8
+ | t.VariableDeclarator
9
+ | t.FunctionExpression
10
+ | t.FunctionDeclaration
11
+ | t.ArrowFunctionExpression,
12
+ ) => {
13
+ let propsName: string | null = null
14
+
15
+ if (functionDeclaration.type === 'FunctionExpression') {
16
+ const firstArgument = functionDeclaration.params[0]
17
+ // handles (props) => {}
18
+ if (firstArgument && firstArgument.type === 'Identifier') {
19
+ propsName = firstArgument.name
20
+ }
21
+ // handles ({ ...props }) => {}
22
+ if (firstArgument && firstArgument.type === 'ObjectPattern') {
23
+ firstArgument.properties.forEach((prop) => {
24
+ if (
25
+ prop.type === 'RestElement' &&
26
+ prop.argument.type === 'Identifier'
27
+ ) {
28
+ propsName = prop.argument.name
29
+ }
30
+ })
31
+ }
32
+ return propsName
33
+ }
34
+ if (functionDeclaration.type === 'ArrowFunctionExpression') {
35
+ const firstArgument = functionDeclaration.params[0]
36
+ // handles (props) => {}
37
+ if (firstArgument && firstArgument.type === 'Identifier') {
38
+ propsName = firstArgument.name
39
+ }
40
+ // handles ({ ...props }) => {}
41
+ if (firstArgument && firstArgument.type === 'ObjectPattern') {
42
+ firstArgument.properties.forEach((prop) => {
43
+ if (
44
+ prop.type === 'RestElement' &&
45
+ prop.argument.type === 'Identifier'
46
+ ) {
47
+ propsName = prop.argument.name
48
+ }
49
+ })
50
+ }
51
+ return propsName
52
+ }
53
+ if (functionDeclaration.type === 'FunctionDeclaration') {
54
+ const firstArgument = functionDeclaration.params[0]
55
+ // handles (props) => {}
56
+ if (firstArgument && firstArgument.type === 'Identifier') {
57
+ propsName = firstArgument.name
58
+ }
59
+ // handles ({ ...props }) => {}
60
+ if (firstArgument && firstArgument.type === 'ObjectPattern') {
61
+ firstArgument.properties.forEach((prop) => {
62
+ if (
63
+ prop.type === 'RestElement' &&
64
+ prop.argument.type === 'Identifier'
65
+ ) {
66
+ propsName = prop.argument.name
67
+ }
68
+ })
69
+ }
70
+ return propsName
71
+ }
72
+ // Arrow function case
73
+ if (
74
+ functionDeclaration.init?.type === 'ArrowFunctionExpression' ||
75
+ functionDeclaration.init?.type === 'FunctionExpression'
76
+ ) {
77
+ const firstArgument = functionDeclaration.init.params[0]
78
+ // handles (props) => {}
79
+ if (firstArgument && firstArgument.type === 'Identifier') {
80
+ propsName = firstArgument.name
81
+ }
82
+ // handles ({ ...props }) => {}
83
+ if (firstArgument && firstArgument.type === 'ObjectPattern') {
84
+ firstArgument.properties.forEach((prop) => {
85
+ if (
86
+ prop.type === 'RestElement' &&
87
+ prop.argument.type === 'Identifier'
88
+ ) {
89
+ propsName = prop.argument.name
90
+ }
91
+ })
92
+ }
93
+ }
94
+ return propsName
95
+ }
96
+
97
+ const transformJSX = (
98
+ element: NodePath<t.JSXOpeningElement>,
99
+ propsName: string | null,
100
+ file: string,
101
+ ) => {
102
+ const loc = element.node.loc
103
+ if (!loc) return
104
+ const line = loc.start.line
105
+ const column = loc.start.column
106
+
107
+ const hasDataSource = element.node.attributes.some(
108
+ (attr) =>
109
+ attr.type === 'JSXAttribute' &&
110
+ attr.name.type === 'JSXIdentifier' &&
111
+ attr.name.name === 'data-tsd-source',
112
+ )
113
+ // Check if props are spread
114
+ const hasSpread = element.node.attributes.some(
115
+ (attr) =>
116
+ attr.type === 'JSXSpreadAttribute' &&
117
+ attr.argument.type === 'Identifier' &&
118
+ attr.argument.name === propsName,
119
+ )
120
+
121
+ if (hasSpread || hasDataSource) {
122
+ // Do not inject if props are spread
123
+ return
124
+ }
125
+
126
+ // Inject data-source as a string: "<file>:<line>:<column>"
127
+ element.node.attributes.push(
128
+ t.jsxAttribute(
129
+ t.jsxIdentifier('data-tsd-source'),
130
+ t.stringLiteral(`${file}:${line}:${column}`),
131
+ ),
132
+ )
133
+
134
+ return true
135
+ }
136
+
6
137
  const transform = (ast: ParseResult<Babel.File>, file: string) => {
7
138
  let didTransform = false
139
+
8
140
  trav(ast, {
9
- JSXOpeningElement(path) {
10
- const loc = path.node.loc
11
- if (!loc) return
12
- const line = loc.start.line
13
- const column = loc.start.column
14
-
15
- // Inject data-source as a string: "<file>:<line>:<column>"
16
- path.node.attributes.push(
17
- t.jsxAttribute(
18
- t.jsxIdentifier('data-tsd-source'),
19
- t.stringLiteral(`${file}:${line}:${column}`),
20
- ),
141
+ FunctionDeclaration(functionDeclaration) {
142
+ const propsName = getPropsNameFromFunctionDeclaration(
143
+ functionDeclaration.node,
21
144
  )
145
+ functionDeclaration.traverse({
146
+ JSXOpeningElement(element) {
147
+ const transformed = transformJSX(element, propsName, file)
148
+ if (transformed) {
149
+ didTransform = true
150
+ }
151
+ },
152
+ })
153
+ },
154
+ ArrowFunctionExpression(path) {
155
+ const propsName = getPropsNameFromFunctionDeclaration(path.node)
156
+ path.traverse({
157
+ JSXOpeningElement(element) {
158
+ const transformed = transformJSX(element, propsName, file)
159
+ if (transformed) {
160
+ didTransform = true
161
+ }
162
+ },
163
+ })
164
+ },
165
+ FunctionExpression(path) {
166
+ const propsName = getPropsNameFromFunctionDeclaration(path.node)
167
+ path.traverse({
168
+ JSXOpeningElement(element) {
169
+ const transformed = transformJSX(element, propsName, file)
170
+ if (transformed) {
171
+ didTransform = true
172
+ }
173
+ },
174
+ })
175
+ },
176
+ VariableDeclaration(path) {
177
+ const functionDeclaration = path.node.declarations.find((decl) => {
178
+ return (
179
+ decl.init?.type === 'ArrowFunctionExpression' ||
180
+ decl.init?.type === 'FunctionExpression'
181
+ )
182
+ })
183
+ if (!functionDeclaration) {
184
+ return
185
+ }
186
+ const propsName = getPropsNameFromFunctionDeclaration(functionDeclaration)
22
187
 
23
- didTransform = true
188
+ path.traverse({
189
+ JSXOpeningElement(element) {
190
+ const transformed = transformJSX(element, propsName, file)
191
+ if (transformed) {
192
+ didTransform = true
193
+ }
194
+ },
195
+ })
24
196
  },
25
197
  })
26
198
 
package/src/plugin.ts CHANGED
@@ -9,8 +9,6 @@ import type { ServerEventBusConfig } from '@tanstack/devtools-event-bus/server'
9
9
  import type { Plugin } from 'vite'
10
10
 
11
11
  export type TanStackDevtoolsViteConfig = {
12
- /** The directory where the react router app is located. Defaults to the "./src" relative to where vite.config is being defined. */
13
- appDir?: string
14
12
  /**
15
13
  * Configuration for the editor integration. Defaults to opening in VS code
16
14
  */
@@ -29,6 +27,16 @@ export type TanStackDevtoolsViteConfig = {
29
27
  */
30
28
  enabled: boolean
31
29
  }
30
+ /**
31
+ * Configuration for source injection.
32
+ */
33
+ injectSource: {
34
+ /**
35
+ * Whether to enable source injection via data-tsd-source.
36
+ * @default true
37
+ */
38
+ enabled: boolean
39
+ }
32
40
  }
33
41
 
34
42
  export const defineDevtoolsConfig = (config: TanStackDevtoolsViteConfig) =>
@@ -36,8 +44,8 @@ export const defineDevtoolsConfig = (config: TanStackDevtoolsViteConfig) =>
36
44
 
37
45
  export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
38
46
  let port = 5173
39
- const appDir = args?.appDir || './src'
40
47
  const enhancedLogsConfig = args?.enhancedLogs ?? { enabled: true }
48
+ const injectSourceConfig = args?.injectSource ?? { enabled: true }
41
49
  const bus = new ServerEventBus(args?.eventBusConfig)
42
50
 
43
51
  return [
@@ -45,7 +53,7 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
45
53
  enforce: 'pre',
46
54
  name: '@tanstack/devtools:inject-source',
47
55
  apply(config) {
48
- return config.mode === 'development'
56
+ return config.mode === 'development' && injectSourceConfig.enabled
49
57
  },
50
58
  transform(code, id) {
51
59
  if (
@@ -83,14 +91,15 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
83
91
  })
84
92
 
85
93
  const editor = args?.editor ?? DEFAULT_EDITOR_CONFIG
86
- const openInEditor = async (
87
- path: string | undefined,
88
- lineNum: string | undefined,
94
+ const openInEditor: EditorConfig['open'] = async (
95
+ path,
96
+ lineNum,
97
+ columnNum,
89
98
  ) => {
90
99
  if (!path) {
91
100
  return
92
101
  }
93
- await editor.open(path, lineNum)
102
+ await editor.open(path, lineNum, columnNum)
94
103
  }
95
104
  server.middlewares.use((req, res, next) =>
96
105
  handleDevToolsViteRequest(req, res, next, (parsedData) => {
@@ -99,7 +108,6 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
99
108
  return handleOpenSource({
100
109
  data: { type: data.type, data },
101
110
  openInEditor,
102
- appDir,
103
111
  })
104
112
  }
105
113
  return
package/src/utils.ts CHANGED
@@ -11,15 +11,16 @@ export const handleDevToolsViteRequest = (
11
11
  if (req.url?.includes('__tsd/open-source')) {
12
12
  const searchParams = new URLSearchParams(req.url.split('?')[1])
13
13
  const source = searchParams.get('source')
14
- const line = searchParams.get('line')
15
- const column = searchParams.get('column')
14
+ if (!source) {
15
+ return
16
+ }
17
+ const [file, line, column] = source.split(':')
18
+
16
19
  cb({
17
20
  type: 'open-source',
18
21
  routine: 'open-source',
19
22
  data: {
20
- source: source
21
- ? normalizePath(`${process.cwd()}/${source}`)
22
- : undefined,
23
+ source: file ? normalizePath(`${process.cwd()}/${file}`) : undefined,
23
24
  line,
24
25
  column,
25
26
  },
@@ -46,25 +47,3 @@ export const handleDevToolsViteRequest = (
46
47
  res.write('OK')
47
48
  })
48
49
  }
49
-
50
- export async function checkPath(
51
- routePath: string,
52
- extensions = ['.tsx', '.jsx', '.ts', '.js'],
53
- ) {
54
- const fs = await import('node:fs')
55
- // Check if the path exists as a directory
56
- if (fs.existsSync(routePath) && fs.lstatSync(routePath).isDirectory()) {
57
- return { validPath: routePath, type: 'directory' } as const
58
- }
59
-
60
- // Check if the path exists as a file with one of the given extensions
61
- for (const ext of extensions) {
62
- const filePath = `${routePath}${ext}`
63
- if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
64
- return { validPath: filePath, type: 'file' } as const
65
- }
66
- }
67
-
68
- // If neither a file nor a directory is found
69
- return null
70
- }