PHP
Article

Meet Elixir, the Laravel Way of Compiling Assets

By Reza Lavaryan

Many thanks to Jad Joubran, Gabriel Zerbib, Anthony Chambers, and Scott Molinari for peer reviewing this post, and thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!


In today’s web applications, we use a wide variety of tools to speed up the development workflow while keeping the code base as lean as possible. However, these tools might slow the process down, as some of them require compilation of the code, which takes time.

One such type of tool are preprocessors for CSS and JavaScript. They reduce the amount of code in our stylesheets and JavaScript files, but we still need to compile them to vanilla CSS and JavaScript by running the right compiler. After a while, this will become a boring task and chances of us forgetting to run something will increase.

Besides preprocessors, there are also many tasks we as developers often have to do, including linting JS files, testing, concatenation, minification, just to name a few.

All this led to the creation of JavaScript-based task runners like Grunt and Gulp. The purpose of these applications is to automate the tasks we need to do repeatedly in our development workflow.

Gulp syntax is clean and easy to use, but there’s always room for doing things the easier way. Laravel 5 introduced Elixir: a Node.js based tool developed by Jeffrey Way, which manages our Gulp tasks the easy way.

Elixir

In this tutorial, we will learn how to use Elixir to manage our Gulp tasks with just a few lines of code. This article focuses on asset compilation in the Laravel environment, though we’ll try to cover usage in other environments as well.

Requirements and Installation

To run Elixir, we’ll need to have the following tools installed on our machine:

  1. Node.js
  2. Gulp
  3. Laravel Elixir

Node.js

Since Gulp is run inside Node.js, we need to make sure Node.js is installed. If we’re using Homestead Improved, then we’re already good to go, otherwise we have to install Node.js.

Gulp.js

As mentioned earlier, Elixir is a wrapper around Gulp, so we will need to have Gulp running as well.

If it’s not installed yet (if we’re using Homestead Improved, it is), we can install it via npm:

npm install --global gulp

Laravel Elixir

In Laravel’s root directory, there’s a package.json file which already has laravel-elixir 3.0.0, as a dependency. To install it, we run npm install from within that directory.

Installing Elixir in Other Environments

To install Elixir 3 in environments other than Laravel:

npm install laravel-elixir --save

Before We Start

It is useful to know that Elixir assumes that all the source files (.less, .sass, .coffee etc.) are located in ./resources/assets/. The output files will be copied to the ./public directory by default:

File type Base source path Output path
Less ./resources/assets/less ./public/css
Sass ./resources/assets/sass ./public/css
CoffeeScript ./resources/assets/coffee ./public/js

To create an Elixir task, we need to call the elixir function in our gulpfile.js file. This function takes a callback with an object as an argument. This object, called mix, exposes all the available methods Elixir provides out of the box.

// ...
elixir(function(mix) {
  mix.less('styles.less');
});

If we pass an array of files or a wildcard matching a group of files, all the files will be compiled and concatenated into a single file. The output filename will be app.css or app.js for the CSS and JavaScript files respectively.

If we pass a single filename to a certain method, it will be compiled to a file of the same name. For instance, if we pass styles.less, the output filename will be styles.css.

However, all these defaults can be customized via a method’s arguments or Elixir’s config object, which we’ll discuss shortly.

Let’s See Some Actions

Elixir provides the basic tasks out of the box, including tasks for compilation, concatenation, minification, running test suites, etc.

Compiling Less Files

To compile Less files, we need to call the less() method of the mix object:

elixir(function(mix) {
    mix.less("styles.less");
});

In the above task, Elixir assumes that styles.less is located under the resources/assets/less path. After the compilation, it will save the output file as public/css/styles.css.

The same goes for .sass files, we will just need to change the method to sass() like so:

elixir(function(mix) {
    mix.sass("styles.sass");
});

Please note that Elixir assumes Sass files are located in resources/assets/sass by default.

It is good to know that Elixir will take care of the vendor prefixes in our stylesheets as well, so we will only need to write the standard syntax.

Compiling CoffeeScript Files

Okay, now let’s look at how CoffeeScript files are compiled:

elixir(function(mix) {
    mix.coffee(['controllers.coffee', 'services.coffee']);
});

Again, Elixir looks inside resources/assets/coffee/, compiles all the .coffee files to JavaScript code and copies them to the public/js directory as app.js

It is as simple as that!

Going Deeper

Compiling Multiple Files

Elixir methods like sass(), less(), coffee() all follow the same prototype. We can either pass a single filename, wildcards, an array of filenames or pass no arguments at all.

To compile multiple Less files:

elixir(function(mix) {
  mix.less([
  'base.less',
  'styles.less',
  'custom.less'
  ]);
});

As a result, the output will be compiled to public/css/app.css.

The same goes for the other methods like sass(), coffee(), etc.

Different Source and Output Directories

If our source or output paths differ from the default ones, we can change them to be compatible with our directory layout with three approaches.

Using Arguments

All the methods accept a second argument, which defines the base output directory:

elixir(function(mix) {
  mix.less([
  'base.less',
  'styles.less'
  'custom.less'
  ], 'resources/build/css');
});

As a result, the above task will compile the Less files to resources/build/css/app.css.

To change the output filename, we can include the filename in the argument:

elixir(function(mix) {
  mix.less([
  'base.less',
  'styles.less'
  'custom.less'
  ], 'resources/build/css/styles.css');
});

Providing Full Paths

besides customizing the base source and output paths, we can pass the file’s full path prefixed with ./. If the given path is prefixed with a period, Elixir will assume that the user wants to start at the project’s root directory.

elixir(function(mix) {
  mix.less(['./resources/assets/src/less/base.less']);
});

Using the Config Object

The preferred way of customizing the default base source and output paths is to change css.output and js.output settings in Elixir’s config object. We’ll discuss that in the Configuring Elixir section.

Concatenating Files

Elixir provides several methods for concatenation of stylesheets and JavaScript files, using scripts() for JavaScript and styles() for CSS files.

elixir(function(mix) {
  mix.styles(['base.css', 'customized.css'])
     .scripts(['app.js', 'code.js']);
});

Base Source and Output Paths For Concatenation

Methods scripts() and styles() assume the source files are located in public/js and resources/assets/css by default. After compilation, the concatenated version will be copied to public/js and public/css:

Type Base source path
CSS ./resources/assets/css
JavaScript ./public/js

These two methods accept a second and third argument as well, for the base output path and for the base source path.

Base Output Path

To change the output path, we can do:

elixir(function(mix) {
  mix.styles(['base.css', 'customized.css'], 
              'public/build/css')
     
     .scripts(['app.js', 'code.js'],
               'public/build/js')
});

By default, the concatenation output filename is all.css for CSS and all.js for JavaScript files. We can change this by including the filename in the argument:

elixir(function(mix) {
  mix.styles(['base.css', 'customized.css'], 
              'public/build/css/application.css')
     
     .scripts(['app.js', 'code.js'],
               'public/build/js/application.js');
});

As a result, the output will be saved as application.js and application.css.

Base Source Path

We can change the base source path via the third argument:

elixir(function(mix) {
  mix.styles(['base.css', 'customized.css'],          
                'public/build/css/application.css',
                'public/css')
     
  .scripts(['app.js', 'code.js'],
            'public/build/js/application.js',
            'public/js');
});

Concatenating All Files

If we need to concatenate all files in a certain directory, we can use scriptsIn() and stylesIn() shortcuts:

elixir(function(mix) {
  mix.scriptsIn('public/js')
     .stylesIn('public/css')
});

Jade to Blade Compilation

This Section covers the compilation of Jade files to Blade templates.

Elixir doesn’t support Jade to Blade compilation out of the box. However, this is possible by using an npm package called laravel-elixir-jade. It compiles jade files into .blade.php files.

To install laravel-elixir-jade:

npm install laravel-elixir-jade@0.1.8 --save-dev

As a result, laravel-elixir-jade will be installed and added to devDependencies inside our package.json file.

Elixir assumes jade files are located in resources/jade, and it will copy the output files to resources/views.

To compile the .jade files, we use the jade method:

var elixir = require('laravel-elixir');
require('laravel-elixir-jade');

elixir(function(mix) {
    mix.jade({
        search: '**/*.jade'
        });
});

jade() expects an object containing all possible options:

{
    baseDir: './resources',
    blade: true,
    dest: '/views/',
    pretty: true,
    search: '**/*.jade',
    src: '/jade/'
}

All options are self explanatory: baseDir, dest, search and src define the source and output directories.

pretty instructs Elixir to preserve all the line breaks and indentations. It is true by default.

By setting blade to false, we instruct Elixir to compile to .php files, instead of .blade.php files.

There’s also a helpful collection of jade mixins developed by @franzose.

File Versioning

Elixir also provides a versioning feature, which appends a unique hash to the file names. This will prevent the browser from loading the asset files from its cache. In the past, we had to append a version number to the file name in the form of a query string(?v=2), but not anymore!

By using Elixir’s version() method, things are a whole lot easier now. The only thing we need to pass to the version() method is the full path to the file:

elixir(function(mix) {
    mix.version("./public/build/js/all.css");
});

// We can also pass an array to the version method.
elixir(function(mix) {
    mix.version(['./public/build/js/all.js', 
                 './public/build/js/code.js']);
});

As a result, the file name will become something like: styles.all-16d570a7.css. Remembering this name is another problem, though. The good thing is, we don’t have to deal with this on our own, as Laravel has already taken care of this for us.

Across our Blade views, we can use the elixir() helper function to refer to the hashed file:

@block('stylesheets')
    <link rel="stylesheet" type="text/css" href="{{ elixir("styles.all.css") }}">
@endblock

We only need to pass the original filename, as we would do without hashing.

Since Elixir 3.0, we need to provide the full path to the file. This means we can even use version() for all the other files inside our project.

Configuring Elixir

One of the good things about Elixir is that it is configurable via its config object. All the available options exist in node_modules/laravel-elixir/Config.js. If we open the file, we’ll see that there’s a config object containing all the settings which define Elixir’s behavior. If we’re using Elixir inside a Laravel environment, we won’t have to change most of these options, as they are already compatible with Laravel’s directory layout.

Settings are grouped inside nested objects (according to their usage) within the config object. For example, settings related to .css files are defined within the css object:

// ...

css: {  
  folder: 'css',
  outputFolder: 'css',
  autoprefix: {
    enabled: true,
    // https://www.npmjs.com/package/gulp-autoprefixer#api
    options:  {
      browsers: ['last 2 versions'],
      cascade: false
    }
  }
} 

// ...

To see all the available options, please check the Config.js file.

To override the default settings, we can access them via the elixir.config object in gulpfile.js:

//...

elixir.config.assetsPath = 'resources/assets/src/'; 

To configure a group of options, we can create an elixir.json file in our project root directory, and declare an object containing the settings we need to override:

elixir.json

{
    "assetsPath": "resources/assets/src",

    "css": {
        "outputFolder": "stylesheets"
      }
}

Anything we add in this object will override the default settings in the main config object.

A Real World Example

Let’s see the power of Elixir in a real world example. We’ll be managing our assets in a Laravel/Angular project.

Assets Directory Layout

Imagine that our resources directory layout is:

.
.
.
resources
├── assets
│   └── src
│       ├── coffee
│       │   ├── app.coffee
│       │   ├── base.coffee
│       │   ├── controllers.coffee
│       │   ├── directives.coffee
│       │   └── services.coffee
│       └── less
│           ├── base.less
│           ├── bootstrap
│           ├── footer.less
│           ├── header.less
│           ├── styles.less
│           ├── typography.less
│           └── Bootstrap
│               └── ...
├── jade
│   ├── dashboard.jade
│   ├── incs
│   │   ├── footer.jade
│   │   └── navbar.jade
│   ├── layouts
│   │   ├── base.jade
│   │   └── master.jade
│   ├── list.jade
│   └── login.jade
.
.
.

All the asset files are located in ./resources/assets/src.

Please note that in the less directory, we only need to compile styles.less, because all other .less files are already imported into it:

/* resources/assets/src/less/styles.less */
@import "bootstrap/bootstrap.less"; 
@import "base.less";
@import "typography.less";
@import "header.less";
@import "footer.less";

The reason why Bootstrap mixins and variables are imported into styles.less is to use the existing mixins and variables that Bootstrap provides. The output will be a customized Bootstrap file. However, it’s just one way of writing stylesheets which may differ from project to project.

Let’s list the steps we have to take for our asset compilation:

  • Step 1: Load Elixir and other required modules into our gulpfile.js

  • Step 2: Configure Elixir using elixir.json

  • Step 3: Compile the styles.less file to public/build/styles.css

  • Step 4: Compile all the .coffee files to JavaScript code and save the output as public/build/js/app.js.

  • Step 5: Compile the .jade files to .blade.php templates, so they can be rendered by Laravel’s blade engine.

  • Step 6: Versioning the files for cache busting.

Writing the Tasks

The beauty of Elixir is that whatever we do, it is defined in gulpfile.js. That means we can mix Elixir’s syntax with that of Gulp’s, especially when we are extending Elixir.

Step 1: Loading Required Modules

In our gulpfile.js, we write the following code:

var elixir = require('laravel-elixir');
require('laravel-elixir-jade');

First, we loaded Elixir and put it in the elixir variable for later reference. Then, we loaded the laravel-elixir-jade package for compiling our jade files.

Step 2: Configuration

Since our source files are located under a path different than the default base path, we need to customize some defaults.

To do this, we create a file in our project root directory named elixir.json with the following content:

{
  assetsPath: 'resources/assets/src',
  publicPath: 'public/build'
}

Step 3: Compiling Less Files

elixir(function(mix) {
    mix.less('styles.less');
});

Step 4: Compiling CoffeeScript Files

elixir(function(mix) {
    mix.coffee([
    'app.coffee',
    'base.coffee',
    'controllers.coffee',
    'directives.coffee',
    'services.coffee'
  ]);
});

The above task will compile all .coffee files to the public/build/js directory as app.js.

Step 5: Compiling Jade Files

In our resources/jade directory, we have two sub directories, incs and layouts, which contain the layout files and a few partials for the navigation bar and the footer section. We also have some files for the views, including the login page, dashboard page and a list.

To compile the .jade files, we use the jade method like so:

elixir(function(mix) {
    mix.jade({
        search: '**/*.jade'
        });
});

The above task will look into ./resources/jadedirectory and sub directories, and compile all the .jade files to .blade.php files.

Step 6: Versioning the Files

In our final step, let’s version the files to take care of the cache busting:

elixir(function(mix) {
  mix.version([
      './public/css/styles.css',
      './public/js/app.js'
    ]);
});

And that’s all there is to it. Our gulpfile.js is ready!

This is the complete code:

var elixir = require('laravel-elixir');

require('laravel-elixir-jade');

elixir(function(mix) {
      mix.less('styles.less')    
      
     .coffee([
    'app.coffee',
    'base.coffee',
    'controllers.coffee',
    'directives.coffee',
    'services.coffee'
   ]);
     
     .jade({
      search: '**/*.jade'
     })
      
     .version([
      'build/css/styles.css',
      'build/js/app.js'
     ]);
});

Elixir allows method chaining as shown in the final code above.

Running The Tasks

To run the tasks, we just need to call gulp:

gulp

This will run all the tasks registered with Elixir.

We can also watch files for changes to automate running of the tasks, so we won’t have to run gulp whenever we make a change in a file:

gulp watch

Running Individual Tasks

Whenever we add a task in Elixir, behind the scenes, a task of the same name is registered with Gulp. As an example, if we use the Elixir’s less() method, the Gulp task name would be less. This means we will be able to run the tasks individually, as we normally do with Gulp.

gulp less

What About Minification?

One common use of task runners like Grunt or Gulp is to minify files content to make them smaller and save bandwidth. By using Elixir, the only thing we need to do is to send the --production option when running the gulp command:

gulp --production

Going Advanced

Custom Tasks and Extensions

Elixir provides the basic tasks out of the box. In most cases, these are more than enough. However, we might encounter situations when existing tasks can’t handle specific operations, which means it’s time to extend Elixir!

For this purpose, Elixir exposes the extend method. The extend method accepts two parameters. The first one is the name of the custom task and the second is a callback, in which we write a Gulp task.

To keep things as bare bones as possible, let’s create a basic task which prints a text message to the console:

var   elixir = require('laravel-elixir'),
      gulp   = require('gulp'),
      shell  = require('gulp-shell');

 Elixir.extend('saysHi', function(message) {

        new Elixir.Task('saysHi', function() {
            return gulp.src('').pipe(shell('Hi, I am a custom task!'));
        })
        .watch('./app/**');

 });

watch() registers a watcher. As a result, when we edit any files matching the regular expression, the task is run.

We can put this block at the top of the gulpfile.js file or put it in an external file, and import it in our gulpfile.js:

var elixir = require('laravel-elixir');
require('./elixir-extensions');

The custom task is now accessible via the mix object:

elixir(function(mix) {
  mix.saysHi();
});

Now each time we run gulp saysHi, the following text will be printed on the screen:

gulp custom Task

[01:10:57] Using gulpfile ~/www/sites/elixir/gulpfile.js
[01:10:57] Starting 'saysHi'...
Hi, I am a custom task!
[01:10:57] Finished 'saysHi' after 370 μs

Using the Assets in the Blade Templates

To use the compiled files, we use the elixir helper function, which is available across all the views.

@block('scripts')
<link rel="stylesheet" type="text/css" href="{{ elixir('css/styles.all.css') }}">
@stop

@block('styles')
<script src="{{ elixir('css/scripts.all.js') }}">
@stop

Now we have peace of mind about versions and can focus on the project!

Wrapping up

Elixir is a wrapper around Gulp.js providing a wide variety of functionality to help us with our assets compilation. In addition to Gulp’s standard tasks, it also provides several methods for versioning, testing, and copying the files, just to name a few. Elixir is very flexible. We can configure it, extend it and even combine it with Gulp’s standard syntax.

The main focus of this tutorial was on assets compilation. However, there are still several of Elixir’s functionalities which we haven’t covered in this tutorial like tasks for copying files, testing, and even running Laravel Artisan commands. To find out more, please refer to Elixir’s official documentation.

For those who always prefer PHP solutions for these sort of tasks and don’t like to switch context between JavaScript and PHP syntax sequentially, check out our article on using PHP solutions for a front end workflow without using Node.

Thanks for reading. Leave your questions and comments below!

  • Fabio Politi

    Thank you for this article, it’s well written.
    I am wondering: is it possible in Elixir to define custom arguments for command line usage as the “–env” param and use them in the building process, something like:

    Gulp –env=production –param1=value1 –param2=value2

    Thanks!

  • Fabio Politi

    Thank you for this article, it’s well written.
    I am wondering: is it possible in Elixir to define custom arguments for command line usage as the “–env” param and use them in the building process, something like:

    Gulp –env=production –param1=value1 –param2=value2

    Thanks!

    • Bouchaala Reda

      I’am not really sure about that.
      But I’am sure that Laravel Elixir can take a flag telling it to minify assets (javascript, css) which is `–production` flag. This latter can also be used with the `gulp watch` command.

    • Bouchaala Reda

      Also as a side note,
      You can always pass your arguments, and then puck them up with the gulp-util npm package, and then maybe use them in a custom task.

  • http://timseverien.nl/ Tim Severien

    Good stuff! Interesting to see a back-end framework trying to close the gap between front and back-end. I certainly hope this will spark off more, similar and perhaps better ways to bring the two together!

  • http://www.phillipharrington.com/ Phillip Harrington

    What’s the benefit of creating templates in jade that compiles to blade, vs. using blade directly? Seems like you’d have to constantly run gulp watch when working with the jade templates, something that isn’t necessary when making changes to the blade templates.

    • Reza Lavaryan

      Well, the benefit will be building more with writing less. That’s the reason why some front-end developers prefer jade syntax over HTML. For compiling these to blade files you won’t have to run gulp each time you make a change. That’s what gulp already covers: Watch

      But yeah some people including myself just prefer writing HTML code in the blade templates. That’s totally a matter of personal preference.

      • http://www.phillipharrington.com/ Phillip Harrington

        Thanks :-)

  • http://www.phillipharrington.com/ Phillip Harrington

    Is there a way to make gulp watch a single task? Or a couple of tasks? For example, if you’re working on SCSS, have it only compile the SCSS and run the Version task?

  • Rilwanrabo

    Hi!
    How do I specify output style for sass….please

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.