Skip to content

Commit 4df0f6a

Browse files
authored
fix(language-core): find node_modules based on file's directory (#5525)
1 parent e1095ed commit 4df0f6a

File tree

8 files changed

+115
-78
lines changed

8 files changed

+115
-78
lines changed

packages/component-meta/lib/base.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,7 @@ export function baseCreate(
6363
let fileNames = new Set(commandLine.fileNames.map(path => path.replace(windowsPathReg, '/')));
6464
let projectVersion = 0;
6565

66-
if (commandLine.vueOptions.globalTypesPath) {
67-
ts.sys.writeFile(
68-
commandLine.vueOptions.globalTypesPath,
69-
vue.generateGlobalTypes(commandLine.vueOptions),
70-
);
71-
}
66+
vue.writeGlobalTypes(commandLine.vueOptions, ts.sys.writeFile);
7267

7368
const projectHost: TypeScriptProjectHost = {
7469
getCurrentDirectory: () => rootPath,

packages/language-core/lib/codegen/script/index.ts

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,7 @@ export interface ScriptCodegenOptions {
2929
export function* generateScript(options: ScriptCodegenOptions): Generator<Code, ScriptCodegenContext> {
3030
const ctx = createScriptCodegenContext(options);
3131

32-
if (options.vueCompilerOptions.globalTypesPath) {
33-
const globalTypesPath = options.vueCompilerOptions.globalTypesPath;
34-
if (path.isAbsolute(globalTypesPath)) {
35-
let relativePath = path.relative(path.dirname(options.fileName), globalTypesPath);
36-
if (
37-
relativePath !== globalTypesPath
38-
&& !relativePath.startsWith('./')
39-
&& !relativePath.startsWith('../')
40-
) {
41-
relativePath = './' + relativePath;
42-
}
43-
yield `/// <reference types="${relativePath}" />${newLine}`;
44-
}
45-
else {
46-
yield `/// <reference types="${globalTypesPath}" />${newLine}`;
47-
}
48-
}
49-
else {
50-
yield `/* placeholder */${newLine}`;
51-
}
32+
yield* generateGlobalTypesPath(options);
5233

5334
if (options.sfc.script?.src) {
5435
yield* generateSrc(options.sfc.script.src);
@@ -171,6 +152,30 @@ export function* generateScript(options: ScriptCodegenOptions): Generator<Code,
171152
return ctx;
172153
}
173154

155+
function* generateGlobalTypesPath(
156+
options: ScriptCodegenOptions,
157+
): Generator<Code> {
158+
const globalTypesPath = options.vueCompilerOptions.globalTypesPath(options.fileName);
159+
160+
if (!globalTypesPath) {
161+
yield `/* placeholder */${newLine}`;
162+
}
163+
else if (path.isAbsolute(globalTypesPath)) {
164+
let relativePath = path.relative(path.dirname(options.fileName), globalTypesPath);
165+
if (
166+
relativePath !== globalTypesPath
167+
&& !relativePath.startsWith('./')
168+
&& !relativePath.startsWith('../')
169+
) {
170+
relativePath = './' + relativePath;
171+
}
172+
yield `/// <reference types="${relativePath}" />${newLine}`;
173+
}
174+
else {
175+
yield `/// <reference types="${globalTypesPath}" />${newLine}`;
176+
}
177+
}
178+
174179
export function* generateScriptSectionPartiallyEnding(
175180
source: string,
176181
end: number,

packages/language-core/lib/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ export type { SFCParseResult } from '@vue/compiler-sfc';
99

1010
export { VueEmbeddedCode };
1111

12-
export type RawVueCompilerOptions = Partial<Omit<VueCompilerOptions, 'target' | 'plugins'>> & {
12+
export type RawVueCompilerOptions = Partial<Omit<VueCompilerOptions, 'target' | 'globalTypesPath' | 'plugins'>> & {
1313
strictTemplates?: boolean;
1414
target?: 'auto' | 2 | 2.7 | 3 | 3.3 | 3.5 | 3.6 | 99 | number;
15+
globalTypesPath?: string;
1516
plugins?: string[];
1617
};
1718

@@ -25,7 +26,7 @@ export type Code = Segment<VueCodeInformation>;
2526
export interface VueCompilerOptions {
2627
target: number;
2728
lib: string;
28-
globalTypesPath?: string;
29+
globalTypesPath: (fileName: string) => string | undefined;
2930
extensions: string[];
3031
vitePressExtensions: string[];
3132
petiteVueExtensions: string[];

packages/language-core/lib/utils/ts.ts

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { camelize } from '@vue/shared';
22
import { posix as path } from 'path-browserify';
33
import type * as ts from 'typescript';
4-
import { getGlobalTypesFileName } from '../codegen/globalTypes';
4+
import { generateGlobalTypes, getGlobalTypesFileName } from '../codegen/globalTypes';
55
import { getAllExtensions } from '../languagePlugin';
66
import type { RawVueCompilerOptions, VueCompilerOptions, VueLanguagePlugin } from '../types';
77
import { hyphenateTag } from './shared';
@@ -147,7 +147,7 @@ function proxyParseConfigHostForExtendConfigPaths(parseConfigHost: ts.ParseConfi
147147

148148
export class CompilerOptionsResolver {
149149
configRoots = new Set<string>();
150-
options: Omit<RawVueCompilerOptions, 'target' | 'plugin'> = {};
150+
options: Omit<RawVueCompilerOptions, 'target' | 'globalTypesPath' | 'plugins'> = {};
151151
target: number | undefined;
152152
globalTypesPath: string | undefined;
153153
plugins: VueLanguagePlugin[] = [];
@@ -233,30 +233,44 @@ export class CompilerOptionsResolver {
233233
};
234234

235235
if (this.fileExists && this.globalTypesPath === undefined) {
236-
root: for (const rootDir of [...this.configRoots].reverse()) {
237-
let dir = rootDir;
238-
while (!this.fileExists(path.join(dir, 'node_modules', resolvedOptions.lib, 'package.json'))) {
239-
const parentDir = path.dirname(dir);
240-
if (dir === parentDir) {
241-
continue root;
242-
}
243-
dir = parentDir;
236+
const fileDirToGlobalTypesPath = new Map<string, string | undefined>();
237+
resolvedOptions.globalTypesPath = fileName => {
238+
const fileDir = path.dirname(fileName);
239+
if (fileDirToGlobalTypesPath.has(fileDir)) {
240+
return fileDirToGlobalTypesPath.get(fileDir);
244241
}
245-
resolvedOptions.globalTypesPath = path.join(
246-
dir,
247-
'node_modules',
248-
'.vue-global-types',
249-
getGlobalTypesFileName(resolvedOptions),
250-
);
251-
break;
252-
}
242+
243+
const root = this.findNodeModulesRoot(fileDir, resolvedOptions.lib);
244+
const result = root
245+
? path.join(
246+
root,
247+
'node_modules',
248+
'.vue-global-types',
249+
getGlobalTypesFileName(resolvedOptions),
250+
)
251+
: undefined;
252+
253+
fileDirToGlobalTypesPath.set(fileDir, result);
254+
return result;
255+
};
253256
}
254257
else {
255-
resolvedOptions.globalTypesPath = this.globalTypesPath;
258+
resolvedOptions.globalTypesPath = () => this.globalTypesPath;
256259
}
257260

258261
return resolvedOptions;
259262
}
263+
264+
private findNodeModulesRoot(dir: string, lib: string) {
265+
while (!this.fileExists!(path.join(dir, 'node_modules', lib, 'package.json'))) {
266+
const parentDir = path.dirname(dir);
267+
if (dir === parentDir) {
268+
return;
269+
}
270+
dir = parentDir;
271+
}
272+
return dir;
273+
}
260274
}
261275

262276
function findVueVersion(rootDir: string) {
@@ -289,6 +303,7 @@ export function getDefaultCompilerOptions(target = 99, lib = 'vue', strictTempla
289303
return {
290304
target,
291305
lib,
306+
globalTypesPath: () => undefined,
292307
extensions: ['.vue'],
293308
vitePressExtensions: [],
294309
petiteVueExtensions: [],
@@ -348,3 +363,22 @@ export function getDefaultCompilerOptions(target = 99, lib = 'vue', strictTempla
348363
},
349364
};
350365
}
366+
367+
export function writeGlobalTypes(
368+
vueOptions: VueCompilerOptions,
369+
writeFile: (fileName: string, data: string) => void,
370+
) {
371+
const originalFn = vueOptions.globalTypesPath;
372+
if (!originalFn) {
373+
return;
374+
}
375+
const writed = new Set<string>();
376+
vueOptions.globalTypesPath = (fileName: string) => {
377+
const result = originalFn(fileName);
378+
if (result && !writed.has(result)) {
379+
writed.add(result);
380+
writeFile(result, generateGlobalTypes(vueOptions));
381+
}
382+
return result;
383+
};
384+
}

packages/language-service/lib/plugins/vue-global-types-error.ts

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import type { DiagnosticSeverity, LanguageServicePlugin } from '@volar/language-service';
2+
import { VueVirtualCode } from '@vue/language-core';
3+
import { URI } from 'vscode-uri';
24

35
export function create(): LanguageServicePlugin {
46
return {
@@ -16,20 +18,35 @@ export function create(): LanguageServicePlugin {
1618
return;
1719
}
1820

19-
const vueCompilerOptions = context.project.vue?.compilerOptions;
20-
if (vueCompilerOptions && vueCompilerOptions.globalTypesPath === undefined) {
21-
return [{
22-
range: {
23-
start: document.positionAt(0),
24-
end: document.positionAt(0),
25-
},
26-
severity: 1 satisfies typeof DiagnosticSeverity.Error,
27-
code: 404,
28-
source: 'vue',
29-
message:
30-
`Write global types file failed. Please ensure that "node_modules" exists and "${vueCompilerOptions.lib}" is a direct dependency, or set "vueCompilerOptions.globalTypesPath" in "tsconfig.json" manually.`,
31-
}];
21+
const uri = URI.parse(document.uri);
22+
const decoded = context.decodeEmbeddedDocumentUri(uri);
23+
const sourceScript = decoded && context.language.scripts.get(decoded[0]);
24+
if (!sourceScript?.generated) {
25+
return;
26+
}
27+
28+
const root = sourceScript.generated.root;
29+
if (!(root instanceof VueVirtualCode)) {
30+
return;
3231
}
32+
33+
const { vueCompilerOptions } = root;
34+
const globalTypesPath = vueCompilerOptions.globalTypesPath(root.fileName);
35+
if (globalTypesPath) {
36+
return;
37+
}
38+
39+
return [{
40+
range: {
41+
start: document.positionAt(0),
42+
end: document.positionAt(0),
43+
},
44+
severity: 1 satisfies typeof DiagnosticSeverity.Error,
45+
code: 404,
46+
source: 'vue',
47+
message:
48+
`Write global types file failed. Please ensure that "node_modules" exists and "${vueCompilerOptions.lib}" is a direct dependency, or set "vueCompilerOptions.globalTypesPath" in "tsconfig.json" manually.`,
49+
}];
3350
},
3451
};
3552
},

packages/tsc/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,7 @@ export function run(tscPath = require.resolve('typescript/lib/tsc')) {
1717
? vue.createParsedCommandLine(ts, ts.sys, configFilePath.replace(windowsPathReg, '/')).vueOptions
1818
: vue.createParsedCommandLineByJson(ts, ts.sys, (options.host ?? ts.sys).getCurrentDirectory(), {})
1919
.vueOptions;
20-
if (vueOptions.globalTypesPath) {
21-
ts.sys.writeFile(
22-
vueOptions.globalTypesPath,
23-
vue.generateGlobalTypes(vueOptions),
24-
);
25-
}
20+
vue.writeGlobalTypes(vueOptions, ts.sys.writeFile);
2621
const allExtensions = vue.getAllExtensions(vueOptions);
2722
if (
2823
runExtensions.length === allExtensions.length

packages/tsc/tests/dts.spec.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,7 @@ describe('vue-tsc-dts', () => {
3636
vueOptions.target = 99;
3737
vueOptions.extensions = ['vue', 'cext'];
3838
}
39-
if (vueOptions.globalTypesPath) {
40-
ts.sys.writeFile(
41-
vueOptions.globalTypesPath,
42-
vue.generateGlobalTypes(vueOptions),
43-
);
44-
}
39+
vue.writeGlobalTypes(vueOptions, ts.sys.writeFile);
4540
const vueLanguagePlugin = vue.createVueLanguagePlugin<string>(
4641
ts,
4742
options.options,

packages/typescript-plugin/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,7 @@ const project2Service = new WeakMap<ts.server.Project, [vue.Language, ts.Languag
1919
export = createLanguageServicePlugin(
2020
(ts, info) => {
2121
const vueOptions = getVueCompilerOptions();
22-
if (vueOptions.globalTypesPath) {
23-
ts.sys.writeFile(
24-
vueOptions.globalTypesPath,
25-
vue.generateGlobalTypes(vueOptions),
26-
);
27-
}
22+
vue.writeGlobalTypes(vueOptions, ts.sys.writeFile);
2823
const languagePlugin = vue.createVueLanguagePlugin<string>(
2924
ts,
3025
info.languageServiceHost.getCompilationSettings(),

0 commit comments

Comments
 (0)