Search code examples
ruby-on-railswebpacktailwind-csshamlpostcss

tailwind 3 not loading completely in rails + shakapacker + HAML (webpacker@v6, webpack@v5, postcss@v8)


UPDATE 2021-09-08: TL;DR. Tailwind was not being applied to HAML templates.

Migrating a project to shakapacker which uses webpack@v6 and postcss under the hood. All other front-end CSS assets are loading properly it seems, except for TailwindCSS.

To make debugging simpler, I removed all other CSS imports from the main file except for Tailwind. I also created the following test HTML page to see if Tailwind CSS styles are applied.

test.html
...
<div class="max-w-sm rounded overflow-hidden shadow-lg">
  <div class="px-6 py-4">
  <div class="font-bold text-xl mb-2">
    The Coldest Sunset
  </div>
  <p class="text-gray-700 text-base">
      Lorem ipsum
    </p>
  </div>
</div>
...

When I view this page, it looks plain, and just text – no shadows, padding, font weights, etc. applied. When I look at the generated CSS output, it seems that only Tailwind's reset styles are loaded, nothing else.

You can see the output here: http://codebin.org/view/bba03ee2.

UPDATE 2022-AUG-11

As suggested by the comments below, I ran yarn run tailwindcss -i ./app/assets/packs/styles/application.scss -o ./output.css and it produced the following error:

.../app/assets/packs/styles/application.scss:4:1: Unknown word
You tried to parse SCSS with the standard CSS parser; try again with the postcss-scss parser

That error is pointing to the first commented out line: // @tailwind "base"; which makes sense since // double forward-slash comments are a SASS/SCSS feature and not standard CSS.

However, I thought that including sass and sass-loader would handle this according to the shakapacker installation instructions.

Because of this, I explicitly specified the loaders in webpack.config.js (see below) and the error is still happening.

I've included all the relevant files below.

CONFIG FILES

application.scss (imports all the CSS assets):
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
// @tailwind "base";
// @tailwind "components";
// @tailwind "utilities";

Using @import or @tailwind produces the same result.

application.js (imports application.scss)

I've only included the relevant lines of code.

...
import "./styles/application.scss"
...
packages.json
{
  "name": "app",
  "private": true,
  "dependencies": {
    "@babel/core": "7",
    "@babel/plugin-transform-runtime": "7",
    "@babel/preset-env": "7",
    "@babel/runtime": "7",
    "@rails/actioncable": "^7.0.3-1",
    "@rails/activestorage": "^7.0.3-1",
    "@rails/ujs": "^7.0.3-1",
    "babel-loader": "8",
    "coffee-loader": "^4.0.0",
    "coffeescript": "^2.7.0",
    "compression-webpack-plugin": "9",
    "jquery": "^3.6.0",
    "jquery-ujs": "^1.2.3",
    "moment": "^2.29.4",
    "postcss-flexbugs-fixes": "^5.0.2",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.7.2",
    "select2": "^4.1.0-rc.0",
    "shakapacker": "6.5.0",
    "tailwindcss": "^3.1.8",
    "terser-webpack-plugin": "5",
    "tributejs": "^5.1.3",
    "turbolinks": "^5.2.0",
    "webpack": "5",
    "webpack-assets-manifest": "5",
    "webpack-cli": "4",
    "webpack-dev-server": "^4.9.3",
    "webpack-merge": "5"
  },
  "version": "0.1.0",
  "babel": {
    "presets": [
      "./node_modules/shakapacker/package/babel/preset.js"
    ]
  },
  "browserslist": [
    "defaults"
  ],
  "devDependencies": {
    "autoprefixer": "^10.4.8",
    "css-loader": "^6.7.1",
    "mini-css-extract-plugin": "^2.6.1",
    "postcss": "^8.4.16",
    "sass": "^1.54.3",
    "sass-loader": "^13.0.2",
    "style-loader": "^3.3.1",
    "tailwindcss": "^3.1.8"
  }
}

webpack.config.js:
const { webpackConfig, merge } = require('shakapacker')

// See the shakacode/shakapacker README and docs directory for advice on customizing your webpackConfig.

const sassLoaderConfig = {
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          // Creates `style` nodes from JS strings
          "style-loader",
          // Translates CSS into CommonJS
          "css-loader",
          // Compiles Sass to CSS
          "sass-loader",
        ],
      },
    ],
  },
};

const options = {
  resolve: {
      extensions: ['.mjs', '.js', '.sass', ".scss", ".css", ".module.sass", ".module.scss", ".module.css", ".png", ".svg", ".gif", ".jpeg", ".jpg"]
  }
}

module.exports = merge(webpackConfig, sassLoaderConfig, options);
postcss.config.js
module.exports = {
  plugins: [
    require('postcss-import'),
    require('tailwindcss'),
    require('autoprefixer'),
    require('postcss-flexbugs-fixes'),
    require('postcss-preset-env')({
      autoprefixer: {
        flexbox: 'no-2009'
      },
      stage: 3
    })
  ]
}
tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './app/**/*.html',
    './app/**/*.html.slim',
    './app/**/*.html.erb',
    './app/**/*.js'    
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
webpacker.yml
# Note: You must restart bin/webpacker-dev-server for changes to take effect

default: &default
  source_path: app/assets

  # You can have a subdirectory of the source_path, like 'packs' (recommended).
  # Alternatively, you can use '/' to use the whole source_path directory.
  source_entry_path: /packs

  # If nested_entries is true, then we'll pick up subdirectories within the source_entry_path.
  # You cannot set this option to true if you set source_entry_path to '/'
  nested_entries: true 

  public_root_path: public
  public_output_path: packs
  cache_path: tmp/webpacker
  webpack_compile_output: true
  # See https://github.com/shakacode/shakapacker#deployment
  webpacker_precompile: true

  # Location for manifest.json, defaults to {public_output_path}/manifest.json if unset
  # manifest_path: public/packs/manifest.json

  # Additional paths webpack should look up modules
  # ['app/assets', 'engine/foo/app/assets']
  additional_paths: []

  # Reload manifest.json on all requests so we reload latest compiled packs
  cache_manifest: false

  # Select loader to use, available options are 'babel' (default), 'swc' or 'esbuild'
  webpack_loader: 'babel'

  # Set to true to enable check for matching versions of shakapacker gem and NPM package - will raise an error if there is a mismatch or wildcard versioning is used
  ensure_consistent_versioning: false

  # Select whether the compiler will use SHA digest ('digest' option) or most most recent modified timestamp ('mtime') to determine freshness
  compiler_strategy: digest
  
  extract_css: true

development:
  <<: *default
  compile: true
  compiler_strategy: mtime

  # Reference: https://webpack.js.org/configuration/dev-server/
  dev_server:
    https: false
    host: localhost
    port: 3035
    # Hot Module Replacement updates modules while the application is running without a full reload
    hmr: false
    # If HMR is on, CSS will by inlined by delivering it as part of the script payload via style-loader. Be sure
    # that you add style-loader to your project dependencies.
    #
    # If you want to instead deliver CSS via <link> with the mini-extract-css-plugin, set inline_css to false.
    # In that case, style-loader is not needed as a dependency.
    #
    # mini-extract-css-plugin is a required dependency in both cases.
    inline_css: true
    # Defaults to the inverse of hmr. Uncomment to manually set this.
    # live_reload: true
    client:
      # Should we show a full-screen overlay in the browser when there are compiler errors or warnings?
      overlay: true
      # May also be a string
      # webSocketURL:
      #  hostname: "0.0.0.0"
      #  pathname: "/ws"
      #  port: 8080
    # Should we use gzip compression?
    compress: true
    # Note that apps that do not check the host are vulnerable to DNS rebinding attacks
    allowed_hosts: "all"
    pretty: true
    headers:
      'Access-Control-Allow-Origin': '*'
    static:
      watch:
        ignored: '**/node_modules/**'

test:
  <<: *default
  compile: true

  # Compile test packs to a separate directory
  public_output_path: packs-test

production:
  <<: *default

  # Production depends on precompilation of packs prior to booting for performance.
  compile: false

  # Cache manifest.json for performance
  cache_manifest: true

Solution

  • So, after all the hunting around, the culprit was in the tailwind.config.js file. I failed to mention that I was using HAML templates, and I forgot to add *.haml extensions to the tailwind configuration.

    I'll update the original question so includes HAML as part of the problem.

    The solution is below.

    const plugin = require('tailwindcss/plugin')
    module.exports = {
      content: [
        ...
        './app/views/**/*.html.haml',
        './app/views/**/*.haml',
        ...
      ],
      plugins: [
          ...
      ]
    }