Skip to content

Commit

Permalink
refactor(language-core): rewrite vueCompilerOptions resolution logic
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Jan 22, 2025
1 parent 8b8bfbb commit 3f138d6
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 162 deletions.
4 changes: 2 additions & 2 deletions packages/language-core/lib/parsers/vueCompilerOptions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { VueCompilerOptions } from '../types';
import type { RawVueCompilerOptions } from '../types';

const syntaxReg = /^\s*@(?<key>.+?)\s+(?<value>.+?)\s*$/m;

export function parseVueCompilerOptions(comments: string[]): Partial<VueCompilerOptions> | undefined {
export function parseVueCompilerOptions(comments: string[]): RawVueCompilerOptions | undefined {
const entries = comments
.map(text => {
try {
Expand Down
11 changes: 7 additions & 4 deletions packages/language-core/lib/plugins/vue-tsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { parseScriptRanges } from '../parsers/scriptRanges';
import { parseScriptSetupRanges } from '../parsers/scriptSetupRanges';
import { parseVueCompilerOptions } from '../parsers/vueCompilerOptions';
import type { Code, Sfc, VueLanguagePlugin } from '../types';
import { resolveVueCompilerOptions } from '../utils/ts';
import { CompilerOptionsResolver } from '../utils/ts';

export const tsCodegen = new WeakMap<Sfc, ReturnType<typeof createTsx>>();

Expand Down Expand Up @@ -83,9 +83,12 @@ function createTsx(
});
const vueCompilerOptions = computed(() => {
const options = parseVueCompilerOptions(_sfc.comments);
return options
? resolveVueCompilerOptions(options, ctx.vueCompilerOptions)
: ctx.vueCompilerOptions;
if (options) {
const resolver = new CompilerOptionsResolver();
resolver.addConfig(options, path.dirname(fileName));
return resolver.build(ctx.vueCompilerOptions);
}
return ctx.vueCompilerOptions;
});
const scriptRanges = computed(() =>
_sfc.script
Expand Down
2 changes: 1 addition & 1 deletion packages/language-core/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type { SFCParseResult } from '@vue/compiler-sfc';
export { VueEmbeddedCode };

export type RawVueCompilerOptions = Partial<Omit<VueCompilerOptions, 'target' | 'plugins'>> & {
target?: 'auto' | 2 | 2.7 | 3 | 3.3;
target?: 'auto' | 2 | 2.7 | 3 | 3.3 | 3.5 | 99 | number;
plugins?: string[];
};

Expand Down
214 changes: 116 additions & 98 deletions packages/language-core/lib/utils/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,19 @@ export function createParsedCommandLineByJson(
const proxyHost = proxyParseConfigHostForExtendConfigPaths(parseConfigHost);
ts.parseJsonConfigFileContent(json, proxyHost.host, rootDir, {}, configFileName);

let vueOptions: Partial<VueCompilerOptions> = {};
const resolver = new CompilerOptionsResolver();

for (const extendPath of proxyHost.extendConfigPaths.reverse()) {
try {
vueOptions = {
...vueOptions,
...getPartialVueCompilerOptions(ts, ts.readJsonConfigFile(extendPath, proxyHost.host.readFile)),
};
const configFile = ts.readJsonConfigFile(extendPath, proxyHost.host.readFile);
const obj = ts.convertToObject(configFile, []);
const rawOptions: RawVueCompilerOptions = obj?.vueCompilerOptions ?? {};
resolver.addConfig(rawOptions, path.dirname(configFile.fileName));
} catch (err) { }
}

const resolvedVueOptions = resolveVueCompilerOptions(vueOptions);
const resolvedVueOptions = resolver.build();

if (skipGlobalTypesSetup) {
resolvedVueOptions.__setupedGlobalTypes = true;
}
Expand Down Expand Up @@ -78,18 +79,19 @@ export function createParsedCommandLine(
const config = ts.readJsonConfigFile(tsConfigPath, proxyHost.host.readFile);
ts.parseJsonSourceFileConfigFileContent(config, proxyHost.host, path.dirname(tsConfigPath), {}, tsConfigPath);

let vueOptions: Partial<VueCompilerOptions> = {};
const resolver = new CompilerOptionsResolver();

for (const extendPath of proxyHost.extendConfigPaths.reverse()) {
try {
vueOptions = {
...vueOptions,
...getPartialVueCompilerOptions(ts, ts.readJsonConfigFile(extendPath, proxyHost.host.readFile)),
};
const configFile = ts.readJsonConfigFile(extendPath, proxyHost.host.readFile);
const obj = ts.convertToObject(configFile, []);
const rawOptions: RawVueCompilerOptions = obj?.vueCompilerOptions ?? {};
resolver.addConfig(rawOptions, path.dirname(configFile.fileName));
} catch (err) { }
}

const resolvedVueOptions = resolveVueCompilerOptions(vueOptions);
const resolvedVueOptions = resolver.build();

if (skipGlobalTypesSetup) {
resolvedVueOptions.__setupedGlobalTypes = true;
}
Expand Down Expand Up @@ -126,7 +128,7 @@ export function createParsedCommandLine(
return {
fileNames: [],
options: {},
vueOptions: resolveVueCompilerOptions({}),
vueOptions: getDefaultCompilerOptions(),
errors: [],
};
}
Expand All @@ -153,76 +155,115 @@ function proxyParseConfigHostForExtendConfigPaths(parseConfigHost: ts.ParseConfi
};
}

function getPartialVueCompilerOptions(
ts: typeof import('typescript'),
tsConfigSourceFile: ts.TsConfigSourceFile
) {

const folder = path.dirname(tsConfigSourceFile.fileName);
const obj = ts.convertToObject(tsConfigSourceFile, []);
const rawOptions: RawVueCompilerOptions = obj?.vueCompilerOptions ?? {};
const result: Partial<VueCompilerOptions> = {
...rawOptions as any,
};
const target = rawOptions.target ?? 'auto';
export class CompilerOptionsResolver {
options: Omit<RawVueCompilerOptions, 'target' | 'plugin'> = {};
fallbackTarget: number | undefined;
target: number | undefined;
plugins: VueLanguagePlugin[] = [];

if (target === 'auto') {
const resolvedPath = resolvePath('vue/package.json');
if (resolvedPath) {
const vuePackageJson = require(resolvedPath);
const versionNumbers = vuePackageJson.version.split('.');
result.target = Number(versionNumbers[0] + '.' + versionNumbers[1]);
addConfig(options: RawVueCompilerOptions, rootDir: string) {
for (const key in options) {
switch (key) {
case 'target':
const target = options.target!;
if (typeof target === 'string') {
this.target = findVueVersion(rootDir);
}
else {
this.target = target;
}
break;
case 'plugins':
this.plugins = (options.plugins ?? [])
.map<VueLanguagePlugin>((pluginPath: string) => {
try {
const resolvedPath = resolvePath(pluginPath, rootDir);
if (resolvedPath) {
const plugin = require(resolvedPath);
plugin.__moduleName = pluginPath;
return plugin;
}
else {
console.warn('[Vue] Load plugin failed:', pluginPath);
}
}
catch (error) {
console.warn('[Vue] Resolve plugin path failed:', pluginPath, error);
}
return [];
});
break;
default:
// @ts-expect-error
this.options[key] = options[key];
break;
}
}
else {
// console.warn('Load vue/package.json failed from', folder);
if (this.target === undefined) {
this.fallbackTarget = findVueVersion(rootDir);
}
}
else {
result.target = target;
}
if (rawOptions.plugins) {
const plugins = rawOptions.plugins
.map<VueLanguagePlugin>((pluginPath: string) => {
try {
const resolvedPath = resolvePath(pluginPath);
if (resolvedPath) {
const plugin = require(resolvedPath);
plugin.__moduleName = pluginPath;
return plugin;
}
else {
console.warn('[Vue] Load plugin failed:', pluginPath);

build(defaults?: VueCompilerOptions): VueCompilerOptions {
const target = this.target ?? this.fallbackTarget;
defaults ??= getDefaultCompilerOptions(target, this.options.lib);
return {
...defaults,
...this.options,
plugins: this.plugins,
macros: {
...defaults.macros,
...this.options.macros,
},
composables: {
...defaults.composables,
...this.options.composables,
},
// https://github.com/vuejs/vue-next/blob/master/packages/compiler-dom/src/transforms/vModel.ts#L49-L51
// https://vuejs.org/guide/essentials/forms.html#form-input-bindings
experimentalModelPropName: Object.fromEntries(Object.entries(
this.options.experimentalModelPropName ?? defaults.experimentalModelPropName ?? {
'': {
input: true
},
value: {
input: { type: 'text' },
textarea: true,
select: true
}
}
catch (error) {
console.warn('[Vue] Resolve plugin path failed:', pluginPath, error);
}
return [];
});

result.plugins = plugins;
).map(([k, v]) => [camelize(k), v])),
};
}
}

return result;
function findVueVersion(rootDir: string) {
const resolvedPath = resolvePath('vue/package.json', rootDir);
if (resolvedPath) {
const vuePackageJson = require(resolvedPath);
const versionNumbers = vuePackageJson.version.split('.');
return Number(versionNumbers[0] + '.' + versionNumbers[1]);
}
else {
// console.warn('Load vue/package.json failed from', folder);
}
}

function resolvePath(scriptPath: string) {
try {
if (require?.resolve) {
return require.resolve(scriptPath, { paths: [folder] });
}
else {
// console.warn('failed to resolve path:', scriptPath, 'require.resolve is not supported in web');
}
function resolvePath(scriptPath: string, root: string) {
try {
if (require?.resolve) {
return require.resolve(scriptPath, { paths: [root] });
}
catch (error) {
// console.warn(error);
else {
// console.warn('failed to resolve path:', scriptPath, 'require.resolve is not supported in web');
}
}
catch (error) {
// console.warn(error);
}
}

function getDefaultOptions(options: Partial<VueCompilerOptions>): VueCompilerOptions {
const target = options.target ?? 3.3;
const lib = options.lib ?? 'vue';
export function getDefaultCompilerOptions(target = 99, lib = 'vue'): VueCompilerOptions {
return {
target,
lib,
Expand Down Expand Up @@ -258,38 +299,15 @@ function getDefaultOptions(options: Partial<VueCompilerOptions>): VueCompilerOpt
experimentalResolveStyleCssClasses: 'scoped',
experimentalModelPropName: null!
};
};
}

export function resolveVueCompilerOptions(
options: Partial<VueCompilerOptions>,
defaults: VueCompilerOptions = getDefaultOptions(options)
): VueCompilerOptions {
/**
* @deprecated use `getDefaultCompilerOptions` instead
*/
export function resolveVueCompilerOptions(options: Partial<VueCompilerOptions>): VueCompilerOptions {
return {
...defaults,
...getDefaultCompilerOptions(options.target, options.lib),
...options,
macros: {
...defaults.macros,
...options.macros,
},
composables: {
...defaults.composables,
...options.composables,
},

// https://github.com/vuejs/vue-next/blob/master/packages/compiler-dom/src/transforms/vModel.ts#L49-L51
// https://vuejs.org/guide/essentials/forms.html#form-input-bindings
experimentalModelPropName: Object.fromEntries(Object.entries(
options.experimentalModelPropName ?? defaults.experimentalModelPropName ?? {
'': {
input: true
},
value: {
input: { type: 'text' },
textarea: true,
select: true
}
}
).map(([k, v]) => [camelize(k), v])),
};
}

Expand Down
4 changes: 2 additions & 2 deletions packages/language-server/lib/initialize.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { LanguageServer } from '@volar/language-server';
import { createTypeScriptProject } from '@volar/language-server/node';
import { createParsedCommandLine, createVueLanguagePlugin, generateGlobalTypes, getAllExtensions, resolveVueCompilerOptions, VueCompilerOptions } from '@vue/language-core';
import { createParsedCommandLine, createVueLanguagePlugin, generateGlobalTypes, getAllExtensions, getDefaultCompilerOptions, VueCompilerOptions } from '@vue/language-core';
import { Disposable, getFullLanguageServicePlugins, InitializeParams } from '@vue/language-service';
import type * as ts from 'typescript';

Expand Down Expand Up @@ -35,7 +35,7 @@ export function initialize(
}
else {
compilerOptions = ts.getDefaultCompilerOptions();
vueCompilerOptions = resolveVueCompilerOptions({});
vueCompilerOptions = getDefaultCompilerOptions();
}
vueCompilerOptions.__test = params.initializationOptions.typescript.disableAutoImportCache;
updateFileWatcher(vueCompilerOptions);
Expand Down
4 changes: 2 additions & 2 deletions packages/language-server/node.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createConnection, createServer, loadTsdkByPath } from '@volar/language-server/node';
import { createParsedCommandLine, createVueLanguagePlugin, resolveVueCompilerOptions } from '@vue/language-core';
import { createParsedCommandLine, createVueLanguagePlugin, getDefaultCompilerOptions } from '@vue/language-core';
import { getHybridModeLanguageServicePlugins } from '@vue/language-service';
import * as namedPipeClient from '@vue/typescript-plugin/lib/client';
import { createHybridModeProject } from './lib/hybridModeProject';
Expand All @@ -23,7 +23,7 @@ connection.onInitialize(params => {
const commandLine = configFileName
? createParsedCommandLine(ts, ts.sys, configFileName)
: {
vueOptions: resolveVueCompilerOptions({}),
vueOptions: getDefaultCompilerOptions(),
options: ts.getDefaultCompilerOptions(),
};
commandLine.vueOptions.__test = params.initializationOptions.typescript.disableAutoImportCache;
Expand Down
4 changes: 2 additions & 2 deletions packages/language-service/tests/utils/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import * as kit from '@volar/kit';
import * as ts from 'typescript';
import { describe, expect, it } from 'vitest';
import type { URI } from 'vscode-uri';
import { createVueLanguagePlugin, getFullLanguageServicePlugins, resolveVueCompilerOptions } from '../..';
import { createVueLanguagePlugin, getDefaultCompilerOptions, getFullLanguageServicePlugins } from '../..';

const resolvedVueOptions = resolveVueCompilerOptions({});
const resolvedVueOptions = getDefaultCompilerOptions();
const vueLanguagePlugin = createVueLanguagePlugin<URI>(
ts,
{},
Expand Down
2 changes: 1 addition & 1 deletion packages/tsc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function run(tscPath = require.resolve('typescript/lib/tsc')) {
const { configFilePath } = options.options;
const vueOptions = typeof configFilePath === 'string'
? vue.createParsedCommandLine(ts, ts.sys, configFilePath.replace(windowsPathReg, '/')).vueOptions
: vue.resolveVueCompilerOptions({});
: vue.getDefaultCompilerOptions();
const allExtensions = vue.getAllExtensions(vueOptions);
if (
runExtensions.length === allExtensions.length
Expand Down
Loading

0 comments on commit 3f138d6

Please sign in to comment.