Webpacker: Vue with global Sass variables does not extract CSS/SCSS

Created on 11 Oct 2018  路  2Comments  路  Source: rails/webpacker

I am really having hard times with this problem.

I created kind of style library which has one big variables-and-mixins.scss i need in every component of my Vue app. There are working solutions for standalone Vue apps with i used before but this time i had to search hard.

The solution for global Sass functionality is using sass-resource-loader.

// config/webpack/loaders/vue.js

const path =  require('path')

const { dev_server: devServer } = require('@rails/webpacker').config

const isProduction = process.env.NODE_ENV === 'production'
const inDevServer = process.argv.find(v => v.includes('webpack-dev-server'))
const extractCSS = !(inDevServer && (devServer && devServer.hmr)) || isProduction

module.exports = {
  test: /\.vue(\.erb)?$/,
  use: [{
    loader: 'vue-loader',
    options: {
      extractCSS,
      loaders: {
        scss: [
            'vue-style-loader',
            { loader: 'css-loader',
              options: {
                minimize: isProduction,
                sourceMap: !isProduction
              }
            },
            'sass-loader',
            {
              loader: 'sass-resources-loader',
              options: {
                resources: path.join(__dirname, '../my-path-to/variables-and-mixins.scss')
              },
            },
          ]
    }
    }
  }]
}

It works totally fine with ./bin/webpack-dev-server but rendering the app with ./bin/webpack to create an extracted CSS file doesn't work anymore and the component css is still in the JS part.

Without global variables the extraction works as expected.

I guess the loader definition overrides something in the vue-loader.

How can i combine both - the extraction and the global variables?

Most helpful comment

I figured it out... It feels like a workaround but it may be the right way to do it.

// config/webpack/enviroment.js

const { environment } = require('@rails/webpacker')
const webpack = require('webpack')
const aliasConfig = require('./alias')
environment.config.merge(aliasConfig)

// Vue Loader
const vue =  require('./loaders/vue')
environment.loaders.append('vue', vue)

module.exports = environment

Create the Vue loader file if it's not already created.

// config/webpack/loaders/vue.js

const utils = require('./vue-style-rules')

const { dev_server: devServer } = require('@rails/webpacker').config

const isProduction = process.env.NODE_ENV === 'production'
const inDevServer = process.argv.find(v => v.includes('webpack-dev-server'))
const extractCSS = !(inDevServer && (devServer && devServer.hmr)) || isProduction

module.exports = {
  test: /\.vue(\.erb)?$/,
  use: [{
    loader: 'vue-loader',
    options: {
      loaders: utils.cssLoaders({
        sourceMap: !isProduction,
        extract: extractCSS
      })
    }
  }]
}

That's the tricky part. I use only SCSS so i configured it this way.

// config/webpack/loaders/vue-style-rules.js

const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')

exports.cssLoaders = function (options) {
  options = options || {}

  const cssLoader = {
    loader: 'css-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }

  // generate loader string to be used with extract text plugin
  function generateLoaders (loader, loaderOptions) {
    const loaders = [cssLoader]
    if (loader) {
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }

    // !!! This part is only important if you want to have global SCSS for every component
    // if (loader === 'sass') {
    //   loaders.push({
    //     loader: 'sass-resources-loader',
    //     options: {
    //       resources: path.join(__dirname, '../../../app/javascript/MYmponents/styles/my-foundation.scss')
    //    },
    //  });
    // }

    // Extract CSS when that option is specified
    // (which is the case during production build)
    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
  }

  // http://vuejs.github.io/vue-loader/en/configurations/extract-css.html
  return {
    css: generateLoaders(),
    scss: generateLoaders('sass'),
  }
}

// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
  const output = []
  const loaders = exports.cssLoaders(options)

  for (const extension in loaders) {
    const loader = loaders[extension]
    output.push({
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    })
  }

  return output
}

You may need some missing modules likevue-style-loader, extract-text-webpack-plugin and maybe path, sass-resources-loader in my case. I'm not sure.

All 2 comments

I'm having the same issue.

I figured it out... It feels like a workaround but it may be the right way to do it.

// config/webpack/enviroment.js

const { environment } = require('@rails/webpacker')
const webpack = require('webpack')
const aliasConfig = require('./alias')
environment.config.merge(aliasConfig)

// Vue Loader
const vue =  require('./loaders/vue')
environment.loaders.append('vue', vue)

module.exports = environment

Create the Vue loader file if it's not already created.

// config/webpack/loaders/vue.js

const utils = require('./vue-style-rules')

const { dev_server: devServer } = require('@rails/webpacker').config

const isProduction = process.env.NODE_ENV === 'production'
const inDevServer = process.argv.find(v => v.includes('webpack-dev-server'))
const extractCSS = !(inDevServer && (devServer && devServer.hmr)) || isProduction

module.exports = {
  test: /\.vue(\.erb)?$/,
  use: [{
    loader: 'vue-loader',
    options: {
      loaders: utils.cssLoaders({
        sourceMap: !isProduction,
        extract: extractCSS
      })
    }
  }]
}

That's the tricky part. I use only SCSS so i configured it this way.

// config/webpack/loaders/vue-style-rules.js

const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')

exports.cssLoaders = function (options) {
  options = options || {}

  const cssLoader = {
    loader: 'css-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }

  // generate loader string to be used with extract text plugin
  function generateLoaders (loader, loaderOptions) {
    const loaders = [cssLoader]
    if (loader) {
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }

    // !!! This part is only important if you want to have global SCSS for every component
    // if (loader === 'sass') {
    //   loaders.push({
    //     loader: 'sass-resources-loader',
    //     options: {
    //       resources: path.join(__dirname, '../../../app/javascript/MYmponents/styles/my-foundation.scss')
    //    },
    //  });
    // }

    // Extract CSS when that option is specified
    // (which is the case during production build)
    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
  }

  // http://vuejs.github.io/vue-loader/en/configurations/extract-css.html
  return {
    css: generateLoaders(),
    scss: generateLoaders('sass'),
  }
}

// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
  const output = []
  const loaders = exports.cssLoaders(options)

  for (const extension in loaders) {
    const loader = loaders[extension]
    output.push({
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    })
  }

  return output
}

You may need some missing modules likevue-style-loader, extract-text-webpack-plugin and maybe path, sass-resources-loader in my case. I'm not sure.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

towry picture towry  路  3Comments

eriknygren picture eriknygren  路  3Comments

naps62 picture naps62  路  3Comments

itay-grudev picture itay-grudev  路  3Comments

christianrojas picture christianrojas  路  3Comments