fix(vue): highlight SCSS in <style lang="scss"> blocks (#2219) (#2221)

The Vue editor mode (`@codemirror/lang-vue` over the stock HTML base)
parses every `<style>` block as plain CSS, so SCSS — nested rules,
$-variables, and so on — rendered uncoloured or mis-highlighted.

Register a Vue mode of our own (overriding the language-data entry) that
gives the HTML base a nested Sass grammar for `<style>` whose `lang`
is `scss` or `sass`; blocks without a dialect keep CSS.
Цей коміт міститься в:
arookieofc authored and GitHub committed 2026-11-09 09:23:17 +08:00
1 джерело 3dac5b8bdc
коміт 95843ea12d
5 files changed
+67

No files matched your search

+2
Переглянути файл
@@ -46,6 +46,8 @@
"@codemirror/lang-html": "^6.4.12",
"@codemirror/lang-java": "^6.0.2",
"@codemirror/lang-javascript": "^6.2.5",
"@codemirror/lang-sass": "^6.0.2",
"@codemirror/lang-vue": "^0.1.3",
"@codemirror/language": "^6.13.1",
"@codemirror/language-data": "^6.5.2",
"@codemirror/legacy-modes": "^6.5.5",
Створено
+6
Переглянути файл
@@ -23,6 +23,12 @@ importers:
'@codemirror/lang-javascript':
specifier: ^6.2.5
version: 6.2.5
'@codemirror/lang-sass':
specifier: ^6.0.2
version: 6.0.2
'@codemirror/lang-vue':
specifier: ^0.1.3
version: 0.1.3
'@codemirror/language':
specifier: ^6.13.1
version: 6.13.1
+6
Переглянути файл
@@ -131,6 +131,12 @@ const OWN_MODES = [
extensions: ["astro"],
load: () => import("./astro").then((m) => m.astro()),
}),
// Overrides the language-data Vue mode so `<style lang="scss">` highlights.
LanguageDescription.of({
name: "Vue",
extensions: ["vue"],
load: () => import("./vue").then((m) => m.vueLanguage()),
}),
LanguageDescription.of({
name: "YAML",
alias: ["yml"],
+26
Переглянути файл
@@ -0,0 +1,26 @@
import { describe, expect, it } from "vitest";
import { findLanguageDescription, loadSupport } from "./languages";
describe("vue language mode", () => {
it('highlights SCSS inside <style lang="scss">', async () => {
const desc = findLanguageDescription("Vue", "/p/a.vue")!;
expect(desc?.name).toBe("Vue");
const support = await loadSupport(desc);
const src = `<template><div/></template>\n` + `<style lang="scss">\n.a { .b { color: red; } $c: 1; }\n</style>`;
const tree = support!.language.parser.parse(src).toString();
// SCSS-only syntax that plain CSS can't parse: a nested rule and a $-variable.
expect(tree).toContain("SassVariableName");
// The nested .b ruleset only parses under the Sass grammar.
expect(tree).toMatch(/RuleSet\(ClassSelector.*RuleSet\(ClassSelector/s);
});
it("still parses a plain <style> block as CSS", async () => {
const support = await loadSupport(findLanguageDescription("Vue", "/p/a.vue")!);
const tree = support!.language.parser
.parse(`<template><div/></template><style>.a { color: red; }</style>`)
.toString();
expect(tree).toContain("StyleSheet");
expect(tree).toContain("Declaration");
expect(tree).not.toContain("SassVariableName");
});
});
+27
Переглянути файл
@@ -0,0 +1,27 @@
/**
* Vue single-file components: `@codemirror/lang-vue`'s parser over an HTML
* base that also highlights `<style lang="scss|sass">` with the Sass grammar —
* stock `vue()` parses every `<style>` as plain CSS, which loses the SCSS
* colours.
*/
import { LanguageSupport } from "@codemirror/language";
import { html } from "@codemirror/lang-html";
import { sassLanguage } from "@codemirror/lang-sass";
import { vue } from "@codemirror/lang-vue";
export function vueLanguage(): LanguageSupport {
return vue({
base: html({
selfClosingTags: true,
// `<style>` without `lang` (or with `lang="css"`) keeps HTML's own CSS
// nesting; only an explicit SCSS/Sass dialect re-parses it.
nestedLanguages: [
{
tag: "style",
attrs: (a) => a.lang === "scss" || a.lang === "sass",
parser: sassLanguage.parser,
},
],
}),
});
}