Search code examples
ruby-on-railsvue.jswebpackersass-loader

SassError: Invalid CSS after "" with Rails 6, Webpacker, and Vue


I've just started a fresh rails-6 project with webpacker and vue. I wanted to have the vue-component styles in sass, but the sass-loader throughs:

Error: Module build failed (from ./node_modules/sass-loader/dist/cjs.js) SassError: Invalid CSS after "": expected 1 selector or at-rule, was ".foo"

Has anyone seen this before? Or maybe, how can I debug what goes wrong here?

Component foo.vue

<template>...</template>
<script>...</script>
<style lang="sass">
  .foo
    margin: 0
    padding: 0
</style>

Removing the initial indention of the sass code does not help.

Versions

  • rails 6.1.3.1
  • webpacker 5.2.1
  • node 14.16.1

Project setup

rails new --database=postgresql --skip-test foo
rails webpacker:install:vue
yarn add vue-turbolinks
rails webpacker:install:coffee
yarn add coffee-loader@1
yarn add sass-loader@10

package.json

{
  "name": "foo",
  "private": true,
  "dependencies": {
    "@popperjs/core": "^2.9.2",
    "@rails/actioncable": "^6.0.0",
    "@rails/activestorage": "^6.0.0",
    "@rails/ujs": "^6.0.0",
    "@rails/webpacker": "5.2.1",
    "@tabler/core": "tabler/tabler",
    "bootstrap": "^5.0.0-beta3",
    "coffee-loader": "1",
    "coffeescript": "1.12.7",
    "sass-loader": "10",
    "tabler": "^1.0.0-alpha.8",
    "turbolinks": "^5.2.0",
    "vue": "^2.6.12",
    "vue-loader": "^15.9.6",
    "vue-select": "^3.11.2",
    "vue-template-compiler": "^2.6.12",
    "vue-turbolinks": "^2.2.2"
  },
  "version": "0.1.0",
  "devDependencies": {
    "webpack-dev-server": "^3.11.2"
  }
}

I've also added vue-select, bootstrap, and tabler. But I don't think they will interfere.

Webpack configuration config/webpack/environment.js

const { environment } = require('@rails/webpacker')
const coffee =  require('./loaders/coffee')
const { VueLoaderPlugin } = require('vue-loader')
const vue = require('./loaders/vue')

environment.plugins.prepend('VueLoaderPlugin', new VueLoaderPlugin())
environment.loaders.prepend('vue', vue)
environment.loaders.prepend('coffee', coffee)
module.exports = environment

Layout application.html.haml

!!!
%html
  %head
    %title Foo
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
  %body
    #vue_app
      = yield

Pack packs/application.js

import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"

Rails.start()
Turbolinks.start()
ActiveStorage.start()

import 'bootstrap'
import '@tabler/core'

import TurbolinksAdapter from 'vue-turbolinks'
import Vue from 'vue/dist/vue.esm'

Vue.use(TurbolinksAdapter)

document.addEventListener('turbolinks:load', () => {
  const app = new Vue({
    el: '#vue_app',
    data: () => {
      return {}
    },
    components: {}
  })
})

Solution

  • Sass vs. SCSS

    There are two syntax variants:

    There are two syntaxes available for Sass. The first, known as SCSS (Sassy CSS) and used throughout this reference, is an extension of the syntax of CSS. This means that every valid CSS stylesheet is a valid SCSS file with the same meaning. This syntax is enhanced with the Sass features described below. Files using this syntax have the .scss extension.

    The second and older syntax, known as the indented syntax (or sometimes just “Sass”), provides a more concise way of writing CSS. It uses indentation rather than brackets to indicate nesting of selectors, and newlines rather than semicolons to separate properties. Files using this syntax have the .sass extension.

    See also: https://stackoverflow.com/a/5654471/2066546, https://sass-lang.com/documentation/syntax

    Which syntax does the sass-loader use?

    The sass-loader chooses the syntax according to the file extension by default:

    The indentedSyntax option has true value for the sass extension.

    This means that the original sass syntax (indented syntax) is only used if the file extension is .sass. Within the vue component, which has the extension .vue, the sass-loader uses the newer "scss" syntax by default.

    How to configure sass-loader to use the sass syntax (indented syntax)?

    The sass-loader documentation has instructions on how to specify indentedSyntax: true in the webpack configuration.

    // webpack.config.js
    
    module.exports = {
      module: {
        rules: [
          {
            test: /\.s[ac]ss$/i,
            use: [
              "style-loader",
              "css-loader",
              {
                loader: "sass-loader",
                options: {
                  sassOptions: {
                    indentedSyntax: true
                  },
                },
              },
            ],
          },
        ],
      },
    };
    

    However, with webpacker, the webpack configuration is composed automatically. So, one needs to modify the existing configuration object:

    // config/webpack/environment.js
    
    const { environment } = require('@rails/webpacker')
    
    // To get an overview, have a look at `environment.loaders`.
    console.log(environment.loaders)
    
    const { merge } = require('webpack-merge')
    const sassConfig = environment.loaders.find(el => el.key == 'sass')
    const sassLoader = sassConfig.value.use.find(el => el.loader == 'sass-loader')
    
    sassLoader.options = merge(sassLoader.options, {
      sassOptions: {
        indentedSyntax: true
      }
    })
    
    module.exports = environment
    

    This requires webpack-merge: yarn add webpack-merge.

    I'm sure there are better ways to do it. Please feel free to add answers or comments!