refactor(nuxt): use errx to handle dev log traces (#28027)

This commit is contained in:
Daniel Roe 2024-07-18 15:59:50 +01:00 committed by GitHub
parent 242b4710ce
commit ffcb5dc3d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 57 additions and 33 deletions

View File

@ -31,6 +31,7 @@
"consola": "^3.2.3", "consola": "^3.2.3",
"defu": "^6.1.4", "defu": "^6.1.4",
"destr": "^2.0.3", "destr": "^2.0.3",
"errx": "^0.1.0",
"globby": "^14.0.2", "globby": "^14.0.2",
"hash-sum": "^2.0.0", "hash-sum": "^2.0.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",

View File

@ -1,6 +1,7 @@
import { pathToFileURL } from 'node:url' import { fileURLToPath, pathToFileURL } from 'node:url'
import { interopDefault, resolvePath, resolvePathSync } from 'mlly' import { interopDefault, resolvePath, resolvePathSync } from 'mlly'
import { createJiti } from 'jiti' import { createJiti } from 'jiti'
import { captureStackTrace } from 'errx'
export interface ResolveModuleOptions { export interface ResolveModuleOptions {
paths?: string | string[] paths?: string | string[]
@ -48,10 +49,12 @@ const warnings = new Set<string>()
* @deprecated Please use `importModule` instead. * @deprecated Please use `importModule` instead.
*/ */
export function requireModule<T = unknown> (id: string, opts?: ImportModuleOptions) { export function requireModule<T = unknown> (id: string, opts?: ImportModuleOptions) {
if (!warnings.has(id)) { const { source, line, column } = captureStackTrace().find(entry => entry.source !== import.meta.url) ?? {}
// TODO: add more information on stack trace const explanation = source ? ` (used at \`${fileURLToPath(source)}:${line}:${column}\`)` : ''
console.warn('[@nuxt/kit] `requireModule` is deprecated. Please use `importModule` instead.') const warning = `[@nuxt/kit] \`requireModule\` is deprecated${explanation}. Please use \`importModule\` instead.`
warnings.add(id) if (!warnings.has(warning)) {
console.warn(warning)
warnings.add(warning)
} }
const resolvedPath = resolveModule(id, opts) const resolvedPath = resolveModule(id, opts)
const jiti = createJiti(import.meta.url, { const jiti = createJiti(import.meta.url, {

View File

@ -125,6 +125,7 @@
"@types/estree": "1.0.5", "@types/estree": "1.0.5",
"@vitejs/plugin-vue": "5.0.5", "@vitejs/plugin-vue": "5.0.5",
"@vue/compiler-sfc": "3.4.31", "@vue/compiler-sfc": "3.4.31",
"errx": "^0.1.0",
"unbuild": "3.0.0-rc.6", "unbuild": "3.0.0-rc.6",
"vite": "5.3.4", "vite": "5.3.4",
"vitest": "2.0.3" "vitest": "2.0.3"

View File

@ -1,6 +1,7 @@
import { createConsola } from 'consola' import { createConsola } from 'consola'
import type { LogObject } from 'consola' import type { LogObject } from 'consola'
import { parse } from 'devalue' import { parse } from 'devalue'
import type { ParsedTrace } from 'errx'
import { h } from 'vue' import { h } from 'vue'
import { defineNuxtPlugin } from '../nuxt' import { defineNuxtPlugin } from '../nuxt'
@ -45,15 +46,24 @@ export default defineNuxtPlugin(async (nuxtApp) => {
} }
}) })
function normalizeFilenames (stack?: string) { function normalizeFilenames (stack?: ParsedTrace[]) {
stack = stack?.split('\n')[0] || '' if (!stack) {
stack = stack.replace(`${devRootDir}/`, '') return ''
stack = stack.replace(/:\d+:\d+\)?$/, '') }
return stack let message = ''
for (const item of stack) {
const source = item.source.replace(`${devRootDir}/`, '')
if (item.function) {
message += ` at ${item.function} (${source})\n`
} else {
message += ` at ${source}\n`
}
}
return message
} }
function normalizeServerLog (log: LogObject) { function normalizeServerLog (log: LogObject) {
log.additional = normalizeFilenames(log.stack as string) log.additional = normalizeFilenames(log.stack as ParsedTrace[])
log.tag = 'ssr' log.tag = 'ssr'
delete log.stack delete log.stack
return log return log

View File

@ -5,6 +5,8 @@ import { stringify } from 'devalue'
import type { H3Event } from 'h3' import type { H3Event } from 'h3'
import { withTrailingSlash } from 'ufo' import { withTrailingSlash } from 'ufo'
import { getContext } from 'unctx' import { getContext } from 'unctx'
import { captureRawStackTrace, parseRawStackTrace } from 'errx'
import type { ParsedTrace } from 'errx'
import { isVNode } from 'vue' import { isVNode } from 'vue'
import type { NitroApp } from 'nitro/types' import type { NitroApp } from 'nitro/types'
@ -34,15 +36,28 @@ export default (nitroApp: NitroApp) => {
const ctx = asyncContext.tryUse() const ctx = asyncContext.tryUse()
if (!ctx) { return } if (!ctx) { return }
const stack = getStack() const rawStack = captureRawStackTrace()
if (stack.includes('runtime/vite-node.mjs')) { return } if (!rawStack || rawStack.includes('runtime/vite-node.mjs')) { return }
const trace: ParsedTrace[] = []
let filename = ''
for (const entry of parseRawStackTrace(rawStack)) {
if (entry.source === import.meta.url) { continue }
if (EXCLUDE_TRACE_RE.test(entry.source)) { continue }
filename ||= entry.source.replace(withTrailingSlash(rootDir), '')
trace.push({
...entry,
source: entry.source.startsWith('file://') ? entry.source.replace('file://', '') : entry.source,
})
}
const log = { const log = {
..._log, ..._log,
// Pass along filename to allow the client to display more info about where log comes from // Pass along filename to allow the client to display more info about where log comes from
filename: extractFilenameFromStack(stack), filename,
// Clean up file names in stack trace // Clean up file names in stack trace
stack: normalizeFilenames(stack), stack: trace,
} }
// retain log to be include in the next render // retain log to be include in the next render
@ -68,24 +83,7 @@ export default (nitroApp: NitroApp) => {
}) })
} }
const EXCLUDE_TRACE_RE = /^.*at.*(\/node_modules\/(.*\/)?(nuxt|nuxt-nightly|nuxt-edge|nuxt3|consola|@vue)\/.*|core\/runtime\/nitro.*)$\n?/gm const EXCLUDE_TRACE_RE = /\/node_modules\/(?:.*\/)?(?:nuxt|nuxt-nightly|nuxt-edge|nuxt3|consola|@vue)\/|core\/runtime\/nitro/
function getStack () {
// Pass along stack traces if needed (for error and warns)
// eslint-disable-next-line unicorn/error-message
const stack = new Error()
Error.captureStackTrace(stack)
return stack.stack?.replace(EXCLUDE_TRACE_RE, '').replace(/^Error.*\n/, '') || ''
}
const FILENAME_RE = /at[^(]*\(([^:)]+)[):]/
const FILENAME_RE_GLOBAL = /at[^(]*\(([^)]+)\)/g
function extractFilenameFromStack (stacktrace: string) {
return stacktrace.match(FILENAME_RE)?.[1].replace(withTrailingSlash(rootDir), '')
}
function normalizeFilenames (stacktrace: string) {
// remove line numbers and file: protocol - TODO: sourcemap support for line numbers
return stacktrace.replace(FILENAME_RE_GLOBAL, (match, filename) => match.replace(filename, filename.replace('file:///', '/').replace(/:.*$/, '')))
}
function onConsoleLog (callback: (log: LogObject) => void) { function onConsoleLog (callback: (log: LogObject) => void) {
consola.addReporter({ consola.addReporter({

View File

@ -187,6 +187,9 @@ importers:
destr: destr:
specifier: ^2.0.3 specifier: ^2.0.3
version: 2.0.3 version: 2.0.3
errx:
specifier: ^0.1.0
version: 0.1.0
globby: globby:
specifier: ^14.0.2 specifier: ^14.0.2
version: 14.0.2 version: 14.0.2
@ -450,6 +453,9 @@ importers:
'@vue/compiler-sfc': '@vue/compiler-sfc':
specifier: 3.4.31 specifier: 3.4.31
version: 3.4.31 version: 3.4.31
errx:
specifier: ^0.1.0
version: 0.1.0
unbuild: unbuild:
specifier: 3.0.0-rc.6 specifier: 3.0.0-rc.6
version: 3.0.0-rc.6(sass@1.69.4)(typescript@5.5.3)(vue-tsc@2.0.26(typescript@5.5.3)) version: 3.0.0-rc.6(sass@1.69.4)(typescript@5.5.3)(vue-tsc@2.0.26(typescript@5.5.3))
@ -3985,6 +3991,9 @@ packages:
error-stack-parser@2.1.4: error-stack-parser@2.1.4:
resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==}
errx@0.1.0:
resolution: {integrity: sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==}
es-define-property@1.0.0: es-define-property@1.0.0:
resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -10987,6 +10996,8 @@ snapshots:
dependencies: dependencies:
stackframe: 1.3.4 stackframe: 1.3.4
errx@0.1.0: {}
es-define-property@1.0.0: es-define-property@1.0.0:
dependencies: dependencies:
get-intrinsic: 1.2.4 get-intrinsic: 1.2.4