Introducing Laravel Mix (new in Laravel 5.4)

Posted on February 08, 2017 | By Matt Stauffer

(This is part of a series of posts on New Features in Laravel 5.4. )

  1. Introducing Laravel Dusk (new in Laravel 5.4)
  2. Introducing Laravel Mix (new in Laravel 5.4)
  3. Laravel Collections’ higher order messaging and "when" method in Laravel 5.4
  4. Real-time (automatic) Facades in Laravel 5.4
  5. Slots and Components in Laravel 5.4 (coming soon)
  6. The --model flag for creating better resourceful controllers in Laravel 5.4 (coming soon)
  7. Binding method calls in the service container in Laravel 5.4 (coming soon)
  8. TrimStrings and ConvertEmptyStringsToNull middleware in Laravel 5.4 (coming soon)
  9. JSON localization files in Laravel 5.4 (coming soon)
  10. New Markdown Mail in Laravel 5.4 (coming soon)

Laravel Mix. The same and yet entirely different from Laravel Elixir.

If you're not familiar with Laravel Elixir, it's a wrapper around Gulp that makes it really simple to handle common build steps—CSS pre-processing like Sass and Less, JavaScript processing like Browserify and Webpack, and more.

In Laravel 5.4, Elixir has been replaced by a new project called Mix. The tools have the same end goals, but go about it in very different ways.

What's so new about Mix?

If you take a look at the default files for Elixir and Mix, you'll see they're very similar:

// Elixir's gulpfile.js
const elixir = require('laravel-elixir');

require('laravel-elixir-vue-2');

elixir((mix) => {
    mix.sass('app.js')
        .webpack('app.js');
});
// Mix's webpack.mix.js
const { mix } = require('laravel-mix');

mix.js('resources/assets/js/app.js', 'public/js')
   .sass('resources/assets/sass/app.scss', 'public/css');

Looks pretty similar, right? Sure, Elixir's calls are happening in an anonymous function, and Mix seems to prefer explicitly providing the source and destination, but we're doing pretty much the same thing here.

There's one big difference you'll experience on day one: where with Elixir you ran using either gulp or gulp watch, with Mix you'll run npm run dev or npm run watch . (You can also run npm run hot for "HMR", or Hot Module Reloading, which "hot reloads" your Vue files but not other assets; or npm run production to generate your assets with production settings like minification).

The default files and folder structure

Just like with Elixir, your default Sass file will be in resources/assets/sass/app.scss (and the file is exactly the same), and just like with Elixir your default JS file will be in resources/assets/js/app.js (and the file is exactly the same—to learn more about the new-to-5.3 Vue-based structure, check out my post about frontend structure in 5.3).

If you dig into the bootstrap file that's included in app.js (resources/assets/js/bootstrap.js), you'll see that we're setting our X-CSRF-TOKEN using Axios instead of Vue-Resource (Vue-Resource was retired in 2016).

If you run npm run dev on a Mix project, this is what you'll see:

Default Mix output

Our generated files end up in the same place by default that they did with Elixir: public/css/app.css and public/js/app.js.

The primary Mix methods

As you've already seen, you can easily mix Sass and JS; Sass, predictably, runs on your Sass file(s) and outputs them as CSS. The JS method gives you access to ES2015, .vue (Vueify) compilation, production minification, and a host of other processing on your JavaScript files.

You can also mix Less:

mix.less('resources/assets/less/app.less', 'public/css');

You can combine files together:

mix.combine([
    'public/css/vendor/jquery-ui-one-thing.css',
    'public/css/vendor/jquery-ui-another-thing.css'
], 'public/css/vendor.css');

You can copy files or directories:

mix.copy('node_modules/jquery-ui/some-theme-thing.css', 'public/css/some-jquery-ui-theme-thing.css');
mix.copy('node_modules/jquery-ui/css', 'public/css/jquery-ui');

Unlike Elixir, source maps are now disabled by default, but you can bring them back:

mix.sourceMaps();

Operating system notifications are enabled by default, but if you don't want them to run, you can disable with the disableNotifications() method.

Mix.manifest.json and cache-busting

If you're familiar with Elixir, you might notice one thing in that output image above that is a little different from Elixir: Mix is generating a manifest file out of the box (public/mix-manifest.json). Elixir also generated a manifest file (public/build/rev-manifest.json), but it would only generate it if you explicitly enabled the cache-busting (versioning) feature. Mix generates it regardless.

If you're not familiar, these manifest files are maps between a file path (e.g. /js/app.js) and the path for the versioned copy of that file (something like /js/app-86ff5d31a2.js). That way you can have simple references in your HTLM (<script src="{{ mix('js/app.js') }}">) that point to your versioned files.

Unlike Elixir, however, Mix generates this file even if you're not using cache busting, but it's just a direct map:

{
  "/js/app.js": "/js/app.js",
  "/css/app.css": "/css/app.css"
}

Another interesting change for those who've used Elixir before: your built files now end up in their normal output directories, not a separate build directory; so your versioned JS file, for example, will live in public/js/app-86ff5d31a2.js.

To enable cache busting in Mix, just append .version() in your Mix file:

mix.js('resources/assets/js/app.js', 'public/js')
    .sass('resources/assets/sass/app.scss', 'public/css')
    .version();

This is a lot simpler than passing the actual file names like you had to in Elixir.

The mix() helper

As I mentioned above, the frontend helper you'll want to use to reference your assets is now mix() instead of elixir(), but it still functions exactly the same. If you use Mix, you'll want to remove these lines in the default Laravel template:

    <link href="/css/app.css" rel="stylesheet">
    ...
    <script src="/js/app.js"></script>

And replace them with these:

    <link href="{{ mix('/css/app.css') }}" rel="stylesheet">
    ...
    <script src="{{ mix('/js/app.js') }}"></script>

Remember, this function just looks that string up in mix-manifest.json and returns the mapped build file. It's only necessary when you're using cache busting, but it doesn't hurt to just use it by default, because that'll make it a lot easier to add cache busting down the road if you want it.

Code splitting

Webpack is exciting to many people in part because of the intelligence it offers about the structure of your code. I don't yet fully understand–and Mix doesn't handle out of the box–anything like tree shaking, but it does make it simple to differentiate your custom code (which might change often) from your vendor code (which shouldn't), making it far less likely that your users will have to refresh all of your vendor code every time you push a new build.

To take advantage of this feature, you'll want to use the extract() function, which allows you to define that a given set of libraries or modules (keyed by the same string they're keyed in npm and require() statements) will be extracted into a separate build file named vendor.js:

mix.js('resources/assets/js/app.js', 'public/js')
    .extract(['vue', 'jquery']);

In this circumstance, Mix has now generated three files for me: public/js/app.js, public/js/vendor.js, and a third Webpack-specific file, public/js/manifest.js. I need to import all three, in this order, in order for it to work:

<script src="{{ mix('/js/manifest.js') }}"></script>
<script src="{{ mix('/js/vendor.js') }}"></script>
<script src="{{ mix('/js/app.js') }}"></script>

If you're using cache busting, and you make changes to your app-specific code, your vendor.js file will now still remain cached, and only your app-specific code will be cache busted–making your site load much faster.

Custom Webpack configuration

If you're interested in adding your own custom Webpack configuration, you can; just pass your Webpack configuration in:

mix.webpackConfig({
    resolve: {
        modules: [
            path.resolve(__dirname, 'vendor/laravel/spark/resources/assets/js')
        ]
    }
});

(I'm not a Webpack guru, so I'm just going to paste that example in straight from the docs.)

Conditional stuff

Let's say you're interested in doing some conditional cleverness in your Webpack file. Maybe you want to copy one thing when production is run but not other times. How exactly would you do that?

The first place I looked was the Node environment object, which we have access to as process.env. We can check any values there–including any global environment variables on your system, which may open up an interesting opportunity, so we could conditionally check the process.env.NODE_ENV value:

if (process.env.NODE_ENV == 'production') {
    mix.webpackConfig({ ... });
}

But after reading the source, I could tell NODE_ENV was not intended to be the primary check; instead, there's a configuration object with an inProduction flag on it. This isn't documented, so use with caution, but you can update the import at the top of your Webpack file and then use that config object:

const { mix, config } = require('laravel-mix');

if (config.inProduction) {
    mix.webpackConfig({ ... });    
}

Default dependencies

You can take a look at your package.json and see the list of dependencies that are included with each project. Remember, these are just those that are pulled by the default app.js and bootstrap.js, but you can just delete the references out of app.js and package.json and re-run npm install and they won't end up in your final files.

In conclusion

Laravel Mix is a build tool that replaces Laravel Elixir. It has almost the same API, but is based on Webpack instead of Gulp. The end. Go build great things.


Comments? I'm @stauffermatt on Twitter


Tags: laravel  •  laravel 5.4  •  mix