From ece9f720fd7ae7aecdb61045480d62b499110933 Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 21 Mar 2024 16:35:55 +0800 Subject: [PATCH] chore: port tests over This port isn't the best. Vite builds are ran multiple times with different modules configuration. The ideal abstraction is to have unit tests instead and Vite should "trust" that it works, but this will do for now. I also skipped lightningcss tests for now. Check css-modules-lightningcss.spec.ts for notes. --- .../css-modules/__tests__/css-modules.spec.ts | 458 ++++++++++++++++++ .../css-modules-lightningcss.spec.ts | 306 ++++++++++++ .../css-modules/css-modules-value/index.js | 2 + .../css-modules-value/style.module.css | 12 + .../css-modules/css-modules-value/utils1.css | 6 + .../css-modules/css-modules-value/utils2.css | 5 + .../css-modules/empty-css-module/index.js | 2 + .../empty-css-module/style.module.css | 0 .../global-module/global.module.css | 6 + playground/css-modules/global-module/index.js | 2 + playground/css-modules/index.html | 67 +++ playground/css-modules/inline-query/index.js | 2 + .../css-modules/inline-query/style.module.css | 4 + playground/css-modules/inline-query/utils.css | 8 + .../index.js | 2 + .../style1.module.css | 3 + .../style2.module.css | 3 + .../vars.module.css | 3 + .../lightningcss-features/index.js | 2 + .../lightningcss-features/style.module.css | 5 + playground/css-modules/lightningcss.html | 22 + .../css-modules/missing-class-export/index.js | 2 + .../missing-class-export/style.module.css | 4 + .../missing-class-export/utils.css | 0 .../css-modules/module-namespace/index.js | 1 + .../module-namespace/style.module.css | 3 + .../css-modules/multi-css-modules/index.js | 2 + .../multi-css-modules/style1.module.css | 9 + .../multi-css-modules/style2.module.css | 4 + .../css-modules/multi-css-modules/utils1.css | 8 + .../css-modules/multi-css-modules/utils2.css | 4 + playground/css-modules/package.json | 20 + playground/css-modules/postcss.config.js | 19 + .../css-modules/reserved-keywords/index.js | 1 + .../reserved-keywords/style.module.css | 8 + .../css-modules/reserved-keywords/utils.css | 8 + .../scss-modules-mixed/css.module.css | 3 + .../css-modules/scss-modules-mixed/index.js | 2 + .../scss-modules-mixed/scss.module.scss | 7 + playground/css-modules/scss-modules/index.js | 2 + .../scss-modules/style.module.scss | 7 + .../css-modules/vite.config-lightningcss.js | 27 ++ playground/css-modules/vite.config.js | 16 + playground/css-modules/vue/Comp.vue | 10 + playground/css-modules/vue/index.js | 1 + playground/css-modules/vue/utils.css | 8 + playground/test-utils.ts | 24 +- pnpm-lock.yaml | 147 ++++-- 48 files changed, 1221 insertions(+), 46 deletions(-) create mode 100644 playground/css-modules/__tests__/css-modules.spec.ts create mode 100644 playground/css-modules/__tests__/lightningcss/css-modules-lightningcss.spec.ts create mode 100644 playground/css-modules/css-modules-value/index.js create mode 100644 playground/css-modules/css-modules-value/style.module.css create mode 100644 playground/css-modules/css-modules-value/utils1.css create mode 100644 playground/css-modules/css-modules-value/utils2.css create mode 100644 playground/css-modules/empty-css-module/index.js create mode 100644 playground/css-modules/empty-css-module/style.module.css create mode 100644 playground/css-modules/global-module/global.module.css create mode 100644 playground/css-modules/global-module/index.js create mode 100644 playground/css-modules/index.html create mode 100644 playground/css-modules/inline-query/index.js create mode 100644 playground/css-modules/inline-query/style.module.css create mode 100644 playground/css-modules/inline-query/utils.css create mode 100644 playground/css-modules/lightningcss-custom-properties-from/index.js create mode 100644 playground/css-modules/lightningcss-custom-properties-from/style1.module.css create mode 100644 playground/css-modules/lightningcss-custom-properties-from/style2.module.css create mode 100644 playground/css-modules/lightningcss-custom-properties-from/vars.module.css create mode 100644 playground/css-modules/lightningcss-features/index.js create mode 100644 playground/css-modules/lightningcss-features/style.module.css create mode 100644 playground/css-modules/lightningcss.html create mode 100644 playground/css-modules/missing-class-export/index.js create mode 100644 playground/css-modules/missing-class-export/style.module.css create mode 100644 playground/css-modules/missing-class-export/utils.css create mode 100644 playground/css-modules/module-namespace/index.js create mode 100644 playground/css-modules/module-namespace/style.module.css create mode 100644 playground/css-modules/multi-css-modules/index.js create mode 100644 playground/css-modules/multi-css-modules/style1.module.css create mode 100644 playground/css-modules/multi-css-modules/style2.module.css create mode 100644 playground/css-modules/multi-css-modules/utils1.css create mode 100644 playground/css-modules/multi-css-modules/utils2.css create mode 100644 playground/css-modules/package.json create mode 100644 playground/css-modules/postcss.config.js create mode 100644 playground/css-modules/reserved-keywords/index.js create mode 100644 playground/css-modules/reserved-keywords/style.module.css create mode 100644 playground/css-modules/reserved-keywords/utils.css create mode 100644 playground/css-modules/scss-modules-mixed/css.module.css create mode 100644 playground/css-modules/scss-modules-mixed/index.js create mode 100644 playground/css-modules/scss-modules-mixed/scss.module.scss create mode 100644 playground/css-modules/scss-modules/index.js create mode 100644 playground/css-modules/scss-modules/style.module.scss create mode 100644 playground/css-modules/vite.config-lightningcss.js create mode 100644 playground/css-modules/vite.config.js create mode 100644 playground/css-modules/vue/Comp.vue create mode 100644 playground/css-modules/vue/index.js create mode 100644 playground/css-modules/vue/utils.css diff --git a/playground/css-modules/__tests__/css-modules.spec.ts b/playground/css-modules/__tests__/css-modules.spec.ts new file mode 100644 index 000000000..7b0f5f12b --- /dev/null +++ b/playground/css-modules/__tests__/css-modules.spec.ts @@ -0,0 +1,458 @@ +import path from 'node:path' +import { describe, expect, test } from 'vitest' +import { type InlineConfig, type Rollup, build } from 'vite' +import { base64Module, isBuild, isServe, page } from '~utils' + +async function getStyleMatchingId(id: string) { + const styleTags = page.locator(`style[data-vite-dev-id*=${id}]`) + let code = '' + for (const style of await styleTags.all()) { + code += await style.textContent() + } + return code +} + +async function viteBuild(root: string, inlineConfig?: InlineConfig) { + const built = await build({ + root: path.resolve(__dirname, root), + configFile: false, + envFile: false, + logLevel: 'error', + ...inlineConfig, + build: { + // Prevents CSS minification from handling the de-duplication of classes + minify: false, + write: false, + lib: { + entry: 'index.js', + formats: ['es'], + }, + ...inlineConfig?.build, + }, + }) + + if (!Array.isArray(built)) { + throw new TypeError('Build result is not an array') + } + + const { output } = built[0]! + const css = output.find( + (file) => file.type === 'asset' && file.fileName.endsWith('.css'), + ) as Rollup.OutputAsset | undefined + + return { + js: output[0].code, + css: css?.source.toString(), + } +} + +test.runIf(isBuild)('Multi CSS modules', async () => { + const { js, css } = await viteBuild('../multi-css-modules', { + build: { + target: 'es2022', + }, + css: { + modules: { + generateScopedName: 'asdf_[local]', + }, + }, + }) + + const exported = await import(base64Module(js)) + expect(exported).toMatchObject({ + style1: { + className1: expect.stringMatching(/^asdf_className1\s+asdf_util-class$/), + }, + style2: { + 'class-name2': expect.stringMatching( + /^asdf_class-name2\s+asdf_util-class$/, + ), + }, + }) + + expect(css).toMatch('--file: "style1.module.css"') + expect(css).toMatch('--file: "style2.module.css"') + + // Ensure that PostCSS is applied to the composed files + expect(css).toMatch('--file: "utils1.css?.module.css"') + expect(css).toMatch('--file: "utils2.css?.module.css"') + + // Util is not duplicated + const utilClass = Array.from(css!.matchAll(/foo/g)) + expect(utilClass.length).toBe(1) +}) + +describe.runIf(isBuild)('localsConvention', () => { + test('camelCase', async () => { + const { js } = await viteBuild('../multi-css-modules', { + build: { + target: 'es2022', + }, + css: { + modules: { + localsConvention: 'camelCase', + }, + }, + }) + + const exported = await import(base64Module(js)) + expect(exported).toMatchObject({ + style1: { + 'class-name2': expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + className1: expect.stringMatching(/_className1_\w+ _util-class_\w+/), + className2: expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + default: { + className1: expect.stringMatching(/_className1_\w+ _util-class_\w+/), + 'class-name2': expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + className2: expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + }, + }, + style2: { + 'class-name2': expect.stringMatching( + /_class-name2_\w+ _util-class_\w+/, + ), + className2: expect.stringMatching(/_class-name2_\w+ _util-class_\w+/), + default: { + 'class-name2': expect.stringMatching( + /_class-name2_\w+ _util-class_\w+/, + ), + className2: expect.stringMatching(/_class-name2_\w+ _util-class_\w+/), + }, + }, + }) + }) + + test('camelCaseOnly', async () => { + const { js } = await viteBuild('../multi-css-modules', { + css: { + modules: { + localsConvention: 'camelCaseOnly', + }, + }, + }) + + const exported = await import(base64Module(js)) + expect(exported).toMatchObject({ + style1: { + className1: expect.stringMatching(/_className1_\w+ _util-class_\w+/), + className2: expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + default: { + className1: expect.stringMatching(/_className1_\w+ _util-class_\w+/), + className2: expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + }, + }, + style2: { + className2: expect.stringMatching(/_class-name2_\w+ _util-class_\w+/), + default: { + className2: expect.stringMatching(/_class-name2_\w+ _util-class_\w+/), + }, + }, + }) + }) + + test('dashes', async () => { + const { js } = await viteBuild('../multi-css-modules', { + build: { + target: 'es2022', + }, + css: { + modules: { + localsConvention: 'dashes', + }, + }, + }) + + const exported = await import(base64Module(js)) + expect(exported).toMatchObject({ + style1: { + 'class-name2': expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + className1: expect.stringMatching(/_className1_\w+ _util-class_\w+/), + className2: expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + default: { + className1: expect.stringMatching(/_className1_\w+ _util-class_\w+/), + 'class-name2': expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + className2: expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + }, + }, + style2: { + 'class-name2': expect.stringMatching( + /_class-name2_\w+ _util-class_\w+/, + ), + className2: expect.stringMatching(/_class-name2_\w+ _util-class_\w+/), + default: { + 'class-name2': expect.stringMatching( + /_class-name2_\w+ _util-class_\w+/, + ), + className2: expect.stringMatching(/_class-name2_\w+ _util-class_\w+/), + }, + }, + }) + }) + + test('dashesOnly', async () => { + const { js } = await viteBuild('../multi-css-modules', { + css: { + modules: { + localsConvention: 'dashesOnly', + }, + }, + }) + + const exported = await import(base64Module(js)) + expect(exported).toMatchObject({ + style1: { + className1: expect.stringMatching(/_className1_\w+ _util-class_\w+/), + className2: expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + default: { + className1: expect.stringMatching(/_className1_\w+ _util-class_\w+/), + className2: expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + }, + }, + style2: { + className2: expect.stringMatching(/_class-name2_\w+ _util-class_\w+/), + default: { + className2: expect.stringMatching(/_class-name2_\w+ _util-class_\w+/), + }, + }, + }) + }) + + test('function', async () => { + const { js } = await viteBuild('../multi-css-modules', { + build: { + target: 'es2022', + }, + css: { + modules: { + localsConvention: (originalClassname) => `${originalClassname}123`, + }, + }, + }) + + const exported = await import(base64Module(js)) + expect(exported).toMatchObject({ + style1: { + className1123: expect.stringMatching(/_className1_\w+ _util-class_\w+/), + 'class-name2123': expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + default: { + className1123: expect.stringMatching( + /_className1_\w+ _util-class_\w+/, + ), + 'class-name2123': expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + }, + }, + style2: { + 'class-name2123': expect.stringMatching( + /_class-name2_\w+ _util-class_\w+/, + ), + default: { + 'class-name2123': expect.stringMatching( + /_class-name2_\w+ _util-class_\w+/, + ), + }, + }, + }) + }) +}) + +test.runIf(isBuild)('globalModulePaths', async () => { + const { js, css } = await viteBuild('../global-module', { + css: { + modules: { + globalModulePaths: [/global\.module\.css/], + }, + }, + }) + + const exported = await import(base64Module(js)) + expect(exported).toMatchObject({ + default: { + title: expect.stringMatching(/^_title_\w{5}/), + }, + title: expect.stringMatching(/^_title_\w{5}/), + }) + + expect(css).toMatch('.page {') +}) + +test.runIf(isBuild)('inline', async () => { + const { js } = await viteBuild('../inline-query') + const exported = await import(base64Module(js)) + + expect(typeof exported.default).toBe('string') + expect(exported.default).toMatch('--file: "style.module.css?inline"') +}) + +test.runIf(isBuild)('getJSON', async () => { + type JSON = { + inputFile: string + exports: Record + outputFile: string + } + const jsons: JSON[] = [] + + await viteBuild('../multi-css-modules', { + css: { + modules: { + localsConvention: 'camelCaseOnly', + getJSON: (inputFile, exports, outputFile) => { + jsons.push({ + inputFile, + exports, + outputFile, + }) + }, + }, + }, + }) + + // This plugin treats each CSS Module as a JS module so it emits on each module + // rather than the final "bundle" which postcss-module emits on + expect(jsons).toHaveLength(4) + jsons.sort((a, b) => a.inputFile.localeCompare(b.inputFile)) + + const [style1, style2, utils1, utils2] = jsons + expect(style1).toMatchObject({ + inputFile: expect.stringMatching(/style1\.module\.css$/), + exports: { + className1: expect.stringMatching(/_className1_\w+ _util-class_\w+/), + className2: expect.stringMatching( + /_class-name2_\w+ _util-class_\w+ _util-class_\w+/, + ), + }, + outputFile: expect.stringMatching(/style1\.module\.css$/), + }) + + expect(style2).toMatchObject({ + inputFile: expect.stringMatching(/style2\.module\.css$/), + exports: { + className2: expect.stringMatching(/_class-name2_\w+ _util-class_\w+/), + }, + outputFile: expect.stringMatching(/style2\.module\.css$/), + }) + + expect(utils1).toMatchObject({ + inputFile: expect.stringMatching(/utils1\.css\?\.module\.css$/), + exports: { + unusedClass: expect.stringMatching(/_unused-class_\w+/), + utilClass: expect.stringMatching(/_util-class_\w+/), + }, + outputFile: expect.stringMatching(/utils1\.css\?\.module\.css$/), + }) + + expect(utils2).toMatchObject({ + inputFile: expect.stringMatching(/utils2\.css\?\.module\.css$/), + exports: { + utilClass: expect.stringMatching(/_util-class_\w+/), + }, + outputFile: expect.stringMatching(/utils2\.css\?\.module\.css$/), + }) +}) + +test.runIf(isBuild)('Empty CSS Module', async () => { + const { js, css } = await viteBuild('../empty-css-module', { + css: { + postcss: {}, + }, + }) + + const exported = await import(base64Module(js)) + expect(exported).toMatchObject({ + default: {}, + }) + expect(css).toBeUndefined() +}) + +describe('@value', () => { + test.runIf(isBuild)('build', async () => { + const { js, css } = await viteBuild('../css-modules-value', { + build: { + target: 'es2022', + }, + }) + + const exported = await import(base64Module(js)) + expect(exported).toMatchObject({ + default: { + 'class-name1': expect.stringMatching( + /^_class-name1_\w+ _util-class_\w+ _util-class_\w+$/, + ), + 'class-name2': expect.stringMatching(/^_class-name2_\w+$/), + }, + 'class-name1': expect.stringMatching( + /_class-name1_\w+ _util-class_\w+ _util-class_\w+/, + ), + 'class-name2': expect.stringMatching(/^_class-name2_\w+$/), + }) + + expect(css).toMatch('color: #fff') + expect(css).toMatch('border: #fff') + expect(css).toMatch('color: #000') + expect(css).toMatch('border: #000') + expect(css).toMatch('border: 1px solid black') + + // Ensure that PostCSS is applied to the composed files + expect(css).toMatch('--file: "style.module.css"') + expect(css).toMatch('--file: "utils1.css?.module.css"') + expect(css).toMatch('--file: "utils2.css?.module.css"') + }) + + test.runIf(isServe)('dev server', async () => { + const code = await getStyleMatchingId('css-modules-value') + + expect(code).toMatch('color: #fff') + expect(code).toMatch('border: #fff') + expect(code).toMatch('color: #000') + expect(code).toMatch('border: #000') + expect(code).toMatch('border: 1px solid black') + + // Ensure that PostCSS is applied to the composed files + expect(code).toMatch('--file: "style.module.css"') + expect(code).toMatch('--file: "utils1.css?.module.css"') + expect(code).toMatch('--file: "utils2.css?.module.css"') + }) +}) + +describe.runIf(isBuild)('error handling', () => { + test('missing class export', async () => { + await expect(() => + viteBuild('../missing-class-export', { + logLevel: 'silent', + }), + ).rejects.toThrow( + '[vite:css-modules] Cannot resolve "non-existent" from "./utils.css"', + ) + }) + + test('exporting a non-safe class name via esm doesnt throw', async () => { + await viteBuild('../module-namespace') + }) +}) diff --git a/playground/css-modules/__tests__/lightningcss/css-modules-lightningcss.spec.ts b/playground/css-modules/__tests__/lightningcss/css-modules-lightningcss.spec.ts new file mode 100644 index 000000000..4e4f53189 --- /dev/null +++ b/playground/css-modules/__tests__/lightningcss/css-modules-lightningcss.spec.ts @@ -0,0 +1,306 @@ +import path from 'node:path' +import { describe, expect, test } from 'vitest' +import { type InlineConfig, type Rollup, build } from 'vite' +import { Features } from 'lightningcss' +import { + base64Module, + getCssSourceMaps, + isBuild, + isServe, + page, + viteTestUrl, +} from '~utils' + +/* +Skipped most tests for now because: +1. For some reason, dev and build exports classes as `"l9uHSq_button": "l9uHSq_l9uHSq_button"` (double hash) +2. I don't undertstand how sourcemaps is being tested +3. The `vite-css-modules` implementation sidesteps the initial parsing to postcss, which we don't want when + using lightningcss. postcss should be completely not used. +*/ + +async function getStyleMatchingId(id: string) { + const styleTags = page.locator(`style[data-vite-dev-id*=${id}]`) + let code = '' + for (const style of await styleTags.all()) { + code += await style.textContent() + } + return code +} + +async function viteBuild(root: string, inlineConfig?: InlineConfig) { + const built = await build({ + root: path.resolve(__dirname, root), + configFile: false, + envFile: false, + logLevel: 'error', + ...inlineConfig, + build: { + // Prevents CSS minification from handling the de-duplication of classes + minify: false, + write: false, + lib: { + entry: 'index.js', + formats: ['es'], + }, + ...inlineConfig?.build, + }, + css: { + postcss: {}, + ...inlineConfig?.css, + }, + }) + + if (!Array.isArray(built)) { + throw new TypeError('Build result is not an array') + } + + const { output } = built[0]! + const css = output.find( + (file) => file.type === 'asset' && file.fileName.endsWith('.css'), + ) as Rollup.OutputAsset | undefined + + return { + js: output[0].code, + css: css?.source.toString(), + } +} + +test.runIf(isBuild).skip('Configured', async () => { + const { js, css } = await viteBuild('../../multi-css-modules', { + css: { + transformer: 'lightningcss', + }, + build: { + target: 'es2022', + }, + }) + + const exported = await import(base64Module(js)) + expect(exported).toMatchObject({ + style1: { + className1: expect.stringMatching( + /^[\w-]+_className1\s+[\w-]+_util-class$/, + ), + default: { + className1: expect.stringMatching( + /^[\w-]+_className1\s+[\w-]+_util-class$/, + ), + 'class-name2': expect.stringMatching( + /^[\w-]+_class-name2\s+[\w-]+_util-class\s+[\w-]+_util-class$/, + ), + }, + }, + style2: { + 'class-name2': expect.stringMatching( + /^[\w-]+_class-name2\s+[\w-]+_util-class$/, + ), + default: { + 'class-name2': expect.stringMatching( + /^[\w-]+_class-name2\s+[\w-]+_util-class$/, + ), + }, + }, + }) + + // Util is not duplicated + const utilClass = Array.from(css!.matchAll(/foo/g)) + expect(utilClass.length).toBe(1) +}) + +test.runIf(isBuild).skip('Empty CSS Module', async () => { + const { js, css } = await viteBuild('../../empty-css-module', { + css: { + transformer: 'lightningcss', + }, + }) + + const exported = await import(base64Module(js)) + expect(exported).toMatchObject({ + default: {}, + }) + expect(css).toBe('\n') +}) + +test('reserved keywords', async () => { + const { js } = await viteBuild('../../reserved-keywords', { + build: { + target: 'es2022', + }, + css: { + transformer: 'lightningcss', + }, + }) + const exported = await import(base64Module(js)) + expect(exported).toMatchObject({ + style: { + default: { + export: 'fk9XWG_export V_YH-W_with', + import: 'fk9XWG_import V_YH-W_if', + }, + export: 'fk9XWG_export V_YH-W_with', + import: 'fk9XWG_import V_YH-W_if', + }, + }) +}) + +describe.skip('Custom property dependencies', () => { + test.runIf(isBuild)('build', async () => { + const { js, css } = await viteBuild( + '../../lightningcss-custom-properties-from', + { + css: { + transformer: 'lightningcss', + lightningcss: { + cssModules: { + dashedIdents: true, + }, + }, + }, + }, + ) + + console.log(js) + + const exported = await import(base64Module(js)) + expect(exported).toMatchObject({ + style1: { + button: expect.stringMatching(/^[\w-]+_button$/), + }, + style2: { + input: expect.stringMatching(/^[\w-]+input$/), + }, + }) + + const variableNameMatches = Array.from(css!.matchAll(/(\S+): hotpink/g))! + expect(variableNameMatches.length).toBe(1) + + const variableName = variableNameMatches[0]![1] + expect(css).toMatch(`color: var(${variableName})`) + expect(css).toMatch(`background: var(${variableName})`) + }) + + test.runIf(isServe)('serve', async () => { + await page.goto(viteTestUrl + '/lightningcss.html') + const code = await getStyleMatchingId('lightningcss-custom-properties-from') + + const variableNameMatches = Array.from(code.matchAll(/(\S+): hotpink/g))! + expect(variableNameMatches.length).toBe(1) + + const variableName = variableNameMatches[0]![1] + expect(code).toMatch(`color: var(${variableName})`) + expect(code).toMatch(`background: var(${variableName})`) + }) +}) + +describe.skip('Other configs', () => { + test.runIf(isBuild)('build', async () => { + const { css } = await viteBuild('../../lightningcss-features', { + css: { + transformer: 'lightningcss', + lightningcss: { + include: Features.Nesting, + }, + }, + }) + + expect(css).toMatch(/\.[\w-]+_button\.[\w-]+_primary/) + }) + + test.runIf(isServe).skip('dev server', async () => { + await page.goto(viteTestUrl + '/lightningcss.html') + const code = await getStyleMatchingId('lightningcss-features') + + const cssSourcemaps = getCssSourceMaps(code) + expect(cssSourcemaps.length).toBe(0) + + expect(code).toMatch(/\.[\w-]+_button\.[\w-]+_primary/) + }) + + test.skip('devSourcemap', async () => { + const code = await getStyleMatchingId('lightningcss-custom-properties-from') + + const cssSourcemaps = getCssSourceMaps(code) + expect(cssSourcemaps.length).toBe(3) + // I'm skeptical these source maps are correct + // Seems lightningCSS is providing these source maps + expect(cssSourcemaps).toMatchObject([ + { + version: 3, + file: expect.stringMatching(/^style1\.module\.css$/), + mappings: 'AAAA', + names: [], + ignoreList: [], + sources: [expect.stringMatching(/^style1\.module\.css$/)], + sourcesContent: [ + '.button {\n\tbackground: var(--accent-color from "./vars.module.css");\n}', + ], + }, + { + version: 3, + file: expect.stringMatching(/^style2\.module\.css$/), + mappings: 'AAAA', + names: [], + ignoreList: [], + sources: [expect.stringMatching(/^style2\.module\.css$/)], + sourcesContent: [ + '.input {\n\tcolor: var(--accent-color from "./vars.module.css");\n}', + ], + }, + { + version: 3, + sourceRoot: null, + mappings: 'AAAA', + sources: [expect.stringMatching(/^vars\.module\.css$/)], + sourcesContent: [':root {\n\t--accent-color: hotpink;\n}'], + names: [], + }, + ]) + }) + + test.skip('devSourcemap with Vue.js', async () => { + const code = await getStyleMatchingId('vue') + const cssSourcemaps = getCssSourceMaps(code) + expect(cssSourcemaps.length).toBe(2) + expect(cssSourcemaps).toMatchObject([ + { + version: 3, + mappings: 'AAKA;;;;ACLA', + names: [], + sources: [expect.stringMatching(/\/comp\.vue$/), '\u0000'], + sourcesContent: [ + '\n' + + '\n' + + '', + null, + ], + file: expect.stringMatching(/\/comp\.vue$/), + }, + { + version: 3, + mappings: 'AAAA;;;;;AAKA;;;;ACLA', + names: [], + sources: [expect.stringMatching(/\/utils\.css$/), '\u0000'], + sourcesContent: [ + '.util-class {\n' + + "\t--name: 'foo';\n" + + '\tcolor: blue;\n' + + '}\n' + + '\n' + + '.unused-class {\n' + + '\tcolor: yellow;\n' + + '}', + null, + ], + file: expect.stringMatching(/\/utils\.css$/), + }, + ]) + }) +}) diff --git a/playground/css-modules/css-modules-value/index.js b/playground/css-modules/css-modules-value/index.js new file mode 100644 index 000000000..46f57acc7 --- /dev/null +++ b/playground/css-modules/css-modules-value/index.js @@ -0,0 +1,2 @@ +export * from './style.module.css' +export { default } from './style.module.css' diff --git a/playground/css-modules/css-modules-value/style.module.css b/playground/css-modules/css-modules-value/style.module.css new file mode 100644 index 000000000..ef6d96e45 --- /dev/null +++ b/playground/css-modules/css-modules-value/style.module.css @@ -0,0 +1,12 @@ +@value primary as p1, simple-border from './utils1.css'; +@value primary as p2 from './utils2.css'; + +.class-name1 { + color: p1; + border: simple-border; + composes: util-class from './utils1.css'; + composes: util-class from './utils2.css'; +} +.class-name2 { + color: p2; +} diff --git a/playground/css-modules/css-modules-value/utils1.css b/playground/css-modules/css-modules-value/utils1.css new file mode 100644 index 000000000..6d906955d --- /dev/null +++ b/playground/css-modules/css-modules-value/utils1.css @@ -0,0 +1,6 @@ +@value primary: #fff; +@value simple-border: 1px solid black; + +.util-class { + border: primary; +} diff --git a/playground/css-modules/css-modules-value/utils2.css b/playground/css-modules/css-modules-value/utils2.css new file mode 100644 index 000000000..97af2a554 --- /dev/null +++ b/playground/css-modules/css-modules-value/utils2.css @@ -0,0 +1,5 @@ +@value primary: #000; + +.util-class { + border: primary; +} diff --git a/playground/css-modules/empty-css-module/index.js b/playground/css-modules/empty-css-module/index.js new file mode 100644 index 000000000..46f57acc7 --- /dev/null +++ b/playground/css-modules/empty-css-module/index.js @@ -0,0 +1,2 @@ +export * from './style.module.css' +export { default } from './style.module.css' diff --git a/playground/css-modules/empty-css-module/style.module.css b/playground/css-modules/empty-css-module/style.module.css new file mode 100644 index 000000000..e69de29bb diff --git a/playground/css-modules/global-module/global.module.css b/playground/css-modules/global-module/global.module.css new file mode 100644 index 000000000..d2ee06d7a --- /dev/null +++ b/playground/css-modules/global-module/global.module.css @@ -0,0 +1,6 @@ +.page { + padding: 20px; +} +:local(.title) { + color: green; +} diff --git a/playground/css-modules/global-module/index.js b/playground/css-modules/global-module/index.js new file mode 100644 index 000000000..dcc5d2ad3 --- /dev/null +++ b/playground/css-modules/global-module/index.js @@ -0,0 +1,2 @@ +export * from './global.module.css' +export { default } from './global.module.css' diff --git a/playground/css-modules/index.html b/playground/css-modules/index.html new file mode 100644 index 000000000..4d1ebf249 --- /dev/null +++ b/playground/css-modules/index.html @@ -0,0 +1,67 @@ +

CSS Modules

+ +

css-modules-value

+

+
+

empty-css-module

+

+
+

global-module

+

+
+

inline-query

+

+
+

module-namespace

+

+
+

multi-css-modules

+

+
+

reserved-keywords

+

+
+

scss-modules

+

+
+

scss-modules-mixed

+

+
+

vue

+

+
+
diff --git a/playground/css-modules/inline-query/index.js b/playground/css-modules/inline-query/index.js
new file mode 100644
index 000000000..e2bafddfb
--- /dev/null
+++ b/playground/css-modules/inline-query/index.js
@@ -0,0 +1,2 @@
+export * from './style.module.css?inline'
+export { default } from './style.module.css?inline'
diff --git a/playground/css-modules/inline-query/style.module.css b/playground/css-modules/inline-query/style.module.css
new file mode 100644
index 000000000..3665babf2
--- /dev/null
+++ b/playground/css-modules/inline-query/style.module.css
@@ -0,0 +1,4 @@
+.class-name1 {
+  composes: util-class from './utils.css';
+  color: red;
+}
diff --git a/playground/css-modules/inline-query/utils.css b/playground/css-modules/inline-query/utils.css
new file mode 100644
index 000000000..088f77c8c
--- /dev/null
+++ b/playground/css-modules/inline-query/utils.css
@@ -0,0 +1,8 @@
+.util-class {
+  --name: 'foo';
+  color: blue;
+}
+
+.unused-class {
+  color: yellow;
+}
diff --git a/playground/css-modules/lightningcss-custom-properties-from/index.js b/playground/css-modules/lightningcss-custom-properties-from/index.js
new file mode 100644
index 000000000..b766717ca
--- /dev/null
+++ b/playground/css-modules/lightningcss-custom-properties-from/index.js
@@ -0,0 +1,2 @@
+export { default as style1 } from './style1.module.css'
+export { default as style2 } from './style2.module.css'
diff --git a/playground/css-modules/lightningcss-custom-properties-from/style1.module.css b/playground/css-modules/lightningcss-custom-properties-from/style1.module.css
new file mode 100644
index 000000000..d6d1d36e4
--- /dev/null
+++ b/playground/css-modules/lightningcss-custom-properties-from/style1.module.css
@@ -0,0 +1,3 @@
+.button {
+  background: var(--accent-color from './vars.module.css');
+}
diff --git a/playground/css-modules/lightningcss-custom-properties-from/style2.module.css b/playground/css-modules/lightningcss-custom-properties-from/style2.module.css
new file mode 100644
index 000000000..3adb970ad
--- /dev/null
+++ b/playground/css-modules/lightningcss-custom-properties-from/style2.module.css
@@ -0,0 +1,3 @@
+.input {
+  color: var(--accent-color from './vars.module.css');
+}
diff --git a/playground/css-modules/lightningcss-custom-properties-from/vars.module.css b/playground/css-modules/lightningcss-custom-properties-from/vars.module.css
new file mode 100644
index 000000000..0168423c6
--- /dev/null
+++ b/playground/css-modules/lightningcss-custom-properties-from/vars.module.css
@@ -0,0 +1,3 @@
+:root {
+  --accent-color: hotpink;
+}
diff --git a/playground/css-modules/lightningcss-features/index.js b/playground/css-modules/lightningcss-features/index.js
new file mode 100644
index 000000000..46f57acc7
--- /dev/null
+++ b/playground/css-modules/lightningcss-features/index.js
@@ -0,0 +1,2 @@
+export * from './style.module.css'
+export { default } from './style.module.css'
diff --git a/playground/css-modules/lightningcss-features/style.module.css b/playground/css-modules/lightningcss-features/style.module.css
new file mode 100644
index 000000000..c986bab2f
--- /dev/null
+++ b/playground/css-modules/lightningcss-features/style.module.css
@@ -0,0 +1,5 @@
+.button {
+  &.primary {
+    color: red;
+  }
+}
diff --git a/playground/css-modules/lightningcss.html b/playground/css-modules/lightningcss.html
new file mode 100644
index 000000000..6a9113658
--- /dev/null
+++ b/playground/css-modules/lightningcss.html
@@ -0,0 +1,22 @@
+

CSS Modules lightningcss

+ +

lightningcss-custom-properties-from

+

+
+

lightningcss-features

+

+
+
diff --git a/playground/css-modules/missing-class-export/index.js b/playground/css-modules/missing-class-export/index.js
new file mode 100644
index 000000000..46f57acc7
--- /dev/null
+++ b/playground/css-modules/missing-class-export/index.js
@@ -0,0 +1,2 @@
+export * from './style.module.css'
+export { default } from './style.module.css'
diff --git a/playground/css-modules/missing-class-export/style.module.css b/playground/css-modules/missing-class-export/style.module.css
new file mode 100644
index 000000000..bc5571d0e
--- /dev/null
+++ b/playground/css-modules/missing-class-export/style.module.css
@@ -0,0 +1,4 @@
+.className1 {
+  composes: non-existent from './utils.css';
+  color: red;
+}
diff --git a/playground/css-modules/missing-class-export/utils.css b/playground/css-modules/missing-class-export/utils.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/playground/css-modules/module-namespace/index.js b/playground/css-modules/module-namespace/index.js
new file mode 100644
index 000000000..fc6ecf3d6
--- /dev/null
+++ b/playground/css-modules/module-namespace/index.js
@@ -0,0 +1 @@
+import('./style.module.css')
diff --git a/playground/css-modules/module-namespace/style.module.css b/playground/css-modules/module-namespace/style.module.css
new file mode 100644
index 000000000..a7355d84b
--- /dev/null
+++ b/playground/css-modules/module-namespace/style.module.css
@@ -0,0 +1,3 @@
+.class-name {
+  color: red;
+}
diff --git a/playground/css-modules/multi-css-modules/index.js b/playground/css-modules/multi-css-modules/index.js
new file mode 100644
index 000000000..3a875519d
--- /dev/null
+++ b/playground/css-modules/multi-css-modules/index.js
@@ -0,0 +1,2 @@
+export * as style1 from './style1.module.css'
+export * as style2 from './style2.module.css'
diff --git a/playground/css-modules/multi-css-modules/style1.module.css b/playground/css-modules/multi-css-modules/style1.module.css
new file mode 100644
index 000000000..286935e3e
--- /dev/null
+++ b/playground/css-modules/multi-css-modules/style1.module.css
@@ -0,0 +1,9 @@
+.className1 {
+  composes: util-class from './utils1.css';
+  color: red;
+}
+
+.class-name2 {
+  composes: util-class from './utils1.css';
+  composes: util-class from './utils2.css';
+}
diff --git a/playground/css-modules/multi-css-modules/style2.module.css b/playground/css-modules/multi-css-modules/style2.module.css
new file mode 100644
index 000000000..f0e72fc36
--- /dev/null
+++ b/playground/css-modules/multi-css-modules/style2.module.css
@@ -0,0 +1,4 @@
+.class-name2 {
+  composes: util-class from './utils1.css';
+  color: red;
+}
diff --git a/playground/css-modules/multi-css-modules/utils1.css b/playground/css-modules/multi-css-modules/utils1.css
new file mode 100644
index 000000000..088f77c8c
--- /dev/null
+++ b/playground/css-modules/multi-css-modules/utils1.css
@@ -0,0 +1,8 @@
+.util-class {
+  --name: 'foo';
+  color: blue;
+}
+
+.unused-class {
+  color: yellow;
+}
diff --git a/playground/css-modules/multi-css-modules/utils2.css b/playground/css-modules/multi-css-modules/utils2.css
new file mode 100644
index 000000000..950866513
--- /dev/null
+++ b/playground/css-modules/multi-css-modules/utils2.css
@@ -0,0 +1,4 @@
+.util-class {
+  --name: 'bar';
+  color: green;
+}
diff --git a/playground/css-modules/package.json b/playground/css-modules/package.json
new file mode 100644
index 000000000..0c1bbf4ec
--- /dev/null
+++ b/playground/css-modules/package.json
@@ -0,0 +1,20 @@
+{
+  "name": "@vitejs/test-css-modules",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview",
+    "dev:lightningcss": "vite --config=vite.config-lightningcss.js",
+    "build:lightningcss": "vite --config=vite.config-lightningcss.js build",
+    "preview:lightningcss": "vite --config=vite.config-lightningcss.js preview"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^5.0.4",
+    "lightningcss": "^1.24.1",
+    "postcss": "^8.4.36",
+    "sass": "^1.72.0",
+    "vue": "^3.4.21"
+  }
+}
diff --git a/playground/css-modules/postcss.config.js b/playground/css-modules/postcss.config.js
new file mode 100644
index 000000000..dd4b0e961
--- /dev/null
+++ b/playground/css-modules/postcss.config.js
@@ -0,0 +1,19 @@
+import path from 'node:path'
+import postcss from 'postcss'
+
+export default {
+  plugins: [
+    /**
+     * PostCSS plugin that adds a "--file" CSS variable to indicate PostCSS
+     * has been successfully applied
+     */
+    (root) => {
+      const newRule = postcss.rule({ selector: ':root' })
+      newRule.append({
+        prop: '--file',
+        value: JSON.stringify(path.basename(root.source.input.file)),
+      })
+      root.append(newRule)
+    },
+  ],
+}
diff --git a/playground/css-modules/reserved-keywords/index.js b/playground/css-modules/reserved-keywords/index.js
new file mode 100644
index 000000000..b45ea7fdc
--- /dev/null
+++ b/playground/css-modules/reserved-keywords/index.js
@@ -0,0 +1 @@
+export * as style from './style.module.css'
diff --git a/playground/css-modules/reserved-keywords/style.module.css b/playground/css-modules/reserved-keywords/style.module.css
new file mode 100644
index 000000000..f52163ddd
--- /dev/null
+++ b/playground/css-modules/reserved-keywords/style.module.css
@@ -0,0 +1,8 @@
+.import {
+  composes: if from './utils.css';
+  color: red;
+}
+
+.export {
+  composes: with from './utils.css';
+}
diff --git a/playground/css-modules/reserved-keywords/utils.css b/playground/css-modules/reserved-keywords/utils.css
new file mode 100644
index 000000000..77b35d896
--- /dev/null
+++ b/playground/css-modules/reserved-keywords/utils.css
@@ -0,0 +1,8 @@
+.if {
+  --name: 'foo';
+  color: blue;
+}
+
+.with {
+  color: yellow;
+}
diff --git a/playground/css-modules/scss-modules-mixed/css.module.css b/playground/css-modules/scss-modules-mixed/css.module.css
new file mode 100644
index 000000000..a7b2031a0
--- /dev/null
+++ b/playground/css-modules/scss-modules-mixed/css.module.css
@@ -0,0 +1,3 @@
+.text-primary {
+  composes: text-primary from './scss.module.scss';
+}
diff --git a/playground/css-modules/scss-modules-mixed/index.js b/playground/css-modules/scss-modules-mixed/index.js
new file mode 100644
index 000000000..db2451f9d
--- /dev/null
+++ b/playground/css-modules/scss-modules-mixed/index.js
@@ -0,0 +1,2 @@
+export * from './css.module.css'
+export { default } from './css.module.css'
diff --git a/playground/css-modules/scss-modules-mixed/scss.module.scss b/playground/css-modules/scss-modules-mixed/scss.module.scss
new file mode 100644
index 000000000..c9c6dbda6
--- /dev/null
+++ b/playground/css-modules/scss-modules-mixed/scss.module.scss
@@ -0,0 +1,7 @@
+$primary: #cc0000;
+
+// comment
+
+.text-primary {
+  color: $primary;
+}
diff --git a/playground/css-modules/scss-modules/index.js b/playground/css-modules/scss-modules/index.js
new file mode 100644
index 000000000..d68529432
--- /dev/null
+++ b/playground/css-modules/scss-modules/index.js
@@ -0,0 +1,2 @@
+export * from './style.module.scss'
+export { default } from './style.module.scss'
diff --git a/playground/css-modules/scss-modules/style.module.scss b/playground/css-modules/scss-modules/style.module.scss
new file mode 100644
index 000000000..c9c6dbda6
--- /dev/null
+++ b/playground/css-modules/scss-modules/style.module.scss
@@ -0,0 +1,7 @@
+$primary: #cc0000;
+
+// comment
+
+.text-primary {
+  color: $primary;
+}
diff --git a/playground/css-modules/vite.config-lightningcss.js b/playground/css-modules/vite.config-lightningcss.js
new file mode 100644
index 000000000..07c727eed
--- /dev/null
+++ b/playground/css-modules/vite.config-lightningcss.js
@@ -0,0 +1,27 @@
+import { resolve } from 'node:path'
+import { Features } from 'lightningcss'
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+export default defineConfig({
+  plugins: [vue()],
+  css: {
+    devSourcemap: true,
+    transformer: 'lightningcss',
+    lightningcss: {
+      include: Features.Nesting,
+      cssModules: {
+        dashedIdents: true,
+      },
+    },
+  },
+  build: {
+    // Prevents CSS minification from handling the de-duplication of classes
+    minify: false,
+    rollupOptions: {
+      input: {
+        lightningcss: resolve(__dirname, 'lightningcss.html'),
+      },
+    },
+  },
+})
diff --git a/playground/css-modules/vite.config.js b/playground/css-modules/vite.config.js
new file mode 100644
index 000000000..d2e324a6c
--- /dev/null
+++ b/playground/css-modules/vite.config.js
@@ -0,0 +1,16 @@
+import { resolve } from 'node:path'
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+export default defineConfig({
+  plugins: [vue()],
+  build: {
+    // Prevents CSS minification from handling the de-duplication of classes
+    minify: false,
+    rollupOptions: {
+      input: {
+        main: resolve(__dirname, 'index.html'),
+      },
+    },
+  },
+})
diff --git a/playground/css-modules/vue/Comp.vue b/playground/css-modules/vue/Comp.vue
new file mode 100644
index 000000000..993a9232e
--- /dev/null
+++ b/playground/css-modules/vue/Comp.vue
@@ -0,0 +1,10 @@
+
+
+
diff --git a/playground/css-modules/vue/index.js b/playground/css-modules/vue/index.js
new file mode 100644
index 000000000..d2c938294
--- /dev/null
+++ b/playground/css-modules/vue/index.js
@@ -0,0 +1 @@
+export { default as Comp } from './Comp.vue'
diff --git a/playground/css-modules/vue/utils.css b/playground/css-modules/vue/utils.css
new file mode 100644
index 000000000..088f77c8c
--- /dev/null
+++ b/playground/css-modules/vue/utils.css
@@ -0,0 +1,8 @@
+.util-class {
+  --name: 'foo';
+  color: blue;
+}
+
+.unused-class {
+  color: yellow;
+}
diff --git a/playground/test-utils.ts b/playground/test-utils.ts
index 2916c350d..be0eca96a 100644
--- a/playground/test-utils.ts
+++ b/playground/test-utils.ts
@@ -9,7 +9,7 @@ import type {
   ElementHandle,
   Locator,
 } from 'playwright-chromium'
-import type { DepOptimizationMetadata, Manifest } from 'vite'
+import type { DepOptimizationMetadata, Manifest, Rollup } from 'vite'
 import { normalizePath } from 'vite'
 import { fromComment } from 'convert-source-map'
 import type { Assertion } from 'vitest'
@@ -408,3 +408,25 @@ export function promiseWithResolvers(): PromiseWithResolvers {
   })
   return { promise, resolve, reject }
 }
+
+export const base64Module = (code: string) =>
+  `data:text/javascript;base64,${Buffer.from(code).toString('base64')}`
+
+export const getCssSourceMaps = (code: string) => {
+  const cssSourcemaps = Array.from(
+    code.matchAll(
+      /\/*# sourceMappingURL=data:application\/json;base64,(.+?) \*\//g,
+    ),
+  )
+
+  const maps = cssSourcemaps.map(
+    ([, base64]) =>
+      JSON.parse(
+        Buffer.from(base64!, 'base64').toString('utf8'),
+      ) as Rollup.SourceMap,
+  )
+
+  maps.sort((a, b) => a.sources[0]!.localeCompare(b.sources[0]!))
+
+  return maps
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6b654967f..6edaa404e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -173,7 +173,7 @@ importers:
         version: 4.17.21
       vitepress:
         specifier: 1.0.0-rc.45
-        version: 1.0.0-rc.45(typescript@5.2.2)
+        version: 1.0.0-rc.45(@types/node@20.11.28)(typescript@5.2.2)
       vue:
         specifier: ^3.4.21
         version: 3.4.21(typescript@5.2.2)
@@ -613,6 +613,24 @@ importers:
         specifier: ^1.24.1
         version: 1.24.1
 
+  playground/css-modules:
+    devDependencies:
+      '@vitejs/plugin-vue':
+        specifier: ^5.0.4
+        version: 5.0.4(vite@packages+vite)(vue@3.4.21)
+      lightningcss:
+        specifier: ^1.24.1
+        version: 1.24.1
+      postcss:
+        specifier: ^8.4.36
+        version: 8.4.36
+      sass:
+        specifier: ^1.72.0
+        version: 1.72.0
+      vue:
+        specifier: ^3.4.21
+        version: 3.4.21(typescript@5.2.2)
+
   playground/css-sourcemap:
     devDependencies:
       less:
@@ -3941,21 +3959,6 @@ packages:
       typescript: 5.2.2
     dev: true
 
-  /@rollup/pluginutils@5.0.4(rollup@3.29.4):
-    resolution: {integrity: sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==}
-    engines: {node: '>=14.0.0'}
-    peerDependencies:
-      rollup: ^1.20.0||^2.0.0||^3.0.0
-    peerDependenciesMeta:
-      rollup:
-        optional: true
-    dependencies:
-      '@types/estree': 1.0.5
-      estree-walker: 2.0.2
-      picomatch: 2.3.1
-      rollup: 3.29.4
-    dev: true
-
   /@rollup/pluginutils@5.1.0(rollup@3.29.4):
     resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
     engines: {node: '>=14.0.0'}
@@ -4318,12 +4321,6 @@ packages:
     resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
     dev: true
 
-  /@types/node@20.10.0:
-    resolution: {integrity: sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==}
-    dependencies:
-      undici-types: 5.26.5
-    dev: true
-
   /@types/node@20.11.28:
     resolution: {integrity: sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==}
     dependencies:
@@ -4344,7 +4341,7 @@ packages:
   /@types/prompts@2.4.9:
     resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==}
     dependencies:
-      '@types/node': 20.10.0
+      '@types/node': 20.11.28
       kleur: 3.0.3
     dev: true
 
@@ -4549,6 +4546,17 @@ packages:
     resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
     dev: true
 
+  /@vitejs/plugin-vue@5.0.4(vite@5.2.2)(vue@3.4.21):
+    resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==}
+    engines: {node: ^18.0.0 || >=20.0.0}
+    peerDependencies:
+      vite: '*'
+      vue: ^3.2.25
+    dependencies:
+      vite: 5.2.2(@types/node@20.11.28)
+      vue: 3.4.21(typescript@5.2.2)
+    dev: true
+
   /@vitejs/plugin-vue@5.0.4(vite@packages+vite)(vue@3.4.21):
     resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==}
     engines: {node: ^18.0.0 || >=20.0.0}
@@ -7500,13 +7508,6 @@ packages:
       '@jridgewell/sourcemap-codec': 1.4.15
     dev: true
 
-  /magic-string@0.30.4:
-    resolution: {integrity: sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==}
-    engines: {node: '>=12'}
-    dependencies:
-      '@jridgewell/sourcemap-codec': 1.4.15
-    dev: true
-
   /magic-string@0.30.8:
     resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
     engines: {node: '>=12'}
@@ -8581,7 +8582,7 @@ packages:
     dependencies:
       icss-utils: 5.1.0(postcss@8.4.36)
       postcss: 8.4.36
-      postcss-selector-parser: 6.0.11
+      postcss-selector-parser: 6.0.16
       postcss-value-parser: 4.2.0
     dev: true
 
@@ -8595,7 +8596,7 @@ packages:
         optional: true
     dependencies:
       postcss: 8.4.36
-      postcss-selector-parser: 6.0.11
+      postcss-selector-parser: 6.0.16
     dev: true
 
   /postcss-modules-values@4.0.0(postcss@8.4.36):
@@ -8621,10 +8622,10 @@ packages:
         optional: true
     dependencies:
       postcss: 8.4.36
-      postcss-selector-parser: 6.0.11
+      postcss-selector-parser: 6.0.16
 
-  /postcss-selector-parser@6.0.11:
-    resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==}
+  /postcss-selector-parser@6.0.16:
+    resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==}
     engines: {node: '>=4'}
     dependencies:
       cssesc: 3.0.0
@@ -9589,7 +9590,7 @@ packages:
       postcss-js: 4.0.1(postcss@8.4.36)
       postcss-load-config: 4.0.2(postcss@8.4.36)(ts-node@10.9.2)
       postcss-nested: 6.0.1(postcss@8.4.36)
-      postcss-selector-parser: 6.0.11
+      postcss-selector-parser: 6.0.16
       resolve: 1.22.4
       sucrase: 3.32.0
     transitivePeerDependencies:
@@ -9874,7 +9875,7 @@ packages:
       '@rollup/plugin-json': 6.0.0(rollup@3.29.4)
       '@rollup/plugin-node-resolve': 15.2.1(rollup@3.29.4)
       '@rollup/plugin-replace': 5.0.2(rollup@3.29.4)
-      '@rollup/pluginutils': 5.0.4(rollup@3.29.4)
+      '@rollup/pluginutils': 5.1.0(rollup@3.29.4)
       chalk: 5.3.0
       citty: 0.1.4
       consola: 3.2.3
@@ -9883,7 +9884,7 @@ packages:
       globby: 13.2.2
       hookable: 5.5.3
       jiti: 1.20.0
-      magic-string: 0.30.4
+      magic-string: 0.30.8
       mkdist: 1.3.0(typescript@5.2.2)
       mlly: 1.4.2
       pathe: 1.1.2
@@ -10075,7 +10076,7 @@ packages:
       - rollup
     dev: true
 
-  /vite-node@1.4.0:
+  /vite-node@1.4.0(@types/node@20.11.28):
     resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
@@ -10084,12 +10085,55 @@ packages:
       debug: 4.3.4
       pathe: 1.1.2
       picocolors: 1.0.0
-      vite: link:packages/vite
+      vite: 5.2.2(@types/node@20.11.28)
     transitivePeerDependencies:
+      - '@types/node'
+      - less
+      - lightningcss
+      - sass
+      - stylus
+      - sugarss
       - supports-color
+      - terser
     dev: true
 
-  /vitepress@1.0.0-rc.45(typescript@5.2.2):
+  /vite@5.2.2(@types/node@20.11.28):
+    resolution: {integrity: sha512-FWZbz0oSdLq5snUI0b6sULbz58iXFXdvkZfZWR/F0ZJuKTSPO7v72QPXt6KqYeMFb0yytNp6kZosxJ96Nr/wDQ==}
+    engines: {node: ^18.0.0 || >=20.0.0}
+    hasBin: true
+    peerDependencies:
+      '@types/node': ^18.0.0 || >=20.0.0
+      less: '*'
+      lightningcss: ^1.21.0
+      sass: '*'
+      stylus: '*'
+      sugarss: '*'
+      terser: ^5.4.0
+    peerDependenciesMeta:
+      '@types/node':
+        optional: true
+      less:
+        optional: true
+      lightningcss:
+        optional: true
+      sass:
+        optional: true
+      stylus:
+        optional: true
+      sugarss:
+        optional: true
+      terser:
+        optional: true
+    dependencies:
+      '@types/node': 20.11.28
+      esbuild: 0.20.1
+      postcss: 8.4.36
+      rollup: 4.13.0
+    optionalDependencies:
+      fsevents: 2.3.3
+    dev: true
+
+  /vitepress@1.0.0-rc.45(@types/node@20.11.28)(typescript@5.2.2):
     resolution: {integrity: sha512-/OiYsu5UKpQKA2c0BAZkfyywjfauDjvXyv6Mo4Ra57m5n4Bxg1HgUGoth1CLH2vwUbR/BHvDA9zOM0RDvgeSVQ==}
     hasBin: true
     peerDependencies:
@@ -10106,7 +10150,7 @@ packages:
       '@shikijs/core': 1.1.5
       '@shikijs/transformers': 1.1.5
       '@types/markdown-it': 13.0.7
-      '@vitejs/plugin-vue': 5.0.4(vite@packages+vite)(vue@3.4.21)
+      '@vitejs/plugin-vue': 5.0.4(vite@5.2.2)(vue@3.4.21)
       '@vue/devtools-api': 7.0.14
       '@vueuse/core': 10.7.2(vue@3.4.21)
       '@vueuse/integrations': 10.7.2(focus-trap@7.5.4)(vue@3.4.21)
@@ -10114,10 +10158,11 @@ packages:
       mark.js: 8.11.1
       minisearch: 6.3.0
       shiki: 1.1.5
-      vite: link:packages/vite
+      vite: 5.2.2(@types/node@20.11.28)
       vue: 3.4.21(typescript@5.2.2)
     transitivePeerDependencies:
       - '@algolia/client-search'
+      - '@types/node'
       - '@types/react'
       - '@vue/composition-api'
       - async-validator
@@ -10127,12 +10172,18 @@ packages:
       - fuse.js
       - idb-keyval
       - jwt-decode
+      - less
+      - lightningcss
       - nprogress
       - qrcode
       - react
       - react-dom
+      - sass
       - search-insights
       - sortablejs
+      - stylus
+      - sugarss
+      - terser
       - typescript
       - universal-cookie
     dev: true
@@ -10180,12 +10231,18 @@ packages:
       strip-literal: 2.0.0
       tinybench: 2.5.1
       tinypool: 0.8.2
-      vite: link:packages/vite
-      vite-node: 1.4.0
+      vite: 5.2.2(@types/node@20.11.28)
+      vite-node: 1.4.0(@types/node@20.11.28)
       why-is-node-running: 2.2.2
     transitivePeerDependencies:
       - acorn
+      - less
+      - lightningcss
+      - sass
+      - stylus
+      - sugarss
       - supports-color
+      - terser
     dev: true
 
   /void-elements@3.1.0: