feat(nuxt): use oxc-parser instead of esbuild + acorn (#30066)

This commit is contained in:
Daniel Roe 2025-03-02 08:36:54 +00:00
parent fa480e0a0a
commit c4e6e8c117
No known key found for this signature in database
GPG Key ID: CBC814C393D93268
14 changed files with 203 additions and 262 deletions

View File

@ -81,7 +81,6 @@
"@unhead/ssr": "^1.11.20", "@unhead/ssr": "^1.11.20",
"@unhead/vue": "^1.11.20", "@unhead/vue": "^1.11.20",
"@vue/shared": "^3.5.13", "@vue/shared": "^3.5.13",
"acorn": "^8.14.0",
"c12": "^3.0.2", "c12": "^3.0.2",
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
"compatx": "^0.1.8", "compatx": "^0.1.8",
@ -111,6 +110,7 @@
"ofetch": "^1.4.1", "ofetch": "^1.4.1",
"ohash": "^2.0.8", "ohash": "^2.0.8",
"on-change": "^5.0.1", "on-change": "^5.0.1",
"oxc-parser": "^0.53.0",
"pathe": "^2.0.3", "pathe": "^2.0.3",
"perfect-debounce": "^1.0.0", "perfect-debounce": "^1.0.0",
"pkg-types": "^2.0.1", "pkg-types": "^2.0.1",

View File

@ -1,8 +1,7 @@
import { pathToFileURL } from 'node:url' import { pathToFileURL } from 'node:url'
import { parseURL } from 'ufo' import { parseURL } from 'ufo'
import MagicString from 'magic-string' import MagicString from 'magic-string'
import type { AssignmentProperty, CallExpression, ObjectExpression, Pattern, Property, ReturnStatement, VariableDeclaration } from 'estree' import type { AssignmentProperty, CallExpression, ObjectExpression, Pattern, Program, Property, ReturnStatement, VariableDeclaration } from 'estree'
import type { Program } from 'acorn'
import { createUnplugin } from 'unplugin' import { createUnplugin } from 'unplugin'
import type { Component } from '@nuxt/schema' import type { Component } from '@nuxt/schema'
import { resolve } from 'pathe' import { resolve } from 'pathe'

View File

@ -7,7 +7,7 @@ import MagicString from 'magic-string'
import { normalize } from 'pathe' import { normalize } from 'pathe'
import type { ObjectPlugin, PluginMeta } from 'nuxt/app' import type { ObjectPlugin, PluginMeta } from 'nuxt/app'
import { parseAndWalk, transform, withLocations } from '../../core/utils/parse' import { parseAndWalk, withLocations } from '../../core/utils/parse'
import { logger } from '../../utils' import { logger } from '../../utils'
const internalOrderMap = { const internalOrderMap = {
@ -38,7 +38,7 @@ export const orderMap: Record<NonNullable<ObjectPlugin['enforce']>, number> = {
} }
const metaCache: Record<string, Omit<PluginMeta, 'enforce'>> = {} const metaCache: Record<string, Omit<PluginMeta, 'enforce'>> = {}
export async function extractMetadata (code: string, loader = 'ts' as 'ts' | 'tsx') { export function extractMetadata (code: string, loader = 'ts' as 'ts' | 'tsx') {
let meta: PluginMeta = {} let meta: PluginMeta = {}
if (metaCache[code]) { if (metaCache[code]) {
return metaCache[code] return metaCache[code]
@ -46,8 +46,7 @@ export async function extractMetadata (code: string, loader = 'ts' as 'ts' | 'ts
if (code.match(/defineNuxtPlugin\s*\([\w(]/)) { if (code.match(/defineNuxtPlugin\s*\([\w(]/)) {
return {} return {}
} }
const js = await transform(code, { loader }) parseAndWalk(code, `file.${loader}`, (node) => {
parseAndWalk(js.code, `file.${loader}`, (node) => {
if (node.type !== 'CallExpression' || node.callee.type !== 'Identifier') { return } if (node.type !== 'CallExpression' || node.callee.type !== 'Identifier') { return }
const name = 'name' in node.callee && node.callee.name const name = 'name' in node.callee && node.callee.name

View File

@ -1,8 +1,7 @@
import { walk as _walk } from 'estree-walker' import { walk as _walk } from 'estree-walker'
import type { Node, SyncHandler } from 'estree-walker' import type { Node, SyncHandler } from 'estree-walker'
import type { ArrowFunctionExpression, CatchClause, Program as ESTreeProgram, FunctionDeclaration, FunctionExpression, Identifier, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, VariableDeclaration } from 'estree' import type { ArrowFunctionExpression, CatchClause, FunctionDeclaration, FunctionExpression, Identifier, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, Program, VariableDeclaration } from 'estree'
import { parse } from 'acorn' import { parseSync } from 'oxc-parser'
import type { Program } from 'acorn'
import { type SameShape, type TransformOptions, type TransformResult, transform as esbuildTransform } from 'esbuild' import { type SameShape, type TransformOptions, type TransformResult, transform as esbuildTransform } from 'esbuild'
import { tryUseNuxt } from '@nuxt/kit' import { tryUseNuxt } from '@nuxt/kit'
@ -22,7 +21,7 @@ interface WalkOptions {
} }
export function walk (ast: Program | Node, callback: Partial<WalkOptions>) { export function walk (ast: Program | Node, callback: Partial<WalkOptions>) {
return _walk(ast as unknown as ESTreeProgram | Node, { return _walk(ast, {
enter (node, parent, key, index) { enter (node, parent, key, index) {
// @ts-expect-error - accessing a protected property // @ts-expect-error - accessing a protected property
callback.scopeTracker?.processNodeEnter(node as WithLocations<Node>) callback.scopeTracker?.processNodeEnter(node as WithLocations<Node>)
@ -33,15 +32,16 @@ export function walk (ast: Program | Node, callback: Partial<WalkOptions>) {
callback.scopeTracker?.processNodeLeave(node as WithLocations<Node>) callback.scopeTracker?.processNodeLeave(node as WithLocations<Node>)
callback.leave?.call(this, node as WithLocations<Node>, parent as WithLocations<Node> | null, { key, index, ast }) callback.leave?.call(this, node as WithLocations<Node>, parent as WithLocations<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, callback: WalkerCallback): Program
export function parseAndWalk (code: string, sourceFilename: string, object: Partial<WalkOptions>): Program export function parseAndWalk (code: string, sourceFilename: string, object: Partial<WalkOptions>): Program
export function parseAndWalk (code: string, sourceFilename: string, callback: Partial<WalkOptions> | WalkerCallback) { export function parseAndWalk (code: string, sourceFilename: string, callback: Partial<WalkOptions> | WalkerCallback) {
const ast = parse (code, { sourceType: 'module', ecmaVersion: 'latest', locations: true, sourceFile: sourceFilename }) const lang = sourceFilename.match(/\.[cm]?([jt]sx?)$/)?.[1] as 'js' | 'ts' | 'jsx' | 'tsx' | undefined
walk(ast, typeof callback === 'function' ? { enter: callback } : callback) const ast = parseSync(sourceFilename, code, { sourceType: 'module', lang })
return ast walk(ast.program as unknown as Program, typeof callback === 'function' ? { enter: callback } : callback)
return ast.program as unknown as Program
} }
export function withLocations<T> (node: T): WithLocations<T> { export function withLocations<T> (node: T): WithLocations<T> {

View File

@ -212,7 +212,7 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions = {}) => createUnp
} }
} }
const ast = parseAndWalk(code, id, { const ast = parseAndWalk(code, id + (query.lang ? '.' + query.lang : '.ts'), {
scopeTracker, scopeTracker,
}) })

View File

@ -4,13 +4,13 @@ import type { NitroRouteConfig } from 'nitropack'
import { normalize } from 'pathe' import { normalize } from 'pathe'
import { getLoader } from '../core/utils' import { getLoader } from '../core/utils'
import { parseAndWalk, transform } from '../core/utils/parse' import { parseAndWalk } from '../core/utils/parse'
import { extractScriptContent, pathToNitroGlob } from './utils' import { extractScriptContent, pathToNitroGlob } from './utils'
const ROUTE_RULE_RE = /\bdefineRouteRules\(/ const ROUTE_RULE_RE = /\bdefineRouteRules\(/
const ruleCache: Record<string, NitroRouteConfig | null> = {} const ruleCache: Record<string, NitroRouteConfig | null> = {}
export async function extractRouteRules (code: string, path: string): Promise<NitroRouteConfig | null> { export function extractRouteRules (code: string, path: string): NitroRouteConfig | null {
if (code in ruleCache) { if (code in ruleCache) {
return ruleCache[code] || null return ruleCache[code] || null
} }
@ -26,12 +26,10 @@ export async function extractRouteRules (code: string, path: string): Promise<Ni
code = script?.code || code code = script?.code || code
const js = await transform(code, { loader: script?.loader || 'ts' }) parseAndWalk(code, 'file.' + (script?.loader || 'ts'), (node) => {
parseAndWalk(js.code, 'file.' + (script?.loader || 'ts'), (node) => {
if (node.type !== 'CallExpression' || node.callee.type !== 'Identifier') { return } if (node.type !== 'CallExpression' || node.callee.type !== 'Identifier') { return }
if (node.callee.name === 'defineRouteRules') { if (node.callee.name === 'defineRouteRules') {
const rulesString = js.code.slice(node.start, node.end) const rulesString = code.slice(node.start, node.end)
try { try {
rule = JSON.parse(runInNewContext(rulesString.replace('defineRouteRules', 'JSON.stringify'), {})) rule = JSON.parse(runInNewContext(rulesString.replace('defineRouteRules', 'JSON.stringify'), {}))
} catch { } catch {

View File

@ -11,7 +11,7 @@ import type { Property } from 'estree'
import type { NuxtPage } from 'nuxt/schema' import type { NuxtPage } from 'nuxt/schema'
import { klona } from 'klona' import { klona } from 'klona'
import { parseAndWalk, transform, withLocations } from '../core/utils/parse' import { parseAndWalk, withLocations } from '../core/utils/parse'
import { getLoader, uniqueBy } from '../core/utils' import { getLoader, uniqueBy } from '../core/utils'
import { logger, toArray } from '../utils' import { logger, toArray } from '../utils'
@ -207,7 +207,7 @@ const DYNAMIC_META_KEY = '__nuxt_dynamic_meta_key' as const
const pageContentsCache: Record<string, string> = {} const pageContentsCache: Record<string, string> = {}
const metaCache: Record<string, Partial<Record<keyof NuxtPage, any>>> = {} const metaCache: Record<string, Partial<Record<keyof NuxtPage, any>>> = {}
export async function getRouteMeta (contents: string, absolutePath: string, extraExtractionKeys: string[] = []): Promise<Partial<Record<keyof NuxtPage, any>>> { export function getRouteMeta (contents: string, absolutePath: string, extraExtractionKeys: string[] = []): Partial<Record<keyof NuxtPage, any>> {
// set/update pageContentsCache, invalidate metaCache on cache mismatch // set/update pageContentsCache, invalidate metaCache on cache mismatch
if (!(absolutePath in pageContentsCache) || pageContentsCache[absolutePath] !== contents) { if (!(absolutePath in pageContentsCache) || pageContentsCache[absolutePath] !== contents) {
pageContentsCache[absolutePath] = contents pageContentsCache[absolutePath] = contents
@ -234,13 +234,11 @@ export async function getRouteMeta (contents: string, absolutePath: string, extr
continue continue
} }
const js = await transform(script.code, { loader: script.loader })
const dynamicProperties = new Set<keyof NuxtPage>() const dynamicProperties = new Set<keyof NuxtPage>()
let foundMeta = false 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 (foundMeta) { return }
if (node.type !== 'ExpressionStatement' || node.expression.type !== 'CallExpression' || node.expression.callee.type !== 'Identifier' || node.expression.callee.name !== 'definePageMeta') { return } if (node.type !== 'ExpressionStatement' || node.expression.type !== 'CallExpression' || node.expression.callee.type !== 'Identifier' || node.expression.callee.name !== 'definePageMeta') { return }
@ -259,7 +257,7 @@ export async function getRouteMeta (contents: string, absolutePath: string, extr
const propertyValue = withLocations(property.value) const propertyValue = withLocations(property.value)
if (propertyValue.type === 'ObjectExpression') { if (propertyValue.type === 'ObjectExpression') {
const valueString = js.code.slice(propertyValue.start, propertyValue.end) const valueString = script.code.slice(propertyValue.start, propertyValue.end)
try { try {
extractedMeta[key] = JSON.parse(runInNewContext(`JSON.stringify(${valueString})`, {})) extractedMeta[key] = JSON.parse(runInNewContext(`JSON.stringify(${valueString})`, {}))
} catch { } catch {

View File

@ -1,7 +1,6 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import type { Component } from '@nuxt/schema' import type { Component } from '@nuxt/schema'
import { compileScript, parse } from '@vue/compiler-sfc' import { compileScript, parse } from '@vue/compiler-sfc'
import * as Parser from 'acorn'
import { ComponentNamePlugin } from '../src/components/plugins/component-names' import { ComponentNamePlugin } from '../src/components/plugins/component-names'
@ -43,14 +42,7 @@ onMounted(() => {
</script> </script>
` `
const res = compileScript(parse(sfc).descriptor, { id: 'test.vue' }) const res = compileScript(parse(sfc).descriptor, { id: 'test.vue' })
const { code } = transformPlugin.transform.call({ const { code } = transformPlugin.transform(res.content, components[0].filePath) ?? {}
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, res.content, components[0].filePath) ?? {}
expect(code?.trim()).toMatchInlineSnapshot(` expect(code?.trim()).toMatchInlineSnapshot(`
"export default Object.assign({ "export default Object.assign({
setup(__props, { expose: __expose }) { setup(__props, { expose: __expose }) {

View File

@ -1,5 +1,4 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import * as Parser from 'acorn'
import { ComposableKeysPlugin, detectImportNames } from '../src/core/plugins/composable-keys' import { ComposableKeysPlugin, detectImportNames } from '../src/core/plugins/composable-keys'
@ -40,14 +39,7 @@ describe('composable keys plugin', () => {
import { useAsyncData } from '#app' import { useAsyncData } from '#app'
useAsyncData(() => {}) useAsyncData(() => {})
` `
expect(transformPlugin.transform.call({ expect(transformPlugin.transform(code, 'plugin.ts')?.code.trim()).toMatchInlineSnapshot(`
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, code, 'plugin.ts')?.code.trim()).toMatchInlineSnapshot(`
"import { useAsyncData } from '#app' "import { useAsyncData } from '#app'
useAsyncData(() => {}, '$HJiaryoL2y')" useAsyncData(() => {}, '$HJiaryoL2y')"
`) `)
@ -55,14 +47,7 @@ useAsyncData(() => {})
it('should not add hash when one exists', () => { it('should not add hash when one exists', () => {
const code = `useAsyncData(() => {}, 'foo')` const code = `useAsyncData(() => {}, 'foo')`
expect(transformPlugin.transform.call({ expect(transformPlugin.transform(code, 'plugin.ts')?.code.trim()).toMatchInlineSnapshot(`undefined`)
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, code, 'plugin.ts')?.code.trim()).toMatchInlineSnapshot(`undefined`)
}) })
it('should not add hash composables is imported from somewhere else', () => { it('should not add hash composables is imported from somewhere else', () => {
@ -70,13 +55,6 @@ useAsyncData(() => {})
const useAsyncData = () => {} const useAsyncData = () => {}
useAsyncData(() => {}) useAsyncData(() => {})
` `
expect(transformPlugin.transform.call({ expect(transformPlugin.transform(code, 'plugin.ts')?.code.trim()).toMatchInlineSnapshot(`undefined`)
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, code, 'plugin.ts')?.code.trim()).toMatchInlineSnapshot(`undefined`)
}) })
}) })

View File

@ -1,8 +1,7 @@
import { type MockedFunction, describe, expect, it, vi } from 'vitest' import { type MockedFunction, describe, expect, it, vi } from 'vitest'
import { compileScript, parse } from '@vue/compiler-sfc' import { compileScript, parse } from '@vue/compiler-sfc'
import * as Parser from 'acorn'
import { klona } from 'klona' import { klona } from 'klona'
import { transform as esbuildTransform } from 'esbuild'
import { PageMetaPlugin } from '../src/pages/plugins/page-meta' import { PageMetaPlugin } from '../src/pages/plugins/page-meta'
import { getRouteMeta, normalizeRoutes } from '../src/pages/utils' import { getRouteMeta, normalizeRoutes } from '../src/pages/utils'
import type { NuxtPage } from '../schema' import type { NuxtPage } from '../schema'
@ -12,15 +11,15 @@ const filePath = '/app/pages/index.vue'
vi.mock('klona', { spy: true }) vi.mock('klona', { spy: true })
describe('page metadata', () => { describe('page metadata', () => {
it('should not extract metadata from empty files', async () => { it('should not extract metadata from empty files', () => {
expect(await getRouteMeta('', filePath)).toEqual({}) expect(getRouteMeta('', filePath)).toEqual({})
expect(await getRouteMeta('<template><div>Hi</div></template>', filePath)).toEqual({}) expect(getRouteMeta('<template><div>Hi</div></template>', filePath)).toEqual({})
}) })
it('should extract metadata from JS/JSX files', async () => { it('should extract metadata from JS/JSX files', () => {
const fileContents = `definePageMeta({ name: 'bar' })` const fileContents = `definePageMeta({ name: 'bar' })`
for (const ext of ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs']) { 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({ expect(meta).toStrictEqual({
'name': 'bar', 'name': 'bar',
'meta': { 'meta': {
@ -30,7 +29,7 @@ describe('page metadata', () => {
} }
}) })
it('should parse JSX files', async () => { it('should parse JSX files', () => {
const fileContents = ` const fileContents = `
export default { export default {
setup () { setup () {
@ -39,7 +38,7 @@ export default {
} }
} }
` `
const meta = await getRouteMeta(fileContents, `/app/pages/index.jsx`) const meta = getRouteMeta(fileContents, `/app/pages/index.jsx`)
// TODO: remove in v4 // TODO: remove in v4
delete meta.meta delete meta.meta
expect(meta).toStrictEqual({ expect(meta).toStrictEqual({
@ -47,14 +46,14 @@ export default {
}) })
}) })
it('should parse lang="jsx" from vue files', async () => { it('should parse lang="jsx" from vue files', () => {
const fileContents = ` const fileContents = `
<script setup lang="jsx"> <script setup lang="jsx">
const foo = <></>; const foo = <></>;
definePageMeta({ name: 'bar' }) definePageMeta({ name: 'bar' })
</script>` </script>`
const meta = await getRouteMeta(fileContents, `/app/pages/index.vue`) const meta = getRouteMeta(fileContents, `/app/pages/index.vue`)
expect(meta).toStrictEqual({ expect(meta).toStrictEqual({
'meta': { 'meta': {
'__nuxt_dynamic_meta_key': new Set(['meta']), '__nuxt_dynamic_meta_key': new Set(['meta']),
@ -63,8 +62,7 @@ export default {
}) })
}) })
// TODO: https://github.com/nuxt/nuxt/pull/30066 it('should handle experimental decorators', () => {
it.todo('should handle experimental decorators', async () => {
const fileContents = ` const fileContents = `
<script setup lang="ts"> <script setup lang="ts">
function something (_method: () => unknown) { function something (_method: () => unknown) {
@ -79,31 +77,31 @@ class SomeClass {
definePageMeta({ name: 'bar' }) definePageMeta({ name: 'bar' })
</script> </script>
` `
const meta = await getRouteMeta(fileContents, `/app/pages/index.vue`) const meta = getRouteMeta(fileContents, `/app/pages/index.vue`)
expect(meta).toStrictEqual({ expect(meta).toStrictEqual({
name: 'bar', name: 'bar',
}) })
}) })
it('should use and invalidate cache', async () => { it('should use and invalidate cache', () => {
const _klona = klona as unknown as MockedFunction<typeof klona> const _klona = klona as unknown as MockedFunction<typeof klona>
_klona.mockImplementation(obj => obj) _klona.mockImplementation(obj => obj)
const fileContents = `<script setup>definePageMeta({ foo: 'bar' })</script>` const fileContents = `<script setup>definePageMeta({ foo: 'bar' })</script>`
const meta = await getRouteMeta(fileContents, filePath) const meta = getRouteMeta(fileContents, filePath)
expect(meta === await getRouteMeta(fileContents, filePath)).toBeTruthy() expect(meta === getRouteMeta(fileContents, filePath)).toBeTruthy()
expect(meta === await getRouteMeta(fileContents, '/app/pages/other.vue')).toBeFalsy() expect(meta === getRouteMeta(fileContents, '/app/pages/other.vue')).toBeFalsy()
expect(meta === await getRouteMeta('<template><div>Hi</div></template>' + fileContents, filePath)).toBeFalsy() expect(meta === getRouteMeta('<template><div>Hi</div></template>' + fileContents, filePath)).toBeFalsy()
_klona.mockReset() _klona.mockReset()
}) })
it('should not share state between page metadata', async () => { it('should not share state between page metadata', () => {
const fileContents = `<script setup>definePageMeta({ foo: 'bar' })</script>` const fileContents = `<script setup>definePageMeta({ foo: 'bar' })</script>`
const meta = await getRouteMeta(fileContents, filePath) const meta = getRouteMeta(fileContents, filePath)
expect(meta === await getRouteMeta(fileContents, filePath)).toBeFalsy() expect(meta === getRouteMeta(fileContents, filePath)).toBeFalsy()
}) })
it('should extract serialisable metadata', async () => { it('should extract serialisable metadata', () => {
const meta = await getRouteMeta(` const meta = getRouteMeta(`
<script setup> <script setup>
definePageMeta({ definePageMeta({
path: '/some-custom-path', path: '/some-custom-path',
@ -144,8 +142,8 @@ definePageMeta({ name: 'bar' })
`) `)
}) })
it('should extract serialisable metadata from files with multiple blocks', async () => { it('should extract serialisable metadata from files with multiple blocks', () => {
const meta = await getRouteMeta(` const meta = getRouteMeta(`
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'thing' name: 'thing'
@ -179,8 +177,8 @@ definePageMeta({ name: 'bar' })
`) `)
}) })
it('should extract serialisable metadata in options api', async () => { it('should extract serialisable metadata in options api', () => {
const meta = await getRouteMeta(` const meta = getRouteMeta(`
<script> <script>
export default { export default {
setup() { setup() {
@ -207,8 +205,8 @@ definePageMeta({ name: 'bar' })
`) `)
}) })
it('should extract serialisable metadata all quoted', async () => { it('should extract serialisable metadata all quoted', () => {
const meta = await getRouteMeta(` const meta = getRouteMeta(`
<script setup> <script setup>
definePageMeta({ definePageMeta({
"otherValue": { "otherValue": {
@ -229,8 +227,8 @@ definePageMeta({ name: 'bar' })
`) `)
}) })
it('should extract configured extra meta', async () => { it('should extract configured extra meta', () => {
const meta = await getRouteMeta(` const meta = getRouteMeta(`
<script setup> <script setup>
definePageMeta({ definePageMeta({
foo: 'bar', foo: 'bar',
@ -249,9 +247,9 @@ definePageMeta({ name: 'bar' })
}) })
describe('normalizeRoutes', () => { describe('normalizeRoutes', () => {
it('should produce valid route objects when used with extracted meta', async () => { it('should produce valid route objects when used with extracted meta', () => {
const page: NuxtPage = { path: '/', file: filePath } const page: NuxtPage = { path: '/', file: filePath }
Object.assign(page, await getRouteMeta(` Object.assign(page, getRouteMeta(`
<script setup> <script setup>
definePageMeta({ definePageMeta({
name: 'some-custom-name', name: 'some-custom-name',
@ -340,14 +338,7 @@ definePageMeta({
</script> </script>
` `
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' }) const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
expect(transformPlugin.transform.call({ expect(transformPlugin.transform(res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
"const __nuxt_page_meta = { "const __nuxt_page_meta = {
name: 'hi', name: 'hi',
other: 'value' other: 'value'
@ -374,14 +365,7 @@ definePageMeta({
</script> </script>
` `
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' }) const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
expect(transformPlugin.transform.call({ expect(transformPlugin.transform(res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
"function isNumber(value) { "function isNumber(value) {
return value && !isNaN(Number(value)) return value && !isNaN(Number(value))
} }
@ -408,14 +392,7 @@ definePageMeta({
</script> </script>
` `
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' }) const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
expect(transformPlugin.transform.call({ expect(transformPlugin.transform(res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
"import { validateIdParam } from './utils' "import { validateIdParam } from './utils'
const __nuxt_page_meta = { const __nuxt_page_meta = {
@ -443,14 +420,7 @@ definePageMeta({
</script> </script>
` `
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' }) const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
expect(transformPlugin.transform.call({ expect(transformPlugin.transform(res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
"const __nuxt_page_meta = { "const __nuxt_page_meta = {
middleware: () => { middleware: () => {
const useState = (key) => ({ value: { isLoggedIn: false } }) const useState = (key) => ({ value: { isLoggedIn: false } })
@ -485,14 +455,7 @@ definePageMeta({
</script> </script>
` `
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' }) const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
expect(transformPlugin.transform.call({ expect(transformPlugin.transform(res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
"const __nuxt_page_meta = { "const __nuxt_page_meta = {
middleware: () => { middleware: () => {
function isLoggedIn() { function isLoggedIn() {
@ -535,14 +498,7 @@ definePageMeta({
</script> </script>
` `
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' }) const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
expect(transformPlugin.transform.call({ expect(transformPlugin.transform(res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
"import { useState } from '#app/composables/state' "import { useState } from '#app/composables/state'
const __nuxt_page_meta = { const __nuxt_page_meta = {
@ -584,14 +540,7 @@ definePageMeta({
</script> </script>
` `
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' }) const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
expect(transformPlugin.transform.call({ expect(transformPlugin.transform(res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
"import { useState } from '#app/composables/state' "import { useState } from '#app/composables/state'
const __nuxt_page_meta = { const __nuxt_page_meta = {
@ -608,7 +557,7 @@ definePageMeta({
`) `)
}) })
it('should work with esbuild.keepNames = true', async () => { it('should work when keeping names = true', () => {
const sfc = ` const sfc = `
<script setup lang="ts"> <script setup lang="ts">
import { foo } from './utils' import { foo } from './utils'
@ -629,37 +578,24 @@ definePageMeta({
</script> </script>
` `
const compiled = compileScript(parse(sfc).descriptor, { id: 'component.vue' }) const compiled = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
const res = await esbuildTransform(compiled.content, { expect(transformPlugin.transform(compiled.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
loader: 'ts', "import { foo } from './utils'
keepNames: true, const checkNum = (value) => {
}) return !isNaN(Number(foo(value)))
expect(transformPlugin.transform.call({ }
parse: (code: string, opts: any = {}) => Parser.parse(code, { function isNumber (value) {
sourceType: 'module', return value && checkNum(value)
ecmaVersion: 'latest', }
locations: true,
...opts,
}),
}, res.code, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
"import { foo } from "./utils";
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
const checkNum = /* @__PURE__ */ __name((value) => {
return !isNaN(Number(foo(value)));
}, "checkNum");
function isNumber(value) {
return value && checkNum(value);
}
const __nuxt_page_meta = { const __nuxt_page_meta = {
validate: /* @__PURE__ */ __name(({ params }) => { validate: ({ params }) => {
return isNumber(params.id); return isNumber(params.id)
}, "validate") },
} }
export default __nuxt_page_meta" export default __nuxt_page_meta"
`) `)
}) })
it('should throw for await expressions', async () => { it('should throw for await expressions', () => {
const sfc = ` const sfc = `
<script setup lang="ts"> <script setup lang="ts">
const asyncValue = await Promise.resolve('test') const asyncValue = await Promise.resolve('test')
@ -670,21 +606,11 @@ definePageMeta({
</script> </script>
` `
const compiled = compileScript(parse(sfc).descriptor, { id: 'component.vue' }) const compiled = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
const res = await esbuildTransform(compiled.content, {
loader: 'ts',
})
let wasErrorThrown = false let wasErrorThrown = false
try { try {
transformPlugin.transform.call({ transformPlugin.transform(compiled.content, 'component.vue?macro=true')
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, res.code, 'component.vue?macro=true')
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
expect(e.message).toMatch(/await in definePageMeta/) expect(e.message).toMatch(/await in definePageMeta/)
@ -751,14 +677,7 @@ const hoisted = ref('hoisted')
</script> </script>
` `
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' }) const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
expect(transformPlugin.transform.call({ expect(transformPlugin.transform(res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
"const foo = 'foo' "const foo = 'foo'
const num = 1 const num = 1
const bar = { bar: 'bar' }.bar, baz = { baz: 'baz' }.baz, x = { foo } const bar = { bar: 'bar' }.bar, baz = { baz: 'baz' }.baz, x = { foo }

View File

@ -1,33 +1,29 @@
import { describe, expect, it, vi } from 'vitest' import { describe, expect, it, vi } from 'vitest'
import * as Parser from 'acorn'
import { RemovePluginMetadataPlugin, extractMetadata } from '../src/core/plugins/plugin-metadata' import { RemovePluginMetadataPlugin, extractMetadata } from '../src/core/plugins/plugin-metadata'
import { checkForCircularDependencies } from '../src/core/app' import { checkForCircularDependencies } from '../src/core/app'
describe('plugin-metadata', () => { describe('plugin-metadata', () => {
it('should extract metadata from object-syntax plugins', async () => { const properties = Object.entries({
const properties = Object.entries({ name: 'test',
name: 'test', enforce: 'post',
enforce: 'post', hooks: { 'app:mounted': () => {} },
hooks: { 'app:mounted': () => {} }, setup: () => { return { provide: { jsx: '[JSX]' } } },
setup: () => { return { provide: { jsx: '[JSX]' } } }, order: 1,
order: 1, })
it.each(properties)('should extract metadata from object-syntax plugins', (k, value) => {
const obj = [...properties.filter(([key]) => key !== k), [k, value]]
const meta = extractMetadata([
'export default defineNuxtPlugin({',
...obj.map(([key, value]) => `${key}: ${typeof value === 'function' ? value.toString().replace('"[JSX]"', '() => <span>JSX</span>') : JSON.stringify(value)},`),
'})',
].join('\n'), 'tsx')
expect(meta).toEqual({
'name': 'test',
'order': 1,
}) })
for (const item of properties) {
const obj = [...properties.filter(([key]) => key !== item[0]), item]
const meta = await extractMetadata([
'export default defineNuxtPlugin({',
...obj.map(([key, value]) => `${key}: ${typeof value === 'function' ? value.toString().replace('"[JSX]"', '() => <span>JSX</span>') : JSON.stringify(value)},`),
'})',
].join('\n'), 'tsx')
expect(meta).toEqual({
'name': 'test',
'order': 1,
})
}
}) })
const transformPlugin: any = RemovePluginMetadataPlugin({ const transformPlugin: any = RemovePluginMetadataPlugin({
@ -41,14 +37,7 @@ describe('plugin-metadata', () => {
'export default function (ctx, inject) {}', 'export default function (ctx, inject) {}',
] ]
for (const plugin of invalidPlugins) { for (const plugin of invalidPlugins) {
expect(transformPlugin.transform.call({ expect(transformPlugin.transform(plugin, 'my-plugin.mjs').code).toBe('export default () => {}')
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, plugin, 'my-plugin.mjs').code).toBe('export default () => {}')
} }
}) })
@ -60,14 +49,7 @@ describe('plugin-metadata', () => {
setup: () => {}, setup: () => {},
}, { order: 10, name: test }) }, { order: 10, name: test })
` `
expect(transformPlugin.transform.call({ expect(transformPlugin.transform(plugin, 'my-plugin.mjs').code).toMatchInlineSnapshot(`
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, plugin, 'my-plugin.mjs').code).toMatchInlineSnapshot(`
" "
export default defineNuxtPlugin({ export default defineNuxtPlugin({
setup: () => {}, setup: () => {},

View File

@ -3,9 +3,9 @@ import { describe, expect, it } from 'vitest'
import { extractRouteRules } from '../src/pages/route-rules' import { extractRouteRules } from '../src/pages/route-rules'
describe('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)) { for (const [path, code] of Object.entries(examples)) {
const result = await extractRouteRules(code, path) const result = extractRouteRules(code, path)
expect(result).toStrictEqual({ expect(result).toStrictEqual({
'prerender': true, 'prerender': true,
@ -33,7 +33,6 @@ defineRouteRules({
`, `,
// vue component with a normal script block, and defineRouteRules ambiently // vue component with a normal script block, and defineRouteRules ambiently
'component.vue': ` 'component.vue': `
<script> <script>
defineRouteRules({ defineRouteRules({
prerender: true prerender: true
@ -43,14 +42,14 @@ export default {
} }
</script> </script>
`, `,
// TODO: JS component with defineRouteRules within a setup function // JS component with defineRouteRules within a setup function
// 'component.ts': ` 'component.ts': `
// export default { export default {
// setup() { setup() {
// defineRouteRules({ defineRouteRules({
// prerender: true prerender: true
// }) })
// } }
// } }
// `, `,
} }

View File

@ -3,7 +3,6 @@ import path from 'node:path'
import { describe, expect, it, vi } from 'vitest' import { describe, expect, it, vi } from 'vitest'
import * as VueCompilerSFC from 'vue/compiler-sfc' import * as VueCompilerSFC from 'vue/compiler-sfc'
import type { Plugin } from 'vite' import type { Plugin } from 'vite'
import * as Parser from 'acorn'
import type { Options } from '@vitejs/plugin-vue' import type { Options } from '@vitejs/plugin-vue'
import _vuePlugin from '@vitejs/plugin-vue' import _vuePlugin from '@vitejs/plugin-vue'
import { TreeShakeTemplatePlugin } from '../src/components/plugins/tree-shake' import { TreeShakeTemplatePlugin } from '../src/components/plugins/tree-shake'
@ -56,14 +55,7 @@ const treeshakeTemplatePlugin = TreeShakeTemplatePlugin({
const treeshake = async (source: string): Promise<string> => { const treeshake = async (source: string): Promise<string> => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
const result = await (treeshakeTemplatePlugin.transform! as Function).call({ const result = await (treeshakeTemplatePlugin.transform! as Function)(source, 'test.ts')
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
...opts,
}),
}, source)
return typeof result === 'string' ? result : result?.code return typeof result === 'string' ? result : result?.code
} }

View File

@ -368,9 +368,6 @@ importers:
'@vue/shared': '@vue/shared':
specifier: 3.5.13 specifier: 3.5.13
version: 3.5.13 version: 3.5.13
acorn:
specifier: ^8.14.0
version: 8.14.0
c12: c12:
specifier: ^3.0.2 specifier: ^3.0.2
version: 3.0.2(magicast@0.3.5) version: 3.0.2(magicast@0.3.5)
@ -458,6 +455,9 @@ importers:
on-change: on-change:
specifier: ^5.0.1 specifier: ^5.0.1
version: 5.0.1 version: 5.0.1
oxc-parser:
specifier: ^0.53.0
version: 0.53.0
pathe: pathe:
specifier: ^2.0.3 specifier: ^2.0.3
version: 2.0.3 version: 2.0.3
@ -2273,6 +2273,49 @@ packages:
'@one-ini/wasm@0.1.1': '@one-ini/wasm@0.1.1':
resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
'@oxc-parser/binding-darwin-arm64@0.53.0':
resolution: {integrity: sha512-wamfZjKPBIbORIL9XeGPvvLVvYvfl/1sOZqKWGFFp+LKBoOqx3X7la8+76SDjgIGkAUyl/FTO2lclFe2h87JWg==}
cpu: [arm64]
os: [darwin]
'@oxc-parser/binding-darwin-x64@0.53.0':
resolution: {integrity: sha512-geeBJOf1FtTQgmgKEUdOEnHP9pTIXSo7HDIJ1tiIO6j6u3j9zss8wFX2khH2XqAxLekSC87x8Bu6iB22ZDqajQ==}
cpu: [x64]
os: [darwin]
'@oxc-parser/binding-linux-arm64-gnu@0.53.0':
resolution: {integrity: sha512-1f5ErSzs5vGSzfMAm+puhKr5J3zqmwtRqoAiW8oZe1TXBk8PSQGaHl43mY8fhfX+ErGwmTdj3z0Q/Y341rkf8Q==}
cpu: [arm64]
os: [linux]
'@oxc-parser/binding-linux-arm64-musl@0.53.0':
resolution: {integrity: sha512-wZ8rNwybcSzzoJzmR1xLUOrum3rYcW2/h4sJSzykI59rzo3Hw21F45EEgCddn3svcgMPK2qW/hcX9SKQ5Ru71Q==}
cpu: [arm64]
os: [linux]
'@oxc-parser/binding-linux-x64-gnu@0.53.0':
resolution: {integrity: sha512-R1Y2hamxRtD1j9sEbyiPTl9rQoyub3tOVaPRG8hSJosymrrfKotrK/S3RNkUWfY5UjKTRFc1c+he3FDMZCRamQ==}
cpu: [x64]
os: [linux]
'@oxc-parser/binding-linux-x64-musl@0.53.0':
resolution: {integrity: sha512-3BlzMhRvfUmF1DNzIcN/TjEqrm9vKHEfPipgZJHG9uh1cr+o5e+whBYhQ2JJ0cd07i7FcNq+gKIjCwv31JNonw==}
cpu: [x64]
os: [linux]
'@oxc-parser/binding-win32-arm64-msvc@0.53.0':
resolution: {integrity: sha512-m7LYYdg8l1h4ozYSHPvuoC4oBEBKBEYniHwHrwRxYoNDlqzkQtnvE/lBwMZnXDbd+STwx1ba7ukxV2XNpv58Wg==}
cpu: [arm64]
os: [win32]
'@oxc-parser/binding-win32-x64-msvc@0.53.0':
resolution: {integrity: sha512-7EhG3JJRttLxfyPriaO7J+2mNQ3tKG25/VkkPW30aH5YL6TKQFUijM/Lh7UW7nRXdvr5Oqn4yVjnDTi8PapFmw==}
cpu: [x64]
os: [win32]
'@oxc-project/types@0.53.0':
resolution: {integrity: sha512-8JXXVoHnRLcl6kDBboSfAmAkKeb6PSvSc5qSJxiOFzFx0ZCLAbUDmuwR2hkBnY7kQS3LmNXaONq1BFAmwTyeZw==}
'@parcel/watcher-android-arm64@2.5.1': '@parcel/watcher-android-arm64@2.5.1':
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
@ -5928,6 +5971,9 @@ packages:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
oxc-parser@0.53.0:
resolution: {integrity: sha512-NjUiQx1ns2l9uIh9aAzXkEakP7GD00rza4FC/cVwxpY/sSvzHlhbuTaRX/91fmVOKJkA3PEMks43UeJCzcLApg==}
p-limit@3.1.0: p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -9041,6 +9087,32 @@ snapshots:
'@one-ini/wasm@0.1.1': {} '@one-ini/wasm@0.1.1': {}
'@oxc-parser/binding-darwin-arm64@0.53.0':
optional: true
'@oxc-parser/binding-darwin-x64@0.53.0':
optional: true
'@oxc-parser/binding-linux-arm64-gnu@0.53.0':
optional: true
'@oxc-parser/binding-linux-arm64-musl@0.53.0':
optional: true
'@oxc-parser/binding-linux-x64-gnu@0.53.0':
optional: true
'@oxc-parser/binding-linux-x64-musl@0.53.0':
optional: true
'@oxc-parser/binding-win32-arm64-msvc@0.53.0':
optional: true
'@oxc-parser/binding-win32-x64-msvc@0.53.0':
optional: true
'@oxc-project/types@0.53.0': {}
'@parcel/watcher-android-arm64@2.5.1': '@parcel/watcher-android-arm64@2.5.1':
optional: true optional: true
@ -13518,6 +13590,19 @@ snapshots:
type-check: 0.4.0 type-check: 0.4.0
word-wrap: 1.2.5 word-wrap: 1.2.5
oxc-parser@0.53.0:
dependencies:
'@oxc-project/types': 0.53.0
optionalDependencies:
'@oxc-parser/binding-darwin-arm64': 0.53.0
'@oxc-parser/binding-darwin-x64': 0.53.0
'@oxc-parser/binding-linux-arm64-gnu': 0.53.0
'@oxc-parser/binding-linux-arm64-musl': 0.53.0
'@oxc-parser/binding-linux-x64-gnu': 0.53.0
'@oxc-parser/binding-linux-x64-musl': 0.53.0
'@oxc-parser/binding-win32-arm64-msvc': 0.53.0
'@oxc-parser/binding-win32-x64-msvc': 0.53.0
p-limit@3.1.0: p-limit@3.1.0:
dependencies: dependencies:
yocto-queue: 0.1.0 yocto-queue: 0.1.0