Skip to content

Conversation

@BobbieGoede
Copy link
Member

@BobbieGoede BobbieGoede commented Oct 26, 2025

🔗 Linked issue

📚 Description

WIP

This is to remove vue compiler sfc as direct prod dependency

Summary by CodeRabbit

  • Refactor

    • Optimized internal script processing with improved error handling for complex component structures.
  • Chores

    • Removed unnecessary dependency to reduce package footprint and installation time.

@BobbieGoede BobbieGoede self-assigned this Oct 26, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 26, 2025

Walkthrough

The changes implement a significant architectural shift in Vue SFC processing and macro transformation. The @vue/compiler-sfc dependency is removed from package.json. A new utility function extractScriptContent() is introduced to extract inline script blocks from SFC strings via regex-based parsing. The pages processing logic is refactored to iterate over multiple script blocks instead of relying on a single descriptor, processing each block individually. The macro transform plugin is reconfigured from pre to post enforcement and switches from Vue SFC parsing to AST walker-based analysis using oxc-walker for scope-tracked CallExpression detection and replacement.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • src/transform/macros.ts: AST walker logic with scope tracking requires careful verification of CallExpression detection and replacement behavior; ensure the tree-shaking replacement pattern preserves intended semantics
  • src/pages.ts: Multi-block iteration logic with conditional transformation based on loader type; verify edge cases where multiple script blocks exist and error handling during transformation
  • src/utils/extract-script.ts: Regex pattern accuracy for capturing script tags with varied attributes; test edge cases with nested content or malformed tags
  • package.json: Verify that removal of @vue/compiler-sfc doesn't introduce runtime failures in other parts of the codebase or create circular dependencies on oxc-based tools

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "refactor: replace @vue/compiler-sfc parsing" directly and accurately summarizes the main objective of the changeset. The PR removes the @vue/compiler-sfc dependency from package.json and replaces its parsing usage throughout the codebase with alternative implementations—including a new extractScriptContent() utility and walker-based AST analysis via oxc-walker. The title is concise, uses proper conventional commit format, and is specific enough that a developer scanning the repository history would immediately understand the primary refactoring effort. The title avoids vague language and noise, instead clearly communicating what is being changed and why.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/replace-parse-sfc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (8)
src/transform/macros.ts (3)

17-17: Avoid dynamic RegExp; use a static literal.

DEFINE_I18N_ROUTE_FN is constant, so build a static regex (or a simple string include) to avoid needless dynamic construction and to appease linters. Example: const I18N_MACRO_FN_RE = /\bdefineI18nRoute\s*(\s*/.


49-55: Skip shadowed identifiers using ScopeTracker.

Currently any local function named defineI18nRoute is transformed. Guard with ScopeTracker to avoid false positives.

               const name = node.callee.name
               if (name !== DEFINE_I18N_ROUTE_FN) return
+              // Ignore locally shadowed identifiers
+              if (scopeTracker.isDeclared?.(name)) return
               s.overwrite(node.start, node.end, ` false && /*@__PURE__*/ ${DEFINE_I18N_ROUTE_FN}${code.slice(node.callee.end, node.end)}`)

Docs show ScopeTracker helpers (isDeclared, getDeclaration). (socket.dev)


58-60: Improve error context.

Prefix errors with plugin name and file id for debuggability.

-            console.error(e)
+            console.error('[nuxtjs:i18n-macros-transform]', id, e)
src/utils/extract-script.ts (2)

7-7: Make loader detection explicit via lang= to reduce false positives.

Rely on lang= when present; fall back to jsx/tsx hint; default to 'ts'.

-        loader: match.groups.attrs && /[tj]sx/.test(match.groups.attrs) ? 'tsx' : 'ts',
+        loader: (() => {
+          const attrs = match.groups.attrs ?? ''
+          const lang = (attrs.match(/\blang\s*=\s*["']?([a-z0-9_-]+)/i)?.[1] || '').toLowerCase()
+          if (lang === 'tsx' || /\bjsx\b/i.test(attrs)) return 'tsx'
+          return 'ts'
+        })(),

1-11: Document/decide behavior for <script src="..."> blocks.

Scripts with external src will be skipped (empty innerHTML). If unsupported by design, note this in docs; otherwise, add resolution logic.

src/pages.ts (3)

394-429: Ignore shadowed defineI18nRoute via ScopeTracker.

Avoid capturing user-defined functions named defineI18nRoute. Add a ScopeTracker and skip when locally declared.

-    for (const script of blocks) {
+    for (const script of blocks) {
       if (extract != null) break
-      parseAndWalk(script.code, absolutePath.replace(/\.\w+$/, '.' + script.loader), (node) => {
+      const scopeTracker = new ScopeTracker({ preserveExitedScopes: true })
+      parseAndWalk(
+        script.code,
+        absolutePath.replace(/\.\w+$/, '.' + script.loader),
+        {
+          scopeTracker,
+          enter(node) {
         if (extract != null) return
         let code = script.code
         if (
           node.type !== 'CallExpression'
           || node.callee.type !== 'Identifier'
           || node.callee.name !== DEFINE_I18N_ROUTE_FN
         )
           return
+        // Skip if symbol is shadowed in the current scope
+        if (scopeTracker.isDeclared?.(DEFINE_I18N_ROUTE_FN)) return
         let routeArgument = node.arguments[0]
         if (routeArgument == null) return
-        if (typeof script.loader === 'string' && /tsx?/.test(script.loader)) {
+        if (typeof script.loader === 'string' && /tsx?/.test(script.loader)) {
           const transformed = transform('', script.code.slice(node.start, node.end).trim(), { lang: script.loader })
           code = transformed.code
           if (transformed.errors.length) {
             for (const error of transformed.errors) {
               console.warn(`Error while transforming \`${DEFINE_I18N_ROUTE_FN}()\`` + error.codeframe)
             }
             return
           }
           // we already know that the first statement is a call expression
           routeArgument = (
             (parseSync('', transformed.code, { lang: 'js' }).program.body[0]! as ExpressionStatement)
               .expression as CallExpression
           ).arguments[0]! as ObjectExpression
         }
         extract = evalAndValidateValue(code.slice(routeArgument.start, routeArgument.end).trim())
-      })
+        }
+      )
     }

Docs show parseAndWalk(..., { scopeTracker, enter }) and helpers like isDeclared. (socket.dev)


414-426: Follow-up: keep parseSync filename meaningful.

Optional: set a synthetic filename for parseSync too (e.g., inline.js) to aid diagnostics and align with parser heuristics.

-            (parseSync('', transformed.code, { lang: 'js' }).program.body[0]! as ExpressionStatement)
+            (parseSync('inline.js', transformed.code, { lang: 'js' }).program.body[0]! as ExpressionStatement)

(npmjs.com)


388-391: Sanity check: prefilter with includes().

Good fast-path. Consider a slightly stricter prefilter like /\bdefineI18nRoute\s*\(/ to reduce false positives from comments/template.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96a59f2 and 81f51ff.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • package.json (0 hunks)
  • src/pages.ts (2 hunks)
  • src/transform/macros.ts (2 hunks)
  • src/utils/extract-script.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • package.json
🧰 Additional context used
🧬 Code graph analysis (2)
src/pages.ts (3)
src/utils/extract-script.ts (1)
  • extractScriptContent (2-14)
src/constants.ts (1)
  • DEFINE_I18N_ROUTE_FN (88-88)
src/transform/resource.ts (1)
  • transform (14-17)
src/transform/macros.ts (2)
src/constants.ts (1)
  • DEFINE_I18N_ROUTE_FN (88-88)
src/transform/utils.ts (1)
  • BundlerPluginOptions (45-47)
🪛 ast-grep (0.39.6)
src/transform/macros.ts

[warning] 16-16: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\b${DEFINE_I18N_ROUTE_FN}\\s*\\(\\s*)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🔇 Additional comments (3)
src/transform/macros.ts (1)

23-23: Confirm plugin ordering after switching to enforce: 'post'.

Changing to post may alter interaction with other transforms (e.g., Vue SFC, typed-router). Please verify this still runs after SFC splitting but before minification/treeshake in your target bundlers.

src/pages.ts (2)

392-399: Nice: multi-block parsing + early exit.

Iterating over all script blocks and exiting on first hit simplifies flow and avoids descriptor coupling.


394-429: No shadowing or external scripts detected—verification concerns unfounded.

The search results confirm:

  1. No shadowed defineI18nRoute declarations: All usages import from '#i18n' macro (e.g., import { defineI18nRoute } from '#i18n'). No local redeclarations exist in any Vue files across the codebase.

  2. No external <script src> tags: Zero matches for <script ... src= patterns in Vue files. All script blocks are inline, not external references.

The verification scenarios outlined in the review comment do not occur in practice, so the code at src/pages.ts lines 394-429 handles the actual cases it will encounter correctly.

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