feat: AST-based compiler macro system for extendComponentMeta#116
feat: AST-based compiler macro system for extendComponentMeta#116hendrikheil wants to merge 8 commits into
Conversation
…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>
22a8396 to
ef01cc7
Compare
commit: |
| */ | ||
| function extractScriptContent(code: string, filename: string): string { | ||
| if (!filename.endsWith('.vue')) return code | ||
| const { descriptor } = parseSfc(code, { filename, ignoreEmpty: false }) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
@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 (
).I'd argue that switching back and accepting the warnings is a better approach?
There was a problem hiding this comment.
@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?
Summary
extendComponentMetawith proper AST-based parsing viaoxc-parser+oxc-walkerextendMetaFunctionsmodule option to register named compiler macros, each with an optionaltransformhookdeclare functiontype declarationsNew API
Produces
component.meta._studio = { filePath: 'file-picker' }— with no runtime cost.Changes
src/parser/macro-extractor.ts— new file:extractMacroMeta()andstripMacroCalls()using oxc ASTsrc/types/module.ts— newExtendMetaFunctioninterface,extendMetaFunctionsoptionsrc/parser/meta-parser.ts— replaces regex+eval withextractMacroMetasrc/utils/unplugin.ts— adds Vitetransformhook to strip macro callssrc/module.ts— wires defaults, generates type declarations for custom macro namessrc/parser/index.ts— addsextendMetaFunctionssupport to standalone parserTest plan
extendComponentMetatest passes (backward compatible)extendPropswith transform test passes (meta._studio.filePath === 'file-picker')🤖 Generated with Claude Code