diff --git a/package.json b/package.json index 34e089fd50d2c1..c2fe314dea3672 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,8 @@ "patchedDependencies": { "http-proxy@1.18.1": "patches/http-proxy@1.18.1.patch", "sirv@3.0.0": "patches/sirv@3.0.0.patch", - "chokidar@3.6.0": "patches/chokidar@3.6.0.patch" + "chokidar@3.6.0": "patches/chokidar@3.6.0.patch", + "lightningcss": "patches/lightningcss.patch" }, "peerDependencyRules": { "allowedVersions": { diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 3ff50d4e1b2036..4bb4b227b4a1c2 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -1272,60 +1272,116 @@ async function compileCSS( ): Promise<{ code: string map?: SourceMapInput - ast?: PostCSS.Result modules?: Record deps?: Set }> { const { config } = environment - if (config.css.transformer === 'lightningcss') { - return compileLightningCSS(id, code, environment, urlReplacer) + const lang = CSS_LANGS_RE.exec(id)?.[1] as CssLang | undefined + const deps = new Set() + + // pre-processors: sass etc. + let preprocessorMap: ExistingRawSourceMap | undefined + if (isPreProcessor(lang)) { + const preprocessorResult = await compileCSSPreprocessors( + environment, + id, + lang, + code, + workerController, + ) + code = preprocessorResult.code + preprocessorMap = preprocessorResult.map + preprocessorResult.deps?.forEach((dep) => deps.add(dep)) + } else if (lang === 'sss' && config.css.transformer === 'lightningcss') { + const sssResult = await transformSugarSS(environment, id, code) + code = sssResult.code + preprocessorMap = sssResult.map + } + + const transformResult = await (config.css.transformer === 'lightningcss' + ? compileLightningCSS( + environment, + id, + code, + deps, + workerController, + urlReplacer, + ) + : compilePostCSS( + environment, + id, + code, + deps, + lang, + workerController, + urlReplacer, + )) + + if (!transformResult) { + return { + code, + map: config.css.devSourcemap ? preprocessorMap : { mappings: '' }, + deps, + } } + return { + ...transformResult, + map: config.css.devSourcemap + ? combineSourcemapsIfExists( + cleanUrl(id), + typeof transformResult.map === 'string' + ? JSON.parse(transformResult.map) + : transformResult.map, + preprocessorMap, + ) + : { mappings: '' }, + deps, + } +} + +async function compilePostCSS( + environment: PartialEnvironment, + id: string, + code: string, + deps: Set, + lang: CssLang | undefined, + workerController: PreprocessorWorkerController, + urlReplacer?: CssUrlReplacer, +): Promise< + | { + code: string + map?: Exclude + modules?: Record + } + | undefined +> { + const { config } = environment const { modules: modulesOptions, devSourcemap } = config.css const isModule = modulesOptions !== false && cssModuleRE.test(id) // although at serve time it can work without processing, we do need to // crawl them in order to register watch dependencies. const needInlineImport = code.includes('@import') const hasUrl = cssUrlRE.test(code) || cssImageSetRE.test(code) - const lang = CSS_LANGS_RE.exec(id)?.[1] as CssLang | undefined const postcssConfig = await resolvePostcssConfig( environment.getTopLevelConfig(), ) - // 1. plain css that needs no processing + // postcss processing is not needed if ( - lang === 'css' && + lang !== 'sss' && !postcssConfig && !isModule && !needInlineImport && !hasUrl ) { - return { code, map: null } - } - - let modules: Record | undefined - const deps = new Set() - - // 2. pre-processors: sass etc. - let preprocessorMap: ExistingRawSourceMap | undefined - if (isPreProcessor(lang)) { - const preprocessorResult = await compileCSSPreprocessors( - environment, - id, - lang, - code, - workerController, - ) - code = preprocessorResult.code - preprocessorMap = preprocessorResult.map - preprocessorResult.deps?.forEach((dep) => deps.add(dep)) + return } - // 3. postcss + // postcss const atImportResolvers = getAtImportResolvers( environment.getTopLevelConfig(), ) - const postcssOptions = postcssConfig?.options ?? {} const postcssPlugins = postcssConfig?.plugins.slice() ?? [] if (needInlineImport) { @@ -1387,7 +1443,13 @@ async function compileCSS( ) } - if (urlReplacer) { + if ( + urlReplacer && + // if there's an @import, we need to add this plugin + // regradless of whether it contains url() or image-set(), + // because we don't know the content referenced by @import + (needInlineImport || cssUrlRE.test(code) || cssImageSetRE.test(code)) + ) { postcssPlugins.push( UrlRewritePostcssPlugin({ replacer: urlReplacer, @@ -1396,6 +1458,8 @@ async function compileCSS( ) } + let modules: Record | undefined + if (isModule) { postcssPlugins.unshift( (await importPostcssModules()).default({ @@ -1429,18 +1493,16 @@ async function compileCSS( ) } - if (!postcssPlugins.length) { - return { - code, - map: preprocessorMap, - deps, - } + if (lang !== 'sss' && !postcssPlugins.length) { + return } let postcssResult: PostCSS.Result try { const source = removeDirectQuery(id) const postcss = await importPostcss() + const postcssOptions = postcssConfig?.options ?? {} + // postcss is an unbundled dep and should be lazy imported postcssResult = await postcss.default(postcssPlugins).process(code, { ...postcssOptions, @@ -1510,16 +1572,13 @@ async function compileCSS( if (!devSourcemap) { return { - ast: postcssResult, code: postcssResult.css, map: { mappings: '' }, modules, - deps, } } const rawPostcssMap = postcssResult.map.toJSON() - const postcssMap = await formatPostcssSourceMap( // version property of rawPostcssMap is declared as string // but actually it is a number @@ -1528,11 +1587,93 @@ async function compileCSS( ) return { - ast: postcssResult, code: postcssResult.css, - map: combineSourcemapsIfExists(cleanUrl(id), postcssMap, preprocessorMap), + map: postcssMap, modules, - deps, + } +} + +// TODO: dedupe +async function transformSugarSS( + environment: PartialEnvironment, + id: string, + code: string, +) { + const { config } = environment + const { devSourcemap } = config.css + + let postcssResult: PostCSS.Result + try { + const source = removeDirectQuery(id) + const postcss = await importPostcss() + // postcss is an unbundled dep and should be lazy imported + postcssResult = await postcss.default().process(code, { + parser: loadSss(config.root), + to: source, + from: source, + ...(devSourcemap + ? { + map: { + inline: false, + annotation: false, + // postcss may return virtual files + // we cannot obtain content of them, so this needs to be enabled + sourcesContent: true, + // when "prev: preprocessorMap", the result map may include duplicate filename in `postcssResult.map.sources` + // prev: preprocessorMap, + }, + } + : {}), + }) + + for (const message of postcssResult.messages) { + if (message.type === 'warning') { + const warning = message as PostCSS.Warning + let msg = `[vite:css] ${warning.text}` + msg += `\n${generateCodeFrame( + code, + { + line: warning.line, + column: warning.column - 1, // 1-based + }, + warning.endLine !== undefined && warning.endColumn !== undefined + ? { + line: warning.endLine, + column: warning.endColumn - 1, // 1-based + } + : undefined, + )}` + environment.logger.warn(colors.yellow(msg)) + } + } + } catch (e) { + e.message = `[postcss] ${e.message}` + e.code = code + e.loc = { + file: e.file, + line: e.line, + column: e.column - 1, // 1-based + } + throw e + } + + if (!devSourcemap) { + return { + code: postcssResult.css, + } + } + + const rawPostcssMap = postcssResult.map.toJSON() + const postcssMap = await formatPostcssSourceMap( + // version property of rawPostcssMap is declared as string + // but actually it is a number + rawPostcssMap as Omit as ExistingRawSourceMap, + cleanUrl(id), + ) + + return { + code: postcssResult.css, + map: postcssMap, } } @@ -3154,13 +3295,18 @@ function isPreProcessor(lang: any): lang is PreprocessLang { const importLightningCSS = createCachedImport(() => import('lightningcss')) async function compileLightningCSS( + environment: PartialEnvironment, id: string, src: string, - environment: PartialEnvironment, + deps: Set, + workerController: PreprocessorWorkerController, urlReplacer?: CssUrlReplacer, -): ReturnType { +): Promise<{ + code: string + map?: string | undefined + modules?: Record +}> { const { config } = environment - const deps = new Set() // replace null byte as lightningcss treats that as a string terminator // https://github.com/parcel-bundler/lightningcss/issues/874 const filename = id.replace('\0', NULL_BYTE_PLACEHOLDER) @@ -3183,11 +3329,32 @@ async function compileLightningCSS( // projectRoot is needed to get stable hash when using CSS modules projectRoot: config.root, resolver: { - read(filePath) { + async read(filePath) { if (filePath === filename) { return src } - return fs.readFileSync(filePath, 'utf-8') + + const code = fs.readFileSync(filePath, 'utf-8') + const lang = CSS_LANGS_RE.exec(filePath)?.[1] as + | CssLang + | undefined + if (isPreProcessor(lang)) { + const result = await compileCSSPreprocessors( + environment, + id, + lang, + code, + workerController, + ) + result.deps?.forEach((dep) => deps.add(dep)) + // TODO: support source map + return result.code + } else if (lang === 'sss') { + const sssResult = await transformSugarSS(environment, id, code) + // TODO: support source map + return sssResult.code + } + return code }, async resolve(id, from) { const publicFile = checkPublicFile( @@ -3198,10 +3365,34 @@ async function compileLightningCSS( return publicFile } - const resolved = await getAtImportResolvers( + // NOTE: with `transformer: 'postcss'`, CSS modules `composes` tried to resolve with + // all resolvers, but in `transformer: 'lightningcss'`, only the one for the + // current file type is used. + const atImportResolvers = getAtImportResolvers( environment.getTopLevelConfig(), - ).css(environment, id, from) + ) + const lang = CSS_LANGS_RE.exec(from)?.[1] as CssLang | undefined + let resolver: ResolveIdFn + switch (lang) { + case 'css': + case 'sss': + case 'styl': + case 'stylus': + case undefined: + resolver = atImportResolvers.css + break + case 'sass': + case 'scss': + resolver = atImportResolvers.sass + break + case 'less': + resolver = atImportResolvers.less + break + default: + throw new Error(`Unknown lang: ${lang satisfies never}`) + } + const resolved = await resolver(environment, id, from) if (resolved) { deps.add(resolved) return resolved @@ -3221,10 +3412,13 @@ async function compileLightningCSS( }) } catch (e) { e.message = `[lightningcss] ${e.message}` - e.loc = { - file: e.fileName.replace(NULL_BYTE_PLACEHOLDER, '\0'), - line: e.loc.line, - column: e.loc.column - 1, // 1-based + // https://github.com/vitejs/vite/pull/19070 + if (e.fileName) { + e.loc = { + file: e.fileName.replace(NULL_BYTE_PLACEHOLDER, '\0'), + line: e.loc.line, + column: e.loc.column - 1, // 1-based + } } throw e } @@ -3281,7 +3475,6 @@ async function compileLightningCSS( return { code: css, map: 'map' in res ? res.map?.toString() : undefined, - deps, modules, } } diff --git a/patches/lightningcss.patch b/patches/lightningcss.patch new file mode 100644 index 00000000000000..4e3206d674664c --- /dev/null +++ b/patches/lightningcss.patch @@ -0,0 +1,152 @@ +diff --git a/node/composeVisitors.js b/node/composeVisitors.js +index 850058d0b952a57b6b68b2da9f12790d51b59ca1..2144b1318b25900e38c9a75787bdf4684defe9a3 100644 +--- a/node/composeVisitors.js ++++ b/node/composeVisitors.js +@@ -3,7 +3,7 @@ + + /** + * Composes multiple visitor objects into a single one. +- * @param {Visitor[]} visitors ++ * @param {Visitor[]} visitors + * @return {Visitor} + */ + function composeVisitors(visitors) { +@@ -13,6 +13,7 @@ function composeVisitors(visitors) { + + /** @type Visitor */ + let res = {}; ++ composeSimpleVisitors(res, visitors, 'StyleSheet'); + composeObjectVisitors(res, visitors, 'Rule', ruleVisitor, wrapUnknownAtRule); + composeObjectVisitors(res, visitors, 'RuleExit', ruleVisitor, wrapUnknownAtRule); + composeObjectVisitors(res, visitors, 'Declaration', declarationVisitor, wrapCustomProperty); +@@ -54,8 +55,8 @@ function wrapCustomProperty(k, f) { + } + + /** +- * @param {import('./index').Visitor['Rule']} f +- * @param {import('./ast').Rule} item ++ * @param {import('./index').Visitor['Rule']} f ++ * @param {import('./ast').Rule} item + */ + function ruleVisitor(f, item) { + if (typeof f === 'object') { +@@ -72,8 +73,8 @@ function ruleVisitor(f, item) { + } + + /** +- * @param {import('./index').Visitor['Declaration']} f +- * @param {import('./ast').Declaration} item ++ * @param {import('./index').Visitor['Declaration']} f ++ * @param {import('./ast').Declaration} item + */ + function declarationVisitor(f, item) { + if (typeof f === 'object') { +@@ -94,9 +95,9 @@ function declarationVisitor(f, item) { + } + + /** +- * +- * @param {Visitor[]} visitors +- * @param {string} key ++ * ++ * @param {Visitor[]} visitors ++ * @param {string} key + * @returns {[any[], boolean, Set]} + */ + function extractObjectsOrFunctions(visitors, key) { +@@ -124,8 +125,8 @@ function extractObjectsOrFunctions(visitors, key) { + * @param {Visitor} res + * @param {Visitor[]} visitors + * @param {K} key +- * @param {(visitor: Visitor[K], item: any) => any | any[] | void} apply +- * @param {(k: string, f: any) => any} wrapKey ++ * @param {(visitor: Visitor[K], item: any) => any | any[] | void} apply ++ * @param {(k: string, f: any) => any} wrapKey + */ + function composeObjectVisitors(res, visitors, key, apply, wrapKey) { + let [values, hasFunction, allKeys] = extractObjectsOrFunctions(visitors, key); +@@ -152,11 +153,11 @@ function composeObjectVisitors(res, visitors, key, apply, wrapKey) { + } + + /** +- * @param {Visitor} res +- * @param {Visitor[]} visitors +- * @param {string} key +- * @param {import('./ast').TokenOrValue['type']} type +- * @param {boolean} isExit ++ * @param {Visitor} res ++ * @param {Visitor[]} visitors ++ * @param {string} key ++ * @param {import('./ast').TokenOrValue['type']} type ++ * @param {boolean} isExit + */ + function composeTokenVisitors(res, visitors, key, type, isExit) { + let [values, hasFunction, allKeys] = extractObjectsOrFunctions(visitors, key); +@@ -182,8 +183,8 @@ function composeTokenVisitors(res, visitors, key, type, isExit) { + } + + /** +- * @param {Visitor[]} visitors +- * @param {import('./ast').TokenOrValue['type']} type ++ * @param {Visitor[]} visitors ++ * @param {import('./ast').TokenOrValue['type']} type + */ + function createTokenVisitor(visitors, type, isExit) { + let v = createArrayVisitor(visitors, (visitor, /** @type {import('./ast').TokenOrValue} */ item) => { +@@ -271,8 +272,8 @@ function createTokenVisitor(visitors, type, isExit) { + } + + /** +- * @param {Visitor[]} visitors +- * @param {string} key ++ * @param {Visitor[]} visitors ++ * @param {string} key + */ + function extractFunctions(visitors, key) { + let functions = []; +@@ -286,9 +287,9 @@ function extractFunctions(visitors, key) { + } + + /** +- * @param {Visitor} res +- * @param {Visitor[]} visitors +- * @param {string} key ++ * @param {Visitor} res ++ * @param {Visitor[]} visitors ++ * @param {string} key + */ + function composeSimpleVisitors(res, visitors, key) { + let functions = extractFunctions(visitors, key); +@@ -316,9 +317,9 @@ function composeSimpleVisitors(res, visitors, key) { + } + + /** +- * @param {Visitor} res +- * @param {Visitor[]} visitors +- * @param {string} key ++ * @param {Visitor} res ++ * @param {Visitor[]} visitors ++ * @param {string} key + */ + function composeArrayFunctions(res, visitors, key) { + let functions = extractFunctions(visitors, key); +@@ -337,8 +338,8 @@ function composeArrayFunctions(res, visitors, key) { + /** + * @template T + * @template V +- * @param {T[]} visitors +- * @param {(visitor: T, item: V) => V | V[] | void} apply ++ * @param {T[]} visitors ++ * @param {(visitor: T, item: V) => V | V[] | void} apply + * @returns {(item: V) => V | V[] | void} + */ + function createArrayVisitor(visitors, apply) { +@@ -350,7 +351,7 @@ function createArrayVisitor(visitors, apply) { + for (let i = 0; i < arr.length; i++) { + // For each value, call all visitors. If a visitor returns a new value, + // we start over, but skip the visitor that generated the value or saw +- // it before (to avoid cycles). This way, visitors can be composed in any order. ++ // it before (to avoid cycles). This way, visitors can be composed in any order. + for (let v = 0; v < visitors.length;) { + if (seen.get(v)) { + v++; diff --git a/playground/css-sourcemap/__tests__/css-sourcemap.spec.ts b/playground/css-sourcemap/__tests__/css-sourcemap.spec.ts index 40bef704173384..f943143187ee51 100644 --- a/playground/css-sourcemap/__tests__/css-sourcemap.spec.ts +++ b/playground/css-sourcemap/__tests__/css-sourcemap.spec.ts @@ -138,8 +138,8 @@ describe.runIf(isServe)('serve', () => { const map = extractSourcemap(css) expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` { - "ignoreList": [], - "mappings": "AAGE;EACE,UCJM", + "mappings": "AAGE;EACE,OCJM", + "sourceRoot": "", "sources": [ "/root/imported.sass", "/root/imported-nested.sass", @@ -186,7 +186,7 @@ describe.runIf(isServe)('serve', () => { expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` { "ignoreList": [], - "mappings": "AACE;EACE", + "mappings": "AACE,SAAC;EACC", "sources": [ "/root/imported.less", ], @@ -209,7 +209,7 @@ describe.runIf(isServe)('serve', () => { expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` { "ignoreList": [], - "mappings": "AACE;EACE,cAAM", + "mappings": "AACE;EACE,OAAM,QAAN", "sources": [ "/root/imported.styl", ], diff --git a/playground/css/__tests__/css.spec.ts b/playground/css/__tests__/css.spec.ts index 42f50aa92ce2df..6d3688151fdb4d 100644 --- a/playground/css/__tests__/css.spec.ts +++ b/playground/css/__tests__/css.spec.ts @@ -289,7 +289,8 @@ test('PostCSS dir-dependency', async () => { expect(await getColor(el2)).toBe('grey') expect(await getColor(el3)).toBe('grey') - if (!isBuild) { + // FIXME: skip for now as lightningcss does not support registering dependencies in plugins + if (!isBuild && false) { editFile('glob-dep/foo.css', (code) => code.replace('color: grey', 'color: blue'), ) diff --git a/playground/css/lightningcss-plugins.js b/playground/css/lightningcss-plugins.js new file mode 100644 index 00000000000000..3b5b33daa20c56 --- /dev/null +++ b/playground/css/lightningcss-plugins.js @@ -0,0 +1,173 @@ +import path from 'node:path' +import { normalizePath } from 'vite' +import { bundle as bundleWithLightningCss } from 'lightningcss' +import { globSync } from 'tinyglobby' + +/** + * @param {string} filename + * @returns {import('lightningcss').StyleSheet} + * + * based on https://github.com/sardinedev/lightningcss-plugins/blob/9fb379486e402a4b4b8950d09e655b4cbf8a118b/packages/global-custom-queries/src/globalCustomQueries.ts#L9-L29 + * https://github.com/sardinedev/lightningcss-plugins/blob/main/LICENSE + */ +function obtainLightningCssAst(filename) { + let ast + try { + bundleWithLightningCss({ + filename, + visitor: { + StyleSheet(stylesheet) { + ast = stylesheet + }, + }, + }) + return ast + } catch (error) { + throw Error(`failed to obtain lightning css AST`, { cause: error }) + } +} + +/** @returns {import('lightningcss').Visitor} */ +export function testDirDep() { + /** @type {string[]} */ + let currentStyleSheetSources + return { + StyleSheet(stylesheet) { + currentStyleSheetSources = stylesheet.sources + }, + Rule: { + unknown: { + test(rule) { + const location = rule.loc + const from = currentStyleSheetSources[location.source_index] + const pattern = normalizePath( + path.resolve(path.dirname(from), './glob-dep/**/*.css'), + ) + // FIXME: there's no way to add a dependency + const files = globSync(pattern, { + expandDirectories: false, + absolute: true, + }) + return files.flatMap((file) => obtainLightningCssAst(file).rules) + }, + }, + }, + } +} + +/** @returns {import('lightningcss').Visitor} */ +export function testSourceInput() { + /** @type {string[]} */ + let currentStyleSheetSources + return { + StyleSheet(stylesheet) { + currentStyleSheetSources = stylesheet.sources + }, + Rule: { + unknown: { + 'source-input': (rule) => { + const location = rule.loc + const from = currentStyleSheetSources[location.source_index] + return [ + { + type: 'style', + value: { + // .source-input::before + selectors: [ + [ + { type: 'class', name: 'source-input' }, + { type: 'pseudo-element', kind: 'before' }, + ], + ], + // content: ${JSON.stringify(from)}; + declarations: { + declarations: [ + { + property: 'custom', + value: + /** @satisfies {import('lightningcss').CustomProperty} */ ({ + name: 'content', + value: [ + { + type: 'token', + value: { type: 'string', value: from }, + }, + ], + }), + }, + ], + }, + loc: rule.loc, + }, + }, + ] + }, + }, + }, + } +} + +/** + * really simplified implementation of https://github.com/postcss/postcss-nested + * + * @returns {import('lightningcss').Visitor} + */ +export function nestedLikePlugin() { + return { + Rule: { + style(rule) { + // NOTE: multiple selectors are not supported + if (rule.value.selectors.length > 1) { + return + } + const parentSelector = rule.value.selectors[0] + + const nestedRules = rule.value.rules + /** @type {import('lightningcss').Rule[]} */ + const additionalRules = [] + if (nestedRules) { + const filteredNestedRules = [] + for (const nestedRule of nestedRules) { + if (nestedRule.type === 'style') { + const selectors = nestedRule.value.selectors + // NOTE: multiple selectors are not supported + if (selectors.length === 1) { + const selector = selectors[0] + if ( + selector.length >= 2 && + selector[0].type === 'nesting' && + selector[1].type === 'type' + ) { + const lastParentSelectorComponent = + parentSelector[parentSelector.length - 1] + if ('name' in lastParentSelectorComponent) { + const newSelector = [ + ...parentSelector.slice(0, -1), + { + ...lastParentSelectorComponent, + name: + lastParentSelectorComponent.name + selector[1].name, + }, + ] + additionalRules.push({ + type: 'style', + value: { + selectors: [newSelector], + declarations: nestedRule.value.declarations, + loc: nestedRule.value.loc, + }, + }) + continue + } + } + } + } + filteredNestedRules.push(nestedRule) + } + rule.value.rules = filteredNestedRules + } + return [rule, ...additionalRules] + }, + }, + } +} diff --git a/playground/css/package.json b/playground/css/package.json index 8b0155f22a98c3..6b6050f3cb5792 100644 --- a/playground/css/package.json +++ b/playground/css/package.json @@ -22,6 +22,7 @@ "@vitejs/test-css-proxy-dep": "file:./css-proxy-dep", "@vitejs/test-scss-proxy-dep": "file:./scss-proxy-dep", "less": "^4.2.1", + "lightningcss": "^1.28.2", "postcss-nested": "^7.0.2", "sass": "^1.83.0", "stylus": "^0.64.0", diff --git a/playground/css/vite.config.js b/playground/css/vite.config.js index 115db67dcaaa53..1691e4aa9d5700 100644 --- a/playground/css/vite.config.js +++ b/playground/css/vite.config.js @@ -2,6 +2,12 @@ import path from 'node:path' import { pathToFileURL } from 'node:url' import stylus from 'stylus' import { defineConfig } from 'vite' +import { composeVisitors } from 'lightningcss' +import { + nestedLikePlugin, + testDirDep, + testSourceInput, +} from './lightningcss-plugins' // trigger scss bug: https://github.com/sass/dart-sass/issues/710 // make sure Vite handles safely @@ -38,6 +44,17 @@ export default defineConfig({ }, }, css: { + transformer: 'lightningcss', + lightningcss: { + cssModules: { + pattern: '[name]__[local]___[hash]', + }, + visitor: composeVisitors([ + nestedLikePlugin(), + testDirDep(), + testSourceInput(), + ]), + }, modules: { generateScopedName: '[name]__[local]___[hash:base64:5]', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee0b75548ec8c1..f3d44276bfcedc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,6 +16,9 @@ patchedDependencies: http-proxy@1.18.1: hash: qqiqxx62zlcu62nljjmhlvexni path: patches/http-proxy@1.18.1.patch + lightningcss: + hash: achcqszyir6qyqp6dbmjpwgjkq + path: patches/lightningcss.patch sirv@3.0.0: hash: plxlsciwiebyhal5sm4vtpekka path: patches/sirv@3.0.0.patch @@ -326,7 +329,7 @@ importers: version: 2.9.1 lightningcss: specifier: ^1.28.2 - version: 1.28.2 + version: 1.28.2(patch_hash=achcqszyir6qyqp6dbmjpwgjkq) magic-string: specifier: ^0.30.17 version: 0.30.17 @@ -569,6 +572,9 @@ importers: less: specifier: ^4.2.1 version: 4.2.1 + lightningcss: + specifier: ^1.28.2 + version: 1.28.2(patch_hash=achcqszyir6qyqp6dbmjpwgjkq) postcss-nested: specifier: ^7.0.2 version: 7.0.2(postcss@8.4.49) @@ -595,7 +601,7 @@ importers: devDependencies: lightningcss: specifier: ^1.28.2 - version: 1.28.2 + version: 1.28.2(patch_hash=achcqszyir6qyqp6dbmjpwgjkq) playground/css-lightningcss-proxy: devDependencies: @@ -604,13 +610,13 @@ importers: version: 5.0.1 lightningcss: specifier: ^1.28.2 - version: 1.28.2 + version: 1.28.2(patch_hash=achcqszyir6qyqp6dbmjpwgjkq) playground/css-lightningcss-root: devDependencies: lightningcss: specifier: ^1.28.2 - version: 1.28.2 + version: 1.28.2(patch_hash=achcqszyir6qyqp6dbmjpwgjkq) playground/css-no-codesplit: {} @@ -2907,42 +2913,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.0': resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.0': resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.0': resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.0': resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.0': resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.0': resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} @@ -3083,61 +3083,51 @@ packages: resolution: {integrity: sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.28.1': resolution: {integrity: sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.28.1': resolution: {integrity: sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.28.1': resolution: {integrity: sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.28.1': resolution: {integrity: sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': resolution: {integrity: sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.28.1': resolution: {integrity: sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.28.1': resolution: {integrity: sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.28.1': resolution: {integrity: sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.28.1': resolution: {integrity: sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.28.1': resolution: {integrity: sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==} @@ -5357,28 +5347,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.28.2: resolution: {integrity: sha512-1SPG1ZTNnphWvAv8RVOymlZ8BDtAg69Hbo7n4QxARvkFVCJAt0cgjAw1Fox0WEhf4PwnyoOBaVH0Z5YNgzt4dA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.28.2: resolution: {integrity: sha512-ZhQy0FcO//INWUdo/iEdbefntTdpPVQ0XJwwtdbBuMQe+uxqZoytm9M+iqR9O5noWFaxK+nbS2iR/I80Q2Ofpg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.28.2: resolution: {integrity: sha512-alb/j1NMrgQmSFyzTbN1/pvMPM+gdDw7YBuQ5VSgcFDypN3Ah0BzC2dTZbzwzaMdUVDszX6zH5MzjfVN1oGuww==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.28.2: resolution: {integrity: sha512-WnwcjcBeAt0jGdjlgbT9ANf30pF0C/QMb1XnLnH272DQU8QXh+kmpi24R55wmWBwaTtNAETZ+m35ohyeMiNt+g==} @@ -11323,7 +11309,7 @@ snapshots: lightningcss-win32-x64-msvc@1.28.2: optional: true - lightningcss@1.28.2: + lightningcss@1.28.2(patch_hash=achcqszyir6qyqp6dbmjpwgjkq): dependencies: detect-libc: 1.0.3 optionalDependencies: