diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 27f95b939ae2db..7a2316a423c85f 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -2275,6 +2275,78 @@ const makeModernScssWorker = ( return worker } +const makeModernCompilerScssWorker = ( + resolvers: CSSAtImportResolvers, + alias: Alias[], + _maxWorkers: number | undefined, +) => { + let compiler: Sass.AsyncCompiler | undefined + + const worker: Awaited> = { + async run(sassPath, data, options) { + const { default: sass }: { default: typeof Sass } = await import(sassPath) + const path = await import('node:path') + const { fileURLToPath, pathToFileURL } = await import('node:url') + compiler ??= await sass.initAsyncCompiler() + + const sassOptions = { ...options } as Sass.StringOptions<'async'> + sassOptions.url = pathToFileURL(options.filename) + sassOptions.sourceMap = options.enableSourcemap + + const internalImporter: Sass.Importer<'async'> = { + async canonicalize(url, context) { + const importer = context.containingUrl + ? fileURLToPath(context.containingUrl) + : options.filename + const resolved = await resolvers.sass(url, cleanScssBugUrl(importer)) + return resolved ? pathToFileURL(resolved) : null + }, + async load(canonicalUrl) { + const ext = path.extname(canonicalUrl.pathname) + let syntax: Sass.Syntax = 'scss' + if (ext && ext.toLowerCase() === '.sass') { + syntax = 'indented' + } else if (ext && ext.toLowerCase() === '.css') { + syntax = 'css' + } + const result = await rebaseUrls( + fileURLToPath(canonicalUrl), + options.filename, + alias, + '$', + resolvers.sass, + ) + const contents = + result.contents ?? + (await fs.promises.readFile(result.file, 'utf-8')) + return { contents, syntax } + }, + } + sassOptions.importers = [ + ...(sassOptions.importers ?? []), + internalImporter, + ] + + const result = await compiler.compileStringAsync(data, sassOptions) + return { + css: result.css, + map: result.sourceMap ? JSON.stringify(result.sourceMap) : undefined, + stats: { + includedFiles: result.loadedUrls + .filter((url) => url.protocol === 'file:') + .map((url) => fileURLToPath(url)), + }, + } satisfies ScssWorkerResult + }, + async stop() { + compiler?.dispose() + compiler = undefined + }, + } + + return worker +} + type ScssWorkerResult = { css: string map?: string | undefined @@ -2298,9 +2370,11 @@ const scssProcessor = ( if (!workerMap.has(options.alias)) { workerMap.set( options.alias, - options.api === 'modern' - ? makeModernScssWorker(resolvers, options.alias, maxWorkers) - : makeScssWorker(resolvers, options.alias, maxWorkers), + options.api === 'modern-compiler' + ? makeModernCompilerScssWorker(resolvers, options.alias, maxWorkers) + : options.api === 'modern' + ? makeModernScssWorker(resolvers, options.alias, maxWorkers) + : makeScssWorker(resolvers, options.alias, maxWorkers), ) } const worker = workerMap.get(options.alias)! diff --git a/playground/css/__tests__/sass-modern-compiler/sass-modern-compiler.spec.ts b/playground/css/__tests__/sass-modern-compiler/sass-modern-compiler.spec.ts new file mode 100644 index 00000000000000..90a9b9774d3ebc --- /dev/null +++ b/playground/css/__tests__/sass-modern-compiler/sass-modern-compiler.spec.ts @@ -0,0 +1 @@ +import '../css.spec' diff --git a/playground/css/vite.config-sass-modern-compiler.js b/playground/css/vite.config-sass-modern-compiler.js new file mode 100644 index 00000000000000..9759d58506f597 --- /dev/null +++ b/playground/css/vite.config-sass-modern-compiler.js @@ -0,0 +1,31 @@ +import { defineConfig } from 'vite' +import baseConfig from './vite.config.js' + +export default defineConfig({ + ...baseConfig, + css: { + ...baseConfig.css, + preprocessorOptions: { + ...baseConfig.css.preprocessorOptions, + scss: { + api: 'modern-compiler', + additionalData: `$injectedColor: orange;`, + importers: [ + { + canonicalize(url) { + return url === 'virtual-dep' + ? new URL('custom-importer:virtual-dep') + : null + }, + load() { + return { + contents: ``, + syntax: 'scss', + } + }, + }, + ], + }, + }, + }, +}) diff --git a/playground/css/vite.config-sass-modern.js b/playground/css/vite.config-sass-modern.js index 9f7acb3a098179..9759d58506f597 100644 --- a/playground/css/vite.config-sass-modern.js +++ b/playground/css/vite.config-sass-modern.js @@ -8,7 +8,7 @@ export default defineConfig({ preprocessorOptions: { ...baseConfig.css.preprocessorOptions, scss: { - api: 'modern', + api: 'modern-compiler', additionalData: `$injectedColor: orange;`, importers: [ { diff --git a/playground/vitestGlobalSetup.ts b/playground/vitestGlobalSetup.ts index 1cf405f84120fd..b884df52160187 100644 --- a/playground/vitestGlobalSetup.ts +++ b/playground/vitestGlobalSetup.ts @@ -45,6 +45,10 @@ export async function setup({ provide }: GlobalSetupContext): Promise { path.resolve(tempDir, 'css'), path.resolve(tempDir, 'css__sass-modern'), ) + await fs.copy( + path.resolve(tempDir, 'css'), + path.resolve(tempDir, 'css__sass-modern-compiler'), + ) } export async function teardown(): Promise {