Skip to content

feat: AST-based compiler macro system for extendComponentMeta#116

Open
hendrikheil wants to merge 8 commits into
nuxt-content:mainfrom
hendrikheil:feat/compiler-macro-extend-meta
Open

feat: AST-based compiler macro system for extendComponentMeta#116
hendrikheil wants to merge 8 commits into
nuxt-content:mainfrom
hendrikheil:feat/compiler-macro-extend-meta

Conversation

@hendrikheil

Copy link
Copy Markdown

Summary

  • Replaces the fragile regex+eval hack for extendComponentMeta with proper AST-based parsing via oxc-parser + oxc-walker
  • Adds extendMetaFunctions module option to register named compiler macros, each with an optional transform hook
  • Macro calls are stripped from browser output by the Vite plugin (true zero-runtime compiler macros)
  • Custom macro names get auto-generated declare function type declarations

New API

// nuxt.config.ts
componentMeta: {
  extendMetaFunctions: [
    { name: 'extendComponentMeta' },
    { name: 'extendProps', transform: (extracted) => ({ _studio: extracted }) }
  ]
}
<!-- In a component -->
<script setup>
extendProps({ filePath: 'file-picker' })
</script>

Produces component.meta._studio = { filePath: 'file-picker' } — with no runtime cost.

Changes

  • src/parser/macro-extractor.ts — new file: extractMacroMeta() and stripMacroCalls() using oxc AST
  • src/types/module.ts — new ExtendMetaFunction interface, extendMetaFunctions option
  • src/parser/meta-parser.ts — replaces regex+eval with extractMacroMeta
  • src/utils/unplugin.ts — adds Vite transform hook to strip macro calls
  • src/module.ts — wires defaults, generates type declarations for custom macro names
  • src/parser/index.ts — adds extendMetaFunctions support to standalone parser

Test plan

  • Existing extendComponentMeta test passes (backward compatible)
  • New extendProps with transform test passes (meta._studio.filePath === 'file-picker')
  • Verify macro calls are absent from browser bundle output

🤖 Generated with Claude Code

hendrikheil and others added 7 commits May 22, 2026 16:32
…macro system

Replaces the fragile regex+eval hack for `extendComponentMeta` with a
proper compiler macro system using oxc-parser and oxc-walker.

- Add `extendMetaFunctions` option to register named compiler macros,
  each with an optional `transform` hook to wrap the extracted argument
- New `src/parser/macro-extractor.ts` handles AST-based extraction
  (extractMacroMeta) and browser-output stripping (stripMacroCalls)
- Vite unplugin gains a transform hook that strips macro calls from
  browser output — making them true zero-runtime compiler macros
- Custom macro names get global `declare function` type declarations
  generated in the component-meta.d.ts template
- Standalone parser (src/parser/index.ts) also supports extendMetaFunctions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces new Function() evaluation with a recursive AST evaluator that
handles MemberExpression values (e.g. InputType.FILE) by tracing imports,
loading the source file, and extracting the literal value from the const
declaration. Also handles TSAsExpression wrappers (`as const`).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces regex-based script extraction with @vue/compiler-sfc's parse,
correctly handling both <script> and <script setup> blocks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Macro transforms can now receive array arguments, not just objects.
Previously, array args were silently dropped even though stripMacroCalls
would still remove the call from the bundle — a silent data loss bug.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@hendrikheil hendrikheil force-pushed the feat/compiler-macro-extend-meta branch from 22a8396 to ef01cc7 Compare May 22, 2026 14:32
@pkg-pr-new

pkg-pr-new Bot commented May 22, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/nuxt-component-meta@116

commit: 05f350d

@atinux atinux requested a review from farnabaz June 3, 2026 11:25
Comment thread src/parser/macro-extractor.ts Outdated
*/
function extractScriptContent(code: string, filename: string): string {
if (!filename.endsWith('.vue')) return code
const { descriptor } = parseSfc(code, { filename, ignoreEmpty: false })

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

When I test the code in playground I got this error:

Cannot find package 'velocityjs' imported from /Users/.../projects/nuxt/nuxt-component-meta/playground/.nuxt/dev/index.mjs

Which comes from '@vue/compiler-sfc'. Since we are using scf compiler just to get the <script> tag content here, I believe using regex will be safe and faster and reduce dependencies.

I'll give it a try

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Tried it — regex extraction works and drops @vue/compiler-sfc entirely. Benchmarks:

before (sfc) after (regex) speedup
script extraction ~0.0009 ms ~0.0001 ms 8.7×
full pipeline (literal arg) 0.636 ms 0.556 ms 1.14×

End-to-end gain is modest (pipeline is dominated by jiti.evalModule), but the crash is gone and we shed a dependency. Only caveat: the regex won't handle a </script> inside a string/comment like the full parser would — a non-issue for top-level macro statements.

Benchmarks added under test/*.bench.ts (pnpm test:bench).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I originally went with compiler-sfc to stay within ecosystem tooling to ensure we're as compatible as possible.

I think since the performance improvement is modest, we should maybe try to fix the issue you encountered in the playground instead of switching to the regex?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@farnabaz I looked into your issue and I think we could easily just solve this by adding @vue/compiler-sfc to externals.
This will emit some warnings still, but not cause any errors now.

These warnings are unlikely to be avoidable unless we explicitly suppress them as the playground imports our usually build-time only code at runtime (

import { getComponentMeta } from "../../../src/parser"
).

I'd argue that switching back and accepting the warnings is a better approach?

@farnabaz farnabaz Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hendrikheil Sorry for the late response
I understand your argument, but we only need the text inside <script> tag, using whole SFC parser to get string content is not ideal IMO.

Have you tested the current state of PR, is there some issues in the behavior?

@hendrikheil hendrikheil requested a review from farnabaz June 25, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants