Sapper: Global SASS/SCSS/Less/Stylus support

Created on 14 Oct 2018  Â·  35Comments  Â·  Source: sveltejs/sapper

I'm trying to convert my Nuxt application to Sapper and I've come across a bit of a roadblock.

In my Nuxt application, I have a layout file which simply includes bulma's SASS version:

<style lang="scss">
  @import '~bulma/scss/_all';
</style>

This is possible, and allows the styles to be used across the application, because Vue doesn't scope styles by default.

I'll caveat this by saying that I prefer scoped styles by default, but I feel like there needs to be a way to unscope the styles when necessary. Because of the way Sapper/Svelte handles styles, and scopes them, including bulma in a similar way to the above means that Svelte will scope all the styles, and then remove nearly all of them as it sees them as "unused".

This means I end up with an application with no styles!

Right now this is a roadblock to my conversion, as I can't see a workaround (other than some horrendous external compilation with gulp or similar and then including the compiled css in my head - which I really want to avoid as it takes from the speed of Sapper's development, as well as adding complexity where it really shouldn't be.

Any tips? Or is there a feature / capability missing from Sapper here?

Most helpful comment

@fanckush For scss/sass, you can prepend the @import 'variables.scss' with the data prop:

preprocess({
  scss: {
    data: `@import 'variables.scss';`
  }
})

All 35 comments

In the Svelte docs there's a section about using :global() to ignore Svelte's scoping mechanism. You need to apply this to each selector you want globally scoped. It can get a bit tedious so I wrote a svelte style preprocessor which does this automatically when the style block has a "global" attribute, eg. <style global>... no docs though as it's just something we've been using internally and it's more for PostCSS.

For SCSS support you might want to have a look at https://github.com/kaisermann/svelte-preprocess and https://github.com/ls-age/svelte-preprocess-sass. Or if you want to write your own preprocessor see https://github.com/sveltejs/svelte#preprocessor-options.

It's also worth mentioning you can import your global styles in a JS file and then handle it with webpack/rollup plugins. This is actually what we do most of the time.

A caveat is _all_ the styles will be included so I recommend using a tool like https://github.com/FullHuman/purgecss to remove any unused styles.

Hi @MaxMilton - thanks for your help, but sadly none of these solutions fix the core issue. I'm quite happily using scss within components, but I need to include the bulma library. I can't (and shouldn't) wrap the entire library in :global { }.

@MaxMilton this is basically what I'm doing now, based on another project I have found, but writing a build file and parallel building my scss outside the app with a separate watcher and then including the compiled css file out of band... is a total hack.

This feels like it should be a simple, easy thing that should be supported out of the box. I would vouch that most non-trivial projects are based on a CSS library (Bootstrap, Bulma, Pure, Semantic etc) - with a customisation file.

In that case it's probably best to import global styles it in your src/client.js (assuming you're using sapper-template) and handle via webpack/rollup. Then in your components you can import variables as you need them.

@MaxMilton thanks - I'll give that a go. If it works then it's more a matter of documentation than it is functionality.

Probably worth updating this with my current solution. I still think this should be native to Sapper as I think it's quite a common usecase and therefore having to get people rolling up their sleeves and fumbling with webpack to do this isn't ideal, but:

https://github.com/kaisermann/svelte-preprocess

Is a very nice solution to getting preprocessed styles working in components, and also allowing processing and live-reloading of a third-party css framework like Bulma / Bootstrap / Semantic.

@antony I had the same issue with Bulma and global CSS. By the way I'm using svelte-preprocess as well. Here is how I made it work by generating a global.css file with Bulma stuff:

Install the following dependencies:

npm i -D node-sass postcss cssnano bulma

Add the following lines to your webpack.config.js file:

const fs = require('fs')
const sass = require('node-sass')
const postcss = require('postcss')
const cssnano = require('cssnano')({preset: 'default'})

// generating global css file https://github.com/sveltejs/sapper/issues/357
sass.render({
  file: './src/style/global.sass',
  indentedSyntax: true,
  outFile: './static/global.css'
}, function (error, result) {
  if (!error) {
    postcss([cssnano])
    .process(result.css, {
      from: './static/global.css',
      to: './static/global.css'
    })
    .then(result =>
      fs.writeFile('./static/global.css', result.css, function (err) {
        if (err) {
          console.log(err)
        }
      })
    )
  } else {
    console.log(error)
  }
})

And create a /src/style/ subdirectory with the 3 following files:

  • variables.sass
// Import a Google Font
@import url('https://fonts.googleapis.com/css?family=Nunito:400,700')

// Set your brand colors
$purple: #8A4D76
$pink: #FA7C91
$brown: #757763
$beige-light: #D0D1CD
$beige-lighter: #EFF0EB

// Update Bulma's global variables
$family-sans-serif: "Nunito", sans-serif
$grey-dark: $brown
$grey-light: $beige-light
$primary: $purple
$link: $pink
$widescreen-enabled: false
$fullhd-enabled: false

// Update some of Bulma's component variables
$body-background-color: $beige-lighter
$control-border-width: 2px
$input-border-color: transparent
$input-shadow: none

  • bulma.sass
// Import only what you need from Bulma
@import "../../node_modules/bulma/sass/utilities/_all.sass"
@import "../../node_modules/bulma/sass/base/_all.sass"
@import "../../node_modules/bulma/sass/elements/button.sass"
@import "../../node_modules/bulma/sass/elements/container.sass"
@import "../../node_modules/bulma/sass/elements/form.sass"
@import "../../node_modules/bulma/sass/elements/title.sass"
@import "../../node_modules/bulma/sass/components/navbar.sass"
@import "../../node_modules/bulma/sass/layout/hero.sass"
@import "../../node_modules/bulma/sass/layout/section.sass"
  • global.sass
@charset "utf-8"
@import "./variables"
@import "./bulma"

// your global styles
p.foo
  color: red

and in your .html files (if you use svelve-preprocess) use

<style lang="sass">
@import "../style/variables"

p.foo
  color: $primary
</style>

Note that you have to re-run npm run dev every time you modify these global Bulma files.

Hope it helps.

Waiting for #357 …

I used @MaxMilton 's code and created this preprocessor which will make any css code global including support for external files and postcss plugins:
https://www.npmjs.com/package/svelte-preprocess-css-global

Probably worth updating this with my current solution. I still think this should be native to Sapper as I think it's quite a common usecase and therefore having to get people rolling up their sleeves and fumbling with webpack to do this isn't ideal, but:

https://github.com/kaisermann/svelte-preprocess

Is a very nice solution to getting preprocessed styles working in components, and also allowing processing and live-reloading of a third-party css framework like Bulma / Bootstrap / Semantic.

Hey man, can you share how you implemented this? I'm new to this build step thing in web development, forgive my ignorance.

Thanks in advance.

@jaybeemind svelte-preprocess has some excellent documentation about how to integrate it into rollup/webpack, you'd just need to follow it. If you need more help - come and talk to us in chat.

To those that would need a simple solution for this (using rollup template), with help from @antony , I was able to do it like this:

you will need:

then in your rollup.config.js file, add these:

...
import autoPreprocess from 'svelte-preprocess'

const preprocessOptions = {
    transformers: {
        scss: {
            includePaths: [
                'node_modules',
                'src'
            ]
        },
        postcss: {
            plugins: [
                require('autoprefixer'),
            ]
        }
    },
}
...
export default {
  client: { // (and probably 'server' too)
    ...
    svelte({
      ...
      preprocess: autoPreprocess(preprocessOptions)
}),

after that, I added a style directory under src, and made a main.scss file.

inside the main.scss file, mine looks like these atm:

@import "../../node_modules/spectre.css/src/spectre.scss";

then import it in _layout.svelte

<style global lang="scss">
    @import './style/main.scss';
...

I came from Vue.js and was impressed by how simple it is to do things with svelte.
I hope many would adapt sapper too, would love for it to mature.

EDIT: Removed transformers as per the authors message on discord.
You can do it this way too:

const preprocessOptions = {
  scss: {
    includePaths: [
      'node_modules',
      'src'
    ]
  },
  postcss: {
    plugins: [
      require('autoprefixer'),
    ]
  }
}

You shouldn't need ../../node_modules/ prefixing your scss path. I believe that includePaths makes this possible.

@jaybeemind Thanks for this solution. I have to hit refresh for the changes to show, is that the expected behavior? Maybe I did something wrong.

@YogliB I got it working, The code for Svelte and Sapper are different. https://github.com/kaisermann/svelte-preprocess#with-sapper

@jaybeemind Tried doing something like that, in order to use Ionic 4 with Sapper and it didn't work...

You shouldn't need ../../node_modules/ prefixing your scss path. I believe that includePaths makes this possible.

I see, thanks! But honestly, I only did it that way because of vscode autocomplete lol 🤣

@jaybeemind Tried doing something like that, in order to use Ionic 4 with Sapper and it didn't work...

Can't help bro unless we see what you did.

Edit:
So this seems to not be possible at the moment https://github.com/kaisermann/svelte-preprocess/issues/58

Original:
is there a way to avoid importing the variables.scss in every