Skip to content

Commit 3bfe5c5

Browse files
authored
refactor(css)!: always use sass compiler API (#19978)
1 parent d0221cd commit 3bfe5c5

File tree

8 files changed

+18
-202
lines changed

8 files changed

+18
-202
lines changed

docs/config/shared-options.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ Note if an inline config is provided, Vite will not search for other PostCSS con
227227
Specify options to pass to CSS pre-processors. The file extensions are used as keys for the options. The supported options for each preprocessor can be found in their respective documentation:
228228

229229
- `sass`/`scss`:
230-
- Select the sass API to use with `api: "modern-compiler" | "modern"` (default `"modern-compiler"` if `sass-embedded` is installed, otherwise `"modern"`). For the best performance, it's recommended to use `api: "modern-compiler"` with the `sass-embedded` package.
230+
- Uses `sass-embedded` if installed, otherwise uses `sass`. For the best performance, it's recommended to install the `sass-embedded` package.
231231
- [Options](https://sass-lang.com/documentation/js-api/interfaces/stringoptions/)
232232
- `less`: [Options](https://lesscss.org/usage/#less-options).
233233
- `styl`/`stylus`: Only [`define`](https://stylus-lang.com/docs/js.html#define-name-node) is supported, which can be passed as an object.
@@ -247,7 +247,6 @@ export default defineConfig({
247247
},
248248
},
249249
scss: {
250-
api: 'modern-compiler', // or "modern"
251250
importers: [
252251
// ...
253252
],

packages/vite/src/node/plugins/css.ts

Lines changed: 16 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -2266,7 +2266,7 @@ type PreprocessorAdditionalData =
22662266

22672267
export type SassPreprocessorOptions = {
22682268
additionalData?: PreprocessorAdditionalData
2269-
} & ({ api?: 'modern' | 'modern-compiler' } & SassModernPreprocessBaseOptions)
2269+
} & SassModernPreprocessBaseOptions
22702270

22712271
export type LessPreprocessorOptions = {
22722272
additionalData?: PreprocessorAdditionalData
@@ -2389,153 +2389,30 @@ function cleanScssBugUrl(url: string) {
23892389

23902390
// #region Sass
23912391
// .scss/.sass processor
2392-
const makeModernScssWorker = (
2392+
const makeScssWorker = (
23932393
environment: PartialEnvironment,
23942394
resolvers: CSSAtImportResolvers,
2395-
maxWorkers: number | undefined,
2395+
_maxWorkers: number | undefined,
23962396
) => {
2397-
const internalCanonicalize = async (
2398-
url: string,
2399-
importer: string,
2400-
): Promise<string | null> => {
2401-
importer = cleanScssBugUrl(importer)
2402-
const resolved = await resolvers.sass(environment, url, importer)
2403-
return resolved ?? null
2404-
}
2405-
2406-
const skipRebaseUrls = (unquotedUrl: string, rawUrl: string) => {
2407-
const isQuoted = rawUrl[0] === '"' || rawUrl[0] === "'"
2408-
// matches `url($foo)`
2409-
if (!isQuoted && unquotedUrl[0] === '$') {
2410-
return true
2411-
}
2412-
// matches `url(#{foo})` and `url('#{foo}')`
2413-
return unquotedUrl.startsWith('#{')
2414-
}
2415-
2416-
const internalLoad = async (file: string, rootFile: string) => {
2417-
const result = await rebaseUrls(
2418-
environment,
2419-
file,
2420-
rootFile,
2421-
resolvers.sass,
2422-
skipRebaseUrls,
2423-
)
2424-
if (result.contents) {
2425-
return result.contents
2426-
}
2427-
return await fsp.readFile(result.file, 'utf-8')
2428-
}
2397+
let compilerPromise: Promise<Sass.AsyncCompiler> | undefined
24292398

2430-
const worker = new WorkerWithFallback(
2431-
() =>
2432-
async (
2399+
// we use the compiler api provided by sass
2400+
// instead of creating a worker pool on our own
2401+
type WorkerType = InstanceType<
2402+
typeof WorkerWithFallback<
2403+
[
24332404
sassPath: string,
24342405
data: string,
24352406
// additionalData can a function that is not cloneable but it won't be used
24362407
options: SassStylePreprocessorInternalOptions & {
2437-
api: 'modern'
24382408
additionalData: undefined
24392409
},
2440-
) => {
2441-
// eslint-disable-next-line no-restricted-globals -- this function runs inside a cjs worker
2442-
const sass: typeof Sass = require(sassPath)
2443-
// eslint-disable-next-line no-restricted-globals
2444-
const path: typeof import('node:path') = require('node:path')
2445-
2446-
const { fileURLToPath, pathToFileURL }: typeof import('node:url') =
2447-
// eslint-disable-next-line no-restricted-globals
2448-
require('node:url')
2449-
2450-
const sassOptions = { ...options } as Sass.StringOptions<'async'>
2451-
sassOptions.url = pathToFileURL(options.filename)
2452-
sassOptions.sourceMap = options.enableSourcemap
2453-
2454-
const internalImporter: Sass.Importer<'async'> = {
2455-
async canonicalize(url, context) {
2456-
const importer = context.containingUrl
2457-
? fileURLToPath(context.containingUrl)
2458-
: options.filename
2459-
const resolved = await internalCanonicalize(url, importer)
2460-
if (
2461-
resolved &&
2462-
// only limit to these extensions because:
2463-
// - for the `@import`/`@use`s written in file loaded by `load` function,
2464-
// the `canonicalize` function of that `importer` is called first
2465-
// - the `load` function of an importer is only called for the importer
2466-
// that returned a non-null result from its `canonicalize` function
2467-
(resolved.endsWith('.css') ||
2468-
resolved.endsWith('.scss') ||
2469-
resolved.endsWith('.sass'))
2470-
) {
2471-
return pathToFileURL(resolved)
2472-
}
2473-
return null
2474-
},
2475-
async load(canonicalUrl) {
2476-
const ext = path.extname(canonicalUrl.pathname)
2477-
let syntax: Sass.Syntax = 'scss'
2478-
if (ext === '.sass') {
2479-
syntax = 'indented'
2480-
} else if (ext === '.css') {
2481-
syntax = 'css'
2482-
}
2483-
const contents = await internalLoad(
2484-
fileURLToPath(canonicalUrl),
2485-
options.filename,
2486-
)
2487-
return { contents, syntax, sourceMapUrl: canonicalUrl }
2488-
},
2489-
}
2490-
sassOptions.importers = [
2491-
...(sassOptions.importers ?? []),
2492-
internalImporter,
2493-
]
2494-
2495-
const result = await sass.compileStringAsync(data, sassOptions)
2496-
return {
2497-
css: result.css,
2498-
map: result.sourceMap ? JSON.stringify(result.sourceMap) : undefined,
2499-
stats: {
2500-
includedFiles: result.loadedUrls
2501-
.filter((url) => url.protocol === 'file:')
2502-
.map((url) => fileURLToPath(url)),
2503-
},
2504-
} satisfies ScssWorkerResult
2505-
},
2506-
{
2507-
parentFunctions: {
2508-
internalCanonicalize,
2509-
internalLoad,
2510-
},
2511-
shouldUseFake(_sassPath, _data, options) {
2512-
// functions and importer is a function and is not serializable
2513-
// in that case, fallback to running in main thread
2514-
return !!(
2515-
(options.functions && Object.keys(options.functions).length > 0) ||
2516-
(options.importers &&
2517-
(!Array.isArray(options.importers) ||
2518-
options.importers.length > 0)) ||
2519-
options.logger
2520-
)
2521-
},
2522-
max: maxWorkers,
2523-
},
2524-
)
2525-
return worker
2526-
}
2527-
2528-
// this is mostly a copy&paste of makeModernScssWorker
2529-
// however sharing code between two is hard because
2530-
// makeModernScssWorker above needs function inlined for worker.
2531-
const makeModernCompilerScssWorker = (
2532-
environment: PartialEnvironment,
2533-
resolvers: CSSAtImportResolvers,
2534-
_maxWorkers: number | undefined,
2535-
) => {
2536-
let compilerPromise: Promise<Sass.AsyncCompiler> | undefined
2410+
],
2411+
ScssWorkerResult
2412+
>
2413+
>
25372414

2538-
const worker: Awaited<ReturnType<typeof makeModernScssWorker>> = {
2415+
const worker: WorkerType = {
25392416
async run(sassPath, data, options) {
25402417
// need pathToFileURL for windows since import("D:...") fails
25412418
// https://github.com/nodejs/node/issues/31710
@@ -2632,25 +2509,15 @@ type ScssWorkerResult = {
26322509
const scssProcessor = (
26332510
maxWorkers: number | undefined,
26342511
): StylePreprocessor<SassStylePreprocessorInternalOptions> => {
2635-
let worker:
2636-
| ReturnType<
2637-
typeof makeModernScssWorker | typeof makeModernCompilerScssWorker
2638-
>
2639-
| undefined
2512+
let worker: ReturnType<typeof makeScssWorker> | undefined
26402513

26412514
return {
26422515
close() {
26432516
worker?.stop()
26442517
},
26452518
async process(environment, source, root, options, resolvers) {
26462519
const sassPackage = loadSassPackage(root)
2647-
const api =
2648-
options.api ??
2649-
(sassPackage.name === 'sass-embedded' ? 'modern-compiler' : 'modern')
2650-
worker ??=
2651-
api === 'modern-compiler'
2652-
? makeModernCompilerScssWorker(environment, resolvers, maxWorkers)
2653-
: makeModernScssWorker(environment, resolvers, maxWorkers)
2520+
worker ??= makeScssWorker(environment, resolvers, maxWorkers)
26542521

26552522
const { content: data, map: additionalMap } = await getSource(
26562523
source,
@@ -2667,7 +2534,6 @@ const scssProcessor = (
26672534
const result = await worker.run(
26682535
sassPackage.path,
26692536
data,
2670-
// @ts-expect-error the correct worker is selected for `options.type`
26712537
optionsWithoutAdditionalData,
26722538
)
26732539
const deps = result.stats.includedFiles.map((f) => cleanScssBugUrl(f))

playground/css-sourcemap/__tests__/sass-modern/sass-modern.spec.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

playground/css-sourcemap/vite.config-sass-modern.js

Lines changed: 0 additions & 15 deletions
This file was deleted.

playground/css/__tests__/sass-modern/sass-modern.spec.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

playground/css/vite.config-sass-modern-compiler-build.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,4 @@ export default defineConfig({
1717
},
1818
},
1919
},
20-
css: {
21-
preprocessorOptions: {
22-
scss: {
23-
api: 'modern-compiler',
24-
},
25-
},
26-
},
2720
})

playground/css/vite.config-sass-modern.js

Lines changed: 0 additions & 18 deletions
This file was deleted.

playground/vitestGlobalSetup.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ export async function setup({ provide }: TestProject): Promise<void> {
4343
})
4444
// also setup dedicated copy for "variant" tests
4545
for (const [original, variants] of [
46-
['css', ['sass-modern', 'lightningcss']],
47-
['css-sourcemap', ['sass-modern']],
46+
['css', ['lightningcss']],
4847
['transform-plugin', ['base']],
4948
] as const) {
5049
for (const variant of variants) {

0 commit comments

Comments
 (0)