Skip to content

Commit de70d31

Browse files
committed
refactor: move preview common logic to @volar/preview
close #1115
1 parent b6bf667 commit de70d31

File tree

14 files changed

+318
-127
lines changed

14 files changed

+318
-127
lines changed

extensions/vscode-vue-language-features/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -637,16 +637,17 @@
637637
"devDependencies": {
638638
"@types/vscode": "1.63.0",
639639
"@types/ws": "^8.5.3",
640+
"@volar/preview": "0.34.2",
640641
"@volar/shared": "0.34.2",
641642
"@volar/vue-language-server": "0.34.2",
642643
"@vue/compiler-dom": "^3.2.31",
643644
"@vue/compiler-sfc": "^3.2.31",
644645
"@vue/reactivity": "^3.2.31",
645646
"esbuild": "latest",
647+
"esbuild-plugin-copy": "latest",
646648
"path-browserify": "^1.0.1",
647649
"vsce": "latest",
648650
"vscode-languageclient": "^8.0.0-next.14",
649-
"vscode-nls": "5.0.0",
650-
"ws": "^8.5.0"
651+
"vscode-nls": "5.0.0"
651652
}
652653
}

extensions/vscode-vue-language-features/scripts/build-node.js

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,29 @@ require('esbuild').build({
1515
define: { 'process.env.NODE_ENV': '"production"' },
1616
minify: process.argv.includes('--minify'),
1717
watch: process.argv.includes('--watch'),
18-
plugins: [{
19-
name: 'umd2esm',
20-
setup(build) {
21-
build.onResolve({ filter: /^(vscode-.*|estree-walker|jsonc-parser)/ }, args => {
22-
const pathUmdMay = require.resolve(args.path, { paths: [args.resolveDir] })
23-
const pathEsm = pathUmdMay.replace('/umd/', '/esm/')
24-
return { path: pathEsm }
25-
})
26-
build.onResolve({ filter: /^\@vue\/compiler-sfc$/ }, args => {
27-
const pathUmdMay = require.resolve(args.path, { paths: [args.resolveDir] })
28-
const pathEsm = pathUmdMay.replace('compiler-sfc.cjs.js', 'compiler-sfc.esm-browser.js')
29-
return { path: pathEsm }
30-
})
18+
plugins: [
19+
{
20+
name: 'umd2esm',
21+
setup(build) {
22+
build.onResolve({ filter: /^(vscode-.*|estree-walker|jsonc-parser)/ }, args => {
23+
const pathUmdMay = require.resolve(args.path, { paths: [args.resolveDir] })
24+
const pathEsm = pathUmdMay.replace('/umd/', '/esm/')
25+
return { path: pathEsm }
26+
})
27+
build.onResolve({ filter: /^\@vue\/compiler-sfc$/ }, args => {
28+
const pathUmdMay = require.resolve(args.path, { paths: [args.resolveDir] })
29+
const pathEsm = pathUmdMay.replace('compiler-sfc.cjs.js', 'compiler-sfc.esm-browser.js')
30+
return { path: pathEsm }
31+
})
32+
},
3133
},
32-
}],
34+
require('esbuild-plugin-copy').copy({
35+
resolveFrom: 'cwd',
36+
assets: {
37+
from: ['./node_modules/@volar/preview/bin/**/*'],
38+
to: ['./dist/preview-bin'],
39+
},
40+
keepStructure: true,
41+
}),
42+
],
3343
}).catch(() => process.exit(1))

extensions/vscode-vue-language-features/src/features/preview.ts

Lines changed: 49 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as fs from '../utils/fs';
55
import * as shared from '@volar/shared';
66
import { userPick } from './splitEditors';
77
import { parse, SFCParseResult } from '@vue/compiler-sfc';
8-
import * as WebSocket from 'ws';
8+
import * as preview from '@volar/preview';
99

1010
interface PreviewState {
1111
mode: 'vite' | 'nuxt',
@@ -28,40 +28,30 @@ export async function activate(context: vscode.ExtensionContext) {
2828
statusBar.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground');
2929
context.subscriptions.push(statusBar);
3030

31-
const wsList: WebSocket.WebSocket[] = [];
32-
let wss: WebSocket.Server | undefined;
31+
let ws: ReturnType<typeof preview.createPreviewWebSocket> | undefined;
3332

34-
function startWsServer() {
35-
wss = new WebSocket.Server({
36-
port: 56789
37-
});
38-
39-
wss.on('connection', ws => {
40-
wsList.push(ws);
41-
ws.on('message', msg => {
42-
webviewEventHandler(JSON.parse(msg.toString()));
43-
});
44-
});
45-
}
4633
if (vscode.window.terminals.some(terminal => terminal.name.startsWith('volar-preview:'))) {
47-
startWsServer();
34+
ws = preview.createPreviewWebSocket({
35+
goToCode: handleGoToCode,
36+
getOpenFileUrl: (fileName, range) => 'vscode://files:/' + fileName,
37+
});
4838
}
4939
vscode.window.onDidOpenTerminal(e => {
5040
if (e.name.startsWith('volar-preview:')) {
51-
startWsServer();
41+
ws = preview.createPreviewWebSocket({
42+
goToCode: handleGoToCode,
43+
getOpenFileUrl: (fileName, range) => 'vscode://files:/' + fileName,
44+
});
5245
}
5346
});
5447
vscode.window.onDidCloseTerminal(e => {
5548
if (e.name.startsWith('volar-preview:')) {
56-
wss?.close();
57-
wsList.length = 0;
49+
ws?.stop();
5850
}
5951
});
6052

6153
const sfcs = new WeakMap<vscode.TextDocument, { version: number, sfc: SFCParseResult }>();
6254

63-
let goToTemplateReq = 0;
64-
6555
class FinderPanelSerializer implements vscode.WebviewPanelSerializer {
6656
async deserializeWebviewPanel(panel: vscode.WebviewPanel, state: PreviewState) {
6757

@@ -166,31 +156,16 @@ export async function activate(context: vscode.ExtensionContext) {
166156
}
167157
}));
168158
context.subscriptions.push(vscode.window.onDidChangeTextEditorSelection(e => {
169-
for (const panel of panels) {
170-
updateSelectionHighlights(e.textEditor, panel, undefined);
171-
}
172-
for (const ws of wsList) {
173-
updateSelectionHighlights(e.textEditor, undefined, ws);
174-
}
159+
updateSelectionHighlights(e.textEditor);
175160
}));
176161
context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(e => {
177162
if (vscode.window.activeTextEditor) {
178-
for (const panel of panels) {
179-
updateSelectionHighlights(vscode.window.activeTextEditor, panel, undefined);
180-
}
181-
for (const ws of wsList) {
182-
updateSelectionHighlights(vscode.window.activeTextEditor, undefined, ws);
183-
}
163+
updateSelectionHighlights(vscode.window.activeTextEditor);
184164
}
185165
}));
186166
context.subscriptions.push(vscode.workspace.onDidSaveTextDocument(e => {
187167
if (vscode.window.activeTextEditor) {
188-
for (const panel of panels) {
189-
updateSelectionHighlights(vscode.window.activeTextEditor, panel, undefined);
190-
}
191-
for (const ws of wsList) {
192-
updateSelectionHighlights(vscode.window.activeTextEditor, undefined, ws);
193-
}
168+
updateSelectionHighlights(vscode.window.activeTextEditor);
194169
}
195170
}));
196171

@@ -223,33 +198,21 @@ export async function activate(context: vscode.ExtensionContext) {
223198
}
224199
}
225200

226-
function updateSelectionHighlights(textEditor: vscode.TextEditor, panel: vscode.WebviewPanel | undefined, ws: WebSocket.WebSocket | undefined) {
201+
function updateSelectionHighlights(textEditor: vscode.TextEditor) {
227202
if (textEditor.document.languageId === 'vue') {
228203
const sfc = getSfc(textEditor.document);
229204
const offset = sfc.descriptor.template?.loc.start.offset ?? 0;
230-
const msg = {
231-
sender: 'volar',
232-
command: 'highlightSelections',
233-
data: {
234-
fileName: textEditor.document.fileName,
235-
ranges: textEditor.selections.map(selection => ({
236-
start: textEditor.document.offsetAt(selection.start) - offset,
237-
end: textEditor.document.offsetAt(selection.end) - offset,
238-
})),
239-
isDirty: textEditor.document.isDirty,
240-
},
241-
};
242-
panel?.webview.postMessage(msg);
243-
ws?.send(JSON.stringify(msg));
205+
ws?.highlight(
206+
textEditor.document.fileName,
207+
textEditor.selections.map(selection => ({
208+
start: textEditor.document.offsetAt(selection.start) - offset,
209+
end: textEditor.document.offsetAt(selection.end) - offset,
210+
})),
211+
textEditor.document.isDirty,
212+
);
244213
}
245214
else {
246-
const msg = {
247-
sender: 'volar',
248-
command: 'highlightSelections',
249-
data: undefined,
250-
};
251-
panel?.webview.postMessage(JSON.stringify(msg));
252-
ws?.send(JSON.stringify(msg));
215+
ws?.unhighlight();
253216
}
254217
}
255218

@@ -394,33 +357,29 @@ export async function activate(context: vscode.ExtensionContext) {
394357
vscode.window.showErrorMessage(text);
395358
break;
396359
}
397-
case 'goToTemplate': {
398-
const req = ++goToTemplateReq;
399-
const data = message.data as {
400-
fileName: string,
401-
range: [number, number],
402-
};
403-
const doc = await vscode.workspace.openTextDocument(data.fileName);
404-
405-
if (req !== goToTemplateReq)
406-
return;
407-
408-
const sfc = getSfc(doc);
409-
const offset = sfc.descriptor.template?.loc.start.offset ?? 0;
410-
const start = doc.positionAt(data.range[0] + offset);
411-
const end = doc.positionAt(data.range[1] + offset);
412-
await vscode.window.showTextDocument(doc, vscode.ViewColumn.One);
413-
414-
if (req !== goToTemplateReq)
415-
return;
416-
417-
const editor = vscode.window.activeTextEditor;
418-
if (editor) {
419-
editor.selection = new vscode.Selection(start, end);
420-
editor.revealRange(new vscode.Range(start, end));
421-
}
422-
break;
423-
}
360+
}
361+
}
362+
363+
async function handleGoToCode(fileName: string, range: [number, number], cancleToken: { readonly isCancelled: boolean }) {
364+
365+
const doc = await vscode.workspace.openTextDocument(fileName);
366+
367+
if (cancleToken.isCancelled)
368+
return;
369+
370+
const sfc = getSfc(doc);
371+
const offset = sfc.descriptor.template?.loc.start.offset ?? 0;
372+
const start = doc.positionAt(range[0] + offset);
373+
const end = doc.positionAt(range[1] + offset);
374+
await vscode.window.showTextDocument(doc, vscode.ViewColumn.One);
375+
376+
if (cancleToken.isCancelled)
377+
return;
378+
379+
const editor = vscode.window.activeTextEditor;
380+
if (editor) {
381+
editor.selection = new vscode.Selection(start, end);
382+
editor.revealRange(new vscode.Range(start, end));
424383
}
425384
}
426385

@@ -429,8 +388,8 @@ export async function activate(context: vscode.ExtensionContext) {
429388
const port = await shared.getLocalHostAvaliablePort(vscode.workspace.getConfiguration('volar').get('preview.port') ?? 3334);
430389
const terminal = vscode.window.createTerminal('volar-preview:' + port);
431390
const viteProxyPath = type === 'vite'
432-
? require.resolve('./bin/vite', { paths: [context.extensionPath] })
433-
: require.resolve('./bin/nuxi', { paths: [context.extensionPath] });
391+
? require.resolve('./dist/preview-bin/vite', { paths: [context.extensionPath] })
392+
: require.resolve('./dist/preview-bin/nuxi', { paths: [context.extensionPath] });
434393

435394
terminal.sendText(`cd ${viteDir}`);
436395

extensions/vscode-vue-language-features/tsconfig.build.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
{
1717
"path": "../../packages/vue-language-server/tsconfig.build.json"
1818
},
19+
{
20+
"path": "../../packages/preview/tsconfig.build.json"
21+
},
1922
{
2023
"path": "../../packages/shared/tsconfig.build.json"
2124
}

packages/preview/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021-present Johnson Chu
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/preview/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# vue-tsc
2+
3+
Install: `npm i vue-tsc -D`
4+
5+
Usage: `vue-tsc --noEmit && vite build`
6+
7+
Vue 3 command line Type-Checking tool base on IDE plugin [Volar](https://github.com/johnsoncodehk/volar).
8+
9+
Roadmap:
10+
11+
- [x] Type-Checking with `--noEmit`
12+
- [x] Use released LSP module
13+
- [x] Make `typescript` as peerDependencies
14+
- [x] Cleaner dependencies (remove `prettyhtml`, `prettier` etc.) (with `vscode-vue-languageservice` version >= 0.26.4)
15+
- [x] dts emit support
16+
- [x] Watch mode support
17+
18+
## Using
19+
20+
Type check:
21+
22+
`vue-tsc --noEmit`
23+
24+
Build dts:
25+
26+
`vue-tsc --declaration --emitDeclarationOnly`
27+
28+
Check out https://github.com/johnsoncodehk/volar/discussions/640#discussioncomment-1555479 for example repo.
29+
30+
## Sponsors
31+
32+
<p align="center">
33+
<a href="https://cdn.jsdelivr.net/gh/johnsoncodehk/sponsors/sponsors.svg">
34+
<img src='https://cdn.jsdelivr.net/gh/johnsoncodehk/sponsors/sponsors.svg'/>
35+
</a>
36+
</p>

extensions/vscode-vue-language-features/bin/nuxi/plugin.ts renamed to packages/preview/bin/nuxi/plugin.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,6 @@ export default defineNuxtPlugin(app => {
4343
const cursorInOverlays = new Map<Element, HTMLElement>();
4444
const rangeCoverOverlays = new Map<Element, HTMLElement>();
4545

46-
window.addEventListener('message', event => {
47-
if (event.data?.command === 'highlightSelections') {
48-
selection = event.data.data;
49-
updateHighlights();
50-
}
51-
});
5246
window.addEventListener('scroll', updateHighlights);
5347

5448
ws.addEventListener('message', event => {
@@ -198,6 +192,13 @@ export default defineNuxtPlugin(app => {
198192
}
199193
});
200194

195+
ws.addEventListener('message', event => {
196+
const data = JSON.parse(event.data);
197+
if (data?.command === 'openFile') {
198+
window.open(data.data);
199+
}
200+
});
201+
201202
const overlay = createOverlay();
202203
const clickMask = createClickMask();
203204

@@ -216,16 +217,19 @@ export default defineNuxtPlugin(app => {
216217
document.body.appendChild(clickMask);
217218
updateOverlay();
218219
}
219-
function disable(openVscode: boolean) {
220+
function disable(openEditor: boolean) {
220221
if (enabled) {
221222
enabled = false;
222223
clickMask.style.pointerEvents = '';
223224
highlightNodes = [];
224225
updateOverlay();
225226
if (lastCodeLoc) {
226227
ws.send(JSON.stringify(lastCodeLoc));
227-
if (openVscode) {
228-
window.open('vscode://files:/' + lastCodeLoc.fileName);
228+
if (openEditor) {
229+
ws.send(JSON.stringify({
230+
command: 'requestOpenFile',
231+
data: lastCodeLoc.data,
232+
}));
229233
}
230234
lastCodeLoc = undefined;
231235
}

0 commit comments

Comments
 (0)