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

fix: import css in less/scss (fix 3293) #7147

Merged
merged 17 commits into from Mar 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -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]
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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<{
Expand Down Expand Up @@ -907,6 +912,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 @@ -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',
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand Down