From 499ce3d5f364f34cea5add3e38863fd6d7e1e040 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 27 Nov 2024 10:38:02 +0000 Subject: [PATCH] feat(nuxt): use `oxc-parser` instead of esbuild + acorn --- packages/nuxt/package.json | 2 +- .../nuxt/src/components/plugins/tree-shake.ts | 64 ++++++------ packages/nuxt/src/core/app.ts | 4 +- .../nuxt/src/core/plugins/composable-keys.ts | 14 +-- .../nuxt/src/core/plugins/plugin-metadata.ts | 21 ++-- packages/nuxt/src/core/plugins/prehydrate.ts | 6 +- packages/nuxt/src/core/utils/parse.ts | 30 +++--- packages/nuxt/src/pages/module.ts | 2 +- packages/nuxt/src/pages/plugins/page-meta.ts | 5 +- packages/nuxt/src/pages/route-rules.ts | 9 +- packages/nuxt/src/pages/utils.ts | 17 ++-- packages/nuxt/test/component-names.test.ts | 10 +- packages/nuxt/test/composable-keys.test.ts | 28 +----- packages/nuxt/test/page-metadata.test.ts | 76 ++++++++------ packages/nuxt/test/plugin-metadata.test.ts | 21 +--- packages/nuxt/test/route-rules.test.ts | 25 +++-- packages/nuxt/test/treeshake-client.test.ts | 10 +- pnpm-lock.yaml | 99 +++++++++++++++++-- 18 files changed, 243 insertions(+), 200 deletions(-) diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index 70e8e35875..009f3fa32f 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -70,7 +70,6 @@ "@unhead/ssr": "^1.11.13", "@unhead/vue": "^1.11.13", "@vue/shared": "^3.5.13", - "acorn": "8.14.0", "c12": "^2.0.1", "chokidar": "^4.0.1", "compatx": "^0.1.8", @@ -99,6 +98,7 @@ "nypm": "^0.4.0", "ofetch": "^1.4.1", "ohash": "^1.1.4", + "oxc-parser": "^0.38.0", "pathe": "^1.1.2", "perfect-debounce": "^1.0.0", "pkg-types": "^1.2.1", diff --git a/packages/nuxt/src/components/plugins/tree-shake.ts b/packages/nuxt/src/components/plugins/tree-shake.ts index d24b5f1c00..1f6ce5fd48 100644 --- a/packages/nuxt/src/components/plugins/tree-shake.ts +++ b/packages/nuxt/src/components/plugins/tree-shake.ts @@ -1,13 +1,12 @@ import { pathToFileURL } from 'node:url' import { parseURL } from 'ufo' import MagicString from 'magic-string' -import type { AssignmentProperty, CallExpression, ObjectExpression, Pattern, Property, ReturnStatement, VariableDeclaration } from 'estree' -import type { Program } from 'acorn' +import type { BindingPattern, BindingProperty, BindingRestElement, CallExpression, Expression, ObjectExpression, ObjectProperty, Program, ReturnStatement, VariableDeclaration } from 'oxc-parser' import { createUnplugin } from 'unplugin' import type { Component } from '@nuxt/schema' import { resolve } from 'pathe' -import { parseAndWalk, walk, withLocations } from '../../core/utils/parse' +import { parseAndWalk, walk } from '../../core/utils/parse' import type { Node } from '../../core/utils/parse' import { distDir } from '../../dirs' @@ -58,15 +57,14 @@ export const TreeShakeTemplatePlugin = (options: TreeShakeTemplatePluginOptions) const [componentCall, _, children] = node.arguments if (!componentCall) { return } - if (componentCall.type === 'Identifier' || componentCall.type === 'MemberExpression' || componentCall.type === 'CallExpression') { + if (componentCall.type === 'Identifier' || componentCall.type === 'ComputedMemberExpression' || componentCall.type === 'StaticMemberExpression' || componentCall.type === 'CallExpression') { const componentName = getComponentName(node) if (!componentName || !COMPONENTS_IDENTIFIERS_RE.test(componentName) || children?.type !== 'ObjectExpression') { return } const isClientOnlyComponent = CLIENT_ONLY_NAME_RE.test(componentName) - const slotsToRemove = isClientOnlyComponent ? children.properties.filter(prop => prop.type === 'Property' && prop.key.type === 'Identifier' && !PLACEHOLDER_EXACT_RE.test(prop.key.name)) as Property[] : children.properties as Property[] + const slotsToRemove = isClientOnlyComponent ? children.properties.filter(prop => prop.type === 'ObjectProperty' && prop.key.type === 'Identifier' && !PLACEHOLDER_EXACT_RE.test(prop.key.name)) as ObjectProperty[] : children.properties as ObjectProperty[] - for (const _slot of slotsToRemove) { - const slot = withLocations(_slot) + for (const slot of slotsToRemove) { s.remove(slot.start, slot.end + 1) const removedCode = `({${code.slice(slot.start, slot.end + 1)}})` const currentState = s.toString() @@ -87,7 +85,7 @@ export const TreeShakeTemplatePlugin = (options: TreeShakeTemplatePluginOptions) }) const componentsToRemove = [...componentsToRemoveSet] - const removedNodes = new WeakSet() + const removedNodes = new WeakSet() for (const componentName of componentsToRemove) { // remove import declaration if it exists @@ -119,18 +117,18 @@ function removeFromSetupReturn (codeAst: Program, name: string, magicString: Mag enter (node) { if (walkedInSetup) { this.skip() - } else if (node.type === 'Property' && node.key.type === 'Identifier' && node.key.name === 'setup' && (node.value.type === 'FunctionExpression' || node.value.type === 'ArrowFunctionExpression')) { + } else if (node.type === 'ObjectProperty' && node.key.type === 'Identifier' && node.key.name === 'setup' && (node.value.type === 'FunctionExpression' || node.value.type === 'ArrowFunctionExpression')) { // walk into the setup function walkedInSetup = true - if (node.value.body?.type === 'BlockStatement') { - const returnStatement = node.value.body.body.find(statement => statement.type === 'ReturnStatement') as ReturnStatement + if (node.value.body?.type === 'FunctionBody') { + const returnStatement = node.value.body.statements.find(statement => statement.type === 'ReturnStatement') as ReturnStatement if (returnStatement && returnStatement.argument?.type === 'ObjectExpression') { // remove from return statement removePropertyFromObject(returnStatement.argument, name, magicString) } // remove from __returned__ - const variableList = node.value.body.body.filter((statement): statement is VariableDeclaration => statement.type === 'VariableDeclaration') + const variableList = node.value.body.statements.filter((statement): statement is VariableDeclaration => statement.type === 'VariableDeclaration') const returnedVariableDeclaration = variableList.find(declaration => declaration.declarations[0]?.id.type === 'Identifier' && declaration.declarations[0]?.id.name === '__returned__' && declaration.declarations[0]?.init?.type === 'ObjectExpression') if (returnedVariableDeclaration) { const init = returnedVariableDeclaration.declarations[0]?.init as ObjectExpression | undefined @@ -149,9 +147,8 @@ function removeFromSetupReturn (codeAst: Program, name: string, magicString: Mag */ function removePropertyFromObject (node: ObjectExpression, name: string, magicString: MagicString) { for (const property of node.properties) { - if (property.type === 'Property' && property.key.type === 'Identifier' && property.key.name === name) { - const _property = withLocations(property) - magicString.remove(_property.start, _property.end + 1) + if (property.type === 'ObjectProperty' && property.key.type === 'Identifier' && property.key.name === name) { + magicString.remove(property.start, property.end + 1) return true } } @@ -173,12 +170,10 @@ function removeImportDeclaration (ast: Program, importName: string, magicString: const specifierIndex = node.specifiers.findIndex(s => s.local.name === importName) if (specifierIndex > -1) { if (node.specifiers!.length > 1) { - const specifier = withLocations(node.specifiers![specifierIndex]) - magicString.remove(specifier.start, specifier.end + 1) + magicString.remove((node.specifiers![specifierIndex])!.start, (node.specifiers![specifierIndex])!.end + 1) node.specifiers!.splice(specifierIndex, 1) } else { - const specifier = withLocations(node) - magicString.remove(specifier.start, specifier.end) + magicString.remove(node.start, node.end) } return true } @@ -195,7 +190,7 @@ function isComponentNotCalledInSetup (code: string, id: string, name: string): s if (!name) { return } let found = false parseAndWalk(code, id, function (node) { - if ((node.type === 'Property' && node.key.type === 'Identifier' && node.value.type === 'FunctionExpression' && node.key.name === 'setup') || (node.type === 'FunctionDeclaration' && (node.id?.name === '_sfc_ssrRender' || node.id?.name === 'ssrRender'))) { + if ((node.type === 'ObjectProperty' && node.key.type === 'Identifier' && node.value.type === 'FunctionExpression' && node.key.name === 'setup') || (node.type === 'FunctionDeclaration' && (node.id?.name === '_sfc_ssrRender' || node.id?.name === 'ssrRender'))) { // walk through the setup function node or the ssrRender function walk(node, { enter (node) { @@ -203,9 +198,11 @@ function isComponentNotCalledInSetup (code: string, id: string, name: string): s this.skip() } else if (node.type === 'Identifier' && node.name === name) { found = true - } else if (node.type === 'MemberExpression') { + } else if (node.type === 'StaticMemberExpression') { // dev only with $setup or _ctx - found = (node.property.type === 'Literal' && node.property.value === name) || (node.property.type === 'Identifier' && node.property.name === name) + found = (node.property.type === 'Identifier' && node.property.name === name) + } else if (node.type === 'ComputedMemberExpression') { + found = (node.expression.type === 'Literal' && node.expression.value === name) } }, }) @@ -224,10 +221,10 @@ function getComponentName (ssrRenderNode: CallExpression): string | undefined { if (componentCall.type === 'Identifier') { return componentCall.name - } else if (componentCall.type === 'MemberExpression') { - if (componentCall.property.type === 'Literal') { - return componentCall.property.value as string - } + } else if (componentCall.type === 'StaticMemberExpression') { + return componentCall.property.name + } else if (componentCall.type === 'ComputedMemberExpression' && componentCall.expression.type === 'Literal') { + return componentCall.expression.value as string } else if (componentCall.type === 'CallExpression') { return getComponentName(componentCall) } @@ -236,14 +233,14 @@ function getComponentName (ssrRenderNode: CallExpression): string | undefined { /** * remove a variable declaration within the code */ -function removeVariableDeclarator (codeAst: Program, name: string, magicString: MagicString, removedNodes: WeakSet): Node | void { +function removeVariableDeclarator (codeAst: Program, name: string, magicString: MagicString, removedNodes: WeakSet): Expression | void { // remove variables walk(codeAst, { enter (node) { if (node.type !== 'VariableDeclaration') { return } for (const declarator of node.declarations) { - const toRemove = withLocations(findMatchingPatternToRemove(declarator.id, node, name, removedNodes)) - if (toRemove) { + const toRemove = findMatchingPatternToRemove(declarator.id, node, name, removedNodes) + if (toRemove && 'start' in toRemove) { magicString.remove(toRemove.start, toRemove.end + 1) removedNodes.add(toRemove) } @@ -255,23 +252,24 @@ function removeVariableDeclarator (codeAst: Program, name: string, magicString: /** * find the Pattern to remove which the identifier is equal to the name parameter. */ -function findMatchingPatternToRemove (node: Pattern, toRemoveIfMatched: Node, name: string, removedNodeSet: WeakSet): Node | undefined { +type PropertyPattern = BindingPattern | BindingRestElement | BindingProperty | Node +function findMatchingPatternToRemove (node: PropertyPattern, toRemoveIfMatched: PropertyPattern, name: string, removedNodeSet: WeakSet): PropertyPattern | undefined { if (node.type === 'Identifier') { if (node.name === name) { return toRemoveIfMatched } } else if (node.type === 'ArrayPattern') { - const elements = node.elements.filter((e): e is Pattern => e !== null && !removedNodeSet.has(e)) + const elements = node.elements.filter((e): e is BindingPattern | BindingRestElement => e !== null && !removedNodeSet.has(e)) for (const element of elements) { const matched = findMatchingPatternToRemove(element, elements.length > 1 ? element : toRemoveIfMatched, name, removedNodeSet) if (matched) { return matched } } } else if (node.type === 'ObjectPattern') { - const properties = node.properties.filter((e): e is AssignmentProperty => e.type === 'Property' && !removedNodeSet.has(e)) + const properties = node.properties.filter((e): e is BindingProperty => e.type === 'BindingProperty' && !removedNodeSet.has(e)) for (const [index, property] of properties.entries()) { - let nodeToRemove: Node = property + let nodeToRemove: PropertyPattern = property if (properties.length < 2) { nodeToRemove = toRemoveIfMatched } diff --git a/packages/nuxt/src/core/app.ts b/packages/nuxt/src/core/app.ts index 4aebfd2902..b8aef0619c 100644 --- a/packages/nuxt/src/core/app.ts +++ b/packages/nuxt/src/core/app.ts @@ -248,15 +248,13 @@ function resolvePaths> (items: Item[], key: { [ })) } -const IS_TSX = /\.[jt]sx$/ - export async function annotatePlugins (nuxt: Nuxt, plugins: NuxtPlugin[]) { const _plugins: Array> = [] for (const plugin of plugins) { try { const code = plugin.src in nuxt.vfs ? nuxt.vfs[plugin.src]! : await fsp.readFile(plugin.src!, 'utf-8') _plugins.push({ - ...await extractMetadata(code, IS_TSX.test(plugin.src) ? 'tsx' : 'ts'), + ...await extractMetadata(code, plugin.src), ...plugin, }) } catch (e) { diff --git a/packages/nuxt/src/core/plugins/composable-keys.ts b/packages/nuxt/src/core/plugins/composable-keys.ts index c9ddfc07a5..2d3bedc0b9 100644 --- a/packages/nuxt/src/core/plugins/composable-keys.ts +++ b/packages/nuxt/src/core/plugins/composable-keys.ts @@ -3,13 +3,13 @@ import { createUnplugin } from 'unplugin' import { isAbsolute, relative } from 'pathe' import MagicString from 'magic-string' import { hash } from 'ohash' -import type { Pattern } from 'estree' import { parseQuery, parseURL } from 'ufo' import escapeRE from 'escape-string-regexp' import { findStaticImports, parseStaticImport } from 'mlly' -import { parseAndWalk, walk } from '../../core/utils/parse' +import type { BindingPattern } from 'oxc-parser' import { matchWithStringOrRegex } from '../utils/plugins' +import { parseAndWalk, walk } from '../utils/parse' interface ComposableKeysOptions { sourcemap: boolean @@ -62,8 +62,10 @@ export const ComposableKeysPlugin = (options: ComposableKeysOptions) => createUn varCollector.refresh(scopeTracker.curScopeKey) } else if (node.type === 'FunctionDeclaration' && node.id) { varCollector.addVar(node.id.name) - } else if (node.type === 'VariableDeclarator') { - varCollector.collect(node.id) + } else if (node.type === 'VariableDeclaration') { + for (const declaration of node.declarations) { + varCollector.collect(declaration.id) + } } }, leave (_node) { @@ -213,11 +215,9 @@ class ScopedVarsCollector { return false } - collect (pattern: Pattern) { + collect (pattern: BindingPattern) { if (pattern.type === 'Identifier') { this.addVar(pattern.name) - } else if (pattern.type === 'RestElement') { - this.collect(pattern.argument) } else if (pattern.type === 'AssignmentPattern') { this.collect(pattern.left) } else if (pattern.type === 'ArrayPattern') { diff --git a/packages/nuxt/src/core/plugins/plugin-metadata.ts b/packages/nuxt/src/core/plugins/plugin-metadata.ts index 3b0b080ac4..437ea46d7c 100644 --- a/packages/nuxt/src/core/plugins/plugin-metadata.ts +++ b/packages/nuxt/src/core/plugins/plugin-metadata.ts @@ -1,5 +1,4 @@ -import type { Literal, Property, SpreadElement } from 'estree' -import { transform } from 'esbuild' +import type { ObjectProperty, SpreadElement, StringLiteral } from 'oxc-parser' import { defu } from 'defu' import { findExports } from 'mlly' import type { Nuxt } from '@nuxt/schema' @@ -8,7 +7,7 @@ import MagicString from 'magic-string' import { normalize } from 'pathe' import { logger } from '@nuxt/kit' -import { parseAndWalk, withLocations } from '../../core/utils/parse' +import { parseAndWalk } from '../../core/utils/parse' import type { ObjectPlugin, PluginMeta } from '#app' @@ -40,13 +39,12 @@ export const orderMap: Record, number> = { } const metaCache: Record> = {} -export async function extractMetadata (code: string, loader = 'ts' as 'ts' | 'tsx') { +export function extractMetadata (code: string, id: string) { let meta: PluginMeta = {} if (metaCache[code]) { return metaCache[code] } - const js = await transform(code, { loader }) - parseAndWalk(js.code, `file.${loader}`, (node) => { + parseAndWalk(code, id, (node) => { if (node.type !== 'CallExpression' || node.callee.type !== 'Identifier') { return } const name = 'name' in node.callee && node.callee.name @@ -87,7 +85,7 @@ function isMetadataKey (key: string): key is PluginMetaKey { return key in keys } -function extractMetaFromObject (properties: Array) { +function extractMetaFromObject (properties: Array) { const meta: PluginMeta = {} for (const property of properties) { if (property.type === 'SpreadElement' || !('name' in property.key)) { @@ -105,7 +103,7 @@ function extractMetaFromObject (properties: Array) { if (property.value.elements.some(e => !e || e.type !== 'Literal' || typeof e.value !== 'string')) { throw new Error('dependsOn must take an array of string literals') } - meta[propertyKey] = property.value.elements.map(e => (e as Literal)!.value as string) + meta[propertyKey] = property.value.elements.map(e => (e as StringLiteral)!.value as string) } } return meta @@ -163,10 +161,11 @@ export const RemovePluginMetadataPlugin = (nuxt: Nuxt) => createUnplugin(() => { const propertyKey = property.key.name if (propertyKey === 'order' || propertyKey === 'enforce' || propertyKey === 'name') { - const nextNode = arg.properties[propertyIndex + 1] || node.arguments[argIndex + 1] - const nextIndex = withLocations(nextNode)?.start || (withLocations(arg).end - 1) + const _nextNode = arg.properties[propertyIndex + 1] || node.arguments[argIndex + 1] + const nextNode = _nextNode as typeof _nextNode & { start: number, end: number } + const nextIndex = nextNode?.start || (arg.end - 1) - s.remove(withLocations(property).start, nextIndex) + s.remove(property.start, nextIndex) } } } diff --git a/packages/nuxt/src/core/plugins/prehydrate.ts b/packages/nuxt/src/core/plugins/prehydrate.ts index ce3a8a6e2a..d22e92747e 100644 --- a/packages/nuxt/src/core/plugins/prehydrate.ts +++ b/packages/nuxt/src/core/plugins/prehydrate.ts @@ -3,7 +3,7 @@ import { createUnplugin } from 'unplugin' import MagicString from 'magic-string' import { hash } from 'ohash' -import { parseAndWalk, withLocations } from '../../core/utils/parse' +import { parseAndWalk } from '../../core/utils/parse' import { isJS, isVue } from '../utils' export function PrehydrateTransformPlugin (options: { sourcemap?: boolean } = {}) { @@ -23,11 +23,11 @@ export function PrehydrateTransformPlugin (options: { sourcemap?: boolean } = {} return } if (node.callee.name === 'onPrehydrate') { - const callback = withLocations(node.arguments[0]) + const callback = node.arguments[0] if (!callback) { return } if (callback.type !== 'ArrowFunctionExpression' && callback.type !== 'FunctionExpression') { return } - const needsAttr = callback.params.length > 0 + const needsAttr = callback.params.items.length > 0 const p = transform(`forEach(${code.slice(callback.start, callback.end)})`, { loader: 'ts', minify: true }) promises.push(p.then(({ code: result }) => { diff --git a/packages/nuxt/src/core/utils/parse.ts b/packages/nuxt/src/core/utils/parse.ts index 34f7af6d79..cb2fd4c997 100644 --- a/packages/nuxt/src/core/utils/parse.ts +++ b/packages/nuxt/src/core/utils/parse.ts @@ -1,33 +1,37 @@ +import { parseSync } from 'oxc-parser' +import type { CatchClause, ClassBody, Declaration, Expression, MethodDefinition, ModuleDeclaration, ObjectProperty, Pattern, PrivateIdentifier, Program, PropertyDefinition, SpreadElement, Statement, Super, SwitchCase, TemplateElement } from 'oxc-parser' import { walk as _walk } from 'estree-walker' -import type { Node, SyncHandler } from 'estree-walker' -import type { Program as ESTreeProgram } from 'estree' -import { parse } from 'acorn' -import type { Program } from 'acorn' +import type { SyncHandler } from 'estree-walker' +import type { Node as ESTreeNode, Program as ESTreeProgram, ModuleSpecifier } from 'estree' -export type { Node } +/** estree also has AssignmentProperty, Identifier and Literal as possible node types */ +export type Node = Declaration | Expression | ClassBody | CatchClause | MethodDefinition | ModuleDeclaration | ModuleSpecifier | Pattern | PrivateIdentifier | Program | SpreadElement | Statement | Super | SwitchCase | TemplateElement | ObjectProperty | PropertyDefinition -type WithLocations = T & { start: number, end: number } -type WalkerCallback = (this: ThisParameterType, node: WithLocations, parent: WithLocations | null, ctx: { key: string | number | symbol | null | undefined, index: number | null | undefined, ast: Program | Node }) => void +type InferThis any> = T extends (this: infer U, ...args: infer A) => any ? U : unknown + +type WalkerCallback = (this: InferThis, node: Node, parent: Node | null, ctx: { key: string | number | symbol | null | undefined, index: number | null | undefined, ast: Program | Node }) => void export function walk (ast: Program | Node, callback: { enter?: WalkerCallback, leave?: WalkerCallback }) { - return _walk(ast as unknown as ESTreeProgram | Node, { + return _walk(ast as unknown as ESTreeProgram | ESTreeNode, { enter (node, parent, key, index) { - callback.enter?.call(this, node as WithLocations, parent as WithLocations | null, { key, index, ast }) + callback.enter?.call(this, node as Node, parent as Node | null, { key, index, ast }) }, leave (node, parent, key, index) { - callback.leave?.call(this, node as WithLocations, parent as WithLocations | null, { key, index, ast }) + callback.leave?.call(this, node as Node, parent as Node | null, { key, index, ast }) }, - }) as Program | Node | null + }) } export function parseAndWalk (code: string, sourceFilename: string, callback: WalkerCallback): Program export function parseAndWalk (code: string, sourceFilename: string, object: { enter?: WalkerCallback, leave?: WalkerCallback }): Program -export function parseAndWalk (code: string, _sourceFilename: string, callback: { enter?: WalkerCallback, leave?: WalkerCallback } | WalkerCallback) { - const ast = parse (code, { sourceType: 'module', ecmaVersion: 'latest', locations: true }) +export function parseAndWalk (code: string, sourceFilename: string, callback: { enter?: WalkerCallback, leave?: WalkerCallback } | WalkerCallback) { + const ast = parseSync(code, { sourceType: 'module', sourceFilename }).program walk(ast, typeof callback === 'function' ? { enter: callback } : callback) return ast } +type WithLocations = T & { start: number, end: number } + export function withLocations (node: T): WithLocations { return node as WithLocations } diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts index 44cda863c3..ef938937ee 100644 --- a/packages/nuxt/src/pages/module.ts +++ b/packages/nuxt/src/pages/module.ts @@ -379,7 +379,7 @@ export default defineNuxtModule({ const glob = pageToGlobMap[path] const code = path in nuxt.vfs ? nuxt.vfs[path]! : await readFile(path!, 'utf-8') try { - const extractedRule = await extractRouteRules(code, path) + const extractedRule = extractRouteRules(code, path) if (extractedRule) { if (!glob) { const relativePath = relative(nuxt.options.srcDir, path) diff --git a/packages/nuxt/src/pages/plugins/page-meta.ts b/packages/nuxt/src/pages/plugins/page-meta.ts index fec8bbb54b..d7bc22c20f 100644 --- a/packages/nuxt/src/pages/plugins/page-meta.ts +++ b/packages/nuxt/src/pages/plugins/page-meta.ts @@ -3,12 +3,11 @@ import { createUnplugin } from 'unplugin' import { parseQuery, parseURL } from 'ufo' import type { StaticImport } from 'mlly' import { findExports, findStaticImports, parseStaticImport } from 'mlly' -import { walk } from 'estree-walker' import MagicString from 'magic-string' import { isAbsolute } from 'pathe' import { logger } from '@nuxt/kit' -import { parseAndWalk, withLocations } from '../../core/utils/parse' +import { parseAndWalk, walk } from '../../core/utils/parse' interface PageMetaPluginOptions { dev?: boolean @@ -116,7 +115,7 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions = {}) => createUnp if (node.type !== 'CallExpression' || node.callee.type !== 'Identifier') { return } if (!('name' in node.callee) || node.callee.name !== 'definePageMeta') { return } - const meta = withLocations(node.arguments[0]) + const meta = node.arguments[0] if (!meta) { return } diff --git a/packages/nuxt/src/pages/route-rules.ts b/packages/nuxt/src/pages/route-rules.ts index c9317176b2..1ae4b3620e 100644 --- a/packages/nuxt/src/pages/route-rules.ts +++ b/packages/nuxt/src/pages/route-rules.ts @@ -1,5 +1,4 @@ import { runInNewContext } from 'node:vm' -import { transform } from 'esbuild' import type { NuxtPage } from '@nuxt/schema' import type { NitroRouteConfig } from 'nitro/types' import { normalize } from 'pathe' @@ -11,7 +10,7 @@ import { extractScriptContent, pathToNitroGlob } from './utils' const ROUTE_RULE_RE = /\bdefineRouteRules\(/ const ruleCache: Record = {} -export async function extractRouteRules (code: string, path: string): Promise { +export function extractRouteRules (code: string, path: string): NitroRouteConfig | null { if (code in ruleCache) { return ruleCache[code] || null } @@ -27,12 +26,10 @@ export async function extractRouteRules (code: string, path: string): Promise { + parseAndWalk(code, 'file.' + (script?.loader || 'ts'), (node) => { if (node.type !== 'CallExpression' || node.callee.type !== 'Identifier') { return } if (node.callee.name === 'defineRouteRules') { - const rulesString = js.code.slice(node.start, node.end) + const rulesString = code.slice(node.start, node.end) try { rule = JSON.parse(runInNewContext(rulesString.replace('defineRouteRules', 'JSON.stringify'), {})) } catch { diff --git a/packages/nuxt/src/pages/utils.ts b/packages/nuxt/src/pages/utils.ts index bd27eb1e97..a204fc68b7 100644 --- a/packages/nuxt/src/pages/utils.ts +++ b/packages/nuxt/src/pages/utils.ts @@ -7,8 +7,7 @@ import { genArrayFromRaw, genDynamicImport, genImport, genSafeVariableName } fro import escapeRE from 'escape-string-regexp' import { filename } from 'pathe/utils' import { hash } from 'ohash' -import { transform } from 'esbuild' -import type { Property } from 'estree' +import type { ObjectProperty } from 'oxc-parser' import type { NuxtPage } from 'nuxt/schema' import { parseAndWalk } from '../core/utils/parse' @@ -166,7 +165,7 @@ export async function augmentPages (routes: NuxtPage[], vfs: Record = {} const metaCache: Record>> = {} -export async function getRouteMeta (contents: string, absolutePath: string, extraExtractionKeys: string[] = []): Promise>> { +export function getRouteMeta (contents: string, absolutePath: string, extraExtractionKeys: string[] = []): Partial> { // set/update pageContentsCache, invalidate metaCache on cache mismatch if (!(absolutePath in pageContentsCache) || pageContentsCache[absolutePath] !== contents) { pageContentsCache[absolutePath] = contents @@ -230,13 +229,11 @@ export async function getRouteMeta (contents: string, absolutePath: string, extr continue } - const js = await transform(script.code, { loader: script.loader }) - const dynamicProperties = new Set() let foundMeta = false - parseAndWalk(js.code, absolutePath.replace(/\.\w+$/, '.' + script.loader), (node) => { + parseAndWalk(script.code, absolutePath.replace(/\.\w+$/, '.' + script.loader), (node) => { if (foundMeta) { return } if (node.type !== 'ExpressionStatement' || node.expression.type !== 'CallExpression' || node.expression.callee.type !== 'Identifier' || node.expression.callee.name !== 'definePageMeta') { return } @@ -246,11 +243,11 @@ export async function getRouteMeta (contents: string, absolutePath: string, extr if (pageMetaArgument?.type !== 'ObjectExpression') { return } for (const key of extractionKeys) { - const property = pageMetaArgument.properties.find((property): property is Property => property.type === 'Property' && property.key.type === 'Identifier' && property.key.name === key) + const property = pageMetaArgument.properties.find((property): property is ObjectProperty => property.type === 'ObjectProperty' && property.key.type === 'Identifier' && property.key.name === key) if (!property) { continue } if (property.value.type === 'ObjectExpression') { - const valueString = js.code.slice(property.value.range![0], property.value.range![1]) + const valueString = script.code.slice(property.value.start, property.value.end) try { extractedMeta[key] = JSON.parse(runInNewContext(`JSON.stringify(${valueString})`, {})) } catch { @@ -286,7 +283,7 @@ export async function getRouteMeta (contents: string, absolutePath: string, extr } for (const property of pageMetaArgument.properties) { - if (property.type !== 'Property') { + if (property.type !== 'ObjectProperty') { continue } const isIdentifierOrLiteral = property.key.type === 'Literal' || property.key.type === 'Identifier' diff --git a/packages/nuxt/test/component-names.test.ts b/packages/nuxt/test/component-names.test.ts index fdb585e614..b26edaa309 100644 --- a/packages/nuxt/test/component-names.test.ts +++ b/packages/nuxt/test/component-names.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from 'vitest' import type { Component } from '@nuxt/schema' import { compileScript, parse } from '@vue/compiler-sfc' -import * as Parser from 'acorn' import { ComponentNamePlugin } from '../src/components/plugins/component-names' @@ -22,14 +21,7 @@ onMounted(() => { ` const res = compileScript(parse(sfc).descriptor, { id: 'test.vue' }) - const { code } = transformPlugin.transform.call({ - parse: (code: string, opts: any = {}) => Parser.parse(code, { - sourceType: 'module', - ecmaVersion: 'latest', - locations: true, - ...opts, - }), - }, res.content, components[0].filePath) ?? {} + const { code } = transformPlugin.transform(res.content, components[0].filePath) ?? {} expect(code?.trim()).toMatchInlineSnapshot(` "export default Object.assign({ setup(__props, { expose: __expose }) { diff --git a/packages/nuxt/test/composable-keys.test.ts b/packages/nuxt/test/composable-keys.test.ts index 38893a0bab..b65b60183e 100644 --- a/packages/nuxt/test/composable-keys.test.ts +++ b/packages/nuxt/test/composable-keys.test.ts @@ -1,5 +1,4 @@ import { describe, expect, it } from 'vitest' -import * as Parser from 'acorn' import { ComposableKeysPlugin, detectImportNames } from '../src/core/plugins/composable-keys' @@ -40,14 +39,7 @@ describe('composable keys plugin', () => { import { useAsyncData } from '#app' useAsyncData(() => {}) ` - expect(transformPlugin.transform.call({ - parse: (code: string, opts: any = {}) => Parser.parse(code, { - sourceType: 'module', - ecmaVersion: 'latest', - locations: true, - ...opts, - }), - }, code, 'plugin.ts')?.code.trim()).toMatchInlineSnapshot(` + expect(transformPlugin.transform(code, 'plugin.ts')?.code.trim()).toMatchInlineSnapshot(` "import { useAsyncData } from '#app' useAsyncData(() => {}, '$yXewDLZblH')" `) @@ -55,14 +47,7 @@ useAsyncData(() => {}) it('should not add hash when one exists', () => { const code = `useAsyncData(() => {}, 'foo')` - expect(transformPlugin.transform.call({ - parse: (code: string, opts: any = {}) => Parser.parse(code, { - sourceType: 'module', - ecmaVersion: 'latest', - locations: true, - ...opts, - }), - }, code, 'plugin.ts')?.code.trim()).toMatchInlineSnapshot(`undefined`) + expect(transformPlugin.transform(code, 'plugin.ts')?.code.trim()).toMatchInlineSnapshot(`undefined`) }) it('should not add hash composables is imported from somewhere else', () => { @@ -70,13 +55,6 @@ useAsyncData(() => {}) const useAsyncData = () => {} useAsyncData(() => {}) ` - expect(transformPlugin.transform.call({ - parse: (code: string, opts: any = {}) => Parser.parse(code, { - sourceType: 'module', - ecmaVersion: 'latest', - locations: true, - ...opts, - }), - }, code, 'plugin.ts')?.code.trim()).toMatchInlineSnapshot(`undefined`) + expect(transformPlugin.transform(code, 'plugin.ts')?.code.trim()).toMatchInlineSnapshot(`undefined`) }) }) diff --git a/packages/nuxt/test/page-metadata.test.ts b/packages/nuxt/test/page-metadata.test.ts index 62b8bfe580..f11431d0ff 100644 --- a/packages/nuxt/test/page-metadata.test.ts +++ b/packages/nuxt/test/page-metadata.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from 'vitest' import { compileScript, parse } from '@vue/compiler-sfc' -import * as Parser from 'acorn' import { PageMetaPlugin } from '../src/pages/plugins/page-meta' import { getRouteMeta, normalizeRoutes } from '../src/pages/utils' @@ -9,22 +8,22 @@ import type { NuxtPage } from '../schema' const filePath = '/app/pages/index.vue' describe('page metadata', () => { - it('should not extract metadata from empty files', async () => { - expect(await getRouteMeta('', filePath)).toEqual({}) - expect(await getRouteMeta('', filePath)).toEqual({}) + it('should not extract metadata from empty files', () => { + expect(getRouteMeta('', filePath)).toEqual({}) + expect(getRouteMeta('', filePath)).toEqual({}) }) - it('should extract metadata from JS/JSX files', async () => { + it('should extract metadata from JS/JSX files', () => { const fileContents = `definePageMeta({ name: 'bar' })` for (const ext of ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs']) { - const meta = await getRouteMeta(fileContents, `/app/pages/index.${ext}`) + const meta = getRouteMeta(fileContents, `/app/pages/index.${ext}`) expect(meta).toStrictEqual({ name: 'bar', }) } }) - it('should parse JSX files', async () => { + it('should parse JSX files', () => { const fileContents = ` export default { setup () { @@ -33,14 +32,13 @@ export default { } } ` - const meta = await getRouteMeta(fileContents, `/app/pages/index.jsx`) + const meta = getRouteMeta(fileContents, `/app/pages/index.jsx`) expect(meta).toStrictEqual({ name: 'bar', }) }) - // TODO: https://github.com/nuxt/nuxt/pull/30066 - it.todo('should handle experimental decorators', async () => { + it('should handle experimental decorators', async () => { const fileContents = ` + ` + const meta = getRouteMeta(fileContents, `/app/pages/index.vue`) + expect(meta).toStrictEqual({ + name: 'bar', + }) + }) + + it('should use and invalidate cache', () => { + const fileContents = `` + const meta = getRouteMeta(fileContents, filePath) + expect(meta === getRouteMeta(fileContents, filePath)).toBeTruthy() + expect(meta === getRouteMeta(fileContents, '/app/pages/other.vue')).toBeFalsy() + expect(meta === getRouteMeta('' + fileContents, filePath)).toBeFalsy() + }) + + it('should extract serialisable metadata', () => { + const meta = getRouteMeta(` ` const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' }) - expect(transformPlugin.transform.call({ - parse: (code: string, opts: any = {}) => Parser.parse(code, { - sourceType: 'module', - ecmaVersion: 'latest', - locations: true, - ...opts, - }), - }, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(` + expect(transformPlugin.transform(res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(` "const __nuxt_page_meta = { name: 'hi', other: 'value' diff --git a/packages/nuxt/test/plugin-metadata.test.ts b/packages/nuxt/test/plugin-metadata.test.ts index a53c287eb2..a063f65a6a 100644 --- a/packages/nuxt/test/plugin-metadata.test.ts +++ b/packages/nuxt/test/plugin-metadata.test.ts @@ -1,5 +1,4 @@ import { describe, expect, it, vi } from 'vitest' -import * as Parser from 'acorn' import { RemovePluginMetadataPlugin, extractMetadata } from '../src/core/plugins/plugin-metadata' import { checkForCircularDependencies } from '../src/core/app' @@ -21,7 +20,7 @@ describe('plugin-metadata', () => { 'export default defineNuxtPlugin({', ...obj.map(([key, value]) => `${key}: ${typeof value === 'function' ? value.toString().replace('"[JSX]"', '() => JSX') : JSON.stringify(value)},`), '})', - ].join('\n'), 'tsx') + ].join('\n'), 'file.tsx') expect(meta).toEqual({ 'name': 'test', @@ -40,14 +39,7 @@ describe('plugin-metadata', () => { 'export const plugin = {}', ] for (const plugin of invalidPlugins) { - expect(transformPlugin.transform.call({ - parse: (code: string, opts: any = {}) => Parser.parse(code, { - sourceType: 'module', - ecmaVersion: 'latest', - locations: true, - ...opts, - }), - }, plugin, 'my-plugin.mjs').code).toBe('export default () => {}') + expect(transformPlugin.transform(plugin, 'my-plugin.mjs').code).toBe('export default () => {}') } }) @@ -59,14 +51,7 @@ describe('plugin-metadata', () => { setup: () => {}, }, { order: 10, name: test }) ` - expect(transformPlugin.transform.call({ - parse: (code: string, opts: any = {}) => Parser.parse(code, { - sourceType: 'module', - ecmaVersion: 'latest', - locations: true, - ...opts, - }), - }, plugin, 'my-plugin.mjs').code).toMatchInlineSnapshot(` + expect(transformPlugin.transform(plugin, 'my-plugin.mjs').code).toMatchInlineSnapshot(` " export default defineNuxtPlugin({ setup: () => {}, diff --git a/packages/nuxt/test/route-rules.test.ts b/packages/nuxt/test/route-rules.test.ts index a5431fab37..c313494318 100644 --- a/packages/nuxt/test/route-rules.test.ts +++ b/packages/nuxt/test/route-rules.test.ts @@ -3,9 +3,9 @@ import { describe, expect, it } from 'vitest' import { extractRouteRules } from '../src/pages/route-rules' describe('route-rules', () => { - it('should extract route rules from pages', async () => { + it('should extract route rules from pages', () => { for (const [path, code] of Object.entries(examples)) { - const result = await extractRouteRules(code, path) + const result = extractRouteRules(code, path) expect(result).toStrictEqual({ 'prerender': true, @@ -33,7 +33,6 @@ defineRouteRules({ `, // vue component with a normal script block, and defineRouteRules ambiently 'component.vue': ` - `, -// TODO: JS component with defineRouteRules within a setup function -// 'component.ts': ` -// export default { -// setup() { -// defineRouteRules({ -// prerender: true -// }) -// } -// } -// `, + // JS component with defineRouteRules within a setup function + 'component.ts': ` +export default { + setup() { + defineRouteRules({ + prerender: true + }) + } +} + `, } diff --git a/packages/nuxt/test/treeshake-client.test.ts b/packages/nuxt/test/treeshake-client.test.ts index 13a0a21f59..871445b496 100644 --- a/packages/nuxt/test/treeshake-client.test.ts +++ b/packages/nuxt/test/treeshake-client.test.ts @@ -3,7 +3,6 @@ import path from 'node:path' import { describe, expect, it, vi } from 'vitest' import * as VueCompilerSFC from 'vue/compiler-sfc' import type { Plugin } from 'vite' -import * as Parser from 'acorn' import type { Options } from '@vitejs/plugin-vue' import _vuePlugin from '@vitejs/plugin-vue' import { TreeShakeTemplatePlugin } from '../src/components/plugins/tree-shake' @@ -56,14 +55,7 @@ const treeshakeTemplatePlugin = TreeShakeTemplatePlugin({ const treeshake = async (source: string): Promise => { // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - const result = await (treeshakeTemplatePlugin.transform! as Function).call({ - parse: (code: string, opts: any = {}) => Parser.parse(code, { - sourceType: 'module', - ecmaVersion: 'latest', - locations: true, - ...opts, - }), - }, source) + const result = await (treeshakeTemplatePlugin.transform! as Function)(source) return typeof result === 'string' ? result : result?.code } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b1a975171..06ff41e52b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -305,9 +305,6 @@ importers: '@vue/shared': specifier: 3.5.13 version: 3.5.13 - acorn: - specifier: 8.14.0 - version: 8.14.0 c12: specifier: 2.0.1 version: 2.0.1(magicast@0.3.5) @@ -392,6 +389,9 @@ importers: ohash: specifier: 1.1.4 version: 1.1.4 + oxc-parser: + specifier: ^0.38.0 + version: 0.38.0 pathe: specifier: ^1.1.2 version: 1.1.2 @@ -2191,6 +2191,49 @@ packages: '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@oxc-parser/binding-darwin-arm64@0.38.0': + resolution: {integrity: sha512-yMDY/KrF2gEEpVIzaVWuO1savy303onvhg96a+1mUfLhgH9lvfZZyus4/LVokRHYCCy+6ey/1ytyilVnSeTDYA==} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.38.0': + resolution: {integrity: sha512-OD8PQT5nAdNCbmzMVfafIQi9/yxSvfEH5fpLXPKVTCtAEc/d5aX6JpipjW231/3TF8mjXO1y3xZM1xKGXJEk/A==} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-linux-arm64-gnu@0.38.0': + resolution: {integrity: sha512-REfFdTk+cdIC8etE8v0CoZtVObKIiymFsPkQTYca2V2ZwJ9/SgUr6NNNjLJdkGOipwjBk8d2cyOZzNDIMfzmqw==} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-arm64-musl@0.38.0': + resolution: {integrity: sha512-tSDRTahOoT4KDS0dicDEFgBH2nEDqNmqFG+N6PS2LrExYEDS01noRIxMFpx2Sz22a+6ZnvkfVmStp6VK+9fpBQ==} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-x64-gnu@0.38.0': + resolution: {integrity: sha512-qc6NdCzcCAxCK5KVde2ULq+7cyiJwrMV/1yozSUqvu26yme56Es3rWHFuPToxrhw+2olvc93AX+PVYMy3DqrGw==} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-linux-x64-musl@0.38.0': + resolution: {integrity: sha512-x/9XyivlMjnQ7gofNK6u+L+BMdwKf/GuLJ0vrmrWolKGR9CBSzZRwZfTw+cG0T7U6Y6CN36tSqijA0eTo3U8zw==} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-win32-arm64-msvc@0.38.0': + resolution: {integrity: sha512-SUlt+MUid0VUL5cLbbZzVGA9xNoqLl29xF1ueTeDt/BdQyrLELjTxoGtgisF6blAD6Rj2JqPtnfk+CT7GBwL2w==} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.38.0': + resolution: {integrity: sha512-l/nFJutJ+zZTGj7ayL22r+og8g6dOC/47fSlsyl+KQXsmAFDStaARioHP9g+von/0bS23LX/9Eo8YeK5kFc7KA==} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.38.0': + resolution: {integrity: sha512-WjRra3cmQt/VPRTwiVuYITm6RNr4SjwYeVZkT0oPn1M0Li2caILj1mlRELhHXad4nLAlnfliH5daHFKjQ9d3jQ==} + '@parcel/watcher-android-arm64@2.5.0': resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} engines: {node: '>= 10.0.0'} @@ -2624,8 +2667,8 @@ packages: engines: {node: '>=8.10'} hasBin: true - '@stripe/stripe-js@4.8.0': - resolution: {integrity: sha512-+4Cb0bVHlV4BJXxkJ3cCLSLuWxm3pXKtgcRacox146EuugjCzRRII5T5gUMgL4HpzrBLVwVxjKaZqntNWAXawQ==} + '@stripe/stripe-js@4.9.0': + resolution: {integrity: sha512-tMPZQZZXGWyNX7hbgenq+1xEj2oigJ54XddbtSX36VedoKsPBq7dxwRXu4Xd5FdpT3JDyyDtnmvYkaSnH1yHTQ==} engines: {node: '>=12.16'} '@stylistic/eslint-plugin@2.11.0': @@ -5915,6 +5958,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + oxc-parser@0.38.0: + resolution: {integrity: sha512-w/cUL64wDb72gaBoOnvodKgHhNF9pDjb2b8gPkDKJDTvIvlbcE9XGDT3cnXOP4N3XCMrRT4MC23bCHGb3gCFSQ==} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -8758,7 +8804,7 @@ snapshots: '@nuxt/devtools-kit': 1.6.1(vite@6.0.1(@types/node@22.10.1)(jiti@2.4.0)(sass@1.78.0)(terser@5.32.0)(tsx@4.19.1)(yaml@2.5.1)) '@nuxt/devtools-ui-kit': 1.5.1(@nuxt/devtools@1.6.1(rollup@4.27.4)(vite@6.0.1(@types/node@22.10.1)(jiti@2.4.0)(sass@1.78.0)(terser@5.32.0)(tsx@4.19.1)(yaml@2.5.1))(vue@3.5.13(typescript@5.6.3)))(@unocss/webpack@0.62.4(rollup@4.27.4)(webpack@5.96.1(esbuild@0.24.0)))(@vue/compiler-core@3.5.13)(change-case@5.4.4)(nuxt@packages+nuxt)(postcss@8.4.49)(rollup@4.27.4)(vite@6.0.1(@types/node@22.10.1)(jiti@2.4.0)(sass@1.78.0)(terser@5.32.0)(tsx@4.19.1)(yaml@2.5.1))(vue@3.5.13(typescript@5.6.3))(webpack@5.96.1(esbuild@0.24.0)) '@nuxt/kit': link:packages/kit - '@stripe/stripe-js': 4.8.0 + '@stripe/stripe-js': 4.9.0 '@types/google.maps': 3.58.1 '@types/vimeo__player': 2.18.3 '@types/youtube': 0.1.0 @@ -8923,6 +8969,32 @@ snapshots: '@one-ini/wasm@0.1.1': {} + '@oxc-parser/binding-darwin-arm64@0.38.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.38.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.38.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.38.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.38.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.38.0': + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.38.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.38.0': + optional: true + + '@oxc-project/types@0.38.0': {} + '@parcel/watcher-android-arm64@2.5.0': optional: true @@ -9343,7 +9415,7 @@ snapshots: ignore: 5.3.2 p-map: 4.0.0 - '@stripe/stripe-js@4.8.0': {} + '@stripe/stripe-js@4.9.0': {} '@stylistic/eslint-plugin@2.11.0(eslint@9.15.0(jiti@2.4.0))(typescript@5.6.3)': dependencies: @@ -13582,6 +13654,19 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + oxc-parser@0.38.0: + dependencies: + '@oxc-project/types': 0.38.0 + optionalDependencies: + '@oxc-parser/binding-darwin-arm64': 0.38.0 + '@oxc-parser/binding-darwin-x64': 0.38.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.38.0 + '@oxc-parser/binding-linux-arm64-musl': 0.38.0 + '@oxc-parser/binding-linux-x64-gnu': 0.38.0 + '@oxc-parser/binding-linux-x64-musl': 0.38.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.38.0 + '@oxc-parser/binding-win32-x64-msvc': 0.38.0 + p-limit@2.3.0: dependencies: p-try: 2.2.0