FullHuman / purgecss

Remove unused CSS
https://purgecss.com
MIT License
7.78k stars 247 forks source link

Purgecss breaks with nuxt and vuetify #67

Closed husayt closed 6 years ago

husayt commented 6 years ago

I followed the instructions on setting up purgecss with nuxt and added it to simple vuetify nuxt project.

https://github.com/husayt/nuxt-vuetify-purgecss

As you can see with npm run build and npm run start layout just breaks apart.

Vuetify is the most popular material ui framework for vue and it would be great to get purgecss working with it

jsnanigans commented 6 years ago

Purgecss can't read the selectors inside the components that come from vuetify, A workaround would be to add the vuetify components into the paths option. Although this would keep a lot of unused css from vuetify, at least it would not break your layout.

husayt commented 6 years ago

Thia could work with vuetify's a-la-carte option. Do I just add paths to vuetify components in nodeModules?

jsnanigans commented 6 years ago

I have not tested it myself but yes, that should work. Let me know if you have any issues with that

husayt commented 6 years ago

@jsnanigans I tried it many times and it still didn't work

jsnanigans commented 6 years ago

@husayt I'm very sorry but I have no other solution for you. I am currently also working on a project where I use Vuetify and am not using purgecss. When the purgecss-loader for webpack will be done, this problem will be solved but until then the only solution I can think of is to include the vue templates from vuetify itself.

syffs commented 6 years ago

@jsnanigans @Ffloriel layout also breaks with nuxt and bootstrap-vue as many classes get wrongly purged

Is purgecss-loader planned to release soon?

jsnanigans commented 6 years ago

@husayt I got purgecss to work with vuetify with this configuration:

paths = glob.sync(
  [
    path.join(__dirname, '../src/**/*.vue'),
    path.join(__dirname, '../index.html'),
    path.join(__dirname, '../node_modules/vuetify/src/**/*.js'),
  ]
).filter(function(f) { return !/\/$/.test(f); })

however this includes all of vuetify and will not purge css for components you don't use, but maybe you can fiddle with path.join(__dirname, '../node_modules/vuetify/src/**/*.js') and only include the components that you use, just be careful because for example VBtn includes VProgressCircular which intern includes ../../mixins/colorable so you'll neet to add all three of those just to keep the styles for v-btn, and even then all the btn--* classes will persist.

@syffs I have not tested this with bootstrap-vue but the structure looks very similar.

purgecss-loader

is sadly nowhere near being released, in fact development has not even really began. but this will solve this issue once and for all because the idea is to tap into webpack compile step and get the content html and the css straight from there.

Ill close this issue because with proper configuration it can work, let me know if you need some more help.

syffs commented 6 years ago

@jsnanigans thanks for the explanation. I've tried adapting your suggestion to bootstrap-vue:

if (!isDev) {
  config.plugins.push(
    new PurgecssPlugin({
    paths: glob.sync([
      path.join(__dirname, './pages/**/*.vue'),
      path.join(__dirname, './layouts/**/*.vue'),
      path.join(__dirname, './components/**/*.vue'),
      path.join(__dirname, './node_modules/bootstrap-vue/src/**/*.js')
    ]).filter(function (f) { return !/\/$/.test(f) }),
    whitelist: ['html', 'body']
    })
  )
}

for some reason, the layout still breaks

jsnanigans commented 6 years ago

@syffs you could try to put your paths into a variable on top of the .js file, then console.log() them out, like this you can see if the files are actually inclduded

syffs commented 6 years ago

@jsnanigans good idea! no problem with the paths though, list is quite huge and bootstrap-vue files are well listed:

...
  './node_modules/bootstrap-vue/src/components/button-group/fixtures/button-group.js',
  './node_modules/bootstrap-vue/src/components/button-group/index.js',
  './node_modules/bootstrap-vue/src/components/button-toolbar/button-toolbar.js',
  './node_modules/bootstrap-vue/src/components/button-toolbar/button-toolbar.spec.js',
  './node_modules/bootstrap-vue/src/components/button-toolbar/fixtures/button-toolbar.js',
  './node_modules/bootstrap-vue/src/components/button-toolbar/index.js',
  './node_modules/bootstrap-vue/src/components/button/button-close.js',
  './node_modules/bootstrap-vue/src/components/button/button-close.spec.js',
  './node_modules/bootstrap-vue/src/components/button/button.js',
...
constantm commented 6 years ago

FYI, if anybody wants to get this working with Vue CLI 3, CSS modules and Bootstrap Vue, you can add the plugin to vue.config.js:

const glob = require("glob-all");
const path = require("path");

module.exports = {
  configureWebpack: {
    plugins: [
      new PurgecssPlugin({
        whitelistPatterns: [/^_/], // Don't remove classes with underscore prefix
        paths: glob.sync([
          path.join(__dirname, "/src/**/*.vue"),
          path.join(__dirname, "/src/**/*.js")
        ])
      }),
      new CompressionPlugin()
    ]
  },
  css: {
    loaderOptions: {
      css: {
        localIdentName: "_[name]-[hash]" // Prefix CSS modules with _ so we can whilelist them
      }
    }
  }
};

For Bootstrap Vue, you need to a add more whitelist items for dynamically generated classes that are not present in the Bootstrap Vue source files. An examples of this is button variants, eg. variant="dark" dynamically generates .btn-dark. The same applies to some responsive classes where you specify the breakpoint on the component as a prop, eg. toggleable="sm", which dynamically generates classes like .navbar-expand-md. To get around this, you can whitelist generated classes with whiteListPattern, eg:

whitelistPatterns: [/(nav).*/, /(btn).*/]
wtoalabi commented 5 years ago

whitelistPatterns: [/(nav)./, /(btn)./]

@constantm Your solution and pattern worked for me like a charm. I am using Laravel-Mix, which is a wrapper around webpack. For anyone with my setup, this was how I got the path: path.join(__dirname, 'node_modules/bootstrap-vue/src/**/**/*.js') While my whitelist pattern looks like this: whitelistPatterns: [/(nav).*/,/(bg).*/, /(btn).*/],

tmorehouse commented 5 years ago

For BootstrapVue, you probably also want to white list the following:

Another alternative is to just include the appropriate Bootstrap v4.3 SCSS files needed (i.e. utilities, grid, table, etc)

lukakoczorowski commented 4 years ago

This works for Vuetify ^2.1.0

module.exports = { "transpileDependencies": [ "vuetify" ], configureWebpack: { plugins: [ new PurgecssPlugin({ paths: glob.sync([ path.join(__dirname, './public/index.html'), path.join(__dirname, './**/*.vue'), path.join(__dirname, './src/**/*.js'), path.join(__dirname, './node_modules/vuetify/src/**/*.ts'), ]) }) ] } }

lobo-tuerto commented 4 years ago

@lukakoczorowski what file is that?

I've just tried adding nuxt-purgecss to a Nuxt + Vuetify application and everything broke as all Vuetify CSS classes got purged.

isokosan commented 4 years ago

@lobo-tuerto that is vue.config.js If you want to use it in nuxt.config.js, add the same directories to the paths module option.

IsraelOrtuno commented 4 years ago

I managed to remove a lot of unused CSS in my Nuxt + Vuetify + Tailwind app. It took me a while to figure out but I ended adding these options to my purgeCSS configuration which matched my requirements pretty nicely.

    whitelist: ['v-application', 'v-application--wrap'],
    whitelistPatterns: [/^v-((?!application).)*$/, /^theme--*/, /.*-transition/],
    whitelistPatternsChildren: [/^v-((?!application).)*$/, /^theme--*/],

I basically wanted to get rid of all v-application--is-rtl classes plus all vuetify utility classes and use tailwinds. My CSS is now ~83kb rather than ~245kb. You could also whitelist just theme--light if you would like to remove the dark theme styles.

I did also disable $color-pack. https://vuetifyjs.com/en/styles/colors/#sass-color-pack

$color-pack: false;
sovofeel commented 4 years ago

my nuxt config with vuetify 2

buildModules: [ '@nuxtjs/vuetify', [ 'nuxt-purgecss', { paths: ['/node_modules/vuetify/src/**/*.ts'], }, ], ], PurgeCSS: { whitelist: ['v-application', 'v-application--wrap'], whitelistPatterns: [/^v-((?!application).)*$/, /^theme--*/, /.*-transition/], whitelistPatternsChildren: [/^v-((?!application).)*$/, /^theme--*/], },

patrickxchong commented 4 years ago

@IsraelOrtuno I tried your method but it strips away too much. Somehow not all classes that are captured by the whitelistPattens regex are preserved. I created a minimal repo here: https://github.com/patrickxchong/nuxt-vuetify-purgecss and you can see how it looks right now here: https://nuxt-vuetify-purgecss.now.sh. I hope that either you or someone else can help me figure out what's wrong :pray:

@sovofeel I tried your method as well but it didn't strip away the css classes that I don't need such as v-application--is-rtl and theme--dark (I tried intentionally whitelisting only theme--light). I'm not too sure why but I think it has to do with declaring module options both within buildModules section and the nuxt.config object.

IsraelOrtuno commented 4 years ago

@patrickxchong since tailwind handles purgeCSS (last version I believe) itself this stopped working

patrickxchong commented 4 years ago

I was trying to get vuetify and purgeCSS to play nicely together first, I've not tried adding tailwind to the mix (I'm using tailwind for another project and I did get the recent update that integrates purgeCSS as well).

I did realize that one reason why it wasn't working was because I copied the line posted by @sovofeel initially but it was using PurgeCSS instead of purgeCSS, that's why it wasn't working (no offense @sovofeel intended, just wanted to make sure that people are aware of the typo).

I fixed the typo and the whitelist is working pretty well now, but it's still a little wonky (there are some things off with the UI) so I'll give an update here once I figure that out.

patrickxchong commented 4 years ago

Just an update for anyone who may be interested to get Vuetify and purgeCSS working together: If you use the whitelist shared by @IsraelOrtuno, it works well in really stripping away a lot of the unnecessary vuetify classes. However, they do strip a little too much away, particularly css that targets tags that are used within vuetify such as input and button, so the input of purgeCSS can look quite ugly: https://nuxt-vuetify-purgecss-3srh60r82.now.sh/.

To deal with that, the best way I can think of is for one to dig around, find out what's missing/looks weird, and whitelist the required tags/classes/attribute selectors. This is how the site looks after whitelisting input, button and spacer: https://nuxt-vuetify-purgecss.now.sh

@sovofeel I think I understand why your approach doesn't work with stripping away the classes, I believe by including all Vuetify components in paths, you're essentially telling purgeCSS to consider that you're using all the Vuetify components and to not purge any of the classes used by them.

lobo-tuerto commented 4 years ago

The reason I wanted to add PurgeCSS to my Vuetify app was to be able to add TailwindCSS to the mix. Now with the lastest TailwindCSS version there is no need to do that because it includes a preconfigured PurgeCSS that just works and doesn't mess up your Vuetify classes anymore.

IsraelOrtuno commented 4 years ago

@lobo-tuerto does that automatic purge coming from new tailwind purge vuetify's unused classes?

lobo-tuerto commented 4 years ago

@IsraelOrtuno No, only Tailwind's own classes. But now you don't have to worry about whitelisting Vuetify or other CSS classes or fear having them wiped out.

stephenjason89 commented 4 years ago

Just an update for anyone who may be interested to get Vuetify and purgeCSS working together: If you use the whitelist shared by @IsraelOrtuno, it works well in really stripping away a lot of the unnecessary vuetify classes. However, they do strip a little too much away, particularly css that targets tags that are used within vuetify such as input and button, so the input of purgeCSS can look quite ugly: https://nuxt-vuetify-purgecss-3srh60r82.now.sh/.

To deal with that, the best way I can think of is for one to dig around, find out what's missing/looks weird, and whitelist the required tags/classes/attribute selectors. This is how the site looks after whitelisting input, button and spacer: https://nuxt-vuetify-purgecss.now.sh

@sovofeel I think I understand why your approach doesn't work with stripping away the classes, I believe by including all Vuetify components in paths, you're essentially telling purgeCSS to consider that you're using all the Vuetify components and to not purge any of the classes used by them.

Can you share an update on @sovofeel 's method which includes input button and spacer and other stuff that you might have found not working?

thank you

patrickxchong commented 4 years ago

@stephenjason89 I decided to revisit how I used PurgeCSS and changed things up a little. I used @fullhuman/postcss-purgecss as a PostCSS plugin on Nuxt and combined it with another PostCSS plugin (css-byebye) and got the CSS file size down by a LOT without requiring too much manual whitelisting labor (you do have manually whitelist the transition classes used).

Attached is the Nuxt build configuration that I used, you can see the full configuration in the demo here: https://github.com/patrickxchong/nuxt-vuetify-purgecss

 /*
  ** Build configuration
  */
  build: {
    extractCSS: true,

    postcss: {
      plugins: {
          "@fullhuman/postcss-purgecss": {
            content: [
              'components/**/*.vue',
              'layouts/**/*.vue',
              'pages/**/*.vue',
              'plugins/**/*.js',
              'node_modules/vuetify/src/**/*.ts',
            ],
            styleExtensions: ['.css'],
            safelist: {
              standard: ["body", "html", "nuxt-progress"],
              deep: [
                /page-enter/,
                /page-leave/,
                /dialog-transition/,
                /tab-transition/,
                /tab-reversetransition/
              ]
            }

          },
          "css-byebye": {
            rulesToRemove: [
              /.*\.v-application--is-rtl.*/,
              /.*\.theme--dark.*/
            ]
          }
        }
    },
    /*
    ** You can extend webpack config here
    */
    extend(config, ctx) {
    }
  }
stephenjason89 commented 4 years ago

@patrickxchong Thank you for your work and the github link. it worked but over stripped styles. as you can see here

image

this is how it was supposed to look like

image

Am i missing something?

Basically I installed the packages (@fullhuman/postcss-purgecss & css/byebye) then copied your postcss config to my build conf

Thank you for taking the time to reply :)

This actually turned my app.css 372kb (unpacked) to 61.5kb

patrickxchong commented 4 years ago

@stephenjason89 I checked out https://foodat.ph/ and it seems like you're writing your own custom css for row and column layouts. I suspect that PurgeCSS stripped away some of those CSS away somehow. Is there a staging environment where I can see the site with PurgeCSS enabled? Otherwise the best advice I can give you is to figure out the classes that PurgeCSS removed and add them to the safelist.

stephenjason89 commented 4 years ago

@patrickxchong I appreciate you taking the time to reply. Here's the template at the home page where you can search address

<template>
    <v-card max-width="344" light rounded class="searchCard">
        <v-card-text>
            <p class="display-1 text--primary">Greetings</p>
            <p class="display-2 text--primary">Let's explore good food near you.</p>
        </v-card-text>

        <v-card-actions>
            <v-row>
                <v-col cols="12">
                    <Geosearch @confirmedAddress="startShopping"></Geosearch>
                </v-col>
                <v-col cols="12">
                    <v-btn width="100%" color="orange accent-3" rounded to="cravings" :disabled="shop">
                        I'm Hungry!
                    </v-btn>
                </v-col>
            </v-row>
        </v-card-actions>
    </v-card>
</template>

and for the template of geosearch component

<template>
    <div>
        <v-row dense align="center">
            <v-col>
                <v-autocomplete
                    v-model="select"
                    light
                    autocomplete="new-password"
                    :outlined="true"
                    :rounded="true"
                    color="orange accent-3"
                    height="12"
                    :loading="isLoading"
                    :items="results"
                    :search-input.sync="search"
                    :filter="filterAddress"
                    cache-items
                    hide-no-data
                    hide-details
                    item-text="description"
                    item-value="place_id"
                    label="Delivery Address"
                    @keyup="keyTimer"
                >
                </v-autocomplete>
            </v-col>
            <v-col cols="1" class="ml-n9 px-0">
                <v-icon class="gpsIcon" @click.stop="findMe">{{ mdiCrosshairsGps }}</v-icon>
            </v-col>
        </v-row>
        <Map v-if="showMap" :place="place" @save="confirmLocation"></Map>
    </div>
</template>

Please just visit https://foodat.ph again, i've updated it to run with postcss-purgecss and css/byebye There you'll be able to see that store layout and cravings were also affected.

patrickxchong commented 4 years ago

@stephenjason89 Thanks for the code snippets. One way of fixing this would be to whitelist all col-* classes in PurgeCSS (see here: https://github.com/patrickxchong/nuxt-vuetify-purgecss/blob/master/nuxt.config.js#L73). You can see your search card working at: https://nuxt-vuetify-purgecss.now.sh/foodat.

I personally DO NOT recommend this, as by whitelisting all col- classes, vuetify ships a lot of its unnecessary layout classes in the css bundle (you can compare the bundle sizes on your end after whitelisting `col-`). I would recommend that you not use v-row and v-col for layout and instead write your own layouts with CSS Grid and Flexbox.

stephenjason89 commented 4 years ago

@patrickxchong Thank you for the help using /col-*/ made the bundle size 62.9kb from 61.5kb which is still a big improvement from the original 372kb. It fixed most of my layout issue. :) You are a lifesaver!

arikw commented 3 years ago

I'm using @nuxtjs/vuetify and nuxt-purgecss, so the following nuxt.config.js configuration worked for me:

export default {
  // ...
  buildModules: [
    // ...
    ['@nuxtjs/vuetify', {
      defaultAssets: false
    }],
    [
      'nuxt-purgecss', {
        paths: [
          'node_modules/@nuxtjs/vuetify/**/*.ts',
          'node_modules/@nuxt/vue-app/template/**/*.html',
          'node_modules/@nuxt/vue-app/template/**/*.vue'
        ],
        whitelist: [
          'v-application',
          'v-application--wrap'
        ],
        whitelistPatterns: () => [
          /^v-((?!application).)*$/,
          /^\.theme--light*/,
          /.*-transition/
        ],
        whitelistPatternsChildren: [/^v-((?!application).)*$/, /^theme--light*/]
      }
    ]
  ],
  build: {
    extractCSS: true
    // ...
  }
}
t1mwillis commented 3 years ago

@patrickxchong thank you for the guidance here. I went ahead and wrote a script to analyze all the vuetify helper classes our application uses and further purge the ones that aren't. Things like mt-md-4 and order-xl-8.

// nuxt.config.js
const rules = require('./purgeUnusedCss.js')
...
"css-byebye": {
    rulesToRemove: [
      /.*\.v-application--is-rtl.*/,
      /.*\.theme--dark.*/,
      ...rules
    ]
  }
andrejsharapov commented 3 years ago

Hey! My nuxt.config.js is giving no results... Only 1 component, 1 plugin, and 1 page connected (test nuxt project).

  buildModules: [
    'nuxt-purgecss',
  ],
  ...
  purgeCSS: {
    styleExtensions: ['.css', '.scss', '.styl', '.sass', '.postcss'],
    paths: [
      'components/**/*.vue',
      'layouts/**/*.vue',
      'pages/**/*.vue',
      'plugins/**/*.js',
      'node_modules/@nuxt/vue-app/template/**/*.html',
      'node_modules/@nuxt/vue-app/template/**/*.vue',
    ],
    extractors: [
      {
        extractor: (content) => content.match(/[A-z0-9-:\\/]+/g) || [],
        extensions: ['html', 'vue', 'js'],
      },
    ],
    whitelist: ['v-application', 'v-application--wrap'],
    whitelistPatterns: () => [
      /^v-((?!application).)*$/,
      /^\.theme--light*/,
      /.*-transition/,
    ],
    whitelistPatternsChildren: [/^v-((?!application).)*$/, /^theme--light*/],
  },
 ...
  build: {
    analyze: true, 
    extractCSS: true,
    postcss: {
      plugins: {
        'postcss-custom-media': {},
        'postcss-hexrgba': {},
        'postcss-mixins': {},
        'postcss-nested': {},
        'postcss-responsive-type': {},
        'postcss-sorting': {},
        'postcss-url': {},
        'postcss-utilities': {},
        precss: {},
        '@fullhuman/postcss-purgecss': {
          content: [
            './components/**/*.vue',
            './layouts/**/*.vue',
            './pages/**/*.vue',
            'plugins/**/*.js',
          ],
          extractors: [
            {
              extractor: (content) =>
                content
                  .replace(/<style[\s\S]*>[\s\S]*<\/style>/gi, '')
                  .match(/[\w-/:]+/g) || [],

              extensions: ['vue'],
            },
          ],
        },
      },
  },
  ...

parsed All (532.1 KB), gzipped All (162.71 KB)

How to exclude unused? Or what's wrongly connected here?

patrickxchong commented 3 years ago

@andrejsharapov I see that you're using both nuxt-purgecss and @fullhuman/postcss-purgecss. For starters, can you remove one of them and refer to the config of either of them in the working examples above?

Working examples @fullhuman/postcss-purgecss: https://github.com/FullHuman/purgecss/issues/67#issuecomment-699853192 nuxt-purgecss: https://github.com/FullHuman/purgecss/issues/67#issuecomment-746988946

andrejsharapov commented 3 years ago

@patrickxchong Thx! I ended up with the same result as in my example. So it works :laughing:

I will leave this for posterity.

My App:

  1. Without all the nuxt-purgecss = parsed All (890.96 KB), gzipped All (208.99 KB)
    2. Only buildModules: nuxt-purgecss (without extractCSS and @fullhuman/postcss-purgecss)
    buildModules: [
    [
      'nuxt-purgecss',
      {
        paths: [
          'node_modules/@nuxtjs/vuetify/**/*.ts',
          'node_modules/@nuxt/vue-app/template/**/*.html',
          'node_modules/@nuxt/vue-app/template/**/*.vue',
        ],
        whitelist: ['v-application', 'v-application--wrap'],
        whitelistPatterns: () => [
          /^v-((?!application).)*$/,
          /^\.theme--light*/,
          /.*-transition/,
        ],
        whitelistPatternsChildren: [/^v-((?!application).)*$/, /^theme--*/],
      },
    ],
    

    parsed All (551.17 KB), gzipped All (167.62 KB)


3. buildModules + extractCSS (without @fullhuman/postcss-purgecss)
  buildModules: [
    [
      'nuxt-purgecss',
      {
        paths: [
          'node_modules/@nuxtjs/vuetify/**/*.ts',
          'node_modules/@nuxt/vue-app/template/**/*.html',
          'node_modules/@nuxt/vue-app/template/**/*.vue',
        ],
        whitelist: ['v-application', 'v-application--wrap'],
        whitelistPatterns: () => [
          /^v-((?!application).)*$/,
          /^\.theme--light*/,
          /.*-transition/,
        ],
        whitelistPatternsChildren: [/^v-((?!application).)*$/, /^theme--*/],
      },
    ],
  ],
  build: {
    analyze: true,
    extractCSS: true,

parsed All (532.1 KB), gzipped All (162.71 KB)


4. buildModules + extractCSS + @fullhuman/postcss-purgecss
  buildModules: [
    [
      'nuxt-purgecss',
      {
        paths: [
          'node_modules/@nuxtjs/vuetify/**/*.ts',
          'node_modules/@nuxt/vue-app/template/**/*.html',
          'node_modules/@nuxt/vue-app/template/**/*.vue',
        ],
        whitelist: ['v-application', 'v-application--wrap'],
        whitelistPatterns: () => [
          /^v-((?!application).)*$/,
          /^\.theme--light*/,
          /.*-transition/,
        ],
        whitelistPatternsChildren: [/^v-((?!application).)*$/, /^theme--*/],
      },
    ],
  ],
  build: {
    analyze: true,
    extractCSS: true,
    postcss: {
      plugins: {
        '@fullhuman/postcss-purgecss': {
          content: [
            'components/**/*.vue',
            'layouts/**/*.vue',
            'pages/**/*.vue',
            'plugins/**/*.js',
            'node_modules/vuetify/src/**/*.ts',
          ],
          styleExtensions: ['.css'],
          safelist: {
            standard: ['body', 'html', 'nuxt-progress'],
            deep: [
              /page-enter/,
              /page-leave/,
              /dialog-transition/,
              /tab-transition/,
              /tab-reversetransition/,
            ],
          },
        },
        'css-byebye': {
          rulesToRemove: [/.*\.v-application--is-rtl.*/, /.*\.theme--dark.*/],
        },
      },
    },
  },

parsed All (532.1 KB), gzipped All (162.71 KB)

alexisskodak commented 3 years ago

@stephenjason89 Thanks for the code snippets. One way of fixing this would be to whitelist all col-* classes in PurgeCSS (see here: https://github.com/patrickxchong/nuxt-vuetify-purgecss/blob/master/nuxt.config.js#L73). You can see your search card working at: https://nuxt-vuetify-purgecss.now.sh/foodat.

I personally DO NOT recommend this, as by whitelisting all col- classes, vuetify ships a lot of its unnecessary layout classes in the css bundle (you can compare the bundle sizes on your end after whitelisting `col-`). I would recommend that you not use v-row and v-col for layout and instead write your own layouts with CSS Grid and Flexbox.

I second this. I was losing my mind trying to whitelist v-layout elements (v-col etc.) which even when whitelisted didn't behave as expected on prod build, so I chose to go with CSS grid layouts. Ended up with 19.kb index.html vs 350kb when not using purgecss / postcss.