Skip to content

Commit

Permalink
refactor: extract preview features to vscode-vue-preview
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Feb 12, 2023
1 parent 0934463 commit 719d31a
Show file tree
Hide file tree
Showing 19 changed files with 337 additions and 164 deletions.
17 changes: 17 additions & 0 deletions .vscode/launch.json
Expand Up @@ -19,6 +19,23 @@
"script": "watch"
}
},
{
"name": "Launch Preview",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--disable-extensions",
"--extensionDevelopmentPath=${workspaceRoot}/packages/vscode-vue-preview"
],
"outFiles": [
"${workspaceRoot}/*/*/out/**/*.js"
],
"preLaunchTask": {
"type": "npm",
"script": "watch"
}
},
{
"type": "extensionHost",
"request": "launch",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -3,9 +3,10 @@
"scripts": {
"build": "tsc -b tsconfig.build.json",
"build-ci": "tsc -b tsconfig.build-ci.json",
"watch": "npm run build && (npm run watch:base & npm run watch:vue & npm run watch:typescript-vue-plugin)",
"watch": "npm run build && (npm run watch:base & npm run watch:vue & npm run watch:vue-preview & npm run watch:typescript-vue-plugin)",
"watch:base": "tsc -b tsconfig.build.json -w",
"watch:vue": "cd ./packages/vscode-vue && npm run watch",
"watch:vue-preview": "cd ./packages/vscode-vue-preview && npm run watch",
"watch:typescript-vue-plugin": "cd ./packages/vscode-typescript-vue-plugin && npm run watch",
"prerelease": "npm run build && npm run test",
"version:test": "lerna version --exact --force-publish --yes --sync-workspace-lock --no-push --no-git-tag-version",
Expand Down
5 changes: 5 additions & 0 deletions packages/vscode-vue-preview/.vscodeignore
@@ -0,0 +1,5 @@
out
src
scripts
tsconfig.build.json
tsconfig.build.tsbuildinfo
21 changes: 21 additions & 0 deletions packages/vscode-vue-preview/LICENSE
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021-present Johnson Chu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
File renamed without changes
File renamed without changes
File renamed without changes
135 changes: 135 additions & 0 deletions packages/vscode-vue-preview/package.json
@@ -0,0 +1,135 @@
{
"private": true,
"name": "vscode-vue-preview",
"version": "1.0.24",
"repository": {
"type": "git",
"url": "https://github.com/johnsoncodehk/volar.git",
"directory": "packages/vscode-vue-preview"
},
"sponsor": {
"url": "https://github.com/sponsors/johnsoncodehk"
},
"displayName": "Vue and Nuxt Preview (from Volar)",
"description": "Component and App Preview For Vue and Nuxt",
"author": "johnsoncodehk",
"publisher": "johnsoncodehk",
"engines": {
"vscode": "^1.67.0"
},
"activationEvents": [
"onLanguage:vue",
"onWebviewPanel:preview"
],
"main": "./dist/extension.js",
"contributes": {
"views": {
"explorer": [
{
"id": "vueComponentPreview",
"name": "Vue Component Preview",
"type": "webview",
"when": "volar.foundViteDir || volar.foundNuxtDir"
}
]
},
"languages": [
{
"id": "vue",
"extensions": [
".vue"
]
}
],
"configuration": {
"type": "object",
"title": "Volar",
"properties": {
"volar.preview.script.vite": {
"type": "string",
"default": "node {VITE_BIN} --port={PORT}"
},
"volar.preview.script.nuxi": {
"type": "string",
"default": "node {NUXI_BIN} dev --port {PORT}"
},
"volar.preview.port": {
"type": "number",
"default": 3333,
"description": "Default port for component preview server."
},
"volar.preview.root": {
"type": "string",
"default": ".",
"description": "Component preview root directory. (For Nuxt, it should be \"./src\" by default.)"
},
"volar.preview.backgroundColor": {
"type": "string",
"default": "#fff",
"description": "Component preview background color."
},
"volar.preview.transparentGrid": {
"type": "boolean",
"default": false,
"description": "Component preview background style."
}
}
},
"commands": [
{
"command": "volar.action.vite",
"title": "Experimental Features for Vite",
"category": "Volar",
"icon": "images/vite-logo.svg"
},
{
"command": "volar.action.nuxt",
"title": "Experimental Features for Nuxt",
"category": "Volar",
"icon": "images/nuxt-logo.svg"
}
],
"menus": {
"commandPalette": [
{
"command": "volar.action.vite",
"when": "editorLangId == vue"
},
{
"command": "volar.action.nuxt",
"when": "editorLangId == vue"
}
],
"editor/title": [
{
"command": "volar.action.vite",
"when": "editorLangId == vue && config.volar.icon.preview && volar.foundViteDir",
"group": "navigation"
},
{
"command": "volar.action.nuxt",
"when": "editorLangId == vue && config.volar.icon.preview && volar.foundNuxtDir",
"group": "navigation"
}
]
}
},
"scripts": {
"prebuild": "cd ../.. && npm run build",
"build": "node scripts/build",
"watch": "npm run build -- --watch",
"prepack": "npm run prebuild && npm run build -- --minify",
"pack": "npm run prepack && vsce package",
"release": "npm run prepack && vsce publish"
},
"devDependencies": {
"@types/vscode": "1.67.0",
"@volar/preview": "1.0.24",
"@volar/vscode-language-client": "1.2.0-alpha.9",
"esbuild": "0.15.18",
"esbuild-plugin-copy": "latest",
"typesafe-path": "^0.2.2",
"vscode-html-languageservice": "^5.0.4",
"vsce": "latest"
}
}
38 changes: 38 additions & 0 deletions packages/vscode-vue-preview/scripts/build.js
@@ -0,0 +1,38 @@
require('esbuild').build({
entryPoints: {
extension: './out/extension.js',
},
bundle: true,
metafile: process.argv.includes('--metafile'),
outdir: './dist',
external: [
'vscode',
],
format: 'cjs',
platform: 'node',
tsconfig: '../../tsconfig.build.json',
define: { 'process.env.NODE_ENV': '"production"' },
minify: process.argv.includes('--minify'),
watch: process.argv.includes('--watch'),
plugins: [
{
name: 'umd2esm',
setup(build) {
build.onResolve({ filter: /^(vscode-.*|estree-walker|jsonc-parser)/ }, args => {
const pathUmdMay = require.resolve(args.path, { paths: [args.resolveDir] })
// Call twice the replace is to solve the problem of the path in Windows
const pathEsm = pathUmdMay.replace('/umd/', '/esm/').replace('\\umd\\', '\\esm\\')
return { path: pathEsm }
})
},
},
require('esbuild-plugin-copy').copy({
resolveFrom: 'cwd',
assets: {
from: ['./node_modules/@volar/preview/bin/**/*'],
to: ['./dist/bin'],
},
keepStructure: true,
}),
],
}).catch(() => process.exit(1))
@@ -1,20 +1,20 @@
import * as vscode from 'vscode';
import * as path from 'typesafe-path';
import * as fs from '../utils/fs';
import * as shared from '@volar/shared';
import * as fs from './utils/fs';
import { quickPick } from '@volar/vscode-language-client/out/common';
import * as preview from '@volar/preview';
import { getLocalHostAvailablePort } from '../utils/http';
import { BaseLanguageClient } from 'vscode-languageclient';
import { ParseSFCRequest } from '@volar/vue-language-server';
import { getLocalHostAvailablePort } from './utils/http';
import * as html from 'vscode-html-languageservice';

const htmlLs = html.getLanguageService();

const enum PreviewType {
Webview = 'volar-webview',
ExternalBrowser = 'volar-start-server',
ExternalBrowser_Component = 'volar-component-preview',
}

export async function register(context: vscode.ExtensionContext, client: BaseLanguageClient) {
export async function activate(context: vscode.ExtensionContext) {

let _loadingPanel: vscode.WebviewPanel | undefined;
let avoidUpdateOnDidChangeActiveTextEditor = false;
Expand Down Expand Up @@ -63,7 +63,7 @@ export async function register(context: vscode.ExtensionContext, client: BaseLan
}
});

const sfcs = new WeakMap<vscode.TextDocument, { version: number, sfc: ParseSFCRequest.ResponseType; }>();
const templateOffsets = new WeakMap<vscode.TextDocument, { version: number, offset: number; }>();

class VueComponentPreview implements vscode.WebviewViewProvider {

Expand Down Expand Up @@ -138,7 +138,7 @@ export async function register(context: vscode.ExtensionContext, client: BaseLan
}

const root = vscode.workspace.getConfiguration('volar').get<path.PosixPath>('preview.root')!;
const relativePath = shared.normalizeFileName(path.relative(path.resolve(path.dirname(configFile), root), fileName));
const relativePath = path.relative(path.resolve(path.dirname(configFile), root), fileName).replace(/\\/g, '/');
let url = `http://localhost:${port}/__preview${relativePath}#`;

if (lastPreviewDocument.isDirty) {
Expand Down Expand Up @@ -279,17 +279,20 @@ export async function register(context: vscode.ExtensionContext, client: BaseLan

updatePreviewIconStatus();

async function getSfc(document: vscode.TextDocument) {
let cache = sfcs.get(document);
function getTemplateOffset(document: vscode.TextDocument) {
let cache = templateOffsets.get(document);
if (!cache || cache.version !== document.version) {
const parsed = await client.sendRequest(ParseSFCRequest.type, document.getText());
cache = {
version: document.version,
sfc: parsed,
};
sfcs.set(document, cache);
templateOffsets.delete(document);
const htmlDocument = htmlLs.parseHTMLDocument(html.TextDocument.create(document.uri.toString(), document.languageId, document.version, document.getText()));
const template = htmlDocument.roots.find(node => node.tag === 'template');
if (template?.startTagEnd !== undefined) {
templateOffsets.set(document, {
version: document.version,
offset: template.startTagEnd,
});
}
}
return cache.sfc;
return templateOffsets.get(document)?.offset ?? 0;
}

async function updatePreviewIconStatus() {
Expand All @@ -305,8 +308,7 @@ export async function register(context: vscode.ExtensionContext, client: BaseLan

async function updateSelectionHighlights(textEditor: vscode.TextEditor) {
if (connection && textEditor.document.languageId === 'vue' && highlightDomElements) {
const sfc = await getSfc(textEditor.document);
const offset = sfc.descriptor.template?.loc.start.offset ?? 0;
const offset = await getTemplateOffset(textEditor.document);
connection.highlight(
textEditor.document.fileName,
textEditor.selections.map(selection => ({
Expand Down Expand Up @@ -369,7 +371,7 @@ export async function register(context: vscode.ExtensionContext, client: BaseLan
}
else if (previewType === PreviewType.ExternalBrowser_Component) {
const root = vscode.workspace.getConfiguration('volar').get<path.PosixPath>('preview.root')!;
const relativePath = shared.normalizeFileName(path.relative(path.resolve(path.dirname(configFile), root), fileName));
const relativePath = path.relative(path.resolve(path.dirname(configFile), root), fileName).replace(/\\/g, '/');
loadingPanel.webview.html = getWebviewContent(`http://localhost:${port}/__preview${relativePath}`, undefined, 'openExternal');
}
else if (previewType === PreviewType.Webview) {
Expand Down Expand Up @@ -419,8 +421,7 @@ export async function register(context: vscode.ExtensionContext, client: BaseLan
if (cancelToken.isCancelled)
return;

const sfc = await getSfc(doc);
const offset = sfc.descriptor.template?.loc.start.offset ?? 0;
const offset = await getTemplateOffset(doc);
const start = doc.positionAt(range[0] + offset);
const end = doc.positionAt(range[1] + offset);
await vscode.window.showTextDocument(doc, vscode.ViewColumn.One);
Expand All @@ -441,10 +442,10 @@ export async function register(context: vscode.ExtensionContext, client: BaseLan
let script = await vscode.workspace.getConfiguration('volar').get<string>('preview.script.' + (type === 'nuxt' ? 'nuxi' : 'vite')) ?? '';

if (script.indexOf('{VITE_BIN}') >= 0) {
script = script.replace('{VITE_BIN}', JSON.stringify(require.resolve('./dist/preview-bin/vite', { paths: [context.extensionPath] })));
script = script.replace('{VITE_BIN}', JSON.stringify(require.resolve('./dist/bin/vite', { paths: [context.extensionPath] })));
}
if (script.indexOf('{NUXI_BIN}') >= 0) {
script = script.replace('{NUXI_BIN}', JSON.stringify(require.resolve('./dist/preview-bin/nuxi', { paths: [context.extensionPath] })));
script = script.replace('{NUXI_BIN}', JSON.stringify(require.resolve('./dist/bin/nuxi', { paths: [context.extensionPath] })));
}
if (script.indexOf('{PORT}') >= 0) {
script = script.replace('{PORT}', port.toString());
Expand Down
File renamed without changes.
File renamed without changes.
16 changes: 16 additions & 0 deletions packages/vscode-vue-preview/tsconfig.build.json
@@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"noEmit": false,
"outDir": "out",
"rootDir": "src",
},
"include": [
"src",
],
"references": [
{
"path": "../preview/tsconfig.build.json"
}
]
}

0 comments on commit 719d31a

Please sign in to comment.