Skip to content

Commit 7c3ca3c

Browse files
committed
feat(compiler-sfc): support export * when resolving types
1 parent f17a82c commit 7c3ca3c

File tree

2 files changed

+71
-39
lines changed

2 files changed

+71
-39
lines changed

packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,24 @@ describe('resolveType', () => {
454454
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
455455
})
456456

457+
test('relative (chained, export *)', () => {
458+
const files = {
459+
'/foo.ts': `export * from './bar'`,
460+
'/bar.ts': 'export type P = { bar: string }'
461+
}
462+
const { props, deps } = resolve(
463+
`
464+
import { P } from './foo'
465+
defineProps<P>()
466+
`,
467+
files
468+
)
469+
expect(props).toStrictEqual({
470+
bar: ['String']
471+
})
472+
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
473+
})
474+
457475
test('ts module resolve', () => {
458476
const files = {
459477
'/node_modules/foo/package.json': JSON.stringify({
@@ -563,10 +581,10 @@ describe('resolveType', () => {
563581
)
564582
})
565583

566-
test('failed improt source resolve', () => {
584+
test('failed import source resolve', () => {
567585
expect(() =>
568586
resolve(`import { X } from './foo'; defineProps<X>()`)
569-
).toThrow(`Failed to resolve import source "./foo" for type X`)
587+
).toThrow(`Failed to resolve import source "./foo"`)
570588
})
571589

572590
test('should not error on unresolved type when inferring runtime type', () => {

packages/compiler-sfc/src/script/resolveType.ts

+51-37
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ function innerResolveTypeReference(
542542
ns = ns._ns
543543
}
544544
if (ns) {
545-
const childScope = moduleDeclToScope(ns, ns._ownerScope || scope)
545+
const childScope = moduleDeclToScope(ctx, ns, ns._ownerScope || scope)
546546
return innerResolveTypeReference(
547547
ctx,
548548
childScope,
@@ -581,7 +581,7 @@ function resolveGlobalScope(ctx: TypeResolveContext): TypeScope[] | undefined {
581581
throw new Error('[vue/compiler-sfc] globalTypeFiles requires fs access.')
582582
}
583583
return ctx.options.globalTypeFiles.map(file =>
584-
fileToScope(normalizePath(file), fs, ctx.options.babelParserPlugins, true)
584+
fileToScope(ctx, normalizePath(file), true)
585585
)
586586
}
587587
}
@@ -603,23 +603,36 @@ function resolveTypeFromImport(
603603
name: string,
604604
scope: TypeScope
605605
): ScopeTypeNode | undefined {
606+
const { source, imported } = scope.imports[name]
607+
const resolved = resolveImportSource(ctx, node, scope, source)
608+
return resolveTypeReference(
609+
ctx,
610+
node,
611+
fileToScope(ctx, resolved),
612+
imported,
613+
true
614+
)
615+
}
616+
617+
function resolveImportSource(
618+
ctx: TypeResolveContext,
619+
node: Node,
620+
scope: TypeScope,
621+
source: string
622+
): string {
606623
const fs: FS = ctx.options.fs || ts?.sys
607624
if (!fs) {
608625
ctx.error(
609626
`No fs option provided to \`compileScript\` in non-Node environment. ` +
610627
`File system access is required for resolving imported types.`,
611-
node
628+
node,
629+
scope
612630
)
613631
}
614-
615-
const containingFile = scope.filename
616-
const { source, imported } = scope.imports[name]
617-
618-
let resolved: string | undefined
619-
632+
let resolved
620633
if (source.startsWith('.')) {
621634
// relative import - fast path
622-
const filename = path.join(containingFile, '..', source)
635+
const filename = path.join(scope.filename, '..', source)
623636
resolved = resolveExt(filename, fs)
624637
} else {
625638
// module or aliased import - use full TS resolution, only supported in Node
@@ -632,36 +645,22 @@ function resolveTypeFromImport(
632645
}
633646
if (!ts) {
634647
ctx.error(
635-
`Failed to resolve type ${imported} from module ${JSON.stringify(
636-
source
637-
)}. ` +
648+
`Failed to resolve import source ${JSON.stringify(source)}. ` +
638649
`typescript is required as a peer dep for vue in order ` +
639650
`to support resolving types from module imports.`,
640651
node,
641652
scope
642653
)
643654
}
644-
resolved = resolveWithTS(containingFile, source, fs)
655+
resolved = resolveWithTS(scope.filename, source, fs)
645656
}
646-
647657
if (resolved) {
648-
resolved = normalizePath(resolved)
649-
650658
// (hmr) register dependency file on ctx
651659
;(ctx.deps || (ctx.deps = new Set())).add(resolved)
652-
653-
return resolveTypeReference(
654-
ctx,
655-
node,
656-
fileToScope(resolved, fs, ctx.options.babelParserPlugins),
657-
imported,
658-
true
659-
)
660+
return normalizePath(resolved)
660661
} else {
661-
ctx.error(
662-
`Failed to resolve import source ${JSON.stringify(
663-
source
664-
)} for type ${name}`,
662+
return ctx.error(
663+
`Failed to resolve import source ${JSON.stringify(source)}.`,
665664
node,
666665
scope
667666
)
@@ -753,18 +752,18 @@ export function invalidateTypeCache(filename: string) {
753752
}
754753

755754
export function fileToScope(
755+
ctx: TypeResolveContext,
756756
filename: string,
757-
fs: FS,
758-
parserPlugins: SFCScriptCompileOptions['babelParserPlugins'],
759757
asGlobal = false
760758
): TypeScope {
761759
const cached = fileToScopeCache.get(filename)
762760
if (cached) {
763761
return cached
764762
}
765-
763+
// fs should be guaranteed to exist here
764+
const fs = ctx.options.fs || ts?.sys
766765
const source = fs.readFile(filename) || ''
767-
const body = parseFile(filename, source, parserPlugins)
766+
const body = parseFile(filename, source, ctx.options.babelParserPlugins)
768767
const scope: TypeScope = {
769768
filename,
770769
source,
@@ -773,7 +772,7 @@ export function fileToScope(
773772
types: Object.create(null),
774773
exportedTypes: Object.create(null)
775774
}
776-
recordTypes(body, scope, asGlobal)
775+
recordTypes(ctx, body, scope, asGlobal)
777776
fileToScopeCache.set(filename, scope)
778777
return scope
779778
}
@@ -846,12 +845,13 @@ function ctxToScope(ctx: TypeResolveContext): TypeScope {
846845
exportedTypes: Object.create(null)
847846
}
848847

849-
recordTypes(body, scope)
848+
recordTypes(ctx, body, scope)
850849

851850
return (ctx.scope = scope)
852851
}
853852

854853
function moduleDeclToScope(
854+
ctx: TypeResolveContext,
855855
node: TSModuleDeclaration & { _resolvedChildScope?: TypeScope },
856856
parentScope: TypeScope
857857
): TypeScope {
@@ -872,15 +872,20 @@ function moduleDeclToScope(
872872
const id = getId(decl.id)
873873
scope.types[id] = scope.exportedTypes[id] = decl
874874
} else {
875-
recordTypes(node.body.body, scope)
875+
recordTypes(ctx, node.body.body, scope)
876876
}
877877

878878
return (node._resolvedChildScope = scope)
879879
}
880880

881881
const importExportRE = /^Import|^Export/
882882

883-
function recordTypes(body: Statement[], scope: TypeScope, asGlobal = false) {
883+
function recordTypes(
884+
ctx: TypeResolveContext,
885+
body: Statement[],
886+
scope: TypeScope,
887+
asGlobal = false
888+
) {
884889
const { types, exportedTypes, imports } = scope
885890
const isAmbient = asGlobal
886891
? !body.some(s => importExportRE.test(s.type))
@@ -932,6 +937,15 @@ function recordTypes(body: Statement[], scope: TypeScope, asGlobal = false) {
932937
}
933938
}
934939
}
940+
} else if (stmt.type === 'ExportAllDeclaration') {
941+
const targetFile = resolveImportSource(
942+
ctx,
943+
stmt.source,
944+
scope,
945+
stmt.source.value
946+
)
947+
const targetScope = fileToScope(ctx, targetFile)
948+
Object.assign(scope.exportedTypes, targetScope.exportedTypes)
935949
}
936950
}
937951
}

0 commit comments

Comments
 (0)