@tanstack/devtools-vite 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ export declare function enhanceConsoleLog(code: string, id: string, port: number): import('@babel/generator').GeneratorResult | {
2
+ code: string;
3
+ };
@@ -0,0 +1,58 @@
1
+ import chalk from "chalk";
2
+ import { normalizePath } from "vite";
3
+ import { gen, trav } from "./babel.js";
4
+ import { parse } from "@babel/parser";
5
+ import * as t from "@babel/types";
6
+ const transform = (ast, filePath, port) => {
7
+ let didTransform = false;
8
+ trav(ast, {
9
+ CallExpression(path) {
10
+ const callee = path.node.callee;
11
+ if (callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === "console" && callee.property.type === "Identifier" && (callee.property.name === "log" || callee.property.name === "error")) {
12
+ const location = path.node.loc;
13
+ if (!location) {
14
+ return;
15
+ }
16
+ const [lineNumber, column] = [
17
+ location.start.line,
18
+ location.start.column
19
+ ];
20
+ const finalPath = `${filePath}:${lineNumber}:${column + 1}`;
21
+ path.node.arguments.unshift(
22
+ t.stringLiteral(
23
+ `${chalk.magenta("LOG")} ${chalk.blueBright(`${finalPath} - http://localhost:${port}/__tsd/open-source?source=${encodeURIComponent(finalPath)}`)}
24
+ → `
25
+ )
26
+ );
27
+ didTransform = true;
28
+ }
29
+ }
30
+ });
31
+ return didTransform;
32
+ };
33
+ function enhanceConsoleLog(code, id, port) {
34
+ const [filePath] = id.split("?");
35
+ const location = filePath?.replace(normalizePath(process.cwd()), "");
36
+ try {
37
+ const ast = parse(code, {
38
+ sourceType: "module",
39
+ plugins: ["jsx", "typescript"]
40
+ });
41
+ const didTransform = transform(ast, location, port);
42
+ if (!didTransform) {
43
+ return { code };
44
+ }
45
+ return gen(ast, {
46
+ sourceMaps: true,
47
+ retainLines: true,
48
+ filename: id,
49
+ sourceFileName: filePath
50
+ });
51
+ } catch (e) {
52
+ return { code };
53
+ }
54
+ }
55
+ export {
56
+ enhanceConsoleLog
57
+ };
58
+ //# sourceMappingURL=enhance-logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enhance-logs.js","sources":["../../src/enhance-logs.ts"],"sourcesContent":["import chalk from 'chalk'\nimport { 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 = (\n ast: ParseResult<Babel.File>,\n filePath: string,\n port: number,\n) => {\n let didTransform = false\n\n trav(ast, {\n CallExpression(path) {\n const callee = path.node.callee\n // Match console.log(...) or console.error(...)\n if (\n callee.type === 'MemberExpression' &&\n callee.object.type === 'Identifier' &&\n callee.object.name === 'console' &&\n callee.property.type === 'Identifier' &&\n (callee.property.name === 'log' || callee.property.name === 'error')\n ) {\n const location = path.node.loc\n if (!location) {\n return\n }\n const [lineNumber, column] = [\n location.start.line,\n location.start.column,\n ]\n const finalPath = `${filePath}:${lineNumber}:${column + 1}`\n path.node.arguments.unshift(\n t.stringLiteral(\n `${chalk.magenta('LOG')} ${chalk.blueBright(`${finalPath} - http://localhost:${port}/__tsd/open-source?source=${encodeURIComponent(finalPath)}`)}\\n → `,\n ),\n )\n didTransform = true\n }\n },\n })\n\n return didTransform\n}\n\nexport function enhanceConsoleLog(code: string, id: string, port: number) {\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, port)\n if (!didTransform) {\n return { code }\n }\n return gen(ast, {\n sourceMaps: true,\n retainLines: true,\n filename: id,\n sourceFileName: filePath,\n })\n } catch (e) {\n return { code }\n }\n}\n"],"names":[],"mappings":";;;;;AAMA,MAAM,YAAY,CAChB,KACA,UACA,SACG;AACH,MAAI,eAAe;AAEnB,OAAK,KAAK;AAAA,IACR,eAAe,MAAM;AACnB,YAAM,SAAS,KAAK,KAAK;AAEzB,UACE,OAAO,SAAS,sBAChB,OAAO,OAAO,SAAS,gBACvB,OAAO,OAAO,SAAS,aACvB,OAAO,SAAS,SAAS,iBACxB,OAAO,SAAS,SAAS,SAAS,OAAO,SAAS,SAAS,UAC5D;AACA,cAAM,WAAW,KAAK,KAAK;AAC3B,YAAI,CAAC,UAAU;AACb;AAAA,QACF;AACA,cAAM,CAAC,YAAY,MAAM,IAAI;AAAA,UAC3B,SAAS,MAAM;AAAA,UACf,SAAS,MAAM;AAAA,QAAA;AAEjB,cAAM,YAAY,GAAG,QAAQ,IAAI,UAAU,IAAI,SAAS,CAAC;AACzD,aAAK,KAAK,UAAU;AAAA,UAClB,EAAE;AAAA,YACA,GAAG,MAAM,QAAQ,KAAK,CAAC,IAAI,MAAM,WAAW,GAAG,SAAS,uBAAuB,IAAI,6BAA6B,mBAAmB,SAAS,CAAC,EAAE,CAAC;AAAA;AAAA,UAAA;AAAA,QAClJ;AAEF,uBAAe;AAAA,MACjB;AAAA,IACF;AAAA,EAAA,CACD;AAED,SAAO;AACT;AAEO,SAAS,kBAAkB,MAAc,IAAY,MAAc;AACxE,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,UAAU,IAAI;AAClD,QAAI,CAAC,cAAc;AACjB,aAAO,EAAE,KAAA;AAAA,IACX;AACA,WAAO,IAAI,KAAK;AAAA,MACd,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,UAAU;AAAA,MACV,gBAAgB;AAAA,IAAA,CACjB;AAAA,EACH,SAAS,GAAG;AACV,WAAO,EAAE,KAAA;AAAA,EACX;AACF;"}
@@ -0,0 +1 @@
1
+ export {};
@@ -91,7 +91,7 @@ const transformJSX = (element, propsName, file) => {
91
91
  element.node.attributes.push(
92
92
  t.jsxAttribute(
93
93
  t.jsxIdentifier("data-tsd-source"),
94
- t.stringLiteral(`${file}:${line}:${column}`)
94
+ t.stringLiteral(`${file}:${line}:${column + 1}`)
95
95
  )
96
96
  );
97
97
  return true;
@@ -168,6 +168,7 @@ function addSourceToJsx(code, id) {
168
168
  }
169
169
  return gen(ast, {
170
170
  sourceMaps: true,
171
+ retainLines: true,
171
172
  filename: id,
172
173
  sourceFileName: filePath
173
174
  });
@@ -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, 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 getNameOfElement = (\n element: t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName,\n): string => {\n if (element.type === 'JSXIdentifier') {\n return element.name\n }\n if (element.type === 'JSXMemberExpression') {\n return `${getNameOfElement(element.object)}.${getNameOfElement(element.property)}`\n }\n\n return `${element.namespace.name}:${element.name.name}`\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 const nameOfElement = getNameOfElement(element.node.name)\n\n if (nameOfElement === 'Fragment' || nameOfElement === 'React.Fragment') {\n return\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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"}
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 getNameOfElement = (\n element: t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName,\n): string => {\n if (element.type === 'JSXIdentifier') {\n return element.name\n }\n if (element.type === 'JSXMemberExpression') {\n return `${getNameOfElement(element.object)}.${getNameOfElement(element.property)}`\n }\n\n return `${element.namespace.name}:${element.name.name}`\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 const nameOfElement = getNameOfElement(element.node.name)\n\n if (nameOfElement === 'Fragment' || nameOfElement === 'React.Fragment') {\n return\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 + 1}`),\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 retainLines: true,\n filename: id,\n sourceFileName: filePath,\n })\n } catch (e) {\n return { code }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,10 +1,9 @@
1
- import { normalizePath } from "vite";
2
- import chalk from "chalk";
3
1
  import { ServerEventBus } from "@tanstack/devtools-event-bus/server";
4
2
  import { handleDevToolsViteRequest } from "./utils.js";
5
3
  import { DEFAULT_EDITOR_CONFIG, handleOpenSource } from "./editor.js";
6
4
  import { removeDevtools } from "./remove-devtools.js";
7
5
  import { addSourceToJsx } from "./inject-source.js";
6
+ import { enhanceConsoleLog } from "./enhance-logs.js";
8
7
  const defineDevtoolsConfig = (config) => config;
9
8
  const devtools = (args) => {
10
9
  let port = 5173;
@@ -90,27 +89,7 @@ const devtools = (args) => {
90
89
  if (!code.includes("console.")) {
91
90
  return code;
92
91
  }
93
- const lines = code.split("\n");
94
- return lines.map((line, lineNumber) => {
95
- if (line.trim().startsWith("//") || line.trim().startsWith("/**") || line.trim().startsWith("*")) {
96
- return line;
97
- }
98
- if (line.replaceAll(" ", "").includes("=>console.") || line.includes("return console.")) {
99
- return line;
100
- }
101
- const column = line.indexOf("console.");
102
- const location = `${id.replace(normalizePath(process.cwd()), "")}:${lineNumber + 1}:${column + 1}`;
103
- 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 → '`;
104
- if (line.includes("console.log(")) {
105
- const newLine = `console.log(${logMessage},`;
106
- return line.replace("console.log(", newLine);
107
- }
108
- if (line.includes("console.error(")) {
109
- const newLine = `console.error(${logMessage},`;
110
- return line.replace("console.error(", newLine);
111
- }
112
- return line;
113
- }).join("\n");
92
+ return enhanceConsoleLog(code, id, port);
114
93
  }
115
94
  }
116
95
  ];
@@ -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 { removeDevtools } from './remove-devtools'\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 * Whether to remove devtools from the production build.\n * @default true\n */\n removeDevtoolsOnBuild?: boolean\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 removeDevtoolsOnBuild = args?.removeDevtoolsOnBuild ?? 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\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 },\n {\n name: '@tanstack/devtools:remove-devtools-on-build',\n apply(_, { command }) {\n return command === 'build' && removeDevtoolsOnBuild\n },\n enforce: 'pre',\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 removeDevtools(code, id)\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 { ServerEventBus } from '@tanstack/devtools-event-bus/server'\nimport { handleDevToolsViteRequest } from './utils'\nimport { DEFAULT_EDITOR_CONFIG, handleOpenSource } from './editor'\nimport { removeDevtools } from './remove-devtools'\nimport { addSourceToJsx } from './inject-source'\nimport { enhanceConsoleLog } from './enhance-logs'\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 * Whether to remove devtools from the production build.\n * @default true\n */\n removeDevtoolsOnBuild?: boolean\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 removeDevtoolsOnBuild = args?.removeDevtoolsOnBuild ?? 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\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 },\n {\n name: '@tanstack/devtools:remove-devtools-on-build',\n apply(_, { command }) {\n return command === 'build' && removeDevtoolsOnBuild\n },\n enforce: 'pre',\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 removeDevtools(code, id)\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 return enhanceConsoleLog(code, id, port)\n },\n },\n ]\n}\n"],"names":[],"mappings":";;;;;;AA8CO,MAAM,uBAAuB,CAAC,WACnC;AAEK,MAAM,WAAW,CAAC,SAAqD;AAC5E,MAAI,OAAO;AACX,QAAM,qBAAqB,MAAM,gBAAgB,EAAE,SAAS,KAAA;AAC5D,QAAM,qBAAqB,MAAM,gBAAgB,EAAE,SAAS,KAAA;AAC5D,QAAM,wBAAwB,MAAM,yBAAyB;AAC7D,QAAM,MAAM,IAAI,eAAe,MAAM,cAAc;AAEnD,SAAO;AAAA,IACL;AAAA,MACE,SAAS;AAAA,MACT,MAAM;AAAA,MACN,MAAM,QAAQ;AACZ,eAAO,OAAO,SAAS,iBAAiB,mBAAmB;AAAA,MAC7D;AAAA,MACA,UAAU,MAAM,IAAI;AAClB,YACE,GAAG,SAAS,cAAc,KAC1B,GAAG,SAAS,MAAM,KAClB,GAAG,SAAS,MAAM,KAClB,GAAG,SAAS,OAAO;AAEnB,iBAAO;AAET,eAAO,eAAe,MAAM,EAAE;AAAA,MAChC;AAAA,IAAA;AAAA,IAEF;AAAA,MACE,SAAS;AAAA,MACT,MAAM;AAAA,MACN,MAAM,QAAQ;AAEZ,eAAO,OAAO,SAAS;AAAA,MACzB;AAAA,MACA,gBAAgB,QAAQ;AACtB,YAAI,MAAA;AAEJ,eAAO,YAAY,IAAI,CAAC,KAAK,MAAM,SAAS;AAC1C,cAAI,IAAI,OAAO,aAAa,IAAI,OAAO,cAAc,MAAM;AACzD,mBAAO,IAAI,OAAO;AAAA,UACpB;AACA,eAAA;AAAA,QACF,CAAC;AACD,YAAI,OAAO,OAAO,OAAO,MAAM;AAC7B,iBAAO,OAAO,OAAO,OAAO;AAAA,QAC9B;AAEA,eAAO,YAAY,GAAG,aAAa,MAAM;AACvC,iBAAO,OAAO,OAAO,OAAO;AAAA,QAC9B,CAAC;AAED,cAAM,SAAS,MAAM,UAAU;AAC/B,cAAM,eAAqC,OACzC,MACA,SACA,cACG;AACH,cAAI,CAAC,MAAM;AACT;AAAA,UACF;AACA,gBAAM,OAAO,KAAK,MAAM,SAAS,SAAS;AAAA,QAC5C;AACA,eAAO,YAAY;AAAA,UAAI,CAAC,KAAK,KAAK,SAChC,0BAA0B,KAAK,KAAK,MAAM,CAAC,eAAe;AACxD,kBAAM,EAAE,MAAM,QAAA,IAAY;AAC1B,gBAAI,YAAY,eAAe;AAC7B,qBAAO,iBAAiB;AAAA,gBACtB,MAAM,EAAE,MAAM,KAAK,MAAM,KAAA;AAAA,gBACzB;AAAA,cAAA,CACD;AAAA,YACH;AACA;AAAA,UACF,CAAC;AAAA,QAAA;AAAA,MAEL;AAAA,IAAA;AAAA,IAEF;AAAA,MACE,MAAM;AAAA,MACN,MAAM,GAAG,EAAE,WAAW;AACpB,eAAO,YAAY,WAAW;AAAA,MAChC;AAAA,MACA,SAAS;AAAA,MACT,UAAU,MAAM,IAAI;AAClB,YACE,GAAG,SAAS,cAAc,KAC1B,GAAG,SAAS,MAAM,KAClB,GAAG,SAAS,MAAM,KAClB,GAAG,SAAS,OAAO;AAEnB,iBAAO;AAET,eAAO,eAAe,MAAM,EAAE;AAAA,MAChC;AAAA,IAAA;AAAA,IAEF;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,MACT,MAAM,QAAQ;AACZ,eAAO,OAAO,SAAS,iBAAiB,mBAAmB;AAAA,MAC7D;AAAA,MACA,UAAU,MAAM,IAAI;AAElB,YACE,GAAG,SAAS,cAAc,KAC1B,GAAG,SAAS,MAAM,KAClB,GAAG,SAAS,MAAM,KAClB,GAAG,SAAS,OAAO;AAEnB,iBAAO;AAET,YAAI,CAAC,KAAK,SAAS,UAAU,GAAG;AAC9B,iBAAO;AAAA,QACT;AACA,eAAO,kBAAkB,MAAM,IAAI,IAAI;AAAA,MACzC;AAAA,IAAA;AAAA,EACF;AAEJ;"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import { normalizePath } from 'vite'\n// import fs from 'node:fs/promises'\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\n const parsed = parseOpenSourceParam(source)\n if (!parsed) {\n return\n }\n const { file, line, column } = parsed\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\nexport const parseOpenSourceParam = (source: string) => {\n // Capture everything up to the last two colon-separated numeric parts as the file.\n // This supports filenames that may themselves contain colons.\n const parts = source.match(/^(.+):(\\d+):(\\d+)$/)\n\n if (!parts) return null\n\n const [, file, line, column] = parts\n return { file, line, column }\n}\n\n/* export const tryReadFile = async (\n filePath: string\n) => {\n try {\n const data = await fs.readFile(filePath, 'utf-8')\n return data\n } catch (error) {\n\n return null\n }\n}\n\nexport const tryParseJson = (jsonString: string) => {\n try {\n const result = JSON.parse(jsonString)\n return result\n } catch (error) {\n return null\n }\n} */\n"],"names":[],"mappings":";AAKO,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;AAEA,UAAM,SAAS,qBAAqB,MAAM;AAC1C,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AACA,UAAM,EAAE,MAAM,MAAM,OAAA,IAAW;AAE/B,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;AAEO,MAAM,uBAAuB,CAAC,WAAmB;AAGtD,QAAM,QAAQ,OAAO,MAAM,oBAAoB;AAE/C,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,GAAG,MAAM,MAAM,MAAM,IAAI;AAC/B,SAAO,EAAE,MAAM,MAAM,OAAA;AACvB;"}
1
+ {"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import { normalizePath } from 'vite'\n// import fs from 'node:fs/promises'\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\n const source = searchParams.get('source')\n if (!source) {\n return\n }\n\n const parsed = parseOpenSourceParam(source)\n if (!parsed) {\n return\n }\n const { file, line, column } = parsed\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\nexport const parseOpenSourceParam = (source: string) => {\n // Capture everything up to the last two colon-separated numeric parts as the file.\n // This supports filenames that may themselves contain colons.\n const parts = source.match(/^(.+):(\\d+):(\\d+)$/)\n\n if (!parts) return null\n\n const [, file, line, column] = parts\n return { file, line, column }\n}\n\n/* export const tryReadFile = async (\n filePath: string\n) => {\n try {\n const data = await fs.readFile(filePath, 'utf-8')\n return data\n } catch (error) {\n\n return null\n }\n}\n\nexport const tryParseJson = (jsonString: string) => {\n try {\n const result = JSON.parse(jsonString)\n return result\n } catch (error) {\n return null\n }\n} */\n"],"names":[],"mappings":";AAKO,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;AAE9D,UAAM,SAAS,aAAa,IAAI,QAAQ;AACxC,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AAEA,UAAM,SAAS,qBAAqB,MAAM;AAC1C,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AACA,UAAM,EAAE,MAAM,MAAM,OAAA,IAAW;AAE/B,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;AAEO,MAAM,uBAAuB,CAAC,WAAmB;AAGtD,QAAM,QAAQ,OAAO,MAAM,oBAAoB;AAE/C,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,GAAG,MAAM,MAAM,MAAM,IAAI;AAC/B,SAAO,EAAE,MAAM,MAAM,OAAA;AACvB;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/devtools-vite",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "TanStack Vite plugin used to enhance the core devtools with additional functionalities",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -0,0 +1,157 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { enhanceConsoleLog } from './enhance-logs'
3
+
4
+ const removeEmptySpace = (str: string) => {
5
+ return str.replace(/\s/g, '').trim()
6
+ }
7
+
8
+ describe('remove-devtools', () => {
9
+ test('it adds enhanced console.logs to console.log()', () => {
10
+ const output = removeEmptySpace(
11
+ enhanceConsoleLog(
12
+ `
13
+ console.log('This is a log')
14
+ `,
15
+ 'test.jsx',
16
+ 3000,
17
+ ).code,
18
+ )
19
+ expect(
20
+ output.includes(
21
+ 'http://localhost:3000/__tsd/open-source?source=test.jsx',
22
+ ),
23
+ ).toEqual(true)
24
+ expect(output.includes('test.jsx:2:9')).toEqual(true)
25
+ expect(output.includes(removeEmptySpace("'This is a log'"))).toEqual(true)
26
+ })
27
+
28
+ test('it does not add enhanced console.logs to console.log that is not called', () => {
29
+ const output = removeEmptySpace(
30
+ enhanceConsoleLog(
31
+ `
32
+ console.log
33
+ `,
34
+ 'test.jsx',
35
+ 3000,
36
+ ).code,
37
+ )
38
+ expect(output).toBe(
39
+ removeEmptySpace(`
40
+ console.log
41
+ `),
42
+ )
43
+ })
44
+
45
+ test('it does not add enhanced console.logs to console.log that is inside a comment', () => {
46
+ const output = removeEmptySpace(
47
+ enhanceConsoleLog(
48
+ `
49
+ // console.log('This is a log')
50
+ `,
51
+ 'test.jsx',
52
+ 3000,
53
+ ).code,
54
+ )
55
+ expect(output).toBe(
56
+ removeEmptySpace(`
57
+ // console.log('This is a log')
58
+ `),
59
+ )
60
+ })
61
+
62
+ test('it does not add enhanced console.logs to console.log that is inside a multiline comment', () => {
63
+ const output = removeEmptySpace(
64
+ enhanceConsoleLog(
65
+ `
66
+ /*
67
+ console.log('This is a log')
68
+ */
69
+ `,
70
+ 'test.jsx',
71
+ 3000,
72
+ ).code,
73
+ )
74
+ expect(output).toBe(
75
+ removeEmptySpace(`
76
+ /*
77
+ console.log('This is a log')
78
+ */
79
+ `),
80
+ )
81
+ })
82
+
83
+ test('it does not add enhanced console.error to console.error that is inside a comment', () => {
84
+ const output = removeEmptySpace(
85
+ enhanceConsoleLog(
86
+ `
87
+ // console.error('This is a log')
88
+ `,
89
+ 'test.jsx',
90
+ 3000,
91
+ ).code,
92
+ )
93
+ expect(output).toBe(
94
+ removeEmptySpace(`
95
+ // console.error('This is a log')
96
+ `),
97
+ )
98
+ })
99
+
100
+ test('it does not add enhanced console.error to console.error that is inside a multiline comment', () => {
101
+ const output = removeEmptySpace(
102
+ enhanceConsoleLog(
103
+ `
104
+ /*
105
+ console.error('This is a log')
106
+ */
107
+ `,
108
+ 'test.jsx',
109
+ 3000,
110
+ ).code,
111
+ )
112
+ expect(output).toBe(
113
+ removeEmptySpace(`
114
+ /*
115
+ console.error('This is a log')
116
+ */
117
+ `),
118
+ )
119
+ })
120
+
121
+ test('it adds enhanced console.error to console.error()', () => {
122
+ const output = removeEmptySpace(
123
+ enhanceConsoleLog(
124
+ `
125
+ console.error('This is a log')
126
+ `,
127
+ 'test.jsx',
128
+ 3000,
129
+ ).code,
130
+ )
131
+ console.log('output', output)
132
+ expect(
133
+ output.includes(
134
+ 'http://localhost:3000/__tsd/open-source?source=test.jsx',
135
+ ),
136
+ ).toEqual(true)
137
+ expect(output.includes('test.jsx:2:9')).toEqual(true)
138
+ expect(output.includes(removeEmptySpace("'This is a log'"))).toEqual(true)
139
+ })
140
+
141
+ test('it does not add enhanced console.error to console.error that is not called', () => {
142
+ const output = removeEmptySpace(
143
+ enhanceConsoleLog(
144
+ `
145
+ console.log
146
+ `,
147
+ 'test.jsx',
148
+ 3000,
149
+ ).code,
150
+ )
151
+ expect(output).toBe(
152
+ removeEmptySpace(`
153
+ console.log
154
+ `),
155
+ )
156
+ })
157
+ })
@@ -0,0 +1,70 @@
1
+ import chalk from 'chalk'
2
+ import { normalizePath } from 'vite'
3
+ import { gen, parse, t, trav } from './babel'
4
+ import type { types as Babel } from '@babel/core'
5
+ import type { ParseResult } from '@babel/parser'
6
+
7
+ const transform = (
8
+ ast: ParseResult<Babel.File>,
9
+ filePath: string,
10
+ port: number,
11
+ ) => {
12
+ let didTransform = false
13
+
14
+ trav(ast, {
15
+ CallExpression(path) {
16
+ const callee = path.node.callee
17
+ // Match console.log(...) or console.error(...)
18
+ if (
19
+ callee.type === 'MemberExpression' &&
20
+ callee.object.type === 'Identifier' &&
21
+ callee.object.name === 'console' &&
22
+ callee.property.type === 'Identifier' &&
23
+ (callee.property.name === 'log' || callee.property.name === 'error')
24
+ ) {
25
+ const location = path.node.loc
26
+ if (!location) {
27
+ return
28
+ }
29
+ const [lineNumber, column] = [
30
+ location.start.line,
31
+ location.start.column,
32
+ ]
33
+ const finalPath = `${filePath}:${lineNumber}:${column + 1}`
34
+ path.node.arguments.unshift(
35
+ t.stringLiteral(
36
+ `${chalk.magenta('LOG')} ${chalk.blueBright(`${finalPath} - http://localhost:${port}/__tsd/open-source?source=${encodeURIComponent(finalPath)}`)}\n → `,
37
+ ),
38
+ )
39
+ didTransform = true
40
+ }
41
+ },
42
+ })
43
+
44
+ return didTransform
45
+ }
46
+
47
+ export function enhanceConsoleLog(code: string, id: string, port: number) {
48
+ const [filePath] = id.split('?')
49
+ // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
50
+ const location = filePath?.replace(normalizePath(process.cwd()), '')!
51
+
52
+ try {
53
+ const ast = parse(code, {
54
+ sourceType: 'module',
55
+ plugins: ['jsx', 'typescript'],
56
+ })
57
+ const didTransform = transform(ast, location, port)
58
+ if (!didTransform) {
59
+ return { code }
60
+ }
61
+ return gen(ast, {
62
+ sourceMaps: true,
63
+ retainLines: true,
64
+ filename: id,
65
+ sourceFileName: filePath,
66
+ })
67
+ } catch (e) {
68
+ return { code }
69
+ }
70
+ }
@@ -79,7 +79,7 @@ describe('inject source', () => {
79
79
  expect(output).toBe(
80
80
  removeEmptySpace(`
81
81
  export const Route = createFileRoute("/test")({
82
- component: function() { return <div data-tsd-source="test.jsx:3:37">Hello World</div>; }
82
+ component: function() { return <div data-tsd-source="test.jsx:3:38">Hello World</div>; }
83
83
  });
84
84
  `),
85
85
  )
@@ -159,7 +159,7 @@ describe('inject source', () => {
159
159
  expect(output).toBe(
160
160
  removeEmptySpace(`
161
161
  export const Route = createFileRoute("/test")({
162
- component: function({...rest}) { return <div data-tsd-source="test.jsx:3:46"><div {...rest}>Hello World</div></div>; }
162
+ component: function({...rest}) { return <div data-tsd-source="test.jsx:3:47"><div {...rest}>Hello World</div></div>; }
163
163
  });
164
164
  `),
165
165
  )
@@ -181,7 +181,7 @@ describe('inject source', () => {
181
181
  expect(output).toBe(
182
182
  removeEmptySpace(`
183
183
  export const Route = createFileRoute("/test")({
184
- component: () => <div data-tsd-source="test.jsx:3:23">Hello World</div>
184
+ component: () => <div data-tsd-source="test.jsx:3:24">Hello World</div>
185
185
  });
186
186
  `),
187
187
  )
@@ -261,7 +261,7 @@ describe('inject source', () => {
261
261
  expect(output).toBe(
262
262
  removeEmptySpace(`
263
263
  export const Route = createFileRoute("/test")({
264
- component: ({...rest}) => <div data-tsd-source="test.jsx:3:32"><div {...rest}>Hello World</div></div>
264
+ component: ({...rest}) => <div data-tsd-source="test.jsx:3:33"><div {...rest}>Hello World</div></div>
265
265
  });
266
266
  `),
267
267
  )
@@ -286,7 +286,7 @@ describe('inject source', () => {
286
286
  removeEmptySpace(`
287
287
  function Parent({ ...props }) {
288
288
  function Child({ ...props }) {
289
- return <div data-tsd-source="test.jsx:4:17" />;
289
+ return <div data-tsd-source="test.jsx:4:18" />;
290
290
  }
291
291
  return <Child {...props} />;
292
292
  }
@@ -312,7 +312,7 @@ function test({...props }) {
312
312
  import Custom from "external";
313
313
 
314
314
  function test({...props }) {
315
- return <Custom children={props.children} data-tsd-source="test.tsx:6:9" />;
315
+ return <Custom children={props.children} data-tsd-source="test.tsx:6:10" />;
316
316
  }`),
317
317
  )
318
318
  })
@@ -330,7 +330,7 @@ function test({...props }) {
330
330
  expect(output).toBe(
331
331
  removeEmptySpace(`
332
332
  function test(props) {
333
- return <button children={props.children} data-tsd-source="test.jsx:3:15" />;
333
+ return <button children={props.children} data-tsd-source="test.jsx:3:16" />;
334
334
  }
335
335
  `),
336
336
  )
@@ -372,7 +372,7 @@ function test(props) {
372
372
  expect(output).toBe(
373
373
  removeEmptySpace(`
374
374
  function test(props) {
375
- return <div data-tsd-source="test.jsx:3:16">
375
+ return <div data-tsd-source="test.jsx:3:17">
376
376
  <button {...props} />
377
377
  </div>;
378
378
  }
@@ -396,7 +396,7 @@ function test(props) {
396
396
  expect(output).toBe(
397
397
  removeEmptySpace(`
398
398
  function test({...props}) {
399
- return <div data-tsd-source="test.jsx:3:16">
399
+ return <div data-tsd-source="test.jsx:3:17">
400
400
  <button {...props} />
401
401
  </div>;
402
402
  }
@@ -420,7 +420,7 @@ function test({...props}) {
420
420
  expect(output).toBe(
421
421
  removeEmptySpace(`
422
422
  function test({...rest}) {
423
- return <div data-tsd-source="test.jsx:3:16">
423
+ return <div data-tsd-source="test.jsx:3:17">
424
424
  <button {...rest} />
425
425
  </div>;
426
426
  }
@@ -462,7 +462,7 @@ function test({ children, ...rest }) {
462
462
  expect(output).toBe(
463
463
  removeEmptySpace(`
464
464
  function test({ ...props }) {
465
- return <button children={props.children} data-tsd-source="test.jsx:3:15" />;
465
+ return <button children={props.children} data-tsd-source="test.jsx:3:16" />;
466
466
  }
467
467
  `),
468
468
  )
@@ -502,7 +502,7 @@ function test({ children, ...rest }) {
502
502
  expect(output).toBe(
503
503
  removeEmptySpace(`
504
504
  function test({ ...props }) {
505
- return <CustomButton children={props.children} data-tsd-source="test.jsx:3:15" />;
505
+ return <CustomButton children={props.children} data-tsd-source="test.jsx:3:16" />;
506
506
  }
507
507
  `),
508
508
  )
@@ -542,7 +542,7 @@ function test({ children, ...rest }) {
542
542
  expect(output).toBe(
543
543
  removeEmptySpace(`
544
544
  function test({ ...props }) {
545
- return <CustomButton children={props.children} data-tsd-source="test.jsx:3:15" />;
545
+ return <CustomButton children={props.children} data-tsd-source="test.jsx:3:16" />;
546
546
  }
547
547
  `),
548
548
  )
@@ -563,7 +563,7 @@ function test({ children, ...rest }) {
563
563
  expect(output).toBe(
564
564
  removeEmptySpace(`
565
565
  const ButtonWithProps = function test(props) {
566
- return <button children={props.children} data-tsd-source="test.jsx:3:15" />;
566
+ return <button children={props.children} data-tsd-source="test.jsx:3:16" />;
567
567
  };
568
568
  `),
569
569
  )
@@ -605,7 +605,7 @@ function test({ children, ...rest }) {
605
605
  expect(output).toBe(
606
606
  removeEmptySpace(`
607
607
  const ButtonWithProps = function test(props) {
608
- return <div data-tsd-source="test.jsx:3:16">
608
+ return <div data-tsd-source="test.jsx:3:17">
609
609
  <button {...props} />
610
610
  </div>;
611
611
  };
@@ -629,7 +629,7 @@ function test({ children, ...rest }) {
629
629
  expect(output).toBe(
630
630
  removeEmptySpace(`
631
631
  const ButtonWithProps = function test({...props}) {
632
- return <div data-tsd-source="test.jsx:3:16">
632
+ return <div data-tsd-source="test.jsx:3:17">
633
633
  <button {...props} />
634
634
  </div>;
635
635
  };
@@ -653,7 +653,7 @@ function test({ children, ...rest }) {
653
653
  expect(output).toBe(
654
654
  removeEmptySpace(`
655
655
  const ButtonWithProps = function test({...rest}) {
656
- return <div data-tsd-source="test.jsx:3:16">
656
+ return <div data-tsd-source="test.jsx:3:17">
657
657
  <button {...rest} />
658
658
  </div>;
659
659
  };
@@ -695,7 +695,7 @@ function test({ children, ...rest }) {
695
695
  expect(output).toBe(
696
696
  removeEmptySpace(`
697
697
  const ButtonWithProps = function test({ ...props }) {
698
- return <button children={props.children} data-tsd-source="test.jsx:3:15" />;
698
+ return <button children={props.children} data-tsd-source="test.jsx:3:16" />;
699
699
  };
700
700
  `),
701
701
  )
@@ -735,7 +735,7 @@ function test({ children, ...rest }) {
735
735
  expect(output).toBe(
736
736
  removeEmptySpace(`
737
737
  const ButtonWithProps = function test({ ...props }) {
738
- return <CustomButton children={props.children} data-tsd-source="test.jsx:3:15" />;
738
+ return <CustomButton children={props.children} data-tsd-source="test.jsx:3:16" />;
739
739
  };
740
740
  `),
741
741
  )
@@ -775,7 +775,7 @@ function test({ children, ...rest }) {
775
775
  expect(output).toBe(
776
776
  removeEmptySpace(`
777
777
  export const ButtonWithProps = function test({ ...props }) {
778
- return <CustomButton children={props.children} data-tsd-source="test.jsx:3:15" />;
778
+ return <CustomButton children={props.children} data-tsd-source="test.jsx:3:16" />;
779
779
  };
780
780
  `),
781
781
  )
@@ -795,8 +795,8 @@ function test({ children, ...rest }) {
795
795
  )
796
796
  expect(output).toBe(
797
797
  removeEmptySpace(`
798
- const ButtonWithProps = props => {
799
- return <button children={props.children} data-tsd-source="test.jsx:3:15" />;
798
+ const ButtonWithProps = (props) => {
799
+ return <button children={props.children} data-tsd-source="test.jsx:3:16" />;
800
800
  };
801
801
  `),
802
802
  )
@@ -837,8 +837,8 @@ function test({ children, ...rest }) {
837
837
  )
838
838
  expect(output).toBe(
839
839
  removeEmptySpace(`
840
- const ButtonWithProps = props => {
841
- return <div data-tsd-source="test.jsx:3:16">
840
+ const ButtonWithProps = (props) => {
841
+ return <div data-tsd-source="test.jsx:3:17">
842
842
  <button {...props} />
843
843
  </div>;
844
844
  };
@@ -862,7 +862,7 @@ function test({ children, ...rest }) {
862
862
  expect(output).toBe(
863
863
  removeEmptySpace(`
864
864
  const ButtonWithProps = ({...props}) => {
865
- return <div data-tsd-source="test.jsx:3:16">
865
+ return <div data-tsd-source="test.jsx:3:17">
866
866
  <button {...props} />
867
867
  </div>;
868
868
  };
@@ -886,7 +886,7 @@ function test({ children, ...rest }) {
886
886
  expect(output).toBe(
887
887
  removeEmptySpace(`
888
888
  const ButtonWithProps = ({...rest}) => {
889
- return <div data-tsd-source= "test.jsx:3:16">
889
+ return <div data-tsd-source= "test.jsx:3:17">
890
890
  <button {...rest} />
891
891
  </div>;
892
892
  };
@@ -908,7 +908,7 @@ function test({ children, ...rest }) {
908
908
  expect(output).toBe(
909
909
  removeEmptySpace(`
910
910
  const ButtonWithProps = ({ children, ...rest }) => {
911
- return <button children={children} data-tsd-source="test.jsx:3:15" />;
911
+ return <button children={children} data-tsd-source="test.jsx:3:16" />;
912
912
  };
913
913
  `),
914
914
  )
@@ -928,7 +928,7 @@ function test({ children, ...rest }) {
928
928
  expect(output).toBe(
929
929
  removeEmptySpace(`
930
930
  const ButtonWithProps = ({ ...props }) => {
931
- return <button children={props.children} data-tsd-source="test.jsx:3:15" />;
931
+ return <button children={props.children} data-tsd-source="test.jsx:3:16" />;
932
932
  };
933
933
  `),
934
934
  )
@@ -968,7 +968,7 @@ function test({ children, ...rest }) {
968
968
  expect(output).toBe(
969
969
  removeEmptySpace(`
970
970
  const ButtonWithProps = ({ ...props }) => {
971
- return <CustomButton children={props.children} data-tsd-source="test.jsx:3:15" />;
971
+ return <CustomButton children={props.children} data-tsd-source="test.jsx:3:16" />;
972
972
  };
973
973
  `),
974
974
  )
@@ -1008,7 +1008,7 @@ function test({ children, ...rest }) {
1008
1008
  expect(output).toBe(
1009
1009
  removeEmptySpace(`
1010
1010
  export const ButtonWithProps = ({ ...props }) => {
1011
- return <CustomButton children={props.children} data-tsd-source="test.jsx:3:15" />;
1011
+ return <CustomButton children={props.children} data-tsd-source="test.jsx:3:16" />;
1012
1012
  };
1013
1013
  `),
1014
1014
  )
@@ -144,7 +144,7 @@ const transformJSX = (
144
144
  element.node.attributes.push(
145
145
  t.jsxAttribute(
146
146
  t.jsxIdentifier('data-tsd-source'),
147
- t.stringLiteral(`${file}:${line}:${column}`),
147
+ t.stringLiteral(`${file}:${line}:${column + 1}`),
148
148
  ),
149
149
  )
150
150
 
@@ -232,6 +232,7 @@ export function addSourceToJsx(code: string, id: string) {
232
232
  }
233
233
  return gen(ast, {
234
234
  sourceMaps: true,
235
+ retainLines: true,
235
236
  filename: id,
236
237
  sourceFileName: filePath,
237
238
  })
package/src/plugin.ts CHANGED
@@ -1,10 +1,9 @@
1
- import { normalizePath } from 'vite'
2
- import chalk from 'chalk'
3
1
  import { ServerEventBus } from '@tanstack/devtools-event-bus/server'
4
2
  import { handleDevToolsViteRequest } from './utils'
5
3
  import { DEFAULT_EDITOR_CONFIG, handleOpenSource } from './editor'
6
4
  import { removeDevtools } from './remove-devtools'
7
5
  import { addSourceToJsx } from './inject-source'
6
+ import { enhanceConsoleLog } from './enhance-logs'
8
7
  import type { EditorConfig } from './editor'
9
8
  import type { ServerEventBusConfig } from '@tanstack/devtools-event-bus/server'
10
9
  import type { Plugin } from 'vite'
@@ -160,38 +159,7 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
160
159
  if (!code.includes('console.')) {
161
160
  return code
162
161
  }
163
- const lines = code.split('\n')
164
- return lines
165
- .map((line, lineNumber) => {
166
- if (
167
- line.trim().startsWith('//') ||
168
- line.trim().startsWith('/**') ||
169
- line.trim().startsWith('*')
170
- ) {
171
- return line
172
- }
173
- // Do not add for arrow functions or return statements
174
- if (
175
- line.replaceAll(' ', '').includes('=>console.') ||
176
- line.includes('return console.')
177
- ) {
178
- return line
179
- }
180
-
181
- const column = line.indexOf('console.')
182
- const location = `${id.replace(normalizePath(process.cwd()), '')}:${lineNumber + 1}:${column + 1}`
183
- 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 → '`
184
- if (line.includes('console.log(')) {
185
- const newLine = `console.log(${logMessage},`
186
- return line.replace('console.log(', newLine)
187
- }
188
- if (line.includes('console.error(')) {
189
- const newLine = `console.error(${logMessage},`
190
- return line.replace('console.error(', newLine)
191
- }
192
- return line
193
- })
194
- .join('\n')
162
+ return enhanceConsoleLog(code, id, port)
195
163
  },
196
164
  },
197
165
  ]
package/src/utils.ts CHANGED
@@ -11,6 +11,7 @@ export const handleDevToolsViteRequest = (
11
11
  ) => {
12
12
  if (req.url?.includes('__tsd/open-source')) {
13
13
  const searchParams = new URLSearchParams(req.url.split('?')[1])
14
+
14
15
  const source = searchParams.get('source')
15
16
  if (!source) {
16
17
  return