Skip to content

Commit

Permalink
fix: import css in less/scss (fix 3293) (#7147)
Browse files Browse the repository at this point in the history
Co-authored-by: Haoqun Jiang <haoqunjiang@gmail.com>
  • Loading branch information
sinoon and sodatea committed Mar 7, 2022
1 parent 3f5b645 commit 9b51a3a
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 4 deletions.
5 changes: 5 additions & 0 deletions packages/playground/css/__tests__/css.spec.ts
Expand Up @@ -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')
})
12 changes: 12 additions & 0 deletions packages/playground/css/index.html
Expand Up @@ -105,6 +105,18 @@ <h1>CSS</h1>
</p>

<p class="inlined">Inlined import - this should NOT be red.</p>

<div class="css-in-less">
test import css in less, this color will be yellow
</div>
<div class="css-in-less-2">
test for import less in less, this color will be blue
</div>

<div class="css-in-scss">
test import css in scss, this color will be orange
</div>

<pre class="inlined-code"></pre>
</div>

Expand Down
1 change: 1 addition & 0 deletions packages/playground/css/less.less
@@ -1,4 +1,5 @@
@import '@/nested/nested';
@import './nested/css-in-less.less';

@color: blue;

Expand Down
2 changes: 2 additions & 0 deletions 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;
Expand Down
3 changes: 3 additions & 0 deletions packages/playground/css/nested/css-in-less-2.less
@@ -0,0 +1,3 @@
.css-in-less-2 {
color: blue;
}
3 changes: 3 additions & 0 deletions packages/playground/css/nested/css-in-less.css
@@ -0,0 +1,3 @@
.css-in-less {
color: yellow;
}
4 changes: 4 additions & 0 deletions 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';
3 changes: 3 additions & 0 deletions packages/playground/css/nested/css-in-scss.css
@@ -0,0 +1,3 @@
.css-in-scss {
color: orange;
}
1 change: 1 addition & 0 deletions packages/playground/css/vite.config.js
@@ -1,4 +1,5 @@
const path = require('path')

/**
* @type {import('vite').UserConfig}
*/
Expand Down
58 changes: 54 additions & 4 deletions packages/vite/src/node/plugins/css.ts
Expand Up @@ -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]
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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<{
Expand Down Expand Up @@ -893,6 +898,16 @@ function rewriteCssUrls(
})
}

function rewriteImportCss(
css: string,
replacer: CssUrlReplacer
): Promise<string> {
return asyncReplace(css, importCssRE, async (match) => {
const [matched, rawUrl] = match
return await doImportCSSReplace(rawUrl, matched, replacer)
})
}

function rewriteCssImageSet(
css: string,
replacer: CssUrlReplacer
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand Down

0 comments on commit 9b51a3a

Please sign in to comment.