A Modern Way To Bundle Assets Using Laravel-Mix
Recently I developed an admin theme using laravel-mix while the world is in lockdown. For those that develop using the Laravel framework, you are probably familiar with laravel-mix. A tool that sits on top of Webpack for compiling and optimizing assets without having to deal with Webpack much. Originally developed for the Laravel framework by Jeffrey Way, this tool can also be used externally as an npm package.
In this tutorial, I will be using laravel-mix externally to manage the assets and using the EJS loader and HTML-webpack-plugin to dynamically include different parts of the page like navbar, footer, and sidebar to generate the HTML pages for our build.
You can download the siQtheme that I’ve released on Github built with the latest Bootstrap 4 and HTML5 using laravel-mix below.
Download siQtheme on Github
Preview siQtheme Dashboard
Technology
Prerequisites
This tutorial assumes you have a basic understanding of working with npm and Node. If you need help installing npm and Node on your machine, go here to read the documentation on installing Node.js and npm.
Initialize the project
Before installing any package through npm, we need to initialize our project with the following command. Open a terminal (Mac) or command prompt (Windows) and cd to your project directory and type the following cmd. The “-y” is to accept all the default fields.
npm init -y
You should see a new file package.json in your project.
Install dependencies for the development environment
If you have Webpack installed globally on your machine you do not need to pull in this package. For this project, I used SASS as my CSS preprocessor. If you use something else like LESS then replace the sass and sass-loader packages, the ejs-compiled-loader for HTML markup templating and the html-webpack-plugin to generate the HTML files. Type the following command to install the packages.
npm i -D webpack laravel-mix browser-sync cross-env ejs-compiled-loader html-webpack-plugin sass sass-loader resolve-url-loader
Project dependencies
The following vendors will be compiled with the app.scss and app.js files for this demonstration. Depending on your theme, you might want to include the vendors that you need.
- jQuery
- popper.js
- Bootstrap 4
- Font-awesome
To install any package with npm just type the following command and replace the package-namewith your package.
npm I package-name
Project structure
Create a new file and name it webpack.mix.js. Add a resource directory where we will put all our source files and assets. I named it src but you can name it anything with the following directories and files as below. We will spend most of our time working from the src directory and the webpack.mix.js file.
|- src/
| |- assets/
| | |- img/
| | |- fonts/
| | |- app.scss
| | |- app.js
| |
| |- partials/
| | |- footer.ejs
| | |- navbar.ejs
| | |- sidebar.ejs
| |
| |- index.ejs
| |- ...
|
|- package.json (existing file)
|- webpack.mix.js
Configure the run commands
Open the file package.json and add the following to the “scripts” section. These are the commands to run our build and to watch for any changes.
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run development -- --watch",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
},
Configure laravel-mix and asset management
Open webpack.mix.js and copy/paste the code below.
const mix = require('laravel-mix');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// HTML Pages
const pages = [
{
title: 'My Awesome Theme',
template: 'src/index.ejs',
filename: 'index.html'
}
];
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
*/
mix.sass('src/assets/sass/app.scss', 'assets/')
.js('src/assets/scripts/app.js', 'assets/')
.setPublicPath('public')
.browserSync({
proxy: 'localhost',
files: ['public/**/*.html', 'public/assets/css/**/*.css', 'public/assets/scripts/**/*.js']
})
.copyDirectory('src/assets/img', 'public/assets/img')
.webpackConfig({
plugins: [
...pages.map(page => {
return new HtmlWebpackPlugin({
title: page.title,
template: '!!ejs-compiled-loader!' + page.template,
filename: page.filename,
inject: false
})
})
]
});
// Fonts path
mix.config.fileLoaderDirs.fonts = 'assets/fonts';
- Line 1–2 import the required modules.
- Line 5–11 an array of the pages with title, template, and filename. This is where we want to add our page information. The title is the page title, template points to the ejs file in the src directory, and the filename is what it will be renamed and copied to the output path.
- Line 18–19 points to the main sass and javascript files.
- Line 20 set the public path.
- Line 21–24 set up browser-sync when we run the watch command. Make sure to change the proxy to match your development environment.
- Line 25 copy the img directory from src to the public folder.
- Line 28 set the output path in this case, it will be public.
- Line 28–35 uses the spread syntax to expand the pages array and map each page as a function returning a new HtmlWebpackPlugin with all the page information. To see all configuration for the HtmlWebpackPlugin read the plugin documentation.
- Line 40 set the fonts path.
Import vendor’s style to app.scss
Open src/assets/app.scss and add the following lines.
@import '~bootstrap/scss/bootstrap';
@import '~font-awesome/scss/font-awesome';
Import vendor’s script to app.js
Open src/assets/app.js and add the following lines.
window.jQuery = window.$ = require("jquery");
require("popper.js");
require("bootstrap");
Now that we completed the configuration, we are ready to build our pages. I’ll be using a basic admin dashboard from getbootstrap.com website for this demonstration. You can get this template here.
index.ejs
Open src/index.ejs and copy/paste the code below. In this file, the header title is getting the data from the webpack plugin. The navbar, sidebar, and footer are inserted from the partials directory. With this setup, we only need to change one file in the partials directory and webpack will update the new change for all the pages.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="assets/app.css">
</head>
<body>
(html comment removed: BOF NAVBAR )
<% include src/partials/navbar.ejs %>
(html comment removed: EOF NAVBAR )
<div class="container-fluid">
<div class="row">
(html comment removed: BOF SIDEBAR )
<% include src/partials/sidebar.ejs %>
(html comment removed: EOF SIDEBAR )
(html comment removed: BOF MAIN-BODY )
<main class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2>Main section title</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptates sapiente velit rerum minus autem illo cupiditate,
corrupti sequi similique nesciunt? Molestiae error est officia? Deleniti dolore amet animi saepe dignissimos!</p>
</main>
(html comment removed: EOF MAIN-BODY )
</div>
(html comment removed: BOF FOOTER )
<% include src/partials/footer.ejs %>
(html comment removed: EOF FOOTER )
</div> (html comment removed: END CONTAINER )
<script src="assets/app.js"></script>
</body>
</html>
navbar.ejs
Copy/paste the following codes to src/partials/navbar.ejs
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="#">Company name</a>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="#">Sign out</a>
</li>
</ul>
</nav>
sidebar.ejs
Copy/paste the following codes to src/partials/sidebar.ejs
<nav class="col-md-2 d-none d-md-block bg-light sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" href="#">
<span data-feather="home"></span>
Dashboard <span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span data-feather="file"></span>
Orders
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span data-feather="shopping-cart"></span>
Products
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span data-feather="users"></span>
Customers
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span data-feather="bar-chart-2"></span>
Reports
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span data-feather="layers"></span>
Integrations
</a>
</li>
</ul>
</div>
</nav>
footer.ejs
Copy/paste the following codes to src/partials/footer.ejs
<footer class="footer">
<p class="text-center my-3">Copyright © 2020 MyTheme. All rights reserved.</p>
</footer>
Add the following styles to src/assets/app.scss below.
body {
font-size: .875rem;
}
.feather {
width: 16px;
height: 16px;
vertical-align: text-bottom;
}
/*
* Sidebar
*/
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 100; /* Behind the navbar */
padding: 0;
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}
.sidebar-sticky {
position: -webkit-sticky;
position: sticky;
top: 48px; /* Height of navbar */
height: calc(100vh - 48px);
padding-top: .5rem;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
}
.sidebar .nav-link {
font-weight: 500;
color: #333;
}
.sidebar .nav-link .feather {
margin-right: 4px;
color: #999;
}
.sidebar .nav-link.active {
color: #007bff;
}
.sidebar .nav-link:hover .feather,
.sidebar .nav-link.active .feather {
color: inherit;
}
.sidebar-heading {
font-size: .75rem;
text-transform: uppercase;
}
/*
* Navbar
*/
.navbar-brand {
padding-top: .75rem;
padding-bottom: .75rem;
font-size: 1rem;
background-color: rgba(0, 0, 0, .25);
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
}
.navbar .form-control {
padding: .75rem 1rem;
border-width: 0;
border-radius: 0;
}
.form-control-dark {
color: #fff;
background-color: rgba(255, 255, 255, .1);
border-color: rgba(255, 255, 255, .1);
}
.form-control-dark:focus {
border-color: transparent;
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
}
/*
* Utilities
*/
.border-top { border-top: 1px solid #e5e5e5; }
.border-bottom { border-bottom: 1px solid #e5e5e5; }
Adding a new theme page
To add a new page, just copy the index.ejs and rename this file to something else (example: page2.ejs) and make the changes to the content. In webpack.mix.js add a new page item to the pages variable like below.
// HTML Pages
const pages = [
{
title: 'My Awesome Theme',
template: 'src/index.ejs',
filename: 'index.html'
}, {
title: 'My 2nd Page',
template: 'src/page2.ejs',
filename: 'page2.html'
}
];
That pretty much wraps it up! The only thing left is to develop your theme. Laravel-mix comes with a few run commands to help with the development process. Any of the following commands will generate a build folder, in this case, it will be public with all the files and assets.
Compiling
Run any of the commands below to compile the build.
npm run dev
development build (uncompress)npm run prod
production build (compressed)npm run watch
browser-sync hot reload
You can download the siQtheme I released on Github as an open-source below to see the full source code.
Laravel-mix can do a lot more than shown in this tutorial. For full API and documentation, visit laravel-mix.com.
Thank you for taking the time to read my article. Have a blessed day!