feat: use async localStorage instead

This commit is contained in:
Anthony Fu 2025-01-14 13:59:20 +08:00
parent 38bec27bf6
commit 693642ff20
No known key found for this signature in database
GPG Key ID: 179936958CD423FF
9 changed files with 77 additions and 45 deletions

View File

@ -39,7 +39,6 @@
"klona": "^2.0.6", "klona": "^2.0.6",
"mlly": "^1.7.3", "mlly": "^1.7.3",
"ohash": "^1.1.4", "ohash": "^1.1.4",
"on-change": "^5.0.1",
"pathe": "^2.0.1", "pathe": "^2.0.1",
"pkg-types": "^1.3.0", "pkg-types": "^1.3.0",
"scule": "^1.3.0", "scule": "^1.3.0",

View File

@ -1,6 +1,6 @@
import { existsSync, promises as fsp, lstatSync } from 'node:fs' import { existsSync, promises as fsp, lstatSync } from 'node:fs'
import { fileURLToPath, pathToFileURL } from 'node:url' import { fileURLToPath, pathToFileURL } from 'node:url'
import type { ModuleMeta, Nuxt, NuxtConfig, NuxtDebugModuleMutationRecord, NuxtModule } from '@nuxt/schema' import type { ModuleMeta, Nuxt, NuxtConfig, NuxtModule } from '@nuxt/schema'
import { dirname, isAbsolute, join, resolve } from 'pathe' import { dirname, isAbsolute, join, resolve } from 'pathe'
import { defu } from 'defu' import { defu } from 'defu'
import { createJiti } from 'jiti' import { createJiti } from 'jiti'
@ -27,44 +27,11 @@ export async function installModule<
} }
} }
let _nuxt = nuxt
if (nuxt.options.debug) {
const onChange = await import('on-change').then(r => r.default)
const moduleName = (await nuxtModule.getMeta?.())?.name || buildTimeModuleMeta?.name || resolvedModulePath
// Unwrap onChange proxy if already wrapped
nuxt = onChange.target(nuxt)
nuxt._debug ||= {}
nuxt._debug.moduleMutationRecords ||= []
_nuxt = onChange(
nuxt,
(keys, value, _, applyData) => {
// We only listen to changes in the `options` object
if (keys[0] !== 'options') {
return
}
const record: NuxtDebugModuleMutationRecord = {
module: moduleName,
keys: keys.slice(1),
value,
timestamp: Date.now(),
}
if (applyData?.name) {
record.method = applyData.name
}
nuxt._debug!.moduleMutationRecords!.push(record)
}, {
ignoreUnderscores: true,
ignoreSymbols: true,
pathAsArray: true,
},
)
}
// Call module // Call module
const res = await nuxtModule(inlineOptions || {}, _nuxt) ?? {} const res = await nuxt._asyncLocalStorageModule.run(
nuxtModule,
() => nuxtModule(inlineOptions || {}, nuxt),
) ?? {}
if (res === false /* setup aborted */) { if (res === false /* setup aborted */) {
return return
} }
@ -89,6 +56,7 @@ export async function installModule<
nuxt.options._installedModules.push({ nuxt.options._installedModules.push({
meta: defu(await nuxtModule.getMeta?.(), buildTimeModuleMeta), meta: defu(await nuxtModule.getMeta?.(), buildTimeModuleMeta),
module: nuxtModule,
timings: res.timings, timings: res.timings,
entryPath, entryPath,
}) })

View File

@ -92,6 +92,7 @@
"globby": "^14.0.2", "globby": "^14.0.2",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"hookable": "^5.5.3", "hookable": "^5.5.3",
"on-change": "^5.0.1",
"ignore": "^7.0.0", "ignore": "^7.0.0",
"impound": "^0.2.0", "impound": "^0.2.0",
"jiti": "^2.4.2", "jiti": "^2.4.2",

View File

@ -1,5 +1,6 @@
import { existsSync } from 'node:fs' import { existsSync } from 'node:fs'
import { rm } from 'node:fs/promises' import { rm } from 'node:fs/promises'
import { AsyncLocalStorage } from 'node:async_hooks'
import { join, normalize, relative, resolve } from 'pathe' import { join, normalize, relative, resolve } from 'pathe'
import { createDebugger, createHooks } from 'hookable' import { createDebugger, createHooks } from 'hookable'
import ignore from 'ignore' import ignore from 'ignore'
@ -10,6 +11,7 @@ import type { PackageJson } from 'pkg-types'
import { readPackageJSON } from 'pkg-types' import { readPackageJSON } from 'pkg-types'
import { hash } from 'ohash' import { hash } from 'ohash'
import consola from 'consola' import consola from 'consola'
import onChange from 'on-change'
import { colorize } from 'consola/utils' import { colorize } from 'consola/utils'
import { updateConfig } from 'c12/update' import { updateConfig } from 'c12/update'
import { formatDate, resolveCompatibilityDatesFromEnv } from 'compatx' import { formatDate, resolveCompatibilityDatesFromEnv } from 'compatx'
@ -51,9 +53,11 @@ import { VirtualFSPlugin } from './plugins/virtual'
export function createNuxt (options: NuxtOptions): Nuxt { export function createNuxt (options: NuxtOptions): Nuxt {
const hooks = createHooks<NuxtHooks>() const hooks = createHooks<NuxtHooks>()
const proxiedOptions = new WeakMap<NuxtModule, NuxtOptions>()
const nuxt: Nuxt = { const nuxt: Nuxt = {
_version: version, _version: version,
options, _asyncLocalStorageModule: new AsyncLocalStorage(),
hooks, hooks,
callHook: hooks.callHook, callHook: hooks.callHook,
addHooks: hooks.addHooks, addHooks: hooks.addHooks,
@ -62,6 +66,50 @@ export function createNuxt (options: NuxtOptions): Nuxt {
close: () => hooks.callHook('close', nuxt), close: () => hooks.callHook('close', nuxt),
vfs: {}, vfs: {},
apps: {}, apps: {},
options,
}
if (options.experimental.debugModuleMutation) {
Object.defineProperty(nuxt, 'options', {
get () {
const currentModule = nuxt._asyncLocalStorageModule.getStore()
if (!currentModule) {
return options
}
if (proxiedOptions.has(currentModule)) {
return proxiedOptions.get(currentModule)!
}
nuxt._debug ||= {}
nuxt._debug.moduleMutationRecords ||= []
const proxied = onChange(
options,
(keys, value, previousValue, applyData) => {
if (value === previousValue && !applyData) {
return
}
nuxt._debug!.moduleMutationRecords!.push({
module: currentModule,
keys,
target: 'nuxt.options',
value: applyData?.args ?? value,
timestamp: Date.now(),
method: applyData?.name,
})
}, {
ignoreUnderscores: true,
ignoreSymbols: true,
pathAsArray: true,
},
)
proxiedOptions.set(currentModule, proxied)
return proxied
},
},
)
} }
hooks.hookOnce('close', () => { hooks.removeAllHooks() }) hooks.hookOnce('close', () => { hooks.removeAllHooks() })

View File

@ -437,5 +437,14 @@ export default defineUntypedSchema({
browserDevtoolsTiming: { browserDevtoolsTiming: {
$resolve: async (val, get) => val ?? await get('dev'), $resolve: async (val, get) => val ?? await get('dev'),
}, },
/**
* Record mutations to `nuxt.options` in module context
*/
debugModuleMutation: {
$resolve: async (val, get) => {
return val ?? await get('debug')
},
},
}, },
}) })

View File

@ -25,7 +25,7 @@ export default defineUntypedSchema({
appDir: '', appDir: '',
/** /**
* @private * @private
* @type {Array<{ meta: ModuleMeta; timings?: Record<string, number | undefined>; entryPath?: string }>} * @type {Array<{ meta: ModuleMeta; module: NuxtModule, timings?: Record<string, number | undefined>; entryPath?: string }>}
*/ */
_installedModules: [], _installedModules: [],
/** @private */ /** @private */

View File

@ -1,3 +1,5 @@
import type { NuxtModule } from './module'
export interface NuxtDebugContext { export interface NuxtDebugContext {
/** /**
* Module mutation records to the `nuxt` instance. * Module mutation records to the `nuxt` instance.
@ -6,8 +8,9 @@ export interface NuxtDebugContext {
} }
export interface NuxtDebugModuleMutationRecord { export interface NuxtDebugModuleMutationRecord {
module: string | undefined module: NuxtModule
keys: (string | symbol)[] keys: (string | symbol)[]
target: 'nuxt.options'
value: any value: any
method?: string method?: string
timestamp: number timestamp: number

View File

@ -1,5 +1,7 @@
import type { AsyncLocalStorage } from 'node:async_hooks'
import type { Hookable } from 'hookable' import type { Hookable } from 'hookable'
import type { Ignore } from 'ignore' import type { Ignore } from 'ignore'
import type { NuxtModule } from './module'
import type { NuxtHooks, NuxtLayout, NuxtMiddleware, NuxtPage } from './hooks' import type { NuxtHooks, NuxtLayout, NuxtMiddleware, NuxtPage } from './hooks'
import type { Component } from './components' import type { Component } from './components'
import type { NuxtOptions } from './config' import type { NuxtOptions } from './config'
@ -85,6 +87,8 @@ export interface Nuxt {
_ignore?: Ignore _ignore?: Ignore
_dependencies?: Set<string> _dependencies?: Set<string>
_debug?: NuxtDebugContext _debug?: NuxtDebugContext
/** Async local storage for current running Nuxt module instance. */
_asyncLocalStorageModule: AsyncLocalStorage<NuxtModule>
/** The resolved Nuxt configuration. */ /** The resolved Nuxt configuration. */
options: NuxtOptions options: NuxtOptions

View File

@ -230,9 +230,6 @@ importers:
ohash: ohash:
specifier: 1.1.4 specifier: 1.1.4
version: 1.1.4 version: 1.1.4
on-change:
specifier: ^5.0.1
version: 5.0.1
pathe: pathe:
specifier: ^2.0.1 specifier: ^2.0.1
version: 2.0.1 version: 2.0.1
@ -405,6 +402,9 @@ importers:
ohash: ohash:
specifier: 1.1.4 specifier: 1.1.4
version: 1.1.4 version: 1.1.4
on-change:
specifier: ^5.0.1
version: 5.0.1
pathe: pathe:
specifier: ^2.0.1 specifier: ^2.0.1
version: 2.0.1 version: 2.0.1