From 572affd5506ea50427abeff384a62692c39c3265 Mon Sep 17 00:00:00 2001 From: Pascal Maximilian Bremer <8161919+Bomberus@users.noreply.github.com> Date: Sat, 27 Dec 2025 23:38:10 +0100 Subject: [PATCH 1/6] support html.customData --- .../lib/plugins/vue-template.ts | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 2999d65665..177944baec 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -31,6 +31,7 @@ import { loadModelModifiersData, loadTemplateData } from '../data'; import { format } from '../htmlFormatter'; import { AttrNameCasing, getAttrNameCasing, getTagNameCasing, TagNameCasing } from '../nameCasing'; import { resolveEmbeddedCode } from '../utils'; +import { readFile } from 'fs/promises'; const EVENT_PROP_REGEX = /^on[A-Z]/; @@ -55,9 +56,30 @@ interface TagInfo { meta: ComponentMeta | undefined | null; } +let htmlCustomData: html.ITagData[] | undefined; let builtInData: html.HTMLDataV1 | undefined; let modelData: html.HTMLDataV1 | undefined; +async function loadHtmlCustomData(context: LanguageServiceContext): Promise { + if (!htmlCustomData) { + htmlCustomData = []; + const sources: string[] = await context.env.getConfiguration?.('html.customData') ?? [] + for (const source of sources) { + try { + const data = JSON.parse(await readFile(source, 'utf-8')); + if (data && data.tags) { + htmlCustomData.push(...data.tags ?? []); + } + } catch(e) { + continue + } + } + if (htmlCustomData.length == 0) { + htmlCustomData = undefined; + } + } +} + export function create( ts: typeof import('typescript'), languageId: 'html' | 'jade', @@ -210,7 +232,7 @@ export function create( builtInData ??= loadTemplateData(context.env.locale ?? 'en'); modelData ??= loadModelModifiersData(context.env.locale ?? 'en'); - + loadHtmlCustomData(context) // https://vuejs.org/api/built-in-directives.html#v-on const vOnModifiers = extractDirectiveModifiers(builtInData.globalAttributes?.find(x => x.name === 'v-on')); // https://vuejs.org/api/built-in-directives.html#v-bind @@ -643,8 +665,17 @@ export function create( const { components, elements } = getComponentsAndElements(); const codegen = tsCodegen.get(root.sfc); const names = new Set(); - const tags: html.ITagData[] = []; + const tags: html.ITagData[] = []; + if (htmlCustomData) { + for (const tag of htmlCustomData ?? []) { + tags.push({ + ...tag, + name: tagNameCasing === TagNameCasing.Kebab ? hyphenateTag(tag.name) : tag.name, + }); + } + } + for (const tag of builtInData?.tags ?? []) { tags.push({ ...tag, From d64d97516e42cf9556c308dcb935bdb8f048c1c8 Mon Sep 17 00:00:00 2001 From: Pascal Maximilian Bremer <8161919+Bomberus@users.noreply.github.com> Date: Sat, 27 Dec 2025 23:40:35 +0100 Subject: [PATCH 2/6] remove whitespace --- packages/language-service/lib/plugins/vue-template.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 177944baec..4f9388b179 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -665,7 +665,7 @@ export function create( const { components, elements } = getComponentsAndElements(); const codegen = tsCodegen.get(root.sfc); const names = new Set(); - const tags: html.ITagData[] = []; + const tags: html.ITagData[] = []; if (htmlCustomData) { for (const tag of htmlCustomData ?? []) { From dfc0f8a5a76a1d47e8077f29c0de4c7849a5d1f2 Mon Sep 17 00:00:00 2001 From: Pascal Maximilian Bremer <8161919+Bomberus@users.noreply.github.com> Date: Sat, 27 Dec 2025 23:55:48 +0100 Subject: [PATCH 3/6] fix lint --- packages/language-service/lib/plugins/vue-template.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 4f9388b179..ba62d9f87a 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -74,7 +74,7 @@ async function loadHtmlCustomData(context: LanguageServiceContext): Promise Date: Sun, 28 Dec 2025 20:06:54 +0100 Subject: [PATCH 4/6] staying closer to original implementation, using vscode native apis, cache the results so they are only fetched once --- .../lib/plugins/vue-template.ts | 60 ++++++++----------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index ba62d9f87a..474c327b49 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -25,13 +25,12 @@ import { convertCompletionInfo, } from 'volar-service-typescript/lib/utils/lspConverters.js'; import * as html from 'vscode-html-languageservice'; -import { URI } from 'vscode-uri'; +import { URI, Utils } from 'vscode-uri'; import type { ComponentMeta, PropertyMeta } from '../../../component-meta'; import { loadModelModifiersData, loadTemplateData } from '../data'; import { format } from '../htmlFormatter'; import { AttrNameCasing, getAttrNameCasing, getTagNameCasing, TagNameCasing } from '../nameCasing'; import { resolveEmbeddedCode } from '../utils'; -import { readFile } from 'fs/promises'; const EVENT_PROP_REGEX = /^on[A-Z]/; @@ -56,30 +55,10 @@ interface TagInfo { meta: ComponentMeta | undefined | null; } -let htmlCustomData: html.ITagData[] | undefined; +let htmlCustomData: html.IHTMLDataProvider[] | undefined = undefined; let builtInData: html.HTMLDataV1 | undefined; let modelData: html.HTMLDataV1 | undefined; -async function loadHtmlCustomData(context: LanguageServiceContext): Promise { - if (!htmlCustomData) { - htmlCustomData = []; - const sources: string[] = await context.env.getConfiguration?.('html.customData') ?? [] - for (const source of sources) { - try { - const data = JSON.parse(await readFile(source, 'utf-8')); - if (data && data.tags) { - htmlCustomData.push(...data.tags ?? []); - } - } catch(e) { - continue - } - } - if (htmlCustomData.length === 0) { - htmlCustomData = undefined; - } - } -} - export function create( ts: typeof import('typescript'), languageId: 'html' | 'jade', @@ -137,7 +116,7 @@ export function create( useDefaultDataProvider: false, getDocumentContext, getCustomData() { - return htmlData; + return htmlCustomData ? [...htmlCustomData, ...htmlData] : htmlData; }, onDidChangeCustomData, }) @@ -146,7 +125,7 @@ export function create( useDefaultDataProvider: false, getDocumentContext, getCustomData() { - return htmlData; + return htmlCustomData ? [...htmlCustomData, ...htmlData] : htmlData; }, onDidChangeCustomData, }); @@ -232,7 +211,6 @@ export function create( builtInData ??= loadTemplateData(context.env.locale ?? 'en'); modelData ??= loadModelModifiersData(context.env.locale ?? 'en'); - loadHtmlCustomData(context) // https://vuejs.org/api/built-in-directives.html#v-on const vOnModifiers = extractDirectiveModifiers(builtInData.globalAttributes?.find(x => x.name === 'v-on')); // https://vuejs.org/api/built-in-directives.html#v-bind @@ -637,6 +615,26 @@ export function create( return { result, ...lastSync }; } + async function loadHtmlCustomData(): Promise { + if (htmlCustomData) { return htmlCustomData } + const newData: html.IHTMLDataProvider[] = []; + const customData: string[] = await context.env.getConfiguration?.('html.customData') ?? [] + const workspaceFolder = context.env.workspaceFolders?.[0] ?? undefined + for (const customDataPath of customData) { + try { + const uri = Utils.resolvePath(URI.parse(workspaceFolder?.toString() ?? "."), customDataPath); + const json = await context.env.fs?.readFile?.(uri, "utf-8"); + if (json) { + const data = JSON.parse(json) as html.HTMLDataV1 + newData.push(html.newHTMLDataProvider(customDataPath, data)); + } + } catch(e) { + continue + } + } + return newData + } + async function provideHtmlData( sourceDocumentUri: URI, root: VueVirtualCode, @@ -656,6 +654,7 @@ export function create( const tasks: Promise[] = []; const tagDataMap = new Map(); + htmlCustomData = await loadHtmlCustomData() updateExtraCustomData([ { @@ -666,15 +665,6 @@ export function create( const codegen = tsCodegen.get(root.sfc); const names = new Set(); const tags: html.ITagData[] = []; - - if (htmlCustomData) { - for (const tag of htmlCustomData ?? []) { - tags.push({ - ...tag, - name: tagNameCasing === TagNameCasing.Kebab ? hyphenateTag(tag.name) : tag.name, - }); - } - } for (const tag of builtInData?.tags ?? []) { tags.push({ From 7feb256fe32671a16094db6088f5a83d26b0235a Mon Sep 17 00:00:00 2001 From: Pascal Maximilian Bremer <8161919+Bomberus@users.noreply.github.com> Date: Sun, 28 Dec 2025 20:08:04 +0100 Subject: [PATCH 5/6] remove unnecessary whitespace --- packages/language-service/lib/plugins/vue-template.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 474c327b49..a2d1d85d41 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -665,7 +665,7 @@ export function create( const codegen = tsCodegen.get(root.sfc); const names = new Set(); const tags: html.ITagData[] = []; - + for (const tag of builtInData?.tags ?? []) { tags.push({ ...tag, From 6d90562351add05db2a186dbfd307452476ae47c Mon Sep 17 00:00:00 2001 From: SerKo Date: Tue, 13 Jan 2026 18:42:05 +0800 Subject: [PATCH 6/6] fix: lint --- .../lib/plugins/vue-template.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 84835aba5f..7a6b8eb5f7 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -618,23 +618,26 @@ export function create( } async function loadHtmlCustomData(): Promise { - if (htmlCustomData) { return htmlCustomData } + if (htmlCustomData) { + return htmlCustomData; + } const newData: html.IHTMLDataProvider[] = []; - const customData: string[] = await context.env.getConfiguration?.('html.customData') ?? [] - const workspaceFolder = context.env.workspaceFolders?.[0] ?? undefined + const customData: string[] = await context.env.getConfiguration?.('html.customData') ?? []; + const workspaceFolder = context.env.workspaceFolders?.[0] ?? undefined; for (const customDataPath of customData) { try { - const uri = Utils.resolvePath(URI.parse(workspaceFolder?.toString() ?? "."), customDataPath); - const json = await context.env.fs?.readFile?.(uri, "utf-8"); + const uri = Utils.resolvePath(URI.parse(workspaceFolder?.toString() ?? '.'), customDataPath); + const json = await context.env.fs?.readFile?.(uri, 'utf-8'); if (json) { - const data = JSON.parse(json) as html.HTMLDataV1 + const data = JSON.parse(json) as html.HTMLDataV1; newData.push(html.newHTMLDataProvider(customDataPath, data)); } - } catch(e) { - continue + } + catch (e) { + continue; } } - return newData + return newData; } async function provideHtmlData( @@ -656,7 +659,7 @@ export function create( const tasks: Promise[] = []; const tagDataMap = new Map(); - htmlCustomData = await loadHtmlCustomData() + htmlCustomData = await loadHtmlCustomData(); updateExtraCustomData([ {