fix(css): hoist charset (#7678)

This commit is contained in:
Bjorn Lu 2022-04-12 02:13:37 +08:00 committed by GitHub
parent 485263cdca
commit 29e622cc7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 35 additions and 9 deletions

View File

@ -247,6 +247,15 @@ test('inline css modules', async () => {
expect(css).toMatch(/\.inline-module__apply-color-inline___[\w-]{5}/)
})
if (isBuild) {
test('@charset hoist', async () => {
serverLogs.forEach((log) => {
// no warning from esbuild css minifier
expect(log).not.toMatch('"@charset" must be the first rule in the file')
})
})
}
test('@import dependency w/ style entry', async () => {
expect(await getColor('.css-dep')).toBe('purple')
})

View File

@ -0,0 +1,5 @@
@charset "utf-8";
.utf8 {
color: green;
}

View File

@ -96,6 +96,9 @@
<p>Inline CSS module:</p>
<pre class="modules-inline"></pre>
<p>CSS with @charset:</p>
<pre class="charset-css"></pre>
<p class="css-dep">
@import dependency w/ style enrtrypoints: this should be purple
</p>

View File

@ -41,6 +41,9 @@ text(
import inlineMod from './inline.module.css?inline'
text('.modules-inline', inlineMod)
import charset from './charset.css'
text('.charset-css', charset)
import './dep.css'
import './glob-dep.css'

View File

@ -427,10 +427,10 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
return `./${path.posix.basename(filename)}`
}
})
// only external @imports should exist at this point - and they need to
// be hoisted to the top of the CSS chunk per spec (#1845)
if (css.includes('@import')) {
css = await hoistAtImports(css)
// only external @imports and @charset should exist at this point
// hoist them to the top of the CSS chunk per spec (#1845 and #6333)
if (css.includes('@import') || css.includes('@charset')) {
css = await hoistAtRules(css)
}
if (minify && config.build.minify) {
css = await minifyCSS(css, config)
@ -1109,27 +1109,33 @@ async function minifyCSS(css: string, config: ResolvedConfig) {
// #1845
// CSS @import can only appear at top of the file. We need to hoist all @import
// to top when multiple files are concatenated.
async function hoistAtImports(css: string) {
// #6333
// CSS @charset must be the top-first in the file, hoist to top too
async function hoistAtRules(css: string) {
const postcss = await import('postcss')
return (await postcss.default([AtImportHoistPlugin]).process(css)).css
return (await postcss.default([AtRuleHoistPlugin]).process(css)).css
}
const AtImportHoistPlugin: PostCSS.PluginCreator<any> = () => {
const AtRuleHoistPlugin: PostCSS.PluginCreator<any> = () => {
return {
postcssPlugin: 'vite-hoist-at-imports',
postcssPlugin: 'vite-hoist-at-rules',
Once(root) {
const imports: PostCSS.AtRule[] = []
let charset: PostCSS.AtRule | undefined
root.walkAtRules((rule) => {
if (rule.name === 'import') {
// record in reverse so that can simply prepend to preserve order
imports.unshift(rule)
} else if (!charset && rule.name === 'charset') {
charset = rule
}
})
imports.forEach((i) => root.prepend(i))
if (charset) root.prepend(charset)
}
}
}
AtImportHoistPlugin.postcss = true
AtRuleHoistPlugin.postcss = true
// Preprocessor support. This logic is largely replicated from @vue/compiler-sfc