Override Vuetify SASS variables with webpack and twig
Asked Answered
I

4

7

I have Vuetify 2.2.11 and I'm trying to override their SASS variables. I'm in a Symfony 3.x project so I didn't install Vuetify with the vue-cli. I followed the Webpack install guide but I can't seem to make it work. The Vuetify styles get dumped to a css file (which is named after my js: app.js -> app.css) but my overrides are not taken into account. As for my project styles (company.scss), they are injected in a <style type="text/css"> tag in the html. There is also a huge bunch of empty <style type="text/css"></style> tags, which I guess are coming from every Vuetify component but I don't know why they're empty.

This is what my code looks like:

// /assets/js/app.js

import Vue from 'vue';
import vuetify from './plugins/Vuetify';
import FooComponent from './components/FooComponent;

import '../styles/company.scss';

const vmConfig = {
    el: '#app',
    vuetify,
    components: {
        FooComponent
    },
};

new Vue(vmConfig);
// /assets/js/plugins/Vuetify

import Vue from 'vue';

// We import from "lib" to enable the "a-la-carte" installation.
// But even tough we use vuetify-loader we still need to manually specify VApp because it's used in a twig template and the loader doesn't read it.
import Vuetify, { VApp } from 'vuetify/lib';

Vue.use(Vuetify, {
    components: {
        VApp
    }
});

export default new Vuetify({
    icons: { iconfont: 'md' }
});
/* /assets/styles/variables.scss */

$font-size-root: 30px;
$body-font-family: 'Times New Roman';

@import '~vuetify/src/styles/styles.sass';
/*@import '~vuetify/src/styles/settings/variables'; // I tried this one instead and it didn't work either*/
/* /assets/styles/company.scss */

#foo-component {
    background-color: pink;
}
{# /app/Resources/views/app.html.twig #}

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" media="all" href="{{ asset("build/app.css") }}" />
    </head>
    <body>
        <div id="app">
            <v-app>
                {% block main %}
                    <h1>My page</h1>
                    <foo-component></foo-component>
                {% endblock %}
            </v-app>
        </div>

        {% block footer_js %}
            <script type="text/javascript" src="{{ asset("build/app.js") }}"></script>
        {% endblock %}
    </body>
</html>
// webpack.config.js

const Encore = require('@symfony/webpack-encore');
const VuetifyLoaderPlugin = require('vuetify-loader/lib/plugin');
const webpack = require('webpack');
let path = require('path');

Encore    
    .setOutputPath('web/build/')
    .setPublicPath('/build')    
    .addEntry('app', './assets/js/app.js')
    .disableSingleRuntimeChunk()
    .enableVueLoader()
    .addPlugin(new VuetifyLoaderPlugin())
    .enableSassLoader()
    .addAliases({
        'vue$': 'vue/dist/vue.esm.js'
    })
    .configureBabel(null, {
        includeNodeModules: ['debug', 'vuetify'] // to make it work in IE11
    })
    .configureLoaderRule('sass', loaderRule => {
        loaderRule.test = /\.sass$/;
        loaderRule.use = [
            'vue-style-loader',
            'css-loader',
            {
                loader: 'sass-loader',
                options: {
                    data: "@import '" + path.resolve(__dirname, 'assets/styles/variables.scss') + "'",
                    implementation: require('sass'),
                    fiber: require('fibers'),
                    indentedSyntax: true
                }
            }
        ];
    })
;

let config = Encore.getWebpackConfig();
config.module.rules.push({
    test: /\.scss$/,
    use: [
        'vue-style-loader',
        'css-loader',
        {
            loader: 'sass-loader',
            options: {
                data: "@import '" + path.resolve(__dirname, 'assets/styles/variables.scss') + "';",
                implementation: require('sass'),
                fiber: require('fibers'),
                indentedSyntax: false
            },
        },
    ]
});

module.exports = config;
Ignorance answered 14/2, 2020 at 15:3 Comment(2)
Hi Emilie, did you find a solution for this?Gladdy
@Gladdy no I gave up on this :(Ignorance
G
2

Okay, I finally got it to work. Here is how:

webpack.config.js:

All you need to do here is to .enableSassLoader(). The config options described here where actually loading but variable declaration had no effect.

app.js

Just include your styles as such:

import '../styles/company.scss';

variables.scss

I've created this file and moved all vuetify specific variables there. You can use the official example.

company.scss

This is the trick:

@import './assets/css/variables.scss';
@import '~vuetify/src/styles/main.sass'; // include this after your variables

And thats it.

I've noticed that hot reloading doesn't work with changes to variables. But that's fine as long as these variables work. I got the hint from this page about the color pack

Gladdy answered 21/9, 2020 at 8:3 Comment(0)
B
2

ferdynator's solution is nice and clean, but in my case that would only let me a override a few variables. For me it had no effect on variables used in components. Utimately you may still need to have your variables.scss appended like documented in https://vuetifyjs.com/en/features/sass-variables/#webpack-install

Doing this with Webpack Encore can be quite confusing: because of .enableSassLoader() there's already a rule for /\.s[c|a]ss$/ that you not want to mess with (unless you know what you're doing, unlike me :p). This complicates things, because you need different rules for sass and scss as documented by Vuetify.

If you're using sass only, the following should suffice:

.enableSassLoader(options => {
    options.additionalData = "@import '" + path.resolve(__dirname, 'assets/styles/variables.scss') + "'";
})

(Note that additionalData applies to sass version 9+. You may need prependData or data for older versions),

If you're using scss, then you need to split the rule added by enableSassLoader. I could make it work by adding the following code in webpack.config.js:

const Encore = require('@symfony/webpack-encore');
const path = require('path');

// .. other dependencies if needed

Encore
   // ... other configurations
    .enableSassLoader(options => {
        options.additionalData = "@import '" + path.resolve(__dirname, 'assets/styles/variables.scss') + "'";
    })
;

const config =  Encore.getWebpackConfig();

// to override vuetify-variable, some rules need to be modified/added
// reference: https://vuetifyjs.com/en/features/sass-variables/#webpack-install
// enableSassLoader already has added a rule for s[ac]ss$, so we need to split in a separate rule for sass and scss.
const sassRule = config.module.rules.find(rule => rule.test.toString().includes('s[ac]ss$'));
sassRule.test = /\.sass$/; // make the rule explicit for sass only.

const scssRule = JSON.parse(JSON.stringify(sassRule));
scssRule.test = /\.scss$/;
scssRule.oneOf.forEach(oneOfRule => {
    if (oneOfRule.resourceQuery) {
        oneOfRule.resourceQuery = /module/; // cannot be parsed by json, so this need to be set manually.
    }
    oneOfRule.use.filter(oneOfUse => oneOfUse.loader.indexOf('sass-loader') !== -1).forEach(oneOfUse => {
        oneOfUse.options.additionalData += ";"; //scss requires semicolon at end of line
    });
});
config.module.rules.push(scssRule);

module.exports = config;

I find the need for this code horrific and love to see a cleaner solution, but at the very least this allows you to override the variables.

Bursary answered 24/8, 2021 at 21:11 Comment(1)
The additional scss Rules were it for me. Thx!Chladek
H
0

After many issues, I solve this on Laravel 8.

// resources/js/vuetify.js
import Vue from 'vue'
import Vuetify from 'vuetify/lib'

Vue.use(Vuetify)

const opts = {}

export default new Vuetify(opts)
// resources/js/app.js
window.Vue = require('vue').default

import vuetify from './vuetify'
import store from './store/store'

Vue.component('g-home', require('./components/pages/GHome.vue').default)

const app = new Vue({
    store,
    vuetify,
    el: '#app',
});
// Dependencies
{
        "laravel-mix": "^6.0.6",
        "sass": "^1.20.1",
        "sass-loader": "^8.0.0",
        "vue": "^2.5.17",
        "vue-loader": "^15.9.5",
        "vue-template-compiler": "^2.6.10",
        "vuetify": "^2.4.3",
        "vuetify-loader": "^1.7.1",
}
// webpack.mix.js
const mix = require('laravel-mix');
const webpack = require('./webpack.config');
Mix.listen('configReady', webpackConfig => {
    // scss
    const scssRule = webpackConfig.module.rules.find(
        rule =>
            String(rule.test) ===
            String(/\.scss$/)
    );
    scssRule.oneOf.forEach(o => {
        const scssOptions = o.use.find(l => l.loader === 'sass-loader').options
        scssOptions.prependData = '@import "./resources/sass/_variables.scss";'
    })

    // sass
    const sassRule = webpackConfig.module.rules.find(
        rule =>
            String(rule.test) ===
            String(/\.sass$/)
    );

    sassRule.oneOf.forEach(o => {
        const scssOptions = o.use.find(l => l.loader === 'sass-loader').options
        scssOptions.prependData = '@import "./resources/sass/_variables.scss"'
    })
})
mix.js('resources/js/app.js', 'public/js')
    .js('resources/js/gift.js', 'public/js')
    .vue()
    .sass('resources/sass/pages/home.scss', 'public/css')
    .sass('resources/sass/pages/gift.scss', 'public/css')
    .webpackConfig(Object.assign(webpack))
    .copyDirectory('resources/images/', 'public/images');

if (mix.inProduction()) {
    mix.version();
};
// webpack.config.js
const VuetifyLoaderPlugin = require('vuetify-loader/lib/plugin');
module.exports = {
    plugins: [
        new VuetifyLoaderPlugin(),
    ]
};
Hiller answered 17/2, 2021 at 18:43 Comment(0)
V
0

A Easier solution for this is to use Laravel mix extensions

You can simply add a path for your _variable.scss like this

mix.js('resources/js/app.js', 'public/js')
    .vuetify('vuetify-loader', 'src/path/to/variables.scss')
    .vue()

Here is the full documentation https://laravel-mix.com/extensions/vuetifyjs

Venter answered 27/10, 2022 at 1:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.