NaturalDevCR
NaturalDevCR

Reputation: 848

Vue 3 - Vite app missing some CSS rules after build

So, I've been struggling really hard on this, been working for a long time on a big update for my app, everything runs smooth and great on development, but once I build for production some of the CSS rules are missing, some elements are not ok, colors aren't showing as they should, and flexbox seems off...

So let me start from the beginning:

Evironment:

Here are my dependencies

  "dependencies": {
    "@ckeditor/ckeditor5-build-classic": "30.0.0",
    "@ckeditor/ckeditor5-vue": "2.0.1",
    "@hennge/vue3-pagination": "^1.0.17",
    "@iconify/iconify": "2.0.4",
    "@mapbox/mapbox-gl-geocoder": "4.7.4",
    "@popperjs/core": "2.10.2",
    "@vueform/multiselect": "2.2.0",
    "@vueform/slider": "2.0.5",
    "@vuelidate/core": "^2.0.0-alpha.21",
    "@vuelidate/validators": "^2.0.0-alpha.18",
    "@vueuse/core": "6.7.3",
    "@vueuse/head": "0.6.0",
    "animate.css": "^4.1.1",
    "ant-design-vue": "^2.2.8",
    "apexcharts": "3.28.3",
    "axios": "0.22.0",
    "billboard.js": "3.1.5",
    "bulma": "0.9.3",
    "bulma-css-vars": "0.7.0",
    "dayjs": "1.10.7",
    "dragula": "3.7.3",
    "dropzone": "5.9.3",
    "filepond": "4.30.3",
    "filepond-plugin-file-validate-size": "2.2.5",
    "filepond-plugin-file-validate-type": "1.2.6",
    "filepond-plugin-image-crop": "2.0.6",
    "filepond-plugin-image-edit": "1.6.3",
    "filepond-plugin-image-exif-orientation": "1.0.11",
    "filepond-plugin-image-preview": "4.6.10",
    "filepond-plugin-image-resize": "2.0.10",
    "filepond-plugin-image-transform": "3.8.7",
    "firebase": "9.1.3",
    "flag-icon-css": "^3.5.0",
    "imask": "6.2.2",
    "mapbox-gl": "2.5.0",
    "markdown-it-emoji": "2.0.0",
    "notyf": "3.10.0",
    "nprogress": "0.2.0",
    "photoswipe": "4.1.3",
    "pinia": "^2.0.0-rc.15",
    "pinia-plugin-persist": "^0.0.92",
    "qrcode-vue3": "^1.4.17",
    "simple-datatables": "3.1.2",
    "simplebar": "6.0.0-beta.10",
    "simplebar-vue": "2.0.0-beta.10",
    "stylelint-csstree-validator": "^1.9.0",
    "sweetalert2": "^10.16.7",
    "tiny-slider": "2.9.3",
    "tippy.js": "6.3.2",
    "tslib": "2.3.1",
    "v-calendar": "3.0.0-alpha.5",
    "v-offline": "^3.0.0",
    "vant": "^3.2.6",
    "vee-validate": "4.5.4",
    "vivus": "0.4.6",
    "vue": "3.2.20",
    "vue-accessible-color-picker": "3.0.0",
    "vue-currency-input": "^2.0.1",
    "vue-i18n": "9.2.0-beta.15",
    "vue-router": "4.0.12",
    "vue-scrollto": "2.20.0",
    "vue-stripe-menu": "^2.1.1",
    "vue-tippy": "6.0.0-alpha.33",
    "vue-toastification": "^2.0.0-rc.1",
    "vue3-apexcharts": "1.4.1",
    "vue3-burger-menu": "^1.1.1",
    "vue3-carousel": "^0.1.28",
    "vue3-clipboard": "^1.0.0",
    "vueperslides": "^3.3.2",
    "xlsx": "^0.17.3",
    "yup": "0.32.9"
  },
  "devDependencies": {
    "@commitlint/cli": "13.2.0",
    "@commitlint/config-conventional": "13.2.0",
    "@commitlint/prompt-cli": "13.2.0",
    "@iconify/json": "1.1.410",
    "@intlify/vite-plugin-vue-i18n": "2.4.0",
    "@types/dragula": "3.7.1",
    "@types/luxon": "^1.27.1",
    "@types/mapbox-gl": "2.4.2",
    "@types/mapbox__mapbox-gl-geocoder": "4.7.1",
    "@types/markdown-it": "12.2.1",
    "@types/node": "16.10.2",
    "@types/nprogress": "0.2.0",
    "@types/photoswipe": "4.1.2",
    "@types/prismjs": "1.16.6",
    "@types/simplebar": "5.3.3",
    "@types/vivus": "0.4.4",
    "@typescript-eslint/eslint-plugin": "4.33.0",
    "@typescript-eslint/parser": "4.33.0",
    "@vitejs/plugin-vue": "1.9.3",
    "@vue/compiler-sfc": "3.2.20",
    "autoprefixer": "9.8.6",
    "commitlint": "13.2.0",
    "cross-env": "7.0.3",
    "cypress": "8.5.0",
    "eslint": "7.32.0",
    "eslint-config-prettier": "8.3.0",
    "eslint-plugin-md": "1.0.19",
    "eslint-plugin-vue": "7.19.0",
    "eslint-plugin-vuejs-accessibility": "^0.7.1",
    "gray-matter": "4.0.3",
    "lint-staged": "11.2.0",
    "markdown-it": "12.2.0",
    "markdown-it-anchor": "8.3.1",
    "npm-run-all": "4.1.5",
    "path": "^0.12.7",
    "plyr": "3.6.8",
    "postcss-nested": "4.2.3",
    "prettier": "2.4.1",
    "prismjs": "1.25.0",
    "rimraf": "3.0.2",
    "rollup": "2.58.3",
    "rollup-plugin-purgecss": "^4.0.3",
    "sass": "1.32.13",
    "standard-version": "9.3.2",
    "stylelint": "13.13.1",
    "stylelint-config-prettier": "8.0.2",
    "stylelint-config-standard": "22.0.0",
    "stylelint-scss": "3.21.0",
    "typescript": "4.4.3",
    "unplugin-vue-components": "0.16.0",
    "vfonts": "^0.1.0",
    "vite": "2.6.11",
    "vite-imagetools": "3.6.8",
    "vite-plugin-fonts": "0.2.2",
    "vite-plugin-imagemin": "0.4.6",
    "vite-plugin-pages": "0.18.1",
    "vite-plugin-purge-icons": "0.7.0",
    "vite-plugin-pwa": "0.11.3",
    "vite-plugin-radar": "0.2.0",
    "vite-svg-loader": "^2.2.0",
    "vue-tsc": "0.3.0",
    "yorkie": "2.0.0"
  }

Here is my vite.config.ts

import { defineConfig } from 'vite'
// @ts-ignore
import path from 'path'
import Vue from '@vitejs/plugin-vue'
import Pages from 'vite-plugin-pages'
import Components from 'unplugin-vue-components/vite'
import ViteFonts from 'vite-plugin-fonts'
import ViteRadar from 'vite-plugin-radar'
import PurgeIcons from 'vite-plugin-purge-icons'
import { imagetools } from 'vite-imagetools'
import ImageMin from 'vite-plugin-imagemin'
import { vueI18n } from '@intlify/vite-plugin-vue-i18n'
import { VitePWA } from 'vite-plugin-pwa'
import purgecss from 'rollup-plugin-purgecss'
import ViteComponents, {
  AntDesignVueResolver,
  VantResolver
} from 'unplugin-vue-components/resolvers'

const SILENT = Boolean(process.env.SILENT) ?? false
const SOURCE_MAP = Boolean(process.env.SOURCE_MAP) ?? false

/**
 * This is the main configuration file for vitejs
 *
 * @see https://vitejs.dev/config
 */
export default defineConfig({
  // Project root directory (where index.html is located).
  root: process.cwd(),
  // Base public path when served in development or production.
  // You also need to add this base like `history: createWebHistory('my-subdirectory')`
  // in ./src/router.ts
  // base: '/my-subdirectory/',
  base: '/',
  // Directory to serve as plain static assets.
  publicDir: 'public',
  // Adjust console output verbosity.
  logLevel: SILENT ? 'error' : 'info',
  /**
   * By default, Vite will crawl your index.html to detect dependencies that
   * need to be pre-bundled. If build.rollupOptions.input is specified,
   * Vite will crawl those entry points instead.
   *
   * @see https://vitejs.dev/config/#optimizedeps-entries
   */
  optimizeDeps: {
    include: [
      '@ckeditor/ckeditor5-vue',
      '@ckeditor/ckeditor5-build-classic',
      '@iconify/iconify',
      '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.min.js',
      '@vueuse/core',
      '@vueuse/head',
      '@vueform/multiselect',
      '@vueform/slider',
      'axios',
      'billboard.js',
      'dayjs',
      'dropzone',
      'dragula',
      'filepond',
      'filepond-plugin-file-validate-size',
      'filepond-plugin-file-validate-type',
      'filepond-plugin-image-exif-orientation',
      'filepond-plugin-image-crop',
      'filepond-plugin-image-edit',
      'filepond-plugin-image-preview',
      'filepond-plugin-image-resize',
      'filepond-plugin-image-transform',
      'imask',
      'nprogress',
      'notyf',
      'mapbox-gl',
      'photoswipe/dist/photoswipe',
      'photoswipe/dist/photoswipe-ui-default',
      'plyr',
      'v-calendar',
      'vee-validate',
      'vue',
      'vue-scrollto',
      'vue3-apexcharts',
      'vue-tippy',
      'simplebar',
      'simple-datatables',
      'tiny-slider/src/tiny-slider',
      'vue-accessible-color-picker',
      'yup',
      'ant-design-vue',
      'vant'
    ],
  },
  // Will be passed to @rollup/plugin-alias as its entries option.
  resolve: {
    alias: [
      {
        find: '/~/',
        replacement: `/src/assets/`,
      },
      {
        find: '/@src/',
        replacement: `/src/`,
      },
    ],
  },

  build: {
    sourcemap: SOURCE_MAP,
    // Turning off brotliSize display can slightly reduce packaging time
    brotliSize: !SILENT,
    chunkSizeWarningLimit: 2000,
    minify: 'esbuild',
    cssCodeSplit: true,
  },
  plugins: [
    /**
     * plugin-vue plugin inject vue library and allow sfc files to work (*.vue)
     *
     * @see https://github.com/vitejs/vite/tree/main/packages/plugin-vue
     */
    Vue({
      include: [/\.vue$/],
    }),

    /**
     * vite-plugin-vue-i18n plugin does i18n resources pre-compilation / optimizations
     *
     * @see https://github.com/intlify/bundle-tools/tree/main/packages/vite-plugin-vue-i18n
     */
    vueI18n({
      include: path.resolve(__dirname, './src/locales/**'),
    }),

    /**
     * unplugin-vue-components plugin is responsible of autoloading components
     * documentation and md file are loaded for elements and components sections
     *
     * @see https://github.com/antfu/unplugin-vue-components
     */
    Components({
      dirs: ['src/components', 'src/layouts', 'src/views'],
      extensions: ['vue', 'md'],
      dts: true,
      include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
      resolvers: [
        AntDesignVueResolver(),
        VantResolver()
      ]
    }),

    /**
     * vite-plugin-purge-icons plugin is responsible of autoloading icones from multiples providers
     *
     * @see https://icones.netlify.app/
     * @see https://github.com/antfu/purge-icons/tree/main/packages/vite-plugin-purge-icons
     */
    PurgeIcons(),

    /**
     * vite-plugin-fonts plugin inject webfonts from differents providers
     *
     * @see https://github.com/stafyniaksacha/vite-plugin-fonts
     */
    ViteFonts({
      google: {
        families: [
          {
            name: 'Fira Code',
            styles: 'wght@400;600',
          },
          {
            name: 'Montserrat',
            styles: 'wght@500;600;700;800;900',
          },
          {
            name: 'Roboto',
            styles: 'wght@300;400;500;600;700',
          },
        ],
      },
    }),

    /**
     * vite-plugin-radar plugin inject snippets from analytics providers
     *
     * @see https://github.com/stafyniaksacha/vite-plugin-radar
     */
    ViteRadar({
      analytics: {
        id: '',
      },
    }),

    /**
     * vite-plugin-pwa generate manifest.json and register services worker to enable PWA
     *
     * @see https://github.com/antfu/vite-plugin-pwa
     */
    VitePWA({
      registerType: 'autoUpdate',
      base: '/',
      includeAssets: [
        'favicon.svg',
        'favicon.ico',
        'robots.txt',
        'apple-touch-icon.png',
      ],
      manifest: {
        name: 'Treebū Admin',
        short_name: 'Treebū Admin',
        start_url: '/?utm_source=pwa',
        display: 'standalone',
        theme_color: '#ffffff',
        background_color: '#ffffff',
        icons: [
          {
            src: 'pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png',
          },
          {
            src: 'pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
          },
          {
            src: 'pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'any maskable',
          },
        ],
      },
    }),

    /**
     * rollup-plugin-purgecss plugin is responsible of purging css rules
     * that are not used in the bundle
     *
     * @see https://github.com/FullHuman/purgecss/tree/main/packages/rollup-plugin-purgecss
     */
    purgecss({
      content: [`./src/**/*.vue`],
      variables: false,
      safelist: {
        standard: [
          /(autv|lnil|lnir|fas?)/,
          /-(leave|enter|appear)(|-(to|from|active))$/,
          /^(?!(|.*?:)cursor-move).+-move$/,
          /^router-link(|-exact)-active$/,
          /data-v-.*/,
        ],
      },
      defaultExtractor(content) {
        const contentWithoutStyleBlocks = content.replace(
          /<style[^]+?<\/style>/gi,
          ''
        )
        return (
          contentWithoutStyleBlocks.match(/[A-Za-z0-9-_/:]*[A-Za-z0-9-_/]+/g) ||
          []
        )
      },
    }),

    /**
     * vite-imagetools plugin allow to perform transformation (blur, resize, crop, etc)
     * on images at build time
     *
     * @see https://github.com/JonasKruckenberg/vite-imagetools
     */
    imagetools({
      silent: SILENT,
    }),

    /**
     * vite-plugin-imagemin optimize all images sizes from public or asset folder
     *
     * @see https://github.com/anncwb/vite-plugin-imagemin
     */
    ImageMin({
      verbose: !SILENT,
      gifsicle: {
        optimizationLevel: 7,
        interlaced: false,
      },
      optipng: {
        optimizationLevel: 7,
      },
      mozjpeg: {
        quality: 60,
      },
      pngquant: {
        quality: [0.8, 0.9],
        speed: 4,
      },
      svgo: {
        plugins: [
          {
            name: 'removeViewBox',
            active: false,
          },
          {
            name: 'removeEmptyAttrs',
            active: false,
          },
        ],
      },
    }),
  ],
})

This is my index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!--iOS configuration-->
    <meta name="apple-mobile-web-app-capable" content="yes">

    <meta name="google" content="notranslate">

    <!--ICONS-->
    <link rel="apple-touch-icon" href="/images/mobile-icons/ios/apple-icon.png">
    <link rel="apple-touch-icon" sizes="57x57" href="/images/mobile-icons/ios/apple-icon-57x57.png">
    <link rel="apple-touch-icon" sizes="60x60" href="/images/mobile-icons/ios/apple-icon-60x60.png">
    <link rel="apple-touch-icon" sizes="72x72" href="/images/mobile-icons/ios/apple-icon-72x72.png">
    <link rel="apple-touch-icon" sizes="76x76" href="/images/mobile-icons/ios/apple-icon-76x76.png">
    <link rel="apple-touch-icon" sizes="114x114" href="/images/mobile-icons/ios/apple-icon-114x114.png">
    <link rel="apple-touch-icon" sizes="120x120" href="/images/mobile-icons/ios/apple-icon-120x120.png">
    <link rel="apple-touch-icon" sizes="144x144" href="/images/mobile-icons/ios/apple-icon-144x144.png">
    <link rel="apple-touch-icon" sizes="152x152" href="/images/mobile-icons/ios/apple-icon-152x152.png">
    <link rel="apple-touch-icon" sizes="180x180" href="/images/mobile-icons/ios/apple-icon-180x180.png">

    <!--    Splashscreen-->
    <link href="/images/splashscreens/iphone5_splash.png" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image" />
    <link href="/images/splashscreens/iphone6_splash.png" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image" />
    <link href="/images/splashscreens/iphoneplus_splash.png" media="(device-width: 621px) and (device-height: 1104px) and (-webkit-device-pixel-ratio: 3)" rel="apple-touch-startup-image" />
    <link href="/images/splashscreens/iphonex_splash.png" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)" rel="apple-touch-startup-image" />
    <link href="/images/splashscreens/iphonexr_splash.png" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image" />
    <link href="/images/splashscreens/iphonexsmax_splash.png" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3)" rel="apple-touch-startup-image" />
    <link href="/images/splashscreens/ipad_splash.png" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image" />
    <link href="/images/splashscreens/ipadpro1_splash.png" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image" />
    <link href="/images/splashscreens/ipadpro3_splash.png" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image" />
    <link href="/images/splashscreens/ipadpro2_splash.png" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image" />

    <!--Status Bar-->
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <meta name="theme-color" content="#2f3048">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta content="initial-scale=1.0, maximum-scale=1.0, width=device-width, user-scalable=no, shrink-to-fit=no" name="viewport">
    <link
      rel="apple-toucv-icon"
      sizes="180x180"
      href="/icons/apple-touch-icon.png"
    />
    <link
      rel="icon"
      type="image/png"
      sizes="32x32"
      href="/icons/favicon-32x32.png"
    />
    <link
      rel="icon"
      type="image/png"
      sizes="16x16"
      href="/icons/favicon-16x16.png"
    />
    <link rel="mask-icon" href="/icons/safari-pinned-tab.svg" color="#5bbad5" />
    <meta name="msapplication-TileColor" content="#232326" />
    <!--    <meta name="theme-color" content="#ffffff" />-->
    <title>Treebū Hotels</title>
    <script>
      /**
       * this is a hack for dragula used on KanbanApp
       *
       * @see src/components/pages/apps/KanbanApp.vue
       */
      var global = global || window
    </script>
    <link
      rel="preload"
      as="style"
      onload="this.rel='stylesheet'"
      href="/vendors/font-awesome-v5.css"
    />
    <link
      rel="preload"
      as="style"
      onload="this.rel='stylesheet'"
      href="/vendors/line-icons-pro.css"
    />
    <link
      rel="preload"
      as="style"
      onload="this.rel='stylesheet'"
      href="/vendors/prism-coldark-cold.css"
    />
  </head>
  <body>
    <div data-teleport-bg></div>
    <div id="app" class="app-wrapper"></div>
    <script type="module" src="/src/styles.ts"></script>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

Please note the differences in the following images between the dev version and the production version:

Dev Version:

Dev version

Prod Version: Prod version

As you can see there is a mismatch in styles... lets continue:

Dev version, flexbox working correctly: enter image description here enter image description here

No flexbox in prod version enter image description here

enter image description here

There are no warnings or errors on console on the build version,

I have tried removing antd, vant, and other custom css styles, just in case there is some conflict, but I get the same result, also tried with

minify: 'esbuild', // or false

cssCodeSplit: true, //or false

In the vite.config

Everything works perfectly on development but it's just messed up on build.

Any hints? ideas? possibilities? chances?

Thanks in advance!.

Upvotes: 6

Views: 15403

Answers (3)

equi
equi

Reputation: 914

The reason behind lost CSS is purgecss library. When building for production, it removes CSS rules that are not being used.

There are some libraries that concatenate CSS class names using JS to define certain looks. Those end-result classes defined in CSS tend to get purged, because purgecss doesn't see them as being used while building up.

To solve this issue, once you discover which classes are getting purged, you can whitelist them.

You can set whitelist patterns in postcss.config.js file, located next to vite.config.js.

Since I can not know which specific library is causing your issues, I will show an example I was having with vue-multiselect library, and the way I solved it:

*index.scss

@import "~vue-multiselect/dist/vue-multiselect.min.css";

postcss.config.js

whitelistPatterns: [
            ...
            /multiselect*/,
        ]

Please note that in newer Vite versions, whitelistPatterns is replaced by safelist

More about whitelisting https://purgecss.com/safelisting.html

Upvotes: 2

DoronG
DoronG

Reputation: 2663

for me this happened when minify was set to false, e.g.:

build: {
  minify: env.mode !== "development", // flase for development mode
}

it was fixed when I added cssCodeSplit: false, i.e.

build: {
  minify: env.mode !== "development",
  cssCodeSplit: env.mode === "development" ? false : undefined
}

note, I used undefined in non-development mode, so it takes the default rather than true

note 2 I do not understand why this is happening, but this is how I resolved it

Upvotes: 0

Zain Ejaz
Zain Ejaz

Reputation: 289

I had also the same problem as yours, So I searched about it and found a solution to it.

I just ran npm run hot this command and by doing this, the issue had been resolved

Upvotes: -4

Related Questions