vuejs / vue-loader

📦 Webpack loader for Vue.js components
MIT License
4.99k stars 915 forks source link

Load a global settings.scss file in every vue component? #328

Closed westwick closed 7 years ago

westwick commented 8 years ago

I find myself repeating this same pattern in every component:

<style lang="scss">
  @import "../styles/settings.scss";

  .someclass { color: $some-variable; }
</style>

My settings.scss only contains variables. An additional pain point is that I have my components nested in several folders for better organization, so I always have to be careful to specify the path to my settings.scss properly. It'd be great if there was a way to globally include a settings.scss or similar file.

lessYFF commented 8 years ago

I have the same question,but also have no answer.

ericcirone commented 7 years ago

I basically have the same question as well.

sqal commented 7 years ago

@westwick have you tried - https://github.com/shakacode/sass-resources-loader

jbruni commented 7 years ago

I do not know the answer to the main question (global scss load).

But regarding the additional pain point (adjust relative path), maybe you can mitigate it by configuring a Webpack alias.

You would use @import "styles/settings.scss" in all your files, having a webpack config like this:

{
  resolve: {
    alias: {
      styles: 'path/to/your/styles'
    }
  }
}

Docs: https://webpack.github.io/docs/resolving.html https://webpack.github.io/docs/configuration.html#resolve-alias

ericcirone commented 7 years ago

Neither of these solutions solves the fact that styles.scss will be repeated on your page for however many files you include it in. That just seems not thought out and very wasteful.

LinusBorg commented 7 years ago

@ericcirone We have to differenciate:

Sharing Variables & Settings

The OP asked about importing a .scss for shared variables & settings.

Such a file usually does not contain any CSS, but only SCSS $variables, mixins etc. (as stated by the OP himself) - so there would be no repeated CSS in your final, compiled files.

Granted, it's tedious to add this @import statement in every component, but it does not change anything about filesize or performance.

Sharing styles

You wrote about importing styles.scss in multiple files - so , judging from the filename, you seem to ask how to deal with shared styles.

For this scenario, you simply do an import of the shared style file in your main.js file once. As CSS has no local scope, these styles will be available anywhere in your app so you don't have to import them anywhere else.

@jbruni The alias is a good solution to relative paths, yep! :+1:

But you should use an absolute path:

{
  resolve: {
    alias: {
      styles: path.resolve(__dirname, '../src/path/to/your/styles') // relative to the location of the webpack config file!
    }
  }
}
ericcirone commented 7 years ago

@LinusBorg i got ya! I'm new to Sass and Vue. My problem was that i'm building a project with Foundation and tried importing in the Foundation main app.scss in all my <style> blocks so I could use the Foundation variables and was getting repeating code everywhere.

gregorskii commented 7 years ago

@ericcirone under the current setup you have to go into the foundation scss folder and find the partials from the src folder that contain the variables and the mixins, then import those in every file. You likely do not need to import the entire main.scss file in every partial if that's what you are doing.

LinusBorg commented 7 years ago

Clsoing as this issue seems to be solved.

uptownhr commented 7 years ago

I'd ask to leave this open as the real issue is being able to use scss variables, mixins in component definitions. Although css automatically cascades down, if you wanted to use a globally defined sass variable inside your component, you will not be able to use the $variable synthaxx.

//global.scss
$white = '#fff'

//component.vue
<style>
.background {
  color: $white;
}
</style>

this will fail and a solution to this would turn my world around.

westwick commented 7 years ago

I also think this should remain open. I'd love to be able to use global scss variables throughout components without importing every time.

LinusBorg commented 7 years ago

Well, this is something that vue-loader is not suited to solve - it would have to be solved in sass-loader etc., because each pre processor might have a different syntax/ way of adding such a file.

Stylus-loader offers such a functionality, by the way.

LinusBorg commented 7 years ago

@uptownhr well you can simply import your variables file in each component. It's a one-line copy&paste for each component, but it works.

jpt commented 7 years ago

@LinusBorg wrote

Stylus-loader offers such a functionality, by the way.

Can you point to an example of this with Stylus?

+1 to everyone asking for this issue to remain open, even if means waiting until a pull request eventually finds its way to sass-loader. This functionality should be a Vue product requirement, frankly.

LinusBorg commented 7 years ago

Can you point to an example of this with Stylus?

This is not very well documented, I only read about it in a docs example for using a plugin:

https://github.com/shama/stylus-loader#using-nib-with-stylus

The import option will load the file "globally" as far as I have tested it.

+1 to everyone asking for this issue to remain open, even if means waiting until a pull request eventually finds its way to sass-loader.

We will not keep issues open for 3rd-party libs that we have no influence on, and likely won't require a change in vue-loader if they and in the 3rd-party lib (such changes would likely be implemented in loader configs, like the above import for stylus-loader, which vue-loader doesn't "care" about).

You will be better of asking for this in the respective loader's repos.

kidBrazil commented 7 years ago

I too have the same problem. I really want to use a full Vue.JS Webpack stack on my next project. However.. not being able to import SASS variables is almost a deal breaker here. I guess for now I will compile global styles separately and use minimal styling in the components themselves.

There has to be a solution to importing variables and mixins no? Or making them globally available on compile?

LinusBorg commented 7 years ago

I'm not sure if you missed it, so repeating myself again: you can of course simply @import your variables.

This threads was about doing that automatically, which is not possible and frankly, (repeating myself again, see previous reply) cannot be the job of vue-loader to provide that for all the available pre-processors.

westwick commented 7 years ago

I think you guys should seriously reconsider your position on this. At this point, SASS is the preprocessor of choice for at least 50% of devs (or a lot more, depending on which poll you're looking at). I really like keeping my components' css local to that component but it's a PITA when developing to have to import my settings.scss every time I start a new component, and having to take care to make the path right, and then it also makes the code slightly more verbose. Then if you ever move your component's path you have to update that import statement, etc. etc. Without this ability, I've resorted to the more traditional method of having all my scss files totally separate from the components, which defeats a good chunk of the the purpose of using vue-loader / .vue components IMO. Although to be totally fair I have not looked into the other options of getting this to work (such as the stylus one you linked). Either way, thanks for your work on this project.

LinusBorg commented 7 years ago

You easily reduce the pain about the paths with a webpack alias, reducing the statement to e.g. @import "vars", working everywhere.

Considering the number of import statements we have in the JavaScript of most components, that should be manageable.

Adding this automatically with vue-loader would be a hack. For starters, `lang="sass" can mean SCSS or SASS syntax, depending on the sass-loader config. So which do we choose when we prep end the @import string to the content of the script tag? We would have to interpret the webpqck settings somehow, etc.

westwick commented 7 years ago

That's a fair point and I think a webpack alias can be a good "workaround". It's definitely more explicit to always use an import statement, which you could argue is better.

LinusBorg commented 7 years ago

Great :)

uptownhr commented 7 years ago

quick question, when you @import 'vars' in all your components, will this bloat your build? Meaning, will webpack be able to compile this into a commons?

LinusBorg commented 7 years ago

Concerning filesize, this has no influence if the imported file only contains SCSS variables and no actual CSS markup.

Concerning memory / compile speed, this should be so small as to be neglegable - I haven't seen any issues with it in dev.

There are other parts of the build process that should be optimized if you experience longer (Re)build times in dev. (vendor chunks, DLL splitting ..)

delucis commented 7 years ago

Is there anything more needed to get Webpack aliases working in .vue components?

I set a resolve.alias for my style directory as suggested above, and can import/require from a Javascript file, but if I do @import "styles/_vars.scss" in a .vue component, I get a “File to import not found or unreadable” error.

File structure:

webpack.config.js
src/
├ components/
│ └ my-component.vue
└ style/
  └ _vars.scss
// webpack.config.js
{
  resolve: {
    alias: {
      'styles': path.resolve(__dirname, './src/style/')
    }
  }
}
<!-- my-component.vue -->
<style lang="sass">
  @import "styles/_vars.scss";
</style>

I’ve tried all kinds of variations on the import directive with no success, and a direct alias to the variables file also produces an error.

It does work using an absolute path (no Webpack alias), but I’d like to avoid the fragility that introduces:

@import "./../style/vars";

Any help appreciated!

LinusBorg commented 7 years ago

@import "~styles/_vars.scss";

w10036w commented 7 years ago

works for stylus: in webpack.config:

const stylus_var = path.resolve(__dirname, './client/styl/var.styl')

resolve: {
    alias: {
      stylus_var,
    }
  },

in *.vue:

@import '~stylus_var'
yaoyonstudio commented 7 years ago

About style in vue project, Here's my solution(for now):

1、I have two separate scss file( src/css/style.scss & src/css/vars.scss ) "style.scss" is a common scss file, which styling the main layout and common style;
"vars.scss" define all the common variable ;

2、In Vue component(single page component), I import the vars.scss file as it needed

3、In webpack , I extract all components style , and combine with the style.scss, when I build, all style combine together perfectly

and for now, it works well , even though import the "vars.scss" file did a little extra job, but not bother too much.

and if there're another more perfectly solution, I glad to hear that.

Here's my main webpack config:

var ExtractTextPlugin = require("extract-text-webpack-plugin")
var extractSCSS = new ExtractTextPlugin({filename: 'css/style.css', disable: false, allChunks: true})
entry: {
    app: ['./css/style.scss', './main.js'],
    vendor: ['vue', 'vue-router', 'axios']
},
rules: [
    {
        test: /\.scss$/,
        use: extractSCSS.extract({
            fallback: "style-loader",
            publicPath: "/",
            use: ['css-loader', 'sass-loader', 'postcss-loader']
        })
    },
    {
        test: /\.vue$/,
        loader: 'vue-loader',
        include: path.join(__dirname, "src"),
        exclude: /node_modules/,
        options: {
            loaders: {
                scss: extractSCSS.extract({
                    use: ['css-loader', 'sass-loader', 'postcss-loader'],
                    fallback: 'style-loader'
                })
            }
        }
    },
]
hilookas commented 7 years ago

Well, I have a problem. Two .vue file. a.vue b.vue In a.vue, I define some vars.(scss) And how can I use these vars in b.vue without making another .scss file??? This problem happens when I try to write a module for vue. b.vue is a file in module, so it can't be changed. a.vue is a file in project, and I want to define some vars for b.vue. Sorry for my bad English. :(

ghost commented 7 years ago

Problem can be solved by following way: 1) include global styles in every *.vue file (webpack aliases can be helpfull as described before) 2) remove dublicating in resulting file by optimize-css-assets-webpack-plugin

NOTE: scoped style in vue component can block styles merging

foundryspatial-duncan commented 7 years ago

I was able to do this today using sass-resources-loader as per @sqal 's suggestion.

I changed vue-cli's familiar loaders block from the default:

loaders: { 
  sass: 'vue-style-loader!css-loader!postcss-loader!sass-loader?indentedSyntax=1', 
  scss: 'vue-style-loader!css-loader!postcss-loader!sass-loader', 
} 

...to the array/object syntax that you'd use in module.rules.use. Here's my entire block for vue-loader:

{
  test: /\.vue$/,
  use: {
    loader: 'vue-loader',
    options: {
      loaders: {
        sass: [
          'vue-style-loader',
          'css-loader',
          'postcss-loader',
          'sass-loader?indentedSyntax=1',
          {
            loader: 'sass-resources-loader',
            options: {
              resources: path.resolve(__dirname, 'app/src/renderer/styles/variables.scss'), // for example
            },
          },
        ],
        scss: [
          'vue-style-loader',
          'css-loader',
          'postcss-loader',
          'sass-loader',
          {
            loader: 'sass-resources-loader',
            options: {
              resources: path.resolve(__dirname, 'app/src/renderer/styles/variables.scss'), // for example
            },
          },
        ],
      },
    },
  },
},

Note that I also added postcss-loader but you don't have to, and that your resources can also be an array.

edit: So now any <style lang="scss"> (or sass) tag will have my variables.scss file available.

yass09 commented 7 years ago

@foundryspatial-duncan, thanks for offering a solution to this issue. However, I just tried this and it doesn't seem to work on my side

This is the base config webpack file which came with the cli

var path = require('path')
var utils = require('./utils')
var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf')

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

module.exports = {
  entry: {
    app: './src/main.js'
  },
  output: {
    path: config.build.assetsRoot,
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src')
    }
  },
  module: {
    rules: [
      {
        test: /\.(js|vue)$/,
        loader: 'eslint-loader',
        enforce: 'pre',
        include: [resolve('src'), resolve('test')],
        options: {
          formatter: require('eslint-friendly-formatter')
        }
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  }
}

I went into the vueLoaderConfig file to add your code because that's where all the options go (I only added the scss part since I don't plan to use the sass syntax)

var utils = require('./utils')
var config = require('../config')
var isProduction = process.env.NODE_ENV === 'production'
var path = require('path')

module.exports = {
  loaders: utils.cssLoaders({
    sourceMap: isProduction
      ? config.build.productionSourceMap
      : config.dev.cssSourceMap,
    extract: isProduction,
  }), scss: [
        'vue-style',
        'css',
        'sass',
        {
          loader: 'sass-resources-loader',
          options: {
            resources: path.resolve(__dirname, 'assets/styles/variables.scss')
          }
        }
      ]

}

and this is the utils.cssLoaders file

var path = require('path')
var config = require('../config')
var ExtractTextPlugin = require('extract-text-webpack-plugin')

exports.assetsPath = function (_path) {
  var assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory
  return path.posix.join(assetsSubDirectory, _path)
}

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

  var cssLoader = {
    loader: 'css-loader',
    options: {
      minimize: process.env.NODE_ENV === 'production',
      sourceMap: options.sourceMap
    }
  }

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

    // 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)
    }
  }

  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
  return {
    css: generateLoaders(),
    postcss: generateLoaders(),
    less: generateLoaders('less'),
    sass: generateLoaders('sass', { indentedSyntax: true }),
    scss: generateLoaders('sass'),
    stylus: generateLoaders('stylus'),
    styl: generateLoaders('stylus')
  }
}

// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
  var output = []
  var loaders = exports.cssLoaders(options)
  for (var extension in loaders) {
    var loader = loaders[extension]
    output.push({
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    })
  }
  return output
}

Any idea why?

foundryspatial-duncan commented 7 years ago

Ah, the example I gave was actually from an electron template but it will work in the webpack-simple one too. For the main webpack template, I think you'd need to modify the generateLoaders function in utils.js.

You basically want to add this object:

{
    loader: 'sass-resources-loader',
    options: {
        resources: path.resolve(__dirname, 'path/to/variables.scss'), // for example
    },
}

to the end of the array that generateLoaders returns for scss. Another approach would be to maybe do a push or a concat on this line:

    scss: generateLoaders('sass'),
NLNicoo commented 7 years ago

I can confirm that this works, thanks!


    scss: generateLoaders('sass').concat(
      {
        loader: 'sass-resources-loader',
        options: {
          resources: path.resolve(__dirname, '../src/style/variables.scss')
        }
      }
    ),
yyx990803 commented 7 years ago

It would be great if anyone could submit a PR to include the solution to this page since it seems to be quite a common request.

westwick commented 7 years ago

Created https://github.com/vuejs/vue-loader/pull/861

Minasokoni commented 7 years ago

@foundryspatial-duncan & @NLNicoo amazing solutions. Thank you

ghost commented 7 years ago

I saw this and decided I wanted to:

I noticed that since I used vue-cli to generate my project, I already had an alias to the src folder.

in webpack.base.conf.js:

  resolve: {
   // snip...
    alias: {
      // snip...
      '@': resolve('src')
    }
  },

So @ in an import points to the src folder. And all I had to do was:

div { background-color: $a-variable-from-the-file; }


... in case that helps anybody else.
johnathankent commented 7 years ago

@dad700, that is awesome! Works for .stylus files, too. This was the simplest solution to date.

PierBover commented 7 years ago

For anyone trying to figure out why you need to use a tilde ~ to make aliases work check this issue in css-loader:

https://github.com/webpack-contrib/css-loader/issues/12

css @import is relative to the current directory. For resolving "like a module" you can prefix ~.

AFAIK it's not documented anywhere.

RomansBermans commented 7 years ago

@dad700, that is perfect! Clean & minimal solution. Big 👍 from me for not over-engineering.

shaun-sweet commented 7 years ago

From the /build folder in the project, i went to utils.js and inside the generateLoaders() function, I added this

// This makes my vars and mixins avail throughout my app at a global level
    if (loader === 'sass') {
      loaders.push({
        loader: 'sass-resources-loader',
        options: {
          resources: [
            path.resolve(__dirname, '../src/styles/vars.scss'), 
            path.resolve(__dirname, '../src/styles/mixins.scss')
          ]
        }
      })
    }

make sure you also run npm install --save-dev sass-resources-loader Last thing is to make sure those files exist or you'll get a compile error. i put them in /src/styles but you can change those lines to put em wherever! OH, also pay attention to the order I have in my resources array. I wanted the vars to get parsed first so that they would be avail to be used from within the mixins. Thanks everyone!

:)

sandangel commented 7 years ago

I'm trying to use material-component-web in my project with webpack-simple-template. I have successfully include ' @import "material-components-web/material-components-web"; ' in my .vue file with this webpack config:

 loaders: {
    scss: [
        'vue-style-loader', 'css-loader', 
        {
             loader: 'sass-loader',
             options: {
                 includePaths: ["./node_modules"]
             }
        }, {
         ...

but it doesn't has a global effect. I still have to import material-components-web or @material/...( individual component ) in every .vue file. As @foundryspatial-duncan suggested, I use sass-resource-loader to load a style.scss file which will import whole material-component-web library. Here is my webpack.config.js


{
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    loaders: {
                        sass: ['vue-style-loader', 'css-loader', {
                            loader: 'sass-loader',
                            options: {
                                includePaths: ["./node_modules"],
                                indentedSyntax: true
                            }
                        }, {
                                loader: 'sass-resources-loader',
                                options: {
                                    resources: [
                                        path.resolve(__dirname, './src/style.scss'),
                                    ],
                                    includePaths: ["./node_modules"]
                                }
                            }],
                        scss: ['vue-style-loader', 'css-loader', {
                            loader: 'sass-loader',
                            options: {
                                includePaths: ["./node_modules"]
                            }
                        }, {
                                loader: 'sass-resources-loader',
                                options: {
                                    resources: [
                                        path.resolve(__dirname, './src/style.scss'),
                                    ],
                                    includePaths: ["./node_modules"]
                                }
                            }]
                    }
                }
            }

and here is my src/style.scss file

@import "material-components-web/material-components-web";

$mdc-theme-primary: #4eadff; //override default
$mdc-theme-accent: #ff4081;
$mdc-theme-background: #c5ff83;

Error:

ERROR in ./node_modules/sass-loader/lib/loader.js?{"includePaths":["./node_modules"]}!./node_modules/sass-resources-loader/lib/loader.js?{"resources":["/home/sand/Data/sand/Downloads/vue-practice/module2/src/style.scss"],"includePaths":["./node_modules"]}!./node_modules/vue-loader/lib/style-compiler?{"vue":true,"id":"data-v-1d1f6298","scoped":true,"hasInlineConfig":false}!./node_modules/vue-loader/lib/selector.js?type=styles&index=0!./src/components/Header.vue
Module build failed:
<template>
^
      File to import not found or unreadable: ../material-components-web/material-components-web.

although I have added an includePaths option under sass-resources-loader in webpack.config.js file, webpack could not find material-components-web under node_modules folder . So my question is how to load a global variables scss file which contain an @import to a third-party library to get that library's variables and mixins ? I had take a look at their vue example but since they use webpack 1, most option is not work in webpack 2,3

sandangel commented 7 years ago

I solved it. I forget to put '~' sign at import path like this

@import "material-components-web/material-components-web";

Sorry everyone

eduardocmoreno commented 7 years ago

That is how I import my vars and mixins sass files globally in my vue templates:

{
  loader: 'sass-loader',
  options: {
    indentedSyntax: true,
    sourceMap: true,
    outputStyle: 'compressed',
    includePaths: ['./app/sass', './app/sass/partials'],
    data: '@import vars\n@import mixins'
  }
}

Hope this helps someone.

adi518 commented 7 years ago

@eduardocmoreno awesome find! better than sass-resources-loader suggested above, which is just adding another dep.

anish000kumar commented 7 years ago

https://stackoverflow.com/a/46015906/5013932

polikin commented 6 years ago

How does it work with Browserify?

PierBover commented 6 years ago

@polikin vue-loader is a Webpack loader.

For Browserify you should be using this: https://github.com/vuejs/vueify

tomanistor commented 6 years ago

@anish000kumar that solution worked for me but I realized that the styles for any component that I used global SCSS in wasn't being extracted a separate style.css that contains all of the app's styling. Instead, it's inserted into the site via style tags. Any reason why that might be the case?

I have a global.scss file in a styles/scss folder that imports a lot of smaller global SCSS files, including variables, mixins, and actual styling I want applied across the app.

In webpack.config.js my vue-loader configuration looks like this:

{
  test: /\.vue$/,
  exclude: /(node_modules|bower_components)/,
  loader: 'vue-loader',
  options: {
    extractCSS: true,
    loaders: {
      scss: 'vue-style-loader!css-loader!sass-loader?data=@import "./static/styles/scss/global.scss";'
    }
  }
}
anish000kumar commented 6 years ago

@tomanistor I have updated my answer in detail over here - https://stackoverflow.com/a/46015906/5013932 I guess it would help you. You would want to look into utils/build.js to see how webpack spits the CSS in a separate stylesheet. A quick look reveals this:

  // 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)
    }
  }

I guess that should suffice would needs :)