Vue-loader: Bourbon Sass Mixin Library with vue-loader

Created on 7 Dec 2015  路  8Comments  路  Source: vuejs/vue-loader

Hi there,
Thank you for the amazing vue-loader. I have been looking for something similar for quite some time and really love the approach of keeping style, template and javascript of a component in a single file.

Is it at all possible to use the bourbon sass mixin library with the vue-loader? I did include it in the webpack setup in different ways but have not been able to use bourbon functions in my styles (i.e. the very handy tint-function).

Any help would be greatly appreciated.

var path = require('path');
var webpack = require('webpack');

var node_modules = path.resolve(__dirname, 'node_modules');
var pathToBourbon = require('node-bourbon').includePaths;

module.exports = {
    entry: {
        public: [path.resolve(__dirname, 'app/public/main.js')],
    },
    output: {
        path: path.resolve(__dirname, 'build/assets'),
        filename: '[name].bundle.js',
        publicPath: '/assets/'
    },
    module: {
        loaders: [
            {
                // parse vue components
                test: /\.vue$/,
                loader: 'vue',
                exclude: node_modules
            }, {
                // edit this for additional asset file types
                test: /\.(png|jpg|gif)$/,
                loader: 'file?name=[name].[ext]?[hash]',
                exclude: node_modules
            }, {
                // parse css styles
                test: /\.css$/,
                loader: 'style!css',
                exclude: node_modules
            }, {
                // parse scss styles
                test: /\.scss$/,
                loader: 'style!css!sass?includePaths[]=' + pathToBourbon
            }, {
                // parse javascript styles
                test: /\.js$/,
                loader: 'babel',
                exclude: node_modules
            }
        ],
    },
    vue: {
        loaders: {
            // scss loader-statement is required to make scss syntax work
            // however, includePaths do not seem to import the tint-function or any mixins
            scss: 'style!css!sass?includePaths[]=' + pathToBourbon
        }
    },
    sassLoader: {
        includePaths: [pathToBourbon]
    },
    babel: {
        presets: ['es2015', 'stage-0'],
        plugins: ['transform-runtime']
    }
};

if (process.env.NODE_ENV === 'production') {
  module.exports.plugins = [
    new webpack.DefinePlugin({
        'process.env': {
            NODE_ENV: '"production"'
        }
    }),
    new webpack.optimize.UglifyJsPlugin({
        compress: {
            warnings: false
        }
    }),
    new webpack.optimize.OccurenceOrderPlugin()
    ]
} else {
    module.exports.devtool = '#source-map'
}

Example component

<style lang="scss">
$custom-red: red;
.header {
    color: $custom-red;
    border: 1px solid tint($custom-red, 20%);
}
</style>

<template>
    <div class="header">
        <h1>Custom header content</h1>
    </div>
</template>

<script>
    module.exports = {}
</script>
``

Most helpful comment

Brilliant! Could have thought of that myself. Thank you very much for your help.

For all those that need a working example:

// webpack.config.js
var path = require('path');
var webpack = require('webpack');

var node_modules = path.resolve(__dirname, 'node_modules');
var pathToBourbon = require('node-bourbon').includePaths;

module.exports = {
    entry: {
        public: [path.resolve(__dirname, 'app/public/main.js')],
    },
    output: {
        path: path.resolve(__dirname, 'build/assets'),
        filename: '[name].bundle.js',
        publicPath: '/assets/'
    },
    module: {
        loaders: [
            {
                // parse vue components
                test: /\.vue$/,
                loader: 'vue',
                exclude: node_modules
            }, {
                // edit this for additional asset file types
                test: /\.(png|jpg|gif)$/,
                loader: 'file?name=[name].[ext]?[hash]',
                exclude: node_modules
            }, {
                // parse css styles
                test: /\.css$/,
                loader: 'style!css',
                exclude: node_modules
            }, {
                // parse scss styles
                test: /\.scss$/,
                loader: 'style!css!sass',
                exclude: node_modules
            }, {
                // parse javascript styles
                test: /\.js$/,
                loader: 'babel',
                exclude: node_modules
            }
        ],
    },
    vue: {
        loaders: {
            scss: 'style!css!sass',
            exclude: node_modules
        }
    },
    sassLoader: {
        includePaths: [pathToBourbon]
    },
    babel: {
        presets: ['es2015', 'stage-0'],
        plugins: ['transform-runtime']
    }
};

if (process.env.NODE_ENV === 'production') {
  module.exports.plugins = [
    new webpack.DefinePlugin({
        'process.env': {
            NODE_ENV: '"production"'
        }
    }),
    new webpack.optimize.UglifyJsPlugin({
        compress: {
            warnings: false
        }
    }),
    new webpack.optimize.OccurenceOrderPlugin()
    ]
} else {
    module.exports.devtool = '#source-map'
}

// vue component
<style lang="scss">
@import "bourbon";
$custom-red: red;
.header {
    color: $custom-red;
    border: 1px solid tint($custom-red, 20%);
}
</style>

<template>
    <div class="header">
        <h1>Custom header content</h1>
    </div>
</template>

<script>
    module.exports = {}
</script>

// extract from package.json (assuming that compiled files are in /build directory)
{
  "scripts": {
    "dev": "webpack-dev-server --content-base build/ --inline --hot --quiet",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
  }
}

// running in console with  "npm run dev"

All 8 comments

You still need to @import "bourbon" because each file is compiled individually.

Brilliant! Could have thought of that myself. Thank you very much for your help.

For all those that need a working example:

// webpack.config.js
var path = require('path');
var webpack = require('webpack');

var node_modules = path.resolve(__dirname, 'node_modules');
var pathToBourbon = require('node-bourbon').includePaths;

module.exports = {
    entry: {
        public: [path.resolve(__dirname, 'app/public/main.js')],
    },
    output: {
        path: path.resolve(__dirname, 'build/assets'),
        filename: '[name].bundle.js',
        publicPath: '/assets/'
    },
    module: {
        loaders: [
            {
                // parse vue components
                test: /\.vue$/,
                loader: 'vue',
                exclude: node_modules
            }, {
                // edit this for additional asset file types
                test: /\.(png|jpg|gif)$/,
                loader: 'file?name=[name].[ext]?[hash]',
                exclude: node_modules
            }, {
                // parse css styles
                test: /\.css$/,
                loader: 'style!css',
                exclude: node_modules
            }, {
                // parse scss styles
                test: /\.scss$/,
                loader: 'style!css!sass',
                exclude: node_modules
            }, {
                // parse javascript styles
                test: /\.js$/,
                loader: 'babel',
                exclude: node_modules
            }
        ],
    },
    vue: {
        loaders: {
            scss: 'style!css!sass',
            exclude: node_modules
        }
    },
    sassLoader: {
        includePaths: [pathToBourbon]
    },
    babel: {
        presets: ['es2015', 'stage-0'],
        plugins: ['transform-runtime']
    }
};

if (process.env.NODE_ENV === 'production') {
  module.exports.plugins = [
    new webpack.DefinePlugin({
        'process.env': {
            NODE_ENV: '"production"'
        }
    }),
    new webpack.optimize.UglifyJsPlugin({
        compress: {
            warnings: false
        }
    }),
    new webpack.optimize.OccurenceOrderPlugin()
    ]
} else {
    module.exports.devtool = '#source-map'
}

// vue component
<style lang="scss">
@import "bourbon";
$custom-red: red;
.header {
    color: $custom-red;
    border: 1px solid tint($custom-red, 20%);
}
</style>

<template>
    <div class="header">
        <h1>Custom header content</h1>
    </div>
</template>

<script>
    module.exports = {}
</script>

// extract from package.json (assuming that compiled files are in /build directory)
{
  "scripts": {
    "dev": "webpack-dev-server --content-base build/ --inline --hot --quiet",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
  }
}

// running in console with  "npm run dev"

And if you want bourbon + neat:

npm install --save-dev node-neat

var sassPaths = require("node-neat").includePaths.map(function(sassPath) {
  return "includePaths[]=" + sassPath;
}).join("&");
...
...
...
sassLoader: {
  includePaths: [sassPaths] 
},
@import "~bourbon";
@import "~bourbon-neat";

Thanks so much for the help on this thread. Saved my rear.

I am doing something similar to this with Concise.css, but this results in the concise.css included multiple times in the output sass file when using the ExtractTextPlugin to create a single css file. Although, even if it was multiple files each file would include all the css from Concise or Bourbon in it. Which seems to be a waste correct?

Should the SASS loader not create a commons chunk with Bourbon/Concise load that first, as commons.css then use the same mixins from Bourbon/Concise on the other chunks?

Thanks

--- think this is covered here:
https://github.com/vuejs/vue-loader/issues/110

@gregorskii I'm hitting the same issue. I'm getting Foundation 6 to work with Vue and Vue-Loader. I have it all working as scss files outside of loader. But when I want to use sass variables inside vue-loader