Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(css): support sass compiler api and sass-embedded package #17754

Merged
merged 59 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
4948f86
feat: support sass modern api
hi-ogawa Jul 20, 2024
0fea9c4
test: add playground
hi-ogawa Jul 20, 2024
2feb149
wip: sassInternalImporter
hi-ogawa Jul 20, 2024
e572b9f
test: add custom importer example
hi-ogawa Jul 20, 2024
6619ade
test: test internal importer
hi-ogawa Jul 20, 2024
fba1d4d
chore: cleanup playground
hi-ogawa Jul 20, 2024
cbfe70d
wip: rebaseUrls
hi-ogawa Jul 20, 2024
b515950
test: test "modern" in playground/css
hi-ogawa Jul 21, 2024
f04f067
chore: cleanup
hi-ogawa Jul 21, 2024
f8154b1
debug
hi-ogawa Jul 21, 2024
1d5ce19
fix: fix stats.includedFiles
hi-ogawa Jul 21, 2024
af7e6dc
chore: cleanup
hi-ogawa Jul 21, 2024
659cd97
chore: remove playground/css-sass-modern
hi-ogawa Jul 21, 2024
8484025
chore: lockfile
hi-ogawa Jul 21, 2024
073392b
docs: update css.preprocessorOptions
hi-ogawa Jul 21, 2024
b0dac17
chore: comment
hi-ogawa Jul 21, 2024
61bc170
chore: tweak
hi-ogawa Jul 21, 2024
1ff2110
refactor: move to makeModernScssWorker
hi-ogawa Jul 23, 2024
e930f1d
chore: USE_LEGACY_SCSS flag in playground/css
hi-ogawa Jul 23, 2024
098f181
chore: cleanup old code
hi-ogawa Jul 23, 2024
96a7336
ci: test sass legacy
hi-ogawa Jul 23, 2024
c2a6256
ci: filter more
hi-ogawa Jul 23, 2024
35bf91a
test: split vite config
hi-ogawa Jul 23, 2024
a9a7038
chore: no console
hi-ogawa Jul 23, 2024
ddd3c14
test: separate `testDir` for variant tests
hi-ogawa Jul 23, 2024
6adda0e
chore: cleanup
hi-ogawa Jul 23, 2024
6e847c0
ci: cleanup
hi-ogawa Jul 23, 2024
3bf2dc1
refactor: fsp + remove toLowerCase
hi-ogawa Jul 24, 2024
c2f82f4
feat(css): support sass compiler api
hi-ogawa Jul 23, 2024
0d314c2
test: copy test
hi-ogawa Jul 23, 2024
c823f43
chore: typo
hi-ogawa Jul 24, 2024
f1435ad
chore: add sass-embedded as optional peer
hi-ogawa Jul 24, 2024
aeee777
feat: try loading sass-embedded before sass
hi-ogawa Jul 24, 2024
0aac866
docs: update
hi-ogawa Jul 29, 2024
79f0f80
docs: tweak
hi-ogawa Jul 29, 2024
c0460f8
wip: how about no sass-embedded?
hi-ogawa Jul 29, 2024
297eb95
Revert "wip: how about no sass-embedded?"
hi-ogawa Jul 29, 2024
52fa97a
test: debug windows
hi-ogawa Jul 29, 2024
409f713
Revert "test: debug windows"
hi-ogawa Jul 29, 2024
fbd5144
Merge branch 'main' into feat-sass-compiler
hi-ogawa Jul 29, 2024
4be7db6
fix: fix fs and path
hi-ogawa Jul 29, 2024
2be6934
Reapply "test: debug windows"
hi-ogawa Jul 29, 2024
2125ef6
Merge branch 'main' into feat-sass-modern
hi-ogawa Jul 29, 2024
c1df7b6
fix: fix fs, path
hi-ogawa Jul 29, 2024
87fb173
Merge branch 'main' into feat-sass-modern
hi-ogawa Jul 30, 2024
609233a
Merge branch 'feat-sass-modern' into feat-sass-compiler
hi-ogawa Jul 30, 2024
f730495
wip: what about build?
hi-ogawa Jul 30, 2024
2a7db07
chore: cleanup
hi-ogawa Jul 30, 2024
64a3f22
wip: debug
hi-ogawa Jul 30, 2024
b514e46
wip: debug
hi-ogawa Jul 30, 2024
1811681
chore: cleanup
hi-ogawa Jul 30, 2024
ce90a9e
wip: debug
hi-ogawa Jul 30, 2024
7829fe3
fix: replace `import` with `require` for Windows?
hi-ogawa Jul 30, 2024
57f48f7
chore: revert debug
hi-ogawa Jul 30, 2024
b432b0c
refactor: replace createRequire
hi-ogawa Jul 30, 2024
7c05d81
chore: revert debug test
hi-ogawa Jul 30, 2024
a150d5a
Merge branch 'main' into feat-sass-compiler
hi-ogawa Jul 30, 2024
320b5a0
fix: apply review
hi-ogawa Jul 30, 2024
81687e3
refactor: fix any
hi-ogawa Jul 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/config/shared-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ Note if an inline config is provided, Vite will not search for other PostCSS con

Specify options to pass to CSS pre-processors. The file extensions are used as keys for the options. The supported options for each preprocessors can be found in their respective documentation:

- `sass`/`scss` - top level option `api: "legacy" | "modern"` (default `"legacy"`) allows switching which sass API to use. [Options (legacy)](https://sass-lang.com/documentation/js-api/interfaces/LegacyStringOptions), [Options (modern)](https://sass-lang.com/documentation/js-api/interfaces/stringoptions/).
- `sass`/`scss` - top level option `api: "legacy" | "modern" | "modern-compiler"` (default `"legacy"`) allows switching which sass API to use. For the best performance, it's recommended to use `api: "modern-compiler"` with `sass-embedded` package. [Options (legacy)](https://sass-lang.com/documentation/js-api/interfaces/LegacyStringOptions), [Options (modern)](https://sass-lang.com/documentation/js-api/interfaces/stringoptions/).
- `less` - [Options](https://lesscss.org/usage/#less-options).
- `styl`/`stylus` - Only [`define`](https://stylus-lang.com/docs/js.html#define-name-node) is supported, which can be passed as an object.

Expand All @@ -244,7 +244,7 @@ export default defineConfig({
},
},
scss: {
api: 'modern', // or "legacy"
api: 'modern-compiler', // or "modern", "legacy"
importers: [
// ...
],
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ That said, Vite does provide built-in support for `.scss`, `.sass`, `.less`, `.s

```bash
# .scss and .sass
npm add -D sass
npm add -D sass-embedded # or sass

# .less
npm add -D less
Expand Down
5 changes: 5 additions & 0 deletions packages/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
"rollup-plugin-esbuild": "^6.1.1",
"rollup-plugin-license": "^3.5.2",
"sass": "^1.77.8",
"sass-embedded": "^1.77.8",
"sirv": "^2.0.4",
"source-map-support": "^0.5.21",
"strip-ansi": "^7.1.0",
Expand All @@ -157,6 +158,7 @@
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
Expand All @@ -168,6 +170,9 @@
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
Expand Down
117 changes: 109 additions & 8 deletions packages/vite/src/node/plugins/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fs from 'node:fs'
import fsp from 'node:fs/promises'
import path from 'node:path'
import { createRequire } from 'node:module'
import { fileURLToPath, pathToFileURL } from 'node:url'
import glob from 'fast-glob'
import postcssrc from 'postcss-load-config'
import type {
Expand Down Expand Up @@ -1945,7 +1946,9 @@ type StylePreprocessorOptions = {
}

type SassStylePreprocessorOptions = StylePreprocessorOptions &
Omit<Sass.LegacyOptions<'async'>, 'data' | 'file' | 'outFile'>
Omit<Sass.LegacyOptions<'async'>, 'data' | 'file' | 'outFile'> & {
api?: 'legacy' | 'modern' | 'modern-compiler'
}

type StylusStylePreprocessorOptions = StylePreprocessorOptions & {
define?: Record<string, any>
Expand Down Expand Up @@ -1990,11 +1993,11 @@ export interface StylePreprocessorResults {
}

const loadedPreprocessorPath: Partial<
Record<PreprocessLang | PostCssDialectLang, string>
Record<PreprocessLang | PostCssDialectLang | 'sass-embedded', string>
> = {}

function loadPreprocessorPath(
lang: PreprocessLang | PostCssDialectLang,
lang: PreprocessLang | PostCssDialectLang | 'sass-embedded',
root: string,
): string {
const cached = loadedPreprocessorPath[lang]
Expand All @@ -2020,6 +2023,24 @@ function loadPreprocessorPath(
}
}

function loadSassPackage(root: string): {
name: 'sass' | 'sass-embedded'
path: string
} {
// try sass-embedded before sass
try {
const path = loadPreprocessorPath('sass-embedded', root)
return { name: 'sass-embedded', path }
} catch (e1) {
try {
const path = loadPreprocessorPath(PreprocessLang.sass, root)
return { name: 'sass', path }
} catch (e2) {
throw e1
}
}
}

let cachedSss: any
function loadSss(root: string) {
if (cachedSss) return cachedSss
Expand Down Expand Up @@ -2277,6 +2298,81 @@ const makeModernScssWorker = (
return worker
}

// this is mostly a copy&paste of makeModernScssWorker
// however sharing code between two is hard because
// makeModernScssWorker above needs function inlined for worker.
const makeModernCompilerScssWorker = (
resolvers: CSSAtImportResolvers,
alias: Alias[],
_maxWorkers: number | undefined,
) => {
let compiler: Sass.AsyncCompiler | undefined

const worker: Awaited<ReturnType<typeof makeModernScssWorker>> = {
async run(sassPath, data, options) {
// need pathToFileURL for windows since import("D:...") fails
// https://github.com/nodejs/node/issues/31710
const sass: typeof Sass = (await import(pathToFileURL(sassPath).href))
.default
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 === '.sass') {
syntax = 'indented'
} else if (ext === '.css') {
syntax = 'css'
}
const result = await rebaseUrls(
fileURLToPath(canonicalUrl),
options.filename,
alias,
'$',
resolvers.sass,
)
const contents =
result.contents ?? (await fsp.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
Expand All @@ -2295,14 +2391,19 @@ const scssProcessor = (
}
},
async process(source, root, options, resolvers) {
const sassPath = loadPreprocessorPath(PreprocessLang.sass, root)
const sassPackage = loadSassPackage(root)
// TODO: change default in v6
// options.api ?? sassPackage.name === "sass-embedded" ? "modern-compiler" : "modern";
const api = options.api ?? 'legacy'

if (!workerMap.has(options.alias)) {
workerMap.set(
options.alias,
options.api === 'modern'
? makeModernScssWorker(resolvers, options.alias, maxWorkers)
: makeScssWorker(resolvers, options.alias, maxWorkers),
api === 'modern-compiler'
? makeModernCompilerScssWorker(resolvers, options.alias, maxWorkers)
: api === 'modern'
? makeModernScssWorker(resolvers, options.alias, maxWorkers)
: makeScssWorker(resolvers, options.alias, maxWorkers),
)
}
const worker = workerMap.get(options.alias)!
Expand All @@ -2320,7 +2421,7 @@ const scssProcessor = (
}
try {
const result = await worker.run(
sassPath,
sassPackage.path,
data,
optionsWithoutAdditionalData,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '../css.spec'
31 changes: 31 additions & 0 deletions playground/css/vite.config-sass-modern-compiler.js
Original file line number Diff line number Diff line change
@@ -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',
}
},
},
],
},
},
},
})
5 changes: 5 additions & 0 deletions playground/vitestGlobalSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ export async function setup({ provide }: GlobalSetupContext): Promise<void> {
path.resolve(tempDir, 'css__sass-modern'),
{ recursive: true },
)
await fs.cp(
path.resolve(tempDir, 'css'),
path.resolve(tempDir, 'css__sass-modern-compiler'),
{ recursive: true },
)
}

export async function teardown(): Promise<void> {
Expand Down
Loading