feat: AST-based compiler macro system for extendComponentMeta#116
feat: AST-based compiler macro system for extendComponentMeta#116hendrikheil wants to merge 1 commit into
Conversation
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?
There was a problem hiding this comment.
I just tested it. There is one real issue right now, generic components. A <script setup> whose
generic attribute contains > (e.g. generic="T extends Record<string, unknown>")
breaks the current regex. <script\b[^>]*> stops at the first > inside the type,
oxc then fails to parse, and the macro is silently dropped. @vue/compiler-sfc
handles that.
That said, the regex can too be adjusted to support it too:
const SCRIPT_BLOCK_RE = /<script\b(?:"[^"]*"|'[^']*'|[^>])*>([\s\S]*?)<\/script>/giSimple / generic / multi-block all pass with that. The only case neither handles is a
literal </script> inside a string. However, that's an HTML tokenization rule, so
compiler-sfc truncates there too.
So with the tweak the regex matches compiler-sfc for every realistic case - you've
convinced me, no need for the full SFC parser (right now). I'll apply the patch and add tests for
the generic + multi-block cases.
There was a problem hiding this comment.
Not sure if you think we should keep the bench tests?
I think in general we could clean the pr at least slimming / removing most of the comments?
There was a problem hiding this comment.
Sure lets clean it up, appreciate if you take care of it.
Also do you mind squashing all commit into a verified commit? new Github organization rule 🙏
There was a problem hiding this comment.
Sure thing, works for me, I'll clean the PR up.
I also had Fable bake up a PoC downstream in nuxt-studio to demo what this enables 🔥
There was a problem hiding this comment.
Ready to go, I think this can be merged and released?
Replaces the regex+eval extraction of `extendComponentMeta` with a compiler macro system built on oxc-parser and oxc-walker. - Add `extendMetaFunctions` option to register named compiler macros, each with an optional `transform` hook applied to the extracted argument - New `src/parser/macro-extractor.ts` handles AST-based extraction (extractMacroMeta) and browser-output stripping (stripMacroCalls) - Macro arguments support object/array literals and imported const references, evaluated at build time via jiti - 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 in the generated component-meta.d.ts - Standalone parser (src/parser/index.ts) also supports extendMetaFunctions Co-Authored-By: Claude <noreply@anthropic.com>
5eaf124 to
1e6bcc6
Compare
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