Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions packages/devtools-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"devtools"
],
"type": "module",
"types": "dist/esm//index.d.ts",
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have also fixed this // that was there for like a decade

"types": "dist/esm/index.d.ts",
"module": "dist/esm/index.js",
"exports": {
".": {
Expand Down Expand Up @@ -56,21 +56,14 @@
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
},
"dependencies": {
"@babel/core": "^7.28.4",
"@babel/generator": "^7.28.3",
"@babel/parser": "^7.28.4",
"@babel/traverse": "^7.28.4",
"@babel/types": "^7.28.4",
"@tanstack/devtools-client": "workspace:*",
"@tanstack/devtools-event-bus": "workspace:*",
"chalk": "^5.6.2",
"launch-editor": "^2.11.1",
"oxc-parser": "0.120.0",
"picomatch": "^4.0.3"
},
"devDependencies": {
"@types/babel__core": "^7.20.5",
"@types/babel__generator": "^7.27.0",
"@types/babel__traverse": "^7.28.0",
"@types/picomatch": "^4.0.2",
"happy-dom": "^18.0.1"
}
Expand Down
18 changes: 0 additions & 18 deletions packages/devtools-vite/src/babel.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/devtools-vite/src/enhance-logs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('enhance-logs', () => {
test('it does not add enhanced console.logs to console.log that is not called', () => {
const output = enhanceConsoleLog(
`
console.log
console.log
`,
'test.jsx',
3000,
Expand Down Expand Up @@ -106,7 +106,7 @@ describe('enhance-logs', () => {
test('it does not add enhanced console.error to console.error that is not called', () => {
const output = enhanceConsoleLog(
`
console.log
console.log
`,
'test.jsx',
3000,
Expand Down
208 changes: 124 additions & 84 deletions packages/devtools-vite/src/enhance-logs.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,73 @@
import chalk from 'chalk'
import { normalizePath } from 'vite'
import { gen, parse, t, trav } from './babel'
import type { types as Babel } from '@babel/core'
import type { ParseResult } from '@babel/parser'

const transform = (
ast: ParseResult<Babel.File>,
filePath: string,
port: number,
) => {
let didTransform = false

trav(ast, {
CallExpression(path) {
const callee = path.node.callee
// Match console.log(...) or console.error(...)
if (
callee.type === 'MemberExpression' &&
callee.object.type === 'Identifier' &&
callee.object.name === 'console' &&
callee.property.type === 'Identifier' &&
(callee.property.name === 'log' || callee.property.name === 'error')
) {
const location = path.node.loc
if (!location) {
return
}
const [lineNumber, column] = [
location.start.line,
location.start.column,
]
const finalPath = `${filePath}:${lineNumber}:${column + 1}`
const logMessage = `${chalk.magenta('LOG')} ${chalk.blueBright(`${finalPath}`)}\n β†’ `

const serverLogMessage = t.arrayExpression([
t.stringLiteral(logMessage),
])
const browserLogMessage = t.arrayExpression([
// LOG with css formatting specifiers: %c
t.stringLiteral(
`%c${'LOG'}%c %c${`Go to Source: http://localhost:${port}/__tsd/open-source?source=${encodeURIComponent(finalPath)}`}%c \n β†’ `,
),
// magenta
t.stringLiteral('color:#A0A'),
t.stringLiteral('color:#FFF'),
// blueBright
t.stringLiteral('color:#55F'),
t.stringLiteral('color:#FFF'),
])

// typeof window === "undefined"
const checkServerCondition = t.binaryExpression(
'===',
t.unaryExpression('typeof', t.identifier('window')),
t.stringLiteral('undefined'),
)

// ...(isServer ? serverLogMessage : browserLogMessage)
path.node.arguments.unshift(
t.spreadElement(
t.conditionalExpression(
checkServerCondition,
serverLogMessage,
browserLogMessage,
),
),
)

didTransform = true
}
},
})

return didTransform
import { Visitor, parseSync } from 'oxc-parser'
import type { CallExpression, MemberExpression } from 'oxc-parser'

type Insertion = {
at: number
text: string
}

const buildLineStarts = (source: string) => {
const starts = [0]
for (let i = 0; i < source.length; i++) {
if (source[i] === '\n') {
starts.push(i + 1)
}
}
return starts
}

const offsetToLineColumn = (offset: number, lineStarts: Array<number>) => {
// Binary search to find the nearest line start <= offset.
let low = 0
let high = lineStarts.length - 1

while (low <= high) {
const mid = (low + high) >> 1
const lineStart = lineStarts[mid]
if (lineStart === undefined) {
break
}

if (lineStart <= offset) {
low = mid + 1
} else {
high = mid - 1
}
}

const lineIndex = Math.max(0, high)
const lineStart = lineStarts[lineIndex] ?? 0

return {
line: lineIndex + 1,
column: offset - lineStart + 1,
}
}

const isConsoleMemberExpression = (
callee: CallExpression['callee'],
): callee is MemberExpression => {
return (
callee.type === 'MemberExpression' &&
callee.computed === false &&
callee.object.type === 'Identifier' &&
callee.object.name === 'console' &&
callee.property.type === 'Identifier' &&
(callee.property.name === 'log' || callee.property.name === 'error')
)
}

const applyInsertions = (source: string, insertions: Array<Insertion>) => {
const ordered = [...insertions].sort((a, b) => b.at - a.at)

let next = source
for (const insertion of ordered) {
next = next.slice(0, insertion.at) + insertion.text + next.slice(insertion.at)
}

return next
}

export function enhanceConsoleLog(code: string, id: string, port: number) {
Expand All @@ -81,21 +76,66 @@ export function enhanceConsoleLog(code: string, id: string, port: number) {
const location = filePath?.replace(normalizePath(process.cwd()), '')!

try {
const ast = parse(code, {
const result = parseSync(filePath ?? id, code, {
sourceType: 'module',
plugins: ['jsx', 'typescript'],
lang: 'tsx',
range: true,
})
const didTransform = transform(ast, location, port)
if (!didTransform) {

if (result.errors.length > 0) {
return
}
return gen(ast, {
sourceMaps: true,
retainLines: true,
filename: id,
sourceFileName: filePath,
})
} catch (e) {

const insertions: Array<Insertion> = []
const lineStarts = buildLineStarts(code)

new Visitor({
CallExpression(node) {
if (!isConsoleMemberExpression(node.callee)) {
return
}

const { line, column } = offsetToLineColumn(node.start, lineStarts)
const finalPath = `${location}:${line}:${column}`

const serverLogMessage = `${chalk.magenta('LOG')} ${chalk.blueBright(finalPath)}\n β†’ `
const browserLogMessage = `%cLOG%c %cGo to Source: http://localhost:${port}/__tsd/open-source?source=${encodeURIComponent(
finalPath,
)}%c \n β†’ `
Comment on lines +101 to +104
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Preserve the console.error() severity in the injected prefix.

Lines 101-104 hardcode LOG, so transformed console.error() calls are labeled as logs in both the terminal and browser output.

✏️ Proposed fix
+        const levelLabel =
+          node.callee.property.name === 'error' ? 'ERROR' : 'LOG'
+
-        const serverLogMessage = `${chalk.magenta('LOG')} ${chalk.blueBright(finalPath)}\n β†’ `
-        const browserLogMessage = `%cLOG%c %cGo to Source: http://localhost:${port}/__tsd/open-source?source=${encodeURIComponent(
+        const serverLogMessage = `${chalk.magenta(levelLabel)} ${chalk.blueBright(finalPath)}\n β†’ `
+        const browserLogMessage = `%c${levelLabel}%c %cGo to Source: http://localhost:${port}/__tsd/open-source?source=${encodeURIComponent(
           finalPath,
         )}%c \n β†’ `
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-vite/src/enhance-logs.ts` around lines 101 - 104,
serverLogMessage and browserLogMessage hardcode the label "LOG", so transformed
console.error/console.warn/etc. calls lose their original severity; update the
prefix construction to use the original console method name (e.g., the variable
capturing the transformed call like method/type/severity) instead of the literal
"LOG", and apply the appropriate styling/color mapping for that severity (use
chalk for serverLogMessage and the %c tokens/color strings for
browserLogMessage) so console.error remains labeled and styled as ERROR,
console.warn as WARN, etc., while still including finalPath and the open-source
URL.


const argsArray =
`[${JSON.stringify(serverLogMessage)}]` +
` : [${JSON.stringify(browserLogMessage)},` +
`${JSON.stringify('color:#A0A')},` +
`${JSON.stringify('color:#FFF')},` +
`${JSON.stringify('color:#55F')},` +
`${JSON.stringify('color:#FFF')}]`

const injectedPrefix =
`...(typeof window === 'undefined' ? ${argsArray})` +
`${node.arguments.length > 0 ? ', ' : ''}`

const insertionPoint =
node.arguments[0]?.start !== undefined
? node.arguments[0].start
: node.end - 1

insertions.push({
at: insertionPoint,
text: injectedPrefix,
})
},
}).visit(result.program)

if (insertions.length === 0) {
return
}

return {
code: applyInsertions(code, insertions),
map: null,
}
} catch {
return
}
}
Loading