From 9b51a3acc0beb784983b97461e32937d82758861 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=B2=A7=E6=B5=AA?=
Date: Mon, 7 Mar 2022 21:35:36 +0800
Subject: [PATCH] fix: import css in less/scss (fix 3293) (#7147)
Co-authored-by: Haoqun Jiang
---
packages/playground/css/__tests__/css.spec.ts | 5 ++
packages/playground/css/index.html | 12 ++++
packages/playground/css/less.less | 1 +
packages/playground/css/nested/_index.scss | 2 +
.../playground/css/nested/css-in-less-2.less | 3 +
.../playground/css/nested/css-in-less.css | 3 +
.../playground/css/nested/css-in-less.less | 4 ++
.../playground/css/nested/css-in-scss.css | 3 +
packages/playground/css/vite.config.js | 1 +
packages/vite/src/node/plugins/css.ts | 58 +++++++++++++++++--
10 files changed, 88 insertions(+), 4 deletions(-)
create mode 100644 packages/playground/css/nested/css-in-less-2.less
create mode 100644 packages/playground/css/nested/css-in-less.css
create mode 100644 packages/playground/css/nested/css-in-less.less
create mode 100644 packages/playground/css/nested/css-in-scss.css
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 85eec565ebb86a..d513c88420b6ff 100644
--- a/packages/vite/src/node/plugins/css.ts
+++ b/packages/vite/src/node/plugins/css.ts
@@ -644,12 +644,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]
}
@@ -680,10 +682,12 @@ async function compileCSS(
if (isHTMLProxy && publicFile) {
return publicFile
}
+
const resolved = await atImportResolvers.css(
id,
path.join(basedir, '*')
)
+
if (resolved) {
return path.resolve(resolved)
}
@@ -844,6 +848,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<{
@@ -893,6 +898,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
@@ -923,6 +938,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',
@@ -1119,12 +1152,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) {
@@ -1137,7 +1177,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