Skip to content

Commit

Permalink
feat!: upgrade to Volar v2 (#129)
Browse files Browse the repository at this point in the history
Upgrades Vue support to use the latest Volar tooling. This requires a new peer depencency `vue-tsc` now, thus it's a breaking change.

Co-authored-by: simonhaenisch <[email protected]>
  • Loading branch information
johnsoncodehk and simonhaenisch authored Jul 1, 2024
1 parent 9609e4e commit 235be2f
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 73 deletions.
23 changes: 1 addition & 22 deletions lib/get-compiler-options.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const { dirname } = require('path');
const ts = require('typescript');
const { findTsconfig } = require('./find-tsconfig');
const { memoize } = require('./memoize');

/**
Expand All @@ -15,29 +14,9 @@ function getCompilerOptions(tsconfig) {
: ts.getDefaultCompilerOptions();

compilerOptions.allowJs = true; // for automatic JS support
compilerOptions.allowNonTsExtensions = true // for Vue support

return compilerOptions;
}

module.exports.getCompilerOptions = memoize(getCompilerOptions);

/**
* Get the Vue compiler options from the path to a tsconfig.
*
* Uses a dynamic require instead of top-level because this is only needed for Vue.
*
* @param {string | undefined} tsconfig path to tsconfig
*/
function getVueCompilerOptions(tsconfig) {
return tsconfig
? require('@volar/vue-language-core').createParsedCommandLine(
// @ts-ignore
ts,
ts.sys,
tsconfig,
[],
).vueOptions
: {};
}

module.exports.getVueCompilerOptions = memoize(getVueCompilerOptions);
92 changes: 87 additions & 5 deletions lib/get-language-service.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
const { getTypeScriptLanguageServiceHost, getVueLanguageServiceHost } = require('./service-host');
const ts = require('typescript');

const { findTsconfig } = require('./find-tsconfig');
const { getTypeScriptLanguageServiceHost } = require('./service-host');

/**
* Get the correct language service for the given parser.
Expand All @@ -10,11 +13,90 @@ const { getTypeScriptLanguageServiceHost, getVueLanguageServiceHost } = require(
* @returns {import('typescript').LanguageService}
*/
const getLanguageService = (parser, filepath, code) => {
if (parser === 'vue') {
return require('@volar/vue-typescript').createLanguageService(getVueLanguageServiceHost(filepath, code));
}
const langaugeServiceHost = getTypeScriptLanguageServiceHost(filepath, code);

const languageService = ts.createLanguageService(langaugeServiceHost);

switch (parser) {
case 'vue':
return getVueDecoratedProxyLanguageService(langaugeServiceHost, languageService, filepath);

return require('typescript').createLanguageService(getTypeScriptLanguageServiceHost(filepath, code));
/** @todo add svelte support */

default:
return languageService;
}
};

/**
* Decorate a language service so it can handle Vue files.
*
* @param {import('typescript').LanguageServiceHost} langaugeServiceHost
* @param {import('typescript').LanguageService} languageService
* @param {string} filepath
*
* @returns {import('typescript').LanguageService}
*/
function getVueDecoratedProxyLanguageService(langaugeServiceHost, languageService, filepath) {
const vueTscDir = tryCatch(() => require('path').dirname(require.resolve('vue-tsc/package.json')));

if (!vueTscDir) {
console.error('Please install vue-tsc to organize imports in Vue files.');
return languageService;
}

const { createProxyLanguageService, decorateLanguageServiceHost } = require(
require.resolve('@volar/typescript', { paths: [vueTscDir] }),
);

/** @type {import('@vue/language-core')} */
const {
createLanguage,
createVueLanguagePlugin,
FileMap,
createParsedCommandLine,
resolveVueCompilerOptions,
} = require(require.resolve('@vue/language-core', { paths: [vueTscDir] }));

const tsconfig = findTsconfig(filepath);

const vueLanguagePlugin = createVueLanguagePlugin(
ts,
(s) => s,
() => '',
() => false,
langaugeServiceHost.getCompilationSettings(),
tsconfig ? createParsedCommandLine(ts, ts.sys, tsconfig).vueOptions : resolveVueCompilerOptions({}),
);

const language = createLanguage([vueLanguagePlugin], new FileMap(ts.sys.useCaseSensitiveFileNames), () => {});

const snapshot = langaugeServiceHost.getScriptSnapshot(filepath);

if (snapshot) {
language.scripts.set(filepath, snapshot);
}

const { initialize, proxy } = createProxyLanguageService(languageService);

initialize(language);

decorateLanguageServiceHost(ts, language, langaugeServiceHost);

return proxy;
}

/**
* @template T
* @param {() => T} fn
* @returns {T | undefined}
*/
function tryCatch(fn) {
try {
return fn();
} catch {
return undefined;
}
}

module.exports = { getLanguageService };
33 changes: 3 additions & 30 deletions lib/service-host.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const { getCompilerOptions, getVueCompilerOptions } = require('./get-compiler-op
function getTypeScriptLanguageServiceHost(path, content) {
const tsconfig = findTsconfig(path);
const compilerOptions = getCompilerOptions(tsconfig);
const snapshot = ts.ScriptSnapshot.fromString(content);

return {
directoryExists: ts.sys.directoryExists,
Expand All @@ -30,38 +31,10 @@ function getTypeScriptLanguageServiceHost(path, content) {
getScriptVersion: () => '0',
getScriptSnapshot: (filePath) => {
if (filePath === path) {
return ts.ScriptSnapshot.fromString(content);
return snapshot;
}
},
};
}

/**
* Get a Language Service Host for Vue support, that has some extra methods needed by `@volar/vue-typescript`.
*
* @typedef {import('@volar/vue-language-core/out/types').LanguageServiceHost} VueLanguageServiceHost
*
* @param {string} path path to file
* @param {string} content file's content
*
* @returns {VueLanguageServiceHost}
*/
function getVueLanguageServiceHost(path, content) {
const tsconfig = findTsconfig(path);
const vueCompilerOptions = getVueCompilerOptions(tsconfig);

return {
...getTypeScriptLanguageServiceHost(path, content),
getVueCompilationSettings: () => vueCompilerOptions,
/**
* Can return either `require('typescript')` or `require('typescript/lib/tsserverlibrary')`.
*
* @see https://github.com/simonhaenisch/prettier-plugin-organize-imports/pull/60#discussion_r934408179
*
* @returns {any}
*/
getTypeScriptModule: () => ts,
};
}

module.exports = { getTypeScriptLanguageServiceHost, getVueLanguageServiceHost };
module.exports = { getTypeScriptLanguageServiceHost };
20 changes: 8 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,22 @@
"repository": "simonhaenisch/prettier-plugin-organize-imports",
"homepage": "https://github.com/simonhaenisch/prettier-plugin-organize-imports#readme",
"peerDependencies": {
"@volar/vue-language-plugin-pug": "^1.0.4",
"@volar/vue-typescript": "^1.0.4",
"prettier": ">=2.0",
"typescript": ">=2.9"
"typescript": ">=2.9",
"vue-tsc": ">=2.0.24"
},
"peerDependenciesMeta": {
"@volar/vue-language-plugin-pug": {
"optional": true
},
"@volar/vue-typescript": {
"vue-tsc": {
"optional": true
}
},
"devDependencies": {
"@types/node": "20.8.10",
"@volar/vue-language-plugin-pug": "1.0.9",
"@volar/vue-typescript": "1.0.9",
"@types/node": "20.14.9",
"@vue/language-plugin-pug": "2.0.24",
"ava": "3.15.0",
"prettier": "3.0.3",
"typescript": "5.2.2"
"prettier": "3.3.2",
"typescript": "5.5.2",
"vue-tsc": "2.0.24"
},
"prettier": {
"printWidth": 120,
Expand Down
6 changes: 3 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,13 @@ Depending on your configuration, if you need the `React` import to stay even if

### Vue.js

Make sure that you have the optional peer dependency `@volar/vue-typescript` installed.
Make sure that you have the optional peer dependency `vue-tsc` installed.

```
npm i --save-dev @volar/vue-typescript
npm install --save-dev vue-tsc
```

If you're using Vue.js with Pug templates, you'll also need to install `@volar/vue-language-plugin-pug` as a dev dependency, and configure it in `vueCompilerOptions` (see [usage](https://www.npmjs.com/package/@volar/vue-language-plugin-pug)).
If you're using Vue.js with Pug templates, you'll also need to install `@vue/language-plugin-pug` as a dev dependency, and configure it in `vueCompilerOptions` (see [usage](https://www.npmjs.com/package/@vue/language-plugin-pug)).

## Debug Logs

Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"checkJs": true,
"strict": true
},
"vueCompilerOptions": { "plugins": ["@volar/vue-language-plugin-pug"] }
"vueCompilerOptions": { "plugins": ["@vue/language-plugin-pug"] }
}

0 comments on commit 235be2f

Please sign in to comment.