diff --git a/docs/config/index.md b/docs/config/index.md index f53d9d51ad3fd9..5fe4de59fd1a93 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -643,6 +643,17 @@ createServer() If disabled, all CSS in the entire project will be extracted into a single CSS file. +### build.cssTarget + +- **Type:** `string | string[]` +- **Default:** the same as [`build.target`](/config/#build-target) + + This options allows users to set a different browser target for CSS minification from the one used for JavaScript transpilation. + + It should only be used when you are targeting a non-mainstream browser. + One example is Android WeChat WebView, which supports most modern JavaScript features but not the [`#RGBA` hexadecimal color notation in CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#rgb_colors). + In this case, you need to set `build.cssTarget` to `chrome61` to prevent vite from transform `rgba()` colors into `#RGBA` hexadecimal notations. + ### build.sourcemap - **Type:** `boolean | 'inline' | 'hidden'` diff --git a/packages/playground/css/__tests__/css.spec.ts b/packages/playground/css/__tests__/css.spec.ts index ad5d9f344813e4..010f028a14ea82 100644 --- a/packages/playground/css/__tests__/css.spec.ts +++ b/packages/playground/css/__tests__/css.spec.ts @@ -338,3 +338,14 @@ test('inlined', async () => { // should not insert css expect(await getColor('.inlined')).toBe('black') }) + +test('minify css', async () => { + if (!isBuild) { + return + } + + // should keep the rgba() syntax + const cssFile = findAssetFile(/index\.\w+\.css$/) + expect(cssFile).toMatch('rgba(') + expect(cssFile).not.toMatch('#ffff00b3') +}) diff --git a/packages/playground/css/main.js b/packages/playground/css/main.js index cd2d6fda9ac0d5..24a278c8687940 100644 --- a/packages/playground/css/main.js +++ b/packages/playground/css/main.js @@ -1,3 +1,5 @@ +import './minify.css' + import css from './imported.css' text('.imported-css', css) diff --git a/packages/playground/css/minify.css b/packages/playground/css/minify.css new file mode 100644 index 00000000000000..ada062407cdb38 --- /dev/null +++ b/packages/playground/css/minify.css @@ -0,0 +1,3 @@ +.test-minify { + color: rgba(255, 255, 0, 0.7); +} diff --git a/packages/playground/css/vite.config.js b/packages/playground/css/vite.config.js index d49dcf4207834d..e4dc8d5a9f265f 100644 --- a/packages/playground/css/vite.config.js +++ b/packages/playground/css/vite.config.js @@ -3,6 +3,9 @@ const path = require('path') * @type {import('vite').UserConfig} */ module.exports = { + build: { + cssTarget: 'chrome61' + }, resolve: { alias: { '@': __dirname diff --git a/packages/plugin-legacy/index.js b/packages/plugin-legacy/index.js index a0a44499e34822..295fff9b95c4c3 100644 --- a/packages/plugin-legacy/index.js +++ b/packages/plugin-legacy/index.js @@ -86,6 +86,15 @@ function viteLegacyPlugin(options = {}) { if (!config.build) { config.build = {} } + + if (!config.build.cssTarget) { + // Hint for esbuild that we are targeting legacy browsers when minifying CSS. + // Full CSS compat table available at https://github.com/evanw/esbuild/blob/78e04680228cf989bdd7d471e02bbc2c8d345dc9/internal/compat/css_table.go + // But note that only the `HexRGBA` feature affects the minify outcome. + // HSL & rebeccapurple values will be minified away regardless the target. + // So targeting `chrome61` suffices to fix the compatiblity issue. + config.build.cssTarget = 'chrome61' + } } } @@ -125,7 +134,7 @@ function viteLegacyPlugin(options = {}) { bundle, facadeToModernPolyfillMap, config.build, - options.externalSystemJS, + options.externalSystemJS ) return } @@ -156,7 +165,7 @@ function viteLegacyPlugin(options = {}) { // force using terser for legacy polyfill minification, since esbuild // isn't legacy-safe config.build, - options.externalSystemJS, + options.externalSystemJS ) } } diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index f11046c97fec25..0d376ac13d8ec8 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -102,6 +102,15 @@ export interface BuildOptions { * @default true */ cssCodeSplit?: boolean + /** + * An optional separate target for CSS minification. + * As esbuild only supports configuring targets to mainstream + * browsers, users may need this option when they are targeting + * a niche browser that comes with most modern JavaScript features + * but has poor CSS support, e.g. Android WeChat WebView, which + * doesn't support the #RGBA syntax. + */ + cssTarget?: TransformOptions['target'] | false /** * If `true`, a separate sourcemap file will be created. If 'inline', the * sourcemap will be appended to the resulting output file as data URI. @@ -232,6 +241,7 @@ export function resolveBuildOptions(raw?: BuildOptions): ResolvedBuildOptions { assetsDir: 'assets', assetsInlineLimit: 4096, cssCodeSplit: !raw?.lib, + cssTarget: false, sourcemap: false, rollupOptions: {}, commonjsOptions: { @@ -275,6 +285,10 @@ export function resolveBuildOptions(raw?: BuildOptions): ResolvedBuildOptions { resolved.target = 'es2019' } + if (!resolved.cssTarget) { + resolved.cssTarget = resolved.target + } + // normalize false string into actual false if ((resolved.minify as any) === 'false') { resolved.minify = false diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index dd7366874d984b..1a62bf7f028d13 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -906,7 +906,7 @@ async function minifyCSS(css: string, config: ResolvedConfig) { const { code, warnings } = await transform(css, { loader: 'css', minify: true, - target: config.build.target || undefined + target: config.build.cssTarget || undefined }) if (warnings.length) { const msgs = await formatMessages(warnings, { kind: 'warning' })