diff --git a/packages/playground/sass-modern/css-dep/index.css b/packages/playground/sass-modern/css-dep/index.css
new file mode 100644
index 00000000000000..0fdcd8118ad54c
--- /dev/null
+++ b/packages/playground/sass-modern/css-dep/index.css
@@ -0,0 +1,3 @@
+.css-dep {
+ color: purple;
+}
diff --git a/packages/playground/sass-modern/css-dep/index.js b/packages/playground/sass-modern/css-dep/index.js
new file mode 100644
index 00000000000000..47b55353d03edb
--- /dev/null
+++ b/packages/playground/sass-modern/css-dep/index.js
@@ -0,0 +1 @@
+throw new Error('should not be imported')
diff --git a/packages/playground/sass-modern/css-dep/index.scss b/packages/playground/sass-modern/css-dep/index.scss
new file mode 100644
index 00000000000000..97a24aa0551eb9
--- /dev/null
+++ b/packages/playground/sass-modern/css-dep/index.scss
@@ -0,0 +1,3 @@
+.css-dep-sass {
+ color: orange;
+}
diff --git a/packages/playground/sass-modern/css-dep/index.styl b/packages/playground/sass-modern/css-dep/index.styl
new file mode 100644
index 00000000000000..68170594a118a9
--- /dev/null
+++ b/packages/playground/sass-modern/css-dep/index.styl
@@ -0,0 +1,2 @@
+.css-dep-stylus
+ color red
diff --git a/packages/playground/sass-modern/css-dep/package.json b/packages/playground/sass-modern/css-dep/package.json
new file mode 100644
index 00000000000000..451510a672485f
--- /dev/null
+++ b/packages/playground/sass-modern/css-dep/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "css-dep",
+ "private": true,
+ "version": "1.0.0",
+ "main": "index.js",
+ "style": "index.css",
+ "sass": "index.scss"
+}
diff --git a/packages/playground/sass-modern/index.html b/packages/playground/sass-modern/index.html
new file mode 100644
index 00000000000000..ae5072ed60655e
--- /dev/null
+++ b/packages/playground/sass-modern/index.html
@@ -0,0 +1,10 @@
+
+
SASS: This should be orange
+
Imported SASS string:
+
+
+ @import dependency w/ sass enrtrypoints: this should be orange
+
+
+
+
diff --git a/packages/playground/sass-modern/main.js b/packages/playground/sass-modern/main.js
new file mode 100644
index 00000000000000..166ecf89244b99
--- /dev/null
+++ b/packages/playground/sass-modern/main.js
@@ -0,0 +1,6 @@
+import sass from './sass.scss'
+text('.imported-sass', sass)
+
+function text(el, text) {
+ document.querySelector(el).textContent = text
+}
diff --git a/packages/playground/sass-modern/package.json b/packages/playground/sass-modern/package.json
new file mode 100644
index 00000000000000..ec0d7f27a9af69
--- /dev/null
+++ b/packages/playground/sass-modern/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "test-css",
+ "private": true,
+ "version": "0.0.0",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../vite/bin/vite",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "sass": "^1.49.9",
+ "cross-env": "^7.0.3"
+ }
+}
diff --git a/packages/playground/sass-modern/sass.scss b/packages/playground/sass-modern/sass.scss
new file mode 100644
index 00000000000000..10bb68947612a1
--- /dev/null
+++ b/packages/playground/sass-modern/sass.scss
@@ -0,0 +1,6 @@
+@import 'css-dep'; // package w/ sass entry points
+
+.sass {
+ /* injected via vite.config.js */
+ color: $injectedColor;
+}
diff --git a/packages/playground/sass-modern/vite.config.js b/packages/playground/sass-modern/vite.config.js
new file mode 100644
index 00000000000000..afe13064352180
--- /dev/null
+++ b/packages/playground/sass-modern/vite.config.js
@@ -0,0 +1,15 @@
+const path = require('path')
+
+/**
+ * @type {import('vite').UserConfig}
+ */
+module.exports = {
+ css: {
+ preprocessorOptions: {
+ scss: {
+ api: 'modern',
+ additionalData: `$injectedColor: orange;`
+ }
+ }
+ }
+}
diff --git a/packages/vite/package.json b/packages/vite/package.json
index 7b6f8357a84bce..85fb01e5b7a8aa 100644
--- a/packages/vite/package.json
+++ b/packages/vite/package.json
@@ -73,7 +73,6 @@
"@types/mime": "^2.0.3",
"@types/node": "^16.11.22",
"@types/resolve": "^1.20.1",
- "@types/sass": "~1.43.1",
"@types/stylus": "^0.48.36",
"@types/ws": "^8.2.2",
"@vue/compiler-dom": "^3.2.30",
@@ -108,6 +107,7 @@
"postcss-modules": "^4.3.0",
"resolve.exports": "^1.1.0",
"rollup-plugin-license": "^2.6.1",
+ "sass": "~1.49.9",
"sirv": "^2.0.2",
"source-map-support": "^0.5.21",
"strip-ansi": "^6.0.1",
diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts
index d513c88420b6ff..5351e532390a3e 100644
--- a/packages/vite/src/node/plugins/css.ts
+++ b/packages/vite/src/node/plugins/css.ts
@@ -44,6 +44,7 @@ import type { Alias } from 'types/alias'
import type { ModuleNode } from '../server/moduleGraph'
import { transform, formatMessages } from 'esbuild'
import { addToHTMLProxyTransformResult } from './html'
+import { pathToFileURL, fileURLToPath } from 'url'
// const debug = createDebugger('vite:css')
@@ -1007,9 +1008,14 @@ type StylePreprocessorOptions = {
additionalData?: PreprocessorAdditionalData
filename: string
alias: Alias[]
+ api: 'legacy' | 'modern'
}
-type SassStylePreprocessorOptions = StylePreprocessorOptions & Sass.Options
+type SassStylePreprocessorOptions = StylePreprocessorOptions &
+ Sass.StringOptions<'sync'>
+
+type SassStylePreprocessorLegacyOptions = StylePreprocessorOptions &
+ Sass.LegacyOptions<'sync'>
type StylePreprocessor = (
source: string,
@@ -1021,7 +1027,7 @@ type StylePreprocessor = (
type SassStylePreprocessor = (
source: string,
root: string,
- options: SassStylePreprocessorOptions,
+ options: SassStylePreprocessorOptions | SassStylePreprocessorLegacyOptions,
resolvers: CSSAtImportResolvers
) => StylePreprocessorResults | Promise
@@ -1073,55 +1079,112 @@ const scss: SassStylePreprocessor = async (
options,
resolvers
) => {
- const render = loadPreprocessor(PreprocessLang.sass, root).render
- const internalImporter: Sass.Importer = (url, importer, done) => {
- resolvers.sass(url, importer).then((resolved) => {
- if (resolved) {
- rebaseUrls(resolved, options.filename, options.alias)
- .then((data) => done?.(data))
- .catch((data) => done?.(data))
- } else {
- done?.(null)
+ const isModern = options.api === 'modern'
+
+ if (isModern) {
+ const compileString = loadPreprocessor(
+ PreprocessLang.sass,
+ root
+ ).compileString
+
+ // TODO: we can't execute vite's internal resolver because we don't have prev in new importer API (as we have in old)
+ const internalImporter: Sass.Importer = {
+ canonicalize(url, options) {
+ return null
+ },
+ load(canonicalUrl) {
+ return null
}
- })
- }
- const importer = [internalImporter]
- if (options.importer) {
- Array.isArray(options.importer)
- ? importer.push(...options.importer)
- : importer.push(options.importer)
- }
+ }
+ const importers = [internalImporter]
+ if (options.importers && Array.isArray(options.importers)) {
+ importers.push(...options.importers)
+ }
- const finalOptions: Sass.Options = {
- ...options,
- data: await getSource(source, options.filename, options.additionalData),
- file: options.filename,
- outFile: options.filename,
- importer
- }
+ if (typeof options.syntax === 'undefined') {
+ const ext = path.extname(options.filename)
- try {
- const result = await new Promise((resolve, reject) => {
- render(finalOptions, (err, res) => {
- if (err) {
- reject(err)
+ if (ext && ext.toLowerCase() === '.scss') {
+ options.syntax = 'scss'
+ } else if (ext && ext.toLowerCase() === '.sass') {
+ options.syntax = 'indented'
+ } else if (ext && ext.toLowerCase() === '.css') {
+ options.syntax = 'css'
+ }
+ }
+
+ const finalOptions: Sass.StringOptions<'sync'> = {
+ ...(options as Sass.StringOptions<'sync'>),
+ url: pathToFileURL(options.filename),
+ importers
+ }
+
+ try {
+ const result = compileString(
+ await getSource(source, options.filename, options.additionalData),
+ finalOptions
+ )
+ const deps = result.loadedUrls.map((url) => fileURLToPath(url))
+
+ return {
+ code: result.css,
+ errors: [],
+ deps
+ }
+ } catch (e) {
+ return { code: '', errors: [e], deps: [] }
+ }
+ } else {
+ const render = loadPreprocessor(PreprocessLang.sass, root).render
+ const internalImporter: Sass.LegacyImporter = (url, importer, done) => {
+ resolvers.sass(url, importer).then((resolved) => {
+ if (resolved) {
+ rebaseUrls(resolved, options.filename, options.alias)
+ .then((data) => done?.(data))
+ .catch((data) => done?.(data))
} else {
- resolve(res)
+ done?.(null)
}
})
- })
- const deps = result.stats.includedFiles
+ }
+ const importer = [internalImporter]
+ if (options.importer) {
+ Array.isArray(options.importer)
+ ? importer.push(...options.importer)
+ : importer.push(options.importer)
+ }
- return {
- code: result.css.toString(),
- errors: [],
- deps
+ const finalOptions: Sass.LegacyOptions<'async'> = {
+ ...(options as Sass.LegacyOptions<'sync'>),
+ data: await getSource(source, options.filename, options.additionalData),
+ file: options.filename,
+ outFile: options.filename,
+ importer
+ }
+
+ try {
+ const result = await new Promise((resolve, reject) => {
+ render(finalOptions, (err, res) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve(res!)
+ }
+ })
+ })
+ const deps = result.stats.includedFiles
+
+ return {
+ code: result.css.toString(),
+ errors: [],
+ deps
+ }
+ } catch (e) {
+ // normalize SASS error
+ e.id = e.file
+ e.frame = e.formatted
+ return { code: '', errors: [e], deps: [] }
}
- } catch (e) {
- // normalize SASS error
- e.id = e.file
- e.frame = e.formatted
- return { code: '', errors: [e], deps: [] }
}
}
@@ -1144,7 +1207,7 @@ async function rebaseUrls(
file: string,
rootFile: string,
alias: Alias[]
-): Promise {
+): Promise {
file = path.resolve(file) // ensure os-specific flashes
// in the same dir, no need to rebase
const fileDir = path.dirname(file)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 58c1ee050f7c26..e4b8f16fc10ba8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -478,6 +478,17 @@ importers:
packages/playground/resolve/inline-package:
specifiers: {}
+ packages/playground/sass-modern:
+ specifiers:
+ cross-env: ^7.0.3
+ sass: ^1.49.9
+ devDependencies:
+ cross-env: 7.0.3
+ sass: 1.49.9
+
+ packages/playground/sass-modern/css-dep:
+ specifiers: {}
+
packages/playground/ssr-deps:
specifiers:
bcrypt: ^5.0.1
@@ -783,7 +794,6 @@ importers:
'@types/mime': ^2.0.3
'@types/node': ^16.11.22
'@types/resolve': ^1.20.1
- '@types/sass': ~1.43.1
'@types/stylus': ^0.48.36
'@types/ws': ^8.2.2
'@vue/compiler-dom': ^3.2.30
@@ -823,6 +833,7 @@ importers:
resolve.exports: ^1.1.0
rollup: ^2.59.0
rollup-plugin-license: ^2.6.1
+ sass: ~1.49.9
sirv: ^2.0.2
source-map-support: ^0.5.21
strip-ansi: ^6.0.1
@@ -860,7 +871,6 @@ importers:
'@types/mime': 2.0.3
'@types/node': 16.11.22
'@types/resolve': 1.20.1
- '@types/sass': 1.43.1
'@types/stylus': 0.48.36
'@types/ws': 8.2.2
'@vue/compiler-dom': 3.2.30
@@ -895,6 +905,7 @@ importers:
postcss-modules: 4.3.0_postcss@8.4.6
resolve.exports: 1.1.0
rollup-plugin-license: 2.6.1_rollup@2.62.0
+ sass: 1.49.9
sirv: 2.0.2
source-map-support: 0.5.21
strip-ansi: 6.0.1
@@ -2462,12 +2473,6 @@ packages:
resolution: {integrity: sha512-Ku5+GPFa12S3W26Uwtw+xyrtIpaZsGYHH6zxNbZlstmlvMYSZRzOwzwsXbxlVUbHyUucctSyuFtu6bNxwYomIw==}
dev: true
- /@types/sass/1.43.1:
- resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
- dependencies:
- '@types/node': 16.11.22
- dev: true
-
/@types/semver/7.3.9:
resolution: {integrity: sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==}
dev: true
@@ -8101,9 +8106,19 @@ packages:
engines: {node: '>=8.9.0'}
hasBin: true
dependencies:
- chokidar: 3.5.2
+ chokidar: 3.5.3
immutable: 4.0.0
- source-map-js: 1.0.1
+ source-map-js: 1.0.2
+ dev: true
+
+ /sass/1.49.9:
+ resolution: {integrity: sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==}
+ engines: {node: '>=12.0.0'}
+ hasBin: true
+ dependencies:
+ chokidar: 3.5.3
+ immutable: 4.0.0
+ source-map-js: 1.0.2
dev: true
/sax/1.2.4:
@@ -8315,11 +8330,6 @@ packages:
smart-buffer: 4.2.0
dev: true
- /source-map-js/1.0.1:
- resolution: {integrity: sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==}
- engines: {node: '>=0.10.0'}
- dev: true
-
/source-map-js/1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}