You’ve probably heard of asset compilers like Webpack or Gulp. They’re a way of using cutting-edge features of JavaScript in the browser, without breaking backward-compatibility with older browsers. But that’s not all that they can do. They also allow you to use language extensions likes SASS, LESS, CoffeeScript, Jade, etc. Using them, you will save precious development time and you will get your projects more organized.

The specific extensions will be documented in future articles. But for now, let’s focus on how to put together a compilator, specifically: Laravel Mix.

Short Introduction to Compilers

Gulp

Gulp is one of the first compilers out there. It follows a simple linear approach of executing the defined tasks. It’s a library which allows you to create tasks and execute them based on some events. This is what a typical Gulp configuration looks like:

import gulp from 'gulp';
import concat from 'gulp-concat';
import minifyCss from 'gulp-minify-css';
import sass from 'gulp-sass';
import autoprefixer from 'gulp-autoprefixer';
import babel from 'gulp-babel';
import sequence from 'gulp-sequence';
import uglify from 'gulp-uglify';
import ngAnnotate from 'gulp-ng-annotate';
import addSrc from 'gulp-add-src';

// The task that defines the watches over our uncompiled assets.
gulp.task('watch', () => {
    gulp.watch('resources/assets/sass/*.sass', ['sass']);
    gulp.watch(['resources/assets/js/*', 'resources/assets/js/**/*.js', 'resources/server.js'], ['js']);
    gulp.watch(['resources/assets/js/pages/**/*.html'], ['move:templates']);
});

// The default task that runs all the specific tasks.
gulp.task('default', callback => sequence('sass', 'js', 'move:templates', callback));

let cssFiles = [
    'resources/assets/bower/animate.css/animate.min.css',
];

// Compiles SASS files.
gulp.task('sass', () => {
    gulp.src(cssFiles.concat(['resources/assets/sass/app.sass']))
        .pipe(sass({ outputStyle: 'compressed' })).on('errpr', sass.logError)
        .pipe(autoprefixer('last 10 versions', 'ie 7', 'ie 8', 'ie 9'))
        .pipe(minifyCss())
        .pipe(concat('app.min.css'))
        .pipe(gulp.dest('public/css'));
});

let jsFiles = [
    'resources/assets/bower/underscore/underscore-min.js',
    'resources/assets/bower/angular-ui-router/release/angular-ui-router.min.js',
    'resources/assets/bower/angular-sanitize/angular-sanitize.min.js',
];

// Compiles ECMA Script 2015.
gulp.task('js', () => {
    // Compile the js from assets/js.
    gulp.src(['resources/assets/js/*', 'resources/assets/js/**/*.js'])
        .pipe(babel({ presets: ['es2015'] }))
        .pipe(ngAnnotate())
        .pipe(addSrc.prepend(jsFiles))
        .pipe(uglify())
        .pipe(concat('app.min.js'))
        .pipe(gulp.dest('public/js'));

    // Compile the server.js.
    gulp.src('resources/server.js')
        .pipe(babel({ presets: ['es2015'] }))
        .pipe(gulp.dest('.'));
});

// Moves necessary HTML into a public folder.
gulp.task('move:templates', () => {
    gulp.src('resources/assets/js/pages/**/*.html')
        .pipe(gulp.dest('public/pages'));
});

If you ignore the contents of a gulp.task(); block, you can see that all we do is create some tasks. Those tasks can be run from the command line via the gulp software, like so: gulp sass. That would run the sass task, if defined. We create individual tasks for specific actions, like gathering all SASS or JS files together and compiling them. Then, we create some special classes, like default and watch, which make use of the specific tasks.

The watch task uses a file watcher built in Gulp, to check the files for changes. When a change occurs, it runs the defined task to the files. This is one of the most important components of compilers, the fact that they are able to see changes and compile everything again. We use this approach because we want to be able to test everything ASAP in the browser, we don’t want to be bothered to manually run the individual tasks ourselves.

The default task simply puts the individual tasks together to generate a production-ready script. That task, as you’ve probably guessed, is mostly used in production, once, after the deploy.

Inside the individual tasks, we use another pattern, which is:

  1. Choose the files that the task applies to.
  2. Apply some modifiers to those files.
  3. Write the final file(s) to a public directory.

The modifiers are Gulp extensions that you install individually. They don’t come bundled with Gulp.

Webpack

This is more advanced than Gulp and it does things differently. The upside is that it is also faster than Gulp. You can find a comprehensive tutorial of Webpack, here.

Let’s look at a typical Webpack configuration:

const path = require('path'),
    webpack = require('webpack'),
    CleanWebpackPlugin = require('clean-webpack-plugin'),
    HtmlWebpackPlugin = require('html-webpack-plugin'),
    ExtractTextPlugin = require('extract-text-webpack-plugin');

const extractPlugin = new ExtractTextPlugin({
    filename: './assets/css/app.css'
});

const config = {
    entry: {
        app: './app.js'
    },

    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: './assets/js/[name].bundle.js'
    },

    module: {
        rules: [
            {
                test: /\.js$/,
                include: /src/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: ['env']
                    }
                }
            },
            {
                test: /\.html$/,
                use: ['html-loader']
            },
            {
                test: /\.scss$/,
                include: [path.resolve(__dirname, 'src', 'assets', 'scss')],
                use: extractPlugin.extract({
                    use: [{
                            loader: 'css-loader',
                            options: {
                                sourceMap: true
                            }
                        },
                        {
                            loader: 'sass-loader',
                            options: {
                                sourceMap: true
                            }
                        }
                    ],
                    fallback: 'style-loader'
                })
            },
            {
                test: /\.(jpg|png|gif|svg)$/,
                use: [{
                    loader: 'file-loader',
                    options: {
                        name: '[name].[ext]',
                        outputPath: './assets/media/'
                    }
                }]
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: ['file-loader']
            }

        ]
    },

    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            template: 'index.html'
        }),
        extractPlugin
    ],

};

module.exports = config;

I’m going to explain in short how it all works.

The entry.app option defines the source file that’s going to be compiled. Notice that there’s only one and keep in mind that we’re also compiling SASS into CSS inside it.

The ouput option defines where the file is going to be saved and under what name.

The module.rules options define how different files are going to be compiled. JS files are compiled using Babel, a popular ES2015 compiler. SCSS files are going to be sent through 2 loaders (compilers), first a SASS compiler, then a CSS compiler, which minifies the file. Image and font files are going to be copied to a public folder.

The plugins option simply registers additional Webpack extensions. extractPlugin is used to extract CSS files from the compiled SCSS. Because, by default, Webpack adds any compiled CSS as an in-page <style> element. The other plugins are there for cleaning directories for old compiled files and minify HTML.

To say that this is a bit of a throw off for a beginner would be an understatement. If you don’t like or understand any of that, don’t worry, not even the Webpack community does. As a matter of fact, if you search for tutorials on it, they all start with jokes on how non-intuitive it all is. But everything can be learned, and if it adds more to it than other compilers, we must adapt, right?

Well, kind of. We did have to adapt to that for a pretty long time, but then, a wrapper was introduced. A wrapper is a tool that takes care of the hard parts of another tool and provides you with an easier way of using it, without learning all the ins and outs of the main tool.

People tend to be hesitant about using wrappers, but when they’re properly built and they also allow you to tap into the underlying tool’s properties at any point, I don’t see a reason not to use one. That’s why I started recommending this specific wrapper, Laravel Mix, to beginners, and that’s why I’m using it myself on most of my projects.

Laravel Mix

As we’ve seen from the previous section, this is a wrapper over Webpack. Meaning that it uses Webpack, but the configuration file is way, way, easier to use.

Let’s see an example of a Laravel Mix configuration file:

const mix = require('laravel-mix');

mix.js('src/js/app.js', 'build/js')
   .sass('src/scss/app.scss', 'build/css')
   .copy('src/scss/fonts/*', 'build/css');

That configuration allows us to compile app.js, app.scss and move some fonts to a public dir. Nope, that’s not a joke. That’s the entire configuration.

Laravel Mix will turn that into a Webpack configuration file automatically under the hood, and you will be able to leverage the same power of Webpack, without having to understand its complicated configuration.

Installing Laravel Mix

NPM

To install Laravel Mix, we need to setup NPM. NPM is a package manager for installing libraries. It’s based on NodeJS, so we’ll need that as well. As a matter of fact, when you install Node, it will install NPM too.

You can download Node.js here, or install it through your OS’ package manager by following this official guide.

After you’ve installed Node, check to see if you have NPM installed by running:

npm --version

If you get a version returned. You’re good. If not, re-check your Node installation.

Laravel Mix

To install Laravel Mix, we need to have a project. We’ll assume that we don’t have one, but nothing changes if you do, simply skip creating a dir and cd into your own project.

We’re going to create an empty dir for the new project and cd into it:

mkdir laravel-mix-tutorial

Let’s initialize NPM inside that dir:

npm init -y

This command created a file called package.json, with some information inside. Be careful if you want to edit it manually because it needs to be a valid JSON file to work.

Let’s pull in the laravel-mix package:

npm install laravel-mix --save-dev

Every time you install a package, it’s going to install it in a dir called npm_modules, along with its dependencies. You’ll notice that the directory is quite large, but don’t worry, we’re not using more than a few files from it. Once compiled, the output files will be stripped down of anything unnecessary.

The --save-dev instruction will store the name of the package in our package.json, in the devDependencies section. By storing it in our package.json file, we remember what are project consists of. This way, when we move the project to another computer, share it with somebody else, or move it to production, we know what’s required. Think of package.json as a list of ingredients of our project.

Now that we have laravel-mix pulled in, we need to pull in the default configuration file:

cp -r node_modules/laravel-mix/setup/webpack.mix.js .

That command will copy the default configuration in, which contains every option available as a comment. You can remove the comments after you’ve familiarized yourself with it.

Compiling Assets

Now that we have everything together. Let’s see how we might go about actually compiling some assets.

Adding the Necessary NPM Scripts

Before we can compile, we need a few NPM scripts that will make our lives easier later. Copy this code inside package.json, replacing the scripts section that’s already in there.

"scripts": {
  "dev": "NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
  "watch": "NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
  "hot": "NODE_ENV=development webpack-dev-server --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
  "production": "NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
}

NPM scripts are aliases that can be run through the npm command, like so: npm run <name>. We’ll going to use it a bit later, but for now, make sure you have those scripts installed.

Folder Structure

Compiling assets usually means getting a source file (uncompiled), compiling it, and saving it to another location, typically a public location (a place where files can be discovered through the web server.) To do this, we need to establish a folder structure, to know where to write the uncompiled code and where to save the compiled one.

There’s a standard structure for this, widely used on most GitHub projects, and it consists of creating a src/ and a build/ dir.

Under src/ we’ll store all the uncompiled code. And we’ll going to send all compiled code to build/.

Let’s create those dirs:

mkdir src build

Under src/ we’re going to create another dir for each type of language or extension that we’re going to use. For example, if we’re using SCSS and ES2015 JS files, we create src/js and src/scss. We’ll talk more about this next.

Compiling SCSS, LESS and Other CSS Extensions

Let’s start by creating a dir for our uncompiled code under src/. For this tutorial we’re going to use SCSS, but you can use anything you want, just replace the name.

mkdir src/scss

Now that we have our directory, let’s write some SCSS. I’m going to create 3 files:

src/scss/_variables.scss:

$blue: #007bff;
$purple: #4d56c5;

src/scss/defaults.scss:

body {
  color: $blue;
}

And src/scss/app.scss which ties them together:

@import "variables";
@import "defaults";

Obviously, we don’t need that many files for this much SCSS, but I created them to test the compiler.

Notice that $purple is not used. We’re using that to check if it takes up space in the compiled CSS file.

Now that we have the files, let’s write the configuration. I’ll open up webpack.mix.js and add:

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

mix.sass('src/scss/app.scss', 'build/css');

That’s all that’s required to compile app.scss.

To compile, simply run:

npm run dev

This will execute the dev script defined in package.json. Specifically, this:

NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js

You’ll see the list of your files popup before the script finishes compilation.

 DONE  Compiled successfully in 528ms

            Asset      Size  Chunks             Chunk Names
build/css/app.css  28 bytes       0  [emitted]  mix

Now, if we list the contents of that .css file, we’ll see:

$ cat build/css/app.css
body {
  color: #007bff;
}

And it worked! This only contains the final CSS code. This is the file that you’d use in your project.

Compiling ES2015 or Above

Let’s see how JS looks when compiled from ES2015 or above into JS that’s understood by older browsers.

I’ll create src/js/app.js and load a library, the display an alert message on the document.body.onload event.

import _each from 'lodash/each';

window.onload = () => {
    _each([1, 2, 3], i => {
        alert(i);
    });
};

Now, we need to add another instruction in the webpack.mix.js file to compile the file.

Including my SASS instruction, my file becomes:

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

mix.sass('src/scss/app.scss', 'build/css')
   .js('src/js/app.js', 'build/js');

If we run the “dev” npm script again, we get:

 DONE  Compiled successfully in 505ms

            Asset      Size  Chunks             Chunk Names
 /build/js/app.js   33.5 kB       0  [emitted]  /build/js/app
build/css/app.css  28 bytes    0, 0  [emitted]  /build/js/app, /build/js/app

Notice that the file size is 33.5 KB, which is a lot for a lib function and some simple code. The reason for that is that Webpack in dev mode uses a lot of code to be able to debug your app easily.

If we run the “production” script, you’ll notice the file size drop significantly:

 DONE  Compiled successfully in 2258ms

            Asset      Size  Chunks             Chunk Names
 /build/js/app.js   6.43 kB       0  [emitted]  /build/js/app
build/css/app.css  19 bytes    0, 0  [emitted]  /build/js/app, /build/js/app

Compiling Locally

To compile things locally, we’ll always use the “dev” NPM script. There are two main reasons to do this:

  1. It doesn’t minify the compiled code, so it’s easier to debug.
  2. It includes more advanced debug features, especially for JS.

Watching for File Changes

To keep the compiler open and watch for file changes and recompile everything again, use the “watch” script. This script is basically the same as “dev”, but it also watches for file changes until you kill the watcher with CTRL + C or CMD + C.

To use it, simply run:

npm run watch

This script is meant to be used locally.

Compiling Assets for Production

When in production, we don’t need the dev or local features enabled, so we can minify the compiled code and strip anything unnecessary. To leverage this, use:

npm run production

Conclusion

Laravel Mix will save you a lot of trouble after you get the hang of it. And the best part is that there’s nothing much to it. You just need to understand the folder structure and how to define the webpack.mix.js configuration. To see all the option of Laravel Mix, visit this page.

If you have any questions, don’t hesitate to post a comment.