-
-
Notifications
You must be signed in to change notification settings - Fork 8.9k
feat(compiler-sfc): support Node subpath imports for type resolution #13813
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(compiler-sfc): support Node subpath imports for type resolution #13813
Conversation
WalkthroughAdds Node.js package.json "exports"/import-map subpath resolution fallback to the type resolver, integrates it into importSourceToScope after TS resolution, introduces helpers to locate/read nearest package.json, and adds tests for node-style subpath imports. Also adds the Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant SFC as SFC Compiler
participant RTS as importSourceToScope
participant TS as TypeScriptResolver
participant NSI as NodeSubpathResolver
participant FS as FileSystem
participant RE as resolve.exports
SFC->>RTS: request resolve(importSource)
RTS->>TS: attempt TS-based resolution
alt TS resolves
TS-->>RTS: resolved path
RTS-->>SFC: return scoped types
else TS fails
note over RTS,NSI: Fallback (CommonJS only)
RTS->>NSI: try Node subpath resolution
NSI->>FS: find nearest package.json (walk up)
FS-->>NSI: package.json path / none
alt package.json found
NSI->>FS: read package.json
NSI->>RE: resolve.exports -> mapped subpath(s)
RE-->>NSI: resolved subpath(s)
NSI->>FS: resolve to real path (realpath optional)
NSI-->>RTS: resolved path
RTS-->>SFC: return scoped types
else not found / mapping fails
NSI-->>RTS: no resolution
RTS-->>SFC: unresolved (existing handling)
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes(none) Suggested labels
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
Size ReportBundles
Usages
|
@vue/compiler-core
@vue/compiler-dom
@vue/compiler-sfc
@vue/compiler-ssr
@vue/reactivity
@vue/runtime-core
@vue/runtime-dom
@vue/server-renderer
@vue/shared
vue
@vue/compat
commit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (2)
packages/compiler-sfc/src/script/resolveType.ts (2)
1159-1173
: Consider caching package.json for performance.The
findPackageJsonFile
function searches for package.json by traversing the directory tree on every call. Since package.json files rarely change during runtime, consider implementing a simple cache to avoid repeated filesystem operations.Here's a suggested implementation with caching:
+const packageJsonCache = new Map<string, string | undefined>() + function findPackageJsonFile(fs: FS): string | undefined { + const cacheKey = process.cwd() + if (packageJsonCache.has(cacheKey)) { + return packageJsonCache.get(cacheKey) + } + let currDir = process.cwd() while (true) { const filePath = joinPaths(currDir, 'package.json') if (fs.fileExists(filePath)) { + packageJsonCache.set(cacheKey, filePath) return filePath } const parentDir = dirname(currDir) if (parentDir === currDir) { + packageJsonCache.set(cacheKey, undefined) return } currDir = parentDir } }
1152-1152
: Consider using joinPaths for consistency.The code uses
resolve
from the path module here, but usesjoinPaths
elsewhere in the codebase for path operations. Consider usingjoinPaths
for consistency with the rest of the codebase.- const resolved = resolve(dirname(pkgPath), resolvedImports[0]) + const resolved = joinPaths(dirname(pkgPath), resolvedImports[0])
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (3)
packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
(1 hunks)packages/compiler-sfc/package.json
(1 hunks)packages/compiler-sfc/src/script/resolveType.ts
(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/compiler-sfc/src/script/resolveType.ts (1)
packages/compiler-sfc/src/script/utils.ts (1)
joinPaths
(111-112)
🪛 GitHub Actions: ci
packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
[error] 1-1: Test 'external type imports > node subpath imports' failed while running 'pnpm run test-unit': [@vue/compiler-sfc] Failed to resolve import source '#t1'.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Redirect rules
- GitHub Check: Header rules
- GitHub Check: Pages changed
🔇 Additional comments (2)
packages/compiler-sfc/src/script/resolveType.ts (1)
42-45
: Incorrect import usage of resolve.exports.The destructured import
{ imports as resolveImports }
is incorrect. According to theresolve.exports
documentation, the correct import should be{ imports }
and it should be used asimports()
notresolveImports()
.-import { imports as resolveImports } from 'resolve.exports' +import { imports } from 'resolve.exports'Likely an incorrect or invalid review comment.
packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts (1)
1201-1235
: Test correctly validates the new Node subpath imports feature.The test effectively covers the main functionality:
- Maps
#t1
to./t1.ts
(relative path)- Maps
#t2
to/t2.ts
(absolute path)- Maps wildcard pattern
#o/*.ts
to./other/*.ts
- Verifies type resolution and dependency tracking
@@ -1198,6 +1198,42 @@ describe('resolveType', () => { | |||
expect(deps && [...deps]).toStrictEqual(['/user.ts']) | |||
}) | |||
|
|||
test('node subpath imports', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Test failure indicates the feature isn't working as expected.
Based on the pipeline failure message "Failed to resolve import source '#t1'", the Node subpath imports resolution is not working correctly. This could be due to:
- The incorrect import statement for
resolve.exports
(as noted in the other file) - The package being in devDependencies instead of dependencies
- Missing
__CJS__
flag during test execution
Let me verify the test environment setup:
🏁 Script executed:
#!/bin/bash
# Check if __CJS__ is defined in the test environment
rg -n "__CJS__" packages/compiler-sfc/__tests__/ -A 2 -B 2
# Check how the tests are built and run
fd "jest.config" --type f | xargs cat 2>/dev/null
# Check the build configuration for tests
fd "rollup.config" --type f | head -5 | xargs grep -l "compiler-sfc" 2>/dev/null | xargs cat 2>/dev/null | grep -A 10 -B 10 "__CJS__"
Length of output: 993
🏁 Script executed:
#!/bin/bash
# Search for usages of resolve.exports in the compiler-sfc package
rg -n "resolve\\.exports" -C3 packages/compiler-sfc
# Inspect dependencies and devDependencies in the compiler-sfc package.json
grep -n "\"dependencies\"" -A20 packages/compiler-sfc/package.json
grep -n "\"devDependencies\"" -A20 packages/compiler-sfc/package.json
Length of output: 2326
🏁 Script executed:
#!/bin/bash
# Show code around __CJS__ usage in resolveType.ts
rg -n "__CJS__" -C5 packages/compiler-sfc/src/script/resolveType.ts
# Show how resolveImports is invoked
rg -n "resolveImports" -C5 packages/compiler-sfc/src/script/resolveType.ts
# Inspect the failing test for '#t1' import source
rg -n "'#t1'" -C5 packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
Length of output: 2807
Ensure Node subpath imports are resolved at runtime and in tests
The failure “Failed to resolve import source ‘#t1’” is expected:
- At runtime,
resolve.exports
is only listed under devDependencies, so it isn’t available when this code path executes. - In the unbundled test harness, the
__CJS__
guard is falsey by default (no global definition), soresolveWithNodeSubpathImports
immediately returns without attempting subpath resolution.
To fix this:
- Move
resolve.exports
intodependencies
so it’s always installed at runtime. - Define
__CJS__ = true
in the test environment (e.g. via a Jest setup file orglobals
in your test config) so the Node‐only resolution path is exercised. - No change is needed to the import statement (
import { imports as resolveImports } from 'resolve.exports'
is correct).
Locations to update:
- packages/compiler-sfc/package.json
"dependencies": { "@babel/parser": "catalog:", // …
- },
- "devDependencies": {
- "resolve.exports": "^2.0.3",
- }
+}, - "dependencies": {
- "resolve.exports": "^2.0.3",
- // …
+}, - "devDependencies": {
- // other dev deps…
// …
}
- **Test setup** (e.g. add a `setupTests.ts` or update Jest/Vitest config)
```ts
// packages/compiler-sfc/__tests__/setupTests.ts
;(global as any).__CJS__ = true
and ensure it’s loaded before tests (via your test runner’s setupFiles
/setup
option).
These changes will install the resolver at runtime and unblock the resolveWithNodeSubpathImports
logic in tests.
🤖 Prompt for AI Agents
In packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts around line
1201, the test fails because resolve.exports is only in devDependencies and the
test runtime doesn't set the __CJS__ global, so Node subpath resolution path
isn't executed; fix by moving "resolve.exports" from devDependencies to
dependencies in packages/compiler-sfc/package.json so it is installed at
runtime, and add a test setup file (or update your Jest/Vitest config's
setupFiles) that sets global.__CJS__ = true before tests run so
resolveWithNodeSubpathImports is exercised (no change to the import statement is
required).
function resolveWithNodeSubpathImports( | ||
source: string, | ||
fs: FS, | ||
): string | undefined { | ||
if (!__CJS__) return | ||
|
||
try { | ||
const pkgPath = findPackageJsonFile(fs) | ||
if (!pkgPath) { | ||
return | ||
} | ||
|
||
const pkgStr = fs.readFile(pkgPath) | ||
if (!pkgStr) { | ||
return | ||
} | ||
|
||
const pkg = JSON.parse(pkgStr) | ||
const resolvedImports = resolveImports(pkg, source) | ||
if (!resolvedImports || !resolvedImports.length) { | ||
return | ||
} | ||
|
||
const resolved = resolve(dirname(pkgPath), resolvedImports[0]) | ||
|
||
return fs.realpath ? fs.realpath(resolved) : resolved | ||
} catch (e) {} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add validation for package.json imports field.
The function assumes that pkg.imports
exists and that resolvedImports
returns an array. Add validation to handle edge cases where the imports field might be missing or malformed.
function resolveWithNodeSubpathImports(
source: string,
fs: FS,
): string | undefined {
if (!__CJS__) return
try {
const pkgPath = findPackageJsonFile(fs)
if (!pkgPath) {
return
}
const pkgStr = fs.readFile(pkgPath)
if (!pkgStr) {
return
}
const pkg = JSON.parse(pkgStr)
+ if (!pkg.imports || typeof pkg.imports !== 'object') {
+ return
+ }
+
- const resolvedImports = imports(pkg, source)
+ const resolvedImports = imports(pkg, source)
if (!resolvedImports || !resolvedImports.length) {
return
}
const resolved = resolve(dirname(pkgPath), resolvedImports[0])
return fs.realpath ? fs.realpath(resolved) : resolved
} catch (e) {}
}
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/compiler-sfc/src/script/resolveType.ts around lines 1129 to 1156,
the code assumes pkg.imports exists and that resolveImports returns an array;
add defensive validation: after parsing package.json validate that pkg && typeof
pkg === 'object' and that pkg.imports exists and is an object (or of the
expected shape) before calling resolveImports; call resolveImports only if
imports look valid, then ensure its return is an array and has at least one
element before using resolvedImports[0]; if validations fail, return undefined
early; also avoid swallowing errors silently — keep the try/catch but return
undefined on error (or rethrow/log if preferred).
} | ||
|
||
const pkg = JSON.parse(pkgStr) | ||
const resolvedImports = resolveImports(pkg, source) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update to use correct import name from resolve.exports.
After fixing the import statement, update this line to use the correct function name.
- const resolvedImports = resolveImports(pkg, source)
+ const resolvedImports = imports(pkg, source)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const resolvedImports = resolveImports(pkg, source) | |
const resolvedImports = imports(pkg, source) |
🤖 Prompt for AI Agents
In packages/compiler-sfc/src/script/resolveType.ts around line 1147, the call
currently uses resolveImports(pkg, source) but the correct function name from
resolve.exports is resolveExports; change this invocation to resolveExports(pkg,
source) and ensure the file's import statement at the top imports resolveExports
(not resolveImports) from resolve.exports so the symbol matches.
There was a problem hiding this 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
♻️ Duplicate comments (2)
packages/compiler-sfc/src/script/resolveType.ts (1)
1129-1159
: Add validation for package.json imports field and improve error handling.The function assumes that
pkg.imports
exists and thatresolveImports
returns an array. Add validation to handle edge cases where the imports field might be missing or malformed.function resolveWithNodeSubpathImports( containingFile: string, source: string, fs: FS, ): string | undefined { if (!__CJS__) return try { const pkgPath = findPackageJsonFile(containingFile, fs) if (!pkgPath) { return } const pkgStr = fs.readFile(pkgPath) if (!pkgStr) { return } const pkg = JSON.parse(pkgStr) + if (!pkg.imports || typeof pkg.imports !== 'object') { + return + } + const resolvedImports = resolveImports(pkg, source) if (!resolvedImports || !resolvedImports.length) { return } const resolved = isAbsolute(resolvedImports[0]) ? resolvedImports[0] : joinPaths(dirname(pkgPath), resolvedImports[0]) return fs.realpath ? fs.realpath(resolved) : resolved } catch (e) {} }packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts (1)
1201-1235
: Test configuration needs setup for Node subpath imports to work.The test failure indicates that the Node subpath imports resolution is not working correctly. This is because:
- The
__CJS__
flag is not set in the test environment, soresolveWithNodeSubpathImports
immediately returns without attempting resolution- The
resolve.exports
package needs to be independencies
instead ofdevDependencies
for runtime availabilityTo fix this, you need to:
- Move
resolve.exports
fromdevDependencies
todependencies
in packages/compiler-sfc/package.json- Set up the test environment to define
__CJS__ = true
(e.g., via Jest/Vitest setup file)These changes will ensure the Node subpath imports resolution path is executed during tests.
🧹 Nitpick comments (1)
packages/compiler-sfc/src/script/resolveType.ts (1)
1161-1179
: Consider caching package.json lookups for better performance.The
findPackageJsonFile
function performs a potentially expensive directory traversal every time it's called. Consider caching the results to improve performance, especially when resolving multiple imports from the same package.+const packageJsonCache = new Map<string, string | undefined>() + function findPackageJsonFile( searchStartPath: string, fs: FS, ): string | undefined { + const cached = packageJsonCache.get(searchStartPath) + if (cached !== undefined) { + return cached || undefined + } + let currDir = searchStartPath while (true) { const filePath = joinPaths(currDir, 'package.json') if (fs.fileExists(filePath)) { + packageJsonCache.set(searchStartPath, filePath) return filePath } const parentDir = dirname(currDir) if (parentDir === currDir) { + packageJsonCache.set(searchStartPath, '') return } currDir = parentDir } }Additionally, you may want to invalidate this cache in the
invalidateTypeCache
function.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (3)
packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
(1 hunks)packages/compiler-sfc/package.json
(1 hunks)packages/compiler-sfc/src/script/resolveType.ts
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/compiler-sfc/package.json
🧰 Additional context used
🧬 Code graph analysis (1)
packages/compiler-sfc/src/script/resolveType.ts (1)
packages/compiler-sfc/src/script/utils.ts (1)
joinPaths
(111-112)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Redirect rules
- GitHub Check: Header rules
- GitHub Check: Pages changed
- GitHub Check: test / e2e-test
🔇 Additional comments (3)
packages/compiler-sfc/src/script/resolveType.ts (2)
42-45
: Import and function naming are correct, no change needed.The import statement
import { imports as resolveImports } from 'resolve.exports'
is the correct syntax to import theimports
function fromresolve.exports
and rename it toresolveImports
for local use. The function is correctly called asresolveImports
on line 1148.
962-964
: LGTM! The Node subpath imports fallback is correctly integrated.The TypeScript resolution is attempted first, followed by the Node subpath imports fallback. This ensures backwards compatibility while adding support for package.json imports mappings.
packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts (1)
1237-1281
: Windows-specific test correctly handles platform differences.The Windows-specific test properly uses backslashes for paths and is correctly guarded with
process.platform === 'win32'
. The test covers various mapping scenarios including relative paths and absolute paths on Windows.
import { minimatch as isMatch } from 'minimatch' | ||
import * as process from 'process' | ||
import { imports as resolveImports } from 'resolve.exports' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it necessary to use resolve.exports
here? We have already imported minimatch
here, so perhaps we can manually match the file paths. Reference:
included?.some(p => isMatch(containingFile, joinPaths(base, p))) |
close #13586
Support reading user-defined Node.js subpath imports for type resolution.
Example:
Comp1.vue
Comp2.vue
package.json
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores