diff --git a/packages/playground/css/__tests__/css.spec.ts b/packages/playground/css/__tests__/css.spec.ts
index c37c52eabdf52a..360e46dbbba150 100644
--- a/packages/playground/css/__tests__/css.spec.ts
+++ b/packages/playground/css/__tests__/css.spec.ts
@@ -364,3 +364,8 @@ test('minify css', async () => {
expect(cssFile).toMatch('rgba(')
expect(cssFile).not.toMatch('#ffff00b3')
})
+
+test('import css in less', async () => {
+ expect(await getColor('.css-in-less')).toBe('yellow')
+ expect(await getColor('.css-in-less-2')).toBe('blue')
+})
diff --git a/packages/playground/css/index.html b/packages/playground/css/index.html
index 4de35d66441bee..acbbe44a7f8a60 100644
--- a/packages/playground/css/index.html
+++ b/packages/playground/css/index.html
@@ -105,6 +105,18 @@
CSS
Inlined import - this should NOT be red.
+
+
+ test import css in less, this color will be yellow
+
+
+ test for import less in less, this color will be blue
+
+
+
+ test import css in scss, this color will be orange
+
+
diff --git a/packages/playground/css/less.less b/packages/playground/css/less.less
index f8870e06f3a72c..69ffa830862014 100644
--- a/packages/playground/css/less.less
+++ b/packages/playground/css/less.less
@@ -1,4 +1,5 @@
@import '@/nested/nested';
+@import './nested/css-in-less.less';
@color: blue;
diff --git a/packages/playground/css/nested/_index.scss b/packages/playground/css/nested/_index.scss
index 6f2103c79fc2c8..48d630b573ae1b 100644
--- a/packages/playground/css/nested/_index.scss
+++ b/packages/playground/css/nested/_index.scss
@@ -1,3 +1,5 @@
+@import './css-in-scss.css';
+
.sass-at-import {
color: olive;
background: url(./icon.png) 10px no-repeat;
diff --git a/packages/playground/css/nested/css-in-less-2.less b/packages/playground/css/nested/css-in-less-2.less
new file mode 100644
index 00000000000000..443d17da34c0da
--- /dev/null
+++ b/packages/playground/css/nested/css-in-less-2.less
@@ -0,0 +1,3 @@
+.css-in-less-2 {
+ color: blue;
+}
diff --git a/packages/playground/css/nested/css-in-less.css b/packages/playground/css/nested/css-in-less.css
new file mode 100644
index 00000000000000..b174a601b1356c
--- /dev/null
+++ b/packages/playground/css/nested/css-in-less.css
@@ -0,0 +1,3 @@
+.css-in-less {
+ color: yellow;
+}
diff --git a/packages/playground/css/nested/css-in-less.less b/packages/playground/css/nested/css-in-less.less
new file mode 100644
index 00000000000000..abdd904b43016a
--- /dev/null
+++ b/packages/playground/css/nested/css-in-less.less
@@ -0,0 +1,4 @@
+@import url('./css-in-less.css');
+@import './css-in-less.css';
+
+@import './css-in-less-2.less';
diff --git a/packages/playground/css/nested/css-in-scss.css b/packages/playground/css/nested/css-in-scss.css
new file mode 100644
index 00000000000000..a63e49e4d6a1fd
--- /dev/null
+++ b/packages/playground/css/nested/css-in-scss.css
@@ -0,0 +1,3 @@
+.css-in-scss {
+ color: orange;
+}
diff --git a/packages/playground/css/vite.config.js b/packages/playground/css/vite.config.js
index e4dc8d5a9f265f..53d001d8387989 100644
--- a/packages/playground/css/vite.config.js
+++ b/packages/playground/css/vite.config.js
@@ -1,4 +1,5 @@
const path = require('path')
+
/**
* @type {import('vite').UserConfig}
*/
diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts
index cb62b785732e0d..780a82c900b9dd 100644
--- a/packages/vite/src/node/plugins/css.ts
+++ b/packages/vite/src/node/plugins/css.ts
@@ -658,12 +658,14 @@ async function compileCSS(
}
// important: set this for relative import resolving
opts.filename = cleanUrl(id)
+
const preprocessResult = await preProcessor(
code,
config.root,
opts,
atImportResolvers
)
+
if (preprocessResult.errors.length) {
throw preprocessResult.errors[0]
}
@@ -694,10 +696,12 @@ async function compileCSS(
if (isHTMLProxy && publicFile) {
return publicFile
}
+
const resolved = await atImportResolvers.css(
id,
path.join(basedir, '*')
)
+
if (resolved) {
return path.resolve(resolved)
}
@@ -858,6 +862,7 @@ type CssUrlReplacer = (
// https://drafts.csswg.org/css-syntax-3/#identifier-code-point
export const cssUrlRE =
/(?<=^|[^\w\-\u0080-\uffff])url\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/
+export const importCssRE = /@import ('[^']+\.css'|"[^"]+\.css"|[^'")]+\.css)/
const cssImageSetRE = /image-set\(([^)]+)\)/
const UrlRewritePostcssPlugin: Postcss.PluginCreator<{
@@ -907,6 +912,16 @@ function rewriteCssUrls(
})
}
+function rewriteImportCss(
+ css: string,
+ replacer: CssUrlReplacer
+): Promise {
+ return asyncReplace(css, importCssRE, async (match) => {
+ const [matched, rawUrl] = match
+ return await doImportCSSReplace(rawUrl, matched, replacer)
+ })
+}
+
function rewriteCssImageSet(
css: string,
replacer: CssUrlReplacer
@@ -937,6 +952,24 @@ async function doUrlReplace(
return `url(${wrap}${await replacer(rawUrl)}${wrap})`
}
+async function doImportCSSReplace(
+ rawUrl: string,
+ matched: string,
+ replacer: CssUrlReplacer
+) {
+ let wrap = ''
+ const first = rawUrl[0]
+ if (first === `"` || first === `'`) {
+ wrap = first
+ rawUrl = rawUrl.slice(1, -1)
+ }
+ if (isExternalUrl(rawUrl) || isDataUrl(rawUrl) || rawUrl.startsWith('#')) {
+ return matched
+ }
+
+ return `@import ${wrap}${await replacer(rawUrl)}${wrap}`
+}
+
async function minifyCSS(css: string, config: ResolvedConfig) {
const { code, warnings } = await transform(css, {
loader: 'css',
@@ -1133,12 +1166,19 @@ async function rebaseUrls(
if (fileDir === rootDir) {
return { file }
}
- // no url()
+
const content = fs.readFileSync(file, 'utf-8')
- if (!cssUrlRE.test(content)) {
+ // no url()
+ const hasUrls = cssUrlRE.test(content)
+ // no @import xxx.css
+ const hasImportCss = importCssRE.test(content)
+
+ if (!hasUrls && !hasImportCss) {
return { file }
}
- const rebased = await rewriteCssUrls(content, (url) => {
+
+ let rebased
+ const rebaseFn = (url: string) => {
if (url.startsWith('/')) return url
// match alias, no need to rewrite
for (const { find } of alias) {
@@ -1151,7 +1191,17 @@ async function rebaseUrls(
const absolute = path.resolve(fileDir, url)
const relative = path.relative(rootDir, absolute)
return normalizePath(relative)
- })
+ }
+
+ // fix css imports in less such as `@import "foo.css"`
+ if (hasImportCss) {
+ rebased = await rewriteImportCss(content, rebaseFn)
+ }
+
+ if (hasUrls) {
+ rebased = await rewriteCssUrls(rebased || content, rebaseFn)
+ }
+
return {
file,
contents: rebased