Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
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
2 changes: 2 additions & 0 deletions packages/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
},
"//": "READ CONTRIBUTING.md to understand what to put under deps vs. devDeps!",
"dependencies": {
"@shikijs/cli": "^3.8.1",
"body-parser": "^2.2.0",
"esbuild": "^0.25.0",
"fdir": "^6.4.6",
"picomatch": "^4.0.3",
Expand Down
15 changes: 15 additions & 0 deletions packages/vite/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,21 @@ if (typeof window !== 'undefined') {
window.addEventListener?.('beforeunload', () => {
willUnload = true
})
window.addEventListener('error', (error) => {
fetch('/@vite/errors', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
stack: error.error.stack,
filename: error.filename,
message: error.message,
colno: error.colno,
lineno: error.lineno,
})
})
})
}

function cleanUrl(pathname: string): string {
Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/node/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export const SPECIAL_QUERY_RE = /[?&](?:worker|sharedworker|raw|url)\b/
export const FS_PREFIX = `/@fs/`

export const CLIENT_PUBLIC_PATH = `/@vite/client`
export const CLIENT_ERROR_RELAY_PATH = '/@vite/errors'
export const ENV_PUBLIC_PATH = `/@vite/env`
export const VITE_PACKAGE_DIR = resolve(
fileURLToPath(import.meta.url),
Expand Down
6 changes: 5 additions & 1 deletion packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@
import { ModuleGraph } from './mixedModuleGraph'
import type { ModuleNode } from './mixedModuleGraph'
import { notFoundMiddleware } from './middlewares/notFound'
import { errorMiddleware } from './middlewares/error'

Check failure on line 95 in packages/vite/src/node/server/index.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

'errorMiddleware' is defined but never used. Allowed unused vars must match /^_/u
import { errorIngestMiddleware } from './middlewares/errorIngest'
import type { HmrOptions, NormalizedHotChannel } from './hmr'
import { handleHMRUpdate, updateModules } from './hmr'
import { openBrowser as _openBrowser } from './openBrowser'
Expand Down Expand Up @@ -910,6 +911,9 @@
// open in editor support
middlewares.use('/__open-in-editor', launchEditorMiddleware())

// error ingest handler
middlewares.use(errorIngestMiddleware(server))

// ping request handler
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
middlewares.use(function viteHMRPingMiddleware(req, res, next) {
Expand Down Expand Up @@ -954,7 +958,7 @@
}

// error handler
middlewares.use(errorMiddleware(server, !!middlewareMode))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This removal was accidental, will fix it!

middlewares.use(errorIngestMiddleware(server))

// httpServer.listen can be called multiple times
// when port when using next port number
Expand Down
67 changes: 67 additions & 0 deletions packages/vite/src/node/server/middlewares/errorIngest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import path from 'node:path'

Check failure on line 1 in packages/vite/src/node/server/middlewares/errorIngest.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

'path' is defined but never used. Allowed unused vars must match /^_/u
import repl from 'node:repl'

Check failure on line 2 in packages/vite/src/node/server/middlewares/errorIngest.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

'repl' is defined but never used. Allowed unused vars must match /^_/u
import { readFileSync } from 'fs';

Check failure on line 3 in packages/vite/src/node/server/middlewares/errorIngest.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

Prefer `node:fs` over `fs`

Check failure on line 3 in packages/vite/src/node/server/middlewares/errorIngest.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

'readFileSync' is defined but never used. Allowed unused vars must match /^_/u
import { codeToANSI } from '@shikijs/cli';

import { stripVTControlCharacters as strip } from 'node:util'

Check failure on line 6 in packages/vite/src/node/server/middlewares/errorIngest.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

'strip' is defined but never used. Allowed unused vars must match /^_/u

Check failure on line 6 in packages/vite/src/node/server/middlewares/errorIngest.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

`node:util` import should occur before import of `@shikijs/cli`
import colors from 'picocolors'
import type { RollupError } from 'rollup'

Check failure on line 8 in packages/vite/src/node/server/middlewares/errorIngest.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

'RollupError' is defined but never used. Allowed unused vars must match /^_/u
import bodyParser from 'body-parser'
import type { Connect } from 'dep-types/connect'
import type { ErrorPayload } from 'types/hmrPayload'

Check failure on line 11 in packages/vite/src/node/server/middlewares/errorIngest.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

'ErrorPayload' is defined but never used. Allowed unused vars must match /^_/u
import { pad } from '../../utils'

Check failure on line 12 in packages/vite/src/node/server/middlewares/errorIngest.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

'pad' is defined but never used. Allowed unused vars must match /^_/u
import type { ViteDevServer } from '../..'
import { CLIENT_ERROR_RELAY_PATH } from '../../constants'

export function logBrowserError(
server: ViteDevServer,
stack: string,
fragment: string
): void {
const msg = `${colors.magenta('[browser]')} ${colors.red(stack)}`
server.config.logger.error(`${msg}\n\n${fragment}`, {
clear: true,
timestamp: true,
})
}

export function errorIngestMiddleware(
server: ViteDevServer,
): Connect.ErrorHandleFunction {
const jsonParser = bodyParser.json()
return function viteErrorIngestMiddleware(req, res, next) {
if (req.url === CLIENT_ERROR_RELAY_PATH) {
jsonParser(req, res, async () => {
const fragment = await getErrorFragment(req.body)
logBrowserError(server, req.body.stack, fragment)
res.statusCode = 201
res.end('')
})
} else {
next()
}
}
}

async function getErrorFragment (info) {
const res = await fetch(info.filename)
const source = await res.text()
const filtered = source.split(/\r?\n/g)
.map((line, i) => [i, line])
.slice(Math.max(1, info.lineno - 2), info.lineno + 3)
.filter(([_,line]) => !line.startsWith('//# sourceMappingURL'))
let fragment = ""
let padding = String(info.lineno).length

Check warning on line 54 in packages/vite/src/node/server/middlewares/errorIngest.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

'padding' is never reassigned. Use 'const' instead
for (const [lineno, line] of filtered) {
fragment += `${
lineno === (info.lineno - 1) ? colors.red('>') : ' '
}${String(lineno).padStart(padding)} | ${
await codeToANSI(line, 'typescript', 'slack-dark')
}`
if (lineno === (info.lineno - 1)) {
const leftPadding = Math.max(0, (info.colno - 1) + padding + 5)
fragment += `${new Array(leftPadding).fill('').join(' ')}${colors.red('^')}\n`
}
}
return fragment
}
2 changes: 2 additions & 0 deletions packages/vite/src/node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { VALID_ID_PREFIX } from '../shared/constants'
import {
CLIENT_ENTRY,
CLIENT_PUBLIC_PATH,
CLIENT_ERROR_RELAY_PATH,
CSS_LANGS_RE,
ENV_PUBLIC_PATH,
FS_PREFIX,
Expand Down Expand Up @@ -326,6 +327,7 @@ const internalPrefixes = [
FS_PREFIX,
VALID_ID_PREFIX,
CLIENT_PUBLIC_PATH,
CLIENT_ERROR_RELAY_PATH,
ENV_PUBLIC_PATH,
]
const InternalPrefixRE = new RegExp(`^(?:${internalPrefixes.join('|')})`)
Expand Down
5 changes: 5 additions & 0 deletions playground/client-errors/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<body>
<h4>Test Client Error Ingestion</h4>
<input type="button" value="Create error">
<script type="module" src="/index.js"></script>
</body>
8 changes: 8 additions & 0 deletions playground/client-errors/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

const input = document.querySelector('input')

input.addEventListener('click', handleClick)

function handleClick () {
throw new Error('Something went wrong')
}
15 changes: 15 additions & 0 deletions playground/client-errors/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "@vitejs/test-client-errors",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"debug": "node --inspect-brk ../../packages/vite/bin/vite",
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "workspace:^"
}
}
5 changes: 5 additions & 0 deletions playground/client-errors/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineConfig } from 'vite'

export default defineConfig({
server: {},
})
46 changes: 26 additions & 20 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading