question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItΒ collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

50 seconds compile time with PostCSS and Tailwind

See original GitHub issue
  • Laravel Mix Version: 5.0.5
  • Node Version: v10.15.1
  • NPM Version: 6.4.1
  • OS: MacOS 10.15.6

Description:

Initial compilation of my app.css takes 50-60 seconds, changes to it need around 40-50 seconds (even if I just press Save again). Removing the Tailwind directives in the app.css makes it fast again, but I thought that these would be incremental builds. Is there any misconfiguration on my side?

Steps To Reproduce:

app.css:

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

And that’s my webpack.mix.js file (based on https://github.com/ben-rogerson/agency-webpack-mix-config, but with a few changes):

/**
 * ===========================
 * Agency Webpack-Mix Config
 * A capable website/webapp config built for the modern web agency.
 * https://github.com/ben-rogerson/agency-webpack-mix-config
 * ===========================
 *
 * Contents
 *
 * 🎚️ Settings
 * 🏠 Templates
 * 🎭 Hashing
 * 🎨 Styles: PostCSS
 * 🎨 Styles: CriticalCSS
 * 🎨 Styles: PurgeCSS
 * 🎨 Styles: Polyfills
 * πŸ“‘ Scripts
 * πŸ“‘ Scripts: Polyfills
 * πŸ“‘ Scripts: Auto import libraries
 * πŸ“‘ Scripts: Vendor
 * πŸ“‘ Scripts: Linting
 * 🏞 Images
 * πŸŽ† Icons
 * πŸ—‚οΈ Static
 * 🚧 Webpack-dev-server
 */

// 🎚️ Base config
const config = {
  // Dev domain to proxy
  devProxyDomain: process.env.DEFAULT_SITE_URL || 'https://myproject.test',
  // Paths to observe for changes then trigger a full page reload
  devWatchPaths: ['templates'],
  // Port to use with webpack-dev-server
  devServerPort: 8080,
  // Folders where purgeCss can look for used selectors
  purgeCssGrabFolders: ['src', 'templates'],
  // Build a static site from the src/template files
  buildStaticSite: false,
  // Urls for CriticalCss to look for "above the fold" Css
  criticalCssUrls: [
    // { urlPath: "/", label: "index" },
    // { urlPath: "/about", label: "about" },
  ],
  // Folder served to users
  publicFolder: 'web/assets',
}

// 🎚️ Imports
require('laravel-mix-react-typescript-extension')
const mix = require('laravel-mix')
const path = require('path')
const globby = require('globby')
const tailwindcss = require('tailwindcss')
const autoprefixer = require('autoprefixer')
const presetenv = require('postcss-preset-env')
const hexrgba = require('postcss-hexrgba')

// 🎚️ Source folders
const source = {
  icons: path.resolve('src/icons'),
  images: path.resolve('src/images'),
  scripts: path.resolve('src/scripts'),
  styles: path.resolve('src/styles'),
  static: path.resolve('src/static'),
  templates: path.resolve('templates'),
}

// 🎚️ Misc
mix.setPublicPath(config.publicFolder)
mix.disableNotifications()
mix.webpackConfig({ resolve: { alias: source } })
!mix.inProduction() && mix.sourceMaps()

/**
 * 🎭 Hashing (for non-static sites)
 * Mix has querystring hashing by default, eg: main.css?id=abcd1234
 * This script converts it to filename hashing, eg: main.abcd1234.css
 * https://github.com/JeffreyWay/laravel-mix/issues/1022#issuecomment-379168021
 */
if (mix.inProduction() && !config.buildStaticSite) {
  // Allow versioning in production
  mix.version()
  // Get the manifest filepath for filehash conversion
  const manifestPath = path.join(config.publicFolder, 'mix-manifest.json')
  // Run after mix finishes
  mix.then(() => {
    const convertToFileHash = require('laravel-mix-make-file-hash')
    convertToFileHash({
      publicPath: config.publicFolder,
      manifestFilePath: manifestPath,
    })
  })
}

/**
 * 🎨 Styles: PostCSS
 * Extend Css with plugins
 * https://laravel-mix.com/docs/4.0/css-preprocessors#postcss-plugins
 */
mix.postCss(path.join(source.styles, 'app.css'), 'css').options({
  postCss: [
    tailwindcss(),
    autoprefixer({
      cascade: false,
    }),
    presetenv({
      stage: 0,
    }),
    hexrgba,
  ],
  processCssUrls: false,
})

/**
 * 🎨 Styles: CriticalCSS
 * https://github.com/addyosmani/critical#options
 */
const criticalDomain = config.devProxyDomain
if (criticalDomain && config.criticalCssUrls && config.criticalCssUrls.length) {
  require('laravel-mix-critical')
  const url = require('url')
  mix.critical({
    enabled: mix.inProduction(),
    urls: config.criticalCssUrls.map((page) => ({
      src: url.resolve(criticalDomain, page.urlPath),
      dest: path.join(config.publicFolder, 'css', `${page.label}-critical.css`),
    })),
    options: {
      width: 1200,
      height: 1200,
    },
  })
}

/**
 * 🎨 Styles: PurgeCSS
 * https://github.com/spatie/laravel-mix-purgecss#usage
 */
if (config.purgeCssGrabFolders.length) {
  require('laravel-mix-purgecss')
  mix.purgeCss({
    enabled: mix.inProduction(),
    folders: config.purgeCssGrabFolders, // Folders scanned for selectors
    whitelist: ['html', 'body', 'active', 'wf-active', 'wf-inactive'],
    whitelistPatterns: [],
    extensions: ['php', 'twig', 'html', 'js', 'mjs', 'ts', 'tsx'],
  })
}

/**
 * πŸ“‘ Scripts: Main
 * Script files are transpiled to vanilla JavaScript
 * https://laravel-mix.com/docs/4.0/mixjs
 */
const scriptFiles = globby.sync(`${source.scripts}/*.{js,mjs,ts,tsx}`)
scriptFiles.forEach((scriptFile) => {
  mix.reactTypeScript(scriptFile, path.join(config.publicFolder, 'js'))
})

/**
 * πŸ“‘ Scripts: Polyfills
 * Automatically add polyfills for target browsers with core-js@3
 * See "browserslist" in package.json for your targets
 * https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md
 * https://github.com/scottcharlesworth/laravel-mix-polyfill#options
 */
require('laravel-mix-polyfill')
mix.polyfill({
  enabled: mix.inProduction(),
  useBuiltIns: 'usage', // Only add a polyfill when a feature is used
  targets: false, // "false" makes the config use browserslist targets in package.json
  corejs: 3,
  debug: false, // "true" to check which polyfills are being used
})

/**
 * πŸ“‘ Scripts: Vendor
 * Separate the JavaScript code imported from node_modules
 * https://laravel-mix.com/docs/4.0/extract
 * Without mix.extract you'll see an annoying js error after
 * launching the dev server - this should be fixed in webpack 5
 */
mix.extract([], path.join(config.publicFolder, 'js', 'vendor')) // Empty params = separate all node_modules
// mix.extract(['jquery']) // Specify packages to add to the vendor file

/**
 * πŸ“‘ Scripts: Linting
 */
if (!mix.inProduction()) {
  require('laravel-mix-eslint')
  mix.eslint()
}

/**
 * 🏞 Images
 * Images are optimized and copied to the build directory
 * https://github.com/CupOfTea696/laravel-mix-imagemin
 * https://github.com/Klathmon/imagemin-webpack-plugin#api
 *
 * Important: laravel-mix-imagemin is incompatible with
 * copy-webpack-plugin > 5.1.1, so keep that dependency at that version.
 * See: https://github.com/CupOfTea696/laravel-mix-imagemin/issues/9
 */
require('laravel-mix-imagemin')
mix.imagemin(
  {
    from: path.join(source.images, '**/*'),
    to: config.publicFolder,
    context: 'src/images',
  },
  {},
  {
    gifsicle: { interlaced: true },
    mozjpeg: { progressive: true, arithmetic: false },
    optipng: { optimizationLevel: 3 }, // Lower number = speedier/reduced compression
    svgo: {
      plugins: [
        { convertPathData: false },
        { convertColors: { currentColor: false } },
        { removeDimensions: true },
        { removeViewBox: false },
        { cleanupIDs: false },
      ],
    },
  },
)

/**
 * πŸŽ† Icons
 * Individual SVG icons are optimised then combined into a single cacheable SVG
 * https://github.com/kisenka/svg-sprite-loader#configuration
 */
require('laravel-mix-svg-sprite')
mix.svgSprite(source.icons, path.join(config.publicFolder, 'sprite.svg'), {
  symbolId: (filePath) => `icon-${path.parse(filePath).name}`,
  extract: true,
})

// Icon options
mix.options({
  imgLoaderOptions: {
    svgo: {
      plugins: [{ convertColors: { currentColor: true } }, { removeDimensions: false }, { removeViewBox: false }],
    },
  },
})

/**
 * πŸ—‚οΈ Static
 * Additional folders with no transform requirements are copied to your build folders
 */
mix.copyDirectory(source.static, path.join(config.publicFolder))

/**
 * 🚧 Webpack-dev-server
 * https://webpack.js.org/configuration/dev-server/
 */
mix.webpackConfig({
  devServer: {
    clientLogLevel: 'none', // Hide console feedback so eslint can take over
    open: true,
    overlay: true,
    port: config.devServerPort,
    public: `localhost:${config.devServerPort}`,
    host: '0.0.0.0', // Allows access from network
    https: config.devProxyDomain.includes('https://'),
    contentBase: config.devWatchPaths.length ? config.devWatchPaths : undefined,
    watchContentBase: config.devWatchPaths.length > 0,
    watchOptions: {
      aggregateTimeout: 200,
      poll: 200, // Lower for faster reloads (more cpu intensive)
      ignored: ['storage', 'node_modules', 'vendor'],
    },
    disableHostCheck: true, // Fixes "Invalid Host header error" on assets
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
    proxy: {
      '**': {
        target: config.devProxyDomain,
        changeOrigin: true,
        secure: false,
      },
    },
    publicPath: '/',
  },
})

mix.options({
  hmrOptions: {
    host: 'localhost',
    port: config.devServerPort,
  },
})

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:1
  • Comments:5

github_iconTop GitHub Comments

2reactions
Ahrengotcommented, Sep 29, 2020

By splitting Tailwind utilities from the rest of the stylesheet, i got down from 26 seconds on every file change to 0.15 seconds. You pretty much get instant feedback.

The initial build still takes 26 seconds because it needs to compile all of those thousands of lines of utility classes, but the subsequent builds a really fast and that’s what matters.

Here’s my setup

webpack.mix.js

const postCssPlugins = [
  require("postcss-import")({
    from: "resources/css/app.css"
  }),

  require("tailwindcss"),

  require("postcss-preset-env")({
    stage: 0
  }),
];

mix
  .postCss("resources/css/app.css", "public/css", postCssPlugins)
  .postCss(
    "resources/css/tailwind-utilities.css",
    "public/css",
    postCssPlugins
  );

app.css

@import 'tailwindcss/base';
@import 'tailwindcss/components';

/* Custom components */
@import 'components/button';

tailwind-utilities.css

@import "tailwindcss/utilities";

app-layout.blade.php

<link rel="stylesheet" href="{{ mix('css/app.css') }}">
<link rel="stylesheet" href="{{ mix('css/tailwind-utilities.css') }}">

This works great. If you want just one compiled css file, you can use mix.combine, but I like to keep them seperate β€” At least during development. It makes it a little easier if you don’t have 20k lines of utility classes mixed in with your actual CSS.

0reactions
JeffreyWaycommented, Oct 8, 2020

It’s hard to debug such a massive mix configuration file. To help, I’d need you to break this down to the simplest reproducible example.

Also check Mix 6 beta. That may have resolved the issue inadvertently.

Read more comments on GitHub >

github_iconTop Results From Across the Web

50 seconds compile time with PostCSS and Tailwind #2470
Description: Initial compilation of my app.css takes 50-60 seconds, changes to it need around 40-50 seconds (even if I just press Save again)....
Read more >
Just-in-Time Mode - Tailwind CSS
Tailwind CSS v2.1 introduces a new just-in-time compiler for Tailwind CSS that generates your styles on-demand as you author your templates instead ofΒ ......
Read more >
Set up Tailwind and PostCSS | egghead.io
In this lesson we'll introduce Tailwind to a basic HTML project and see how it compiles into CSS with PostCSS. Tailwind can be...
Read more >
Set up Tailwind CSS JIT in a Rails project to compile styles ...
Watch this video to learn more. 2.5MB of CSS takes up to 4.5 seconds to compile, which is no way to live.
Read more >
Checking Tailwind Class Names at Compile Time with Rust
My editor takes a slight pause when it loads, but it's only a second or two. And jumping around the file and searching...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found