tailwindlabs/tailwindcss

@apply doesn't work with Laravel Mix

HapLifeMan opened this issue · 12 comments

Hey,

Everything works fine until I decided to use components as it's said in the documentation.

Compiling failed :

 ERROR  Failed to compile with 2 errors                                                                                                                                                                                                                                                                                                               12:41:50 PM

 error  in ./resources/assets/sass/app.css

Module build failed: ModuleBuildError: Module build failed: Syntax Error 

(2:3) No .text-white class found.

  1 | .btn {
> 2 |   @apply .text-white;
    |   ^
  3 | }
  4 | 

    at runLoaders (/Users/thomas/Sites/cdsud/www/node_modules/webpack/lib/NormalModule.js:195:19)
    at /Users/thomas/Sites/cdsud/www/node_modules/loader-runner/lib/LoaderRunner.js:364:11
    at /Users/thomas/Sites/cdsud/www/node_modules/loader-runner/lib/LoaderRunner.js:230:18
    at context.callback (/Users/thomas/Sites/cdsud/www/node_modules/loader-runner/lib/LoaderRunner.js:111:13)
    at Promise.resolve.then.then.catch (/Users/thomas/Sites/cdsud/www/node_modules/postcss-loader/lib/index.js:185:44)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)

 @ multi ./node_modules/laravel-mix/src/builder/mock-entry.js ./resources/assets/sass/app.css

 error  in ./resources/assets/sass/app.css

Module build failed: ModuleBuildError: Module build failed: Syntax Error 

(2:3) No .text-white class found.

  1 | .btn {
> 2 |   @apply .text-white;
    |   ^
  3 | }
  4 | 

    at runLoaders (/Users/thomas/Sites/cdsud/www/node_modules/webpack/lib/NormalModule.js:195:19)
    at /Users/thomas/Sites/cdsud/www/node_modules/loader-runner/lib/LoaderRunner.js:364:11
    at /Users/thomas/Sites/cdsud/www/node_modules/loader-runner/lib/LoaderRunner.js:230:18
    at context.callback (/Users/thomas/Sites/cdsud/www/node_modules/loader-runner/lib/LoaderRunner.js:111:13)
    at Promise.resolve.then.then.catch (/Users/thomas/Sites/cdsud/www/node_modules/postcss-loader/lib/index.js:185:44)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)

My wepback.mix.js :

let mix      = require('laravel-mix');
let tailwind = require('tailwindcss');
mix.postCss('./resources/assets/sass/app.css', './public/css/main.css', [tailwind('./tailwind.js')]).disableNotifications();

... and app.css:

@tailwind preflight;

@import "./components/buttons.css";

@tailwind utilities;

@import './main.css';
@import './admin.css';

What's wrong? :/

Can you share a public repo that reproduces this? I would expect a different error since @import doesn't inline imports in normal CSS and is also required to be the first statement in a file.

Are you using CSS here or Sass?

Ah ok I've reproduced this now.

The issue is that @import doesn't work the same way in normal CSS as it does in Sass or Less. Mix seems to be trying to process your buttons.css file with Tailwind independently, and it can't find any of the other classes because they aren't in the same file.

There's a couple ways to work around this:

  1. Use Less just for it's import functionality. Your mix file would look like this then:

    mix.less('./resources/assets/less/app.less', './public/css/main.css')
        .options({ postCss: [ tailwind('./tailwind.js')]})
        .disableNotifications()

    You can even keep your other files as plain CSS files and import them like this:

    @tailwind preflight;
    
    @import (inline) "./components/buttons.css";
    
    @tailwind utilities;
    
    @import (inline) './main.css';
    @import (inline) './admin.css';
  2. Use the postcss-import plugin to inline your imports. I would probably lean towards this option if you want to stay away from existing preprocessors and just use Tailwind with raw CSS.

    There's one gotcha with this plugin which is that it's very strict about the CSS spec and the CSS spec says that @import rules have to come first in a file, which means you can't have @tailwind preflight come before an @import rule.

    The way to get around that is to move your Tailwind calls to their own files, and make your main CSS file just imports:

    @import "./tailwind-preflight.css";
    
    @import "./components/buttons.css";
    
    @import "./tailwind-utilities.css";
    
    @import "./main.css";
    @import "./admin.css";

    tailwind-preflight.css would just look like this:

    @tailwind preflight;
    

    ...and tailwind-utilities.css would just look like this:

    @tailwind utilities;
    

    To use the import plugin, add it to the beginning of your PostCSS plugin chain:

    let mix      = require('laravel-mix');
    let tailwind = require('tailwindcss');
    let postcssImport = require('postcss-import');
    
    mix.less('./resources/assets/css/app.css', './public/css/main.css', [
        postcssImport(),
        tailwind('./tailwind.js'),
    ])
    .disableNotifications();

Let me know if that fixes your issue! We'll definitely have to add some more documentation about this.

Bug fixed! Thank you ❤️
I can now move on serious stuff 😈

I have quite large webpack.mix.js - for anyone that finds this issue, it might be helpful to know:

If you have any weird problems (like your css file being literally copied and not processed) you should put that last snippet (excluding the first line) somewhere at the end of the webpack.mix.js. This did the trick for me.

Hey @adamwathan, just noticed I think you have a minor mistake in your tip for postcss-import.

You have tailwind-utilities.css as having @tailwind preflight; when it should be @tailwind utilities; right?

Good catch, fixed!

jcush commented

@adamwathan I've tried using your postcss-import method but am still getting the same issue about "missing '}'" when trying to build using @apply.

webpack.mix.js

let mix = require('laravel-mix');
let tailwindcss = require( 'tailwindcss' );

let dirs = {
	src: {
		js: 'src/js/',
		scss: 'src/scss/'
	},
	dist: {
		js: 'web/js/',
		css: 'web/css/'
	}
}

mix.js( dirs.src.js + 'main.js', dirs.dist.js )
.sass( dirs.src.scss + 'tailwind.scss', dirs.dist.css ).options({
	postCss: [ tailwindcss('tailwind.config.js') ]
})
.sass( dirs.src.scss + 'style.scss', dirs.dist.css, [
	portcssImport(),
	tailwind()
])

tailwind.scss

@tailwind base;
@tailwind components;
@tailwind utilities;

body{
	/* This comment solves Mix's issue with needing an SCSS file with at least one non-empty ruleset */
}

style.scss

html{
	font-size: 20px;
}
body{
	padding:
		env( safe-area-inset-top, 0 )
		env( safe-area-inset-right, 0 )
		env( safe-area-inset-bottom, 0 )
		env( safe-area-inset-left, 0 );

	font-weight: 200;
}

.plate{
	@apply bg-gray-900 text-white p-4;
}

To confirm, if I remove the @apply declaration then everything works fine...

Can you create a public GitHub repo that reproduces the issue? I can play with it and figure it out much faster that way than having to recreate it all from scratch myself.

jcush commented

tw-bug-example.zip

I tried creating a public GitLab repo but couldn't push to it for some reason... so I've attached a zip here. I removed all sensitive info, node modules and composer packages. It's a Craft CMS site

@adamwathan have you found what the problem with @jcush problem, please? I'm facing the same problem here.

jcush commented

@thamerbelfkihthamer I haven't tried this yet, but just re-reading the issue I think in the meantime while we let @adamwathan dig into it it could be an idea to create a separate stylesheet for utilities created by applying tailwind classes, then less that one and sass the others. So you'd do all your @apply calls in one sheet, which would use less, then put the rest of your styles in sass.

In the nicest way possible without any offence meant to Tailwind or Adam (big Refactoring UI fan here) I'm currently customising Bootstrap 4 for theming instead. I just have more experience with it personally and wanted to crack on with the project.