How to Make Your Front-End Development Faster in Magento 2 Using Grunt
Tags: javascript, programming, webdev, magento
Published: 2021-05-08 21:55:12

_This article was originally posted on [my personal blog](https://blog.shahednasser.com/how-to-make-your-front-end-development-faster-in-magento-2-using-grunt/)_

Magento 2 is a great e-commerce platform, as it comes with many features built-in. It makes it easier for store owners to create a store that exactly fits their needs.

However, developing with Magento can be a pain, as it is not easy to learn, and even when you do, there is a lot of build or compilation required for a lot of minor changes, especially for front-end development.

Here's where Grunt comes in. Grunt is a Javascript task runner that helps in automating repeated tasks like compilation, minification, etc...

We'll go over how to use Magento's out-of-the-box commands in Grunt, and how to add our own to make our development faster.

---

## Install Grunt-CLI

If you don't have `grunt-cli` installed globally, you need to install it with the following command:

```bash
npm install -g grunt-cli
```

If you get an error that permission is denied, you'll need to run the command as `sudo` or on Windows, you'll need to run PowerShell or Command Prompt as an administrator.

---

## Setting up Magento with Grunt

If you look at your Magento project directory, you'll see that in the root there are two files: `package.json` and `package.json.sample`. If you open `package.json`, it will be just an empty JSON object like this:

```json
{}
```

But if you open `package.json.sample`, you'll find a proper `package.json` that will look something like this:

```json
{
    "name": "magento2",
    "author": "Magento Commerce Inc.",
    "description": "Magento2 node modules dependencies for local development",
    "license": "(OSL-3.0 OR AFL-3.0)",
    "repository": {
        "type": "git",
        "url": "https://github.com/magento/magento2.git"
    },
    "homepage": "http://magento.com/",
    "devDependencies": {
        "glob": "~7.1.1",
        "grunt": "~1.0.1",
        "grunt-autoprefixer": "~3.0.4",
        "grunt-banner": "~0.6.0",
        "grunt-continue": "~0.1.0",
        "grunt-contrib-clean": "~1.1.0",
        "grunt-contrib-connect": "~1.0.2",
        "grunt-contrib-cssmin": "~2.2.1",
        "grunt-contrib-imagemin": "~2.0.1",
        "grunt-contrib-jasmine": "~1.1.0",
        "grunt-contrib-less": "~1.4.1",
        "grunt-contrib-watch": "~1.0.0",
        "grunt-eslint": "~20.1.0",
        "grunt-exec": "~3.0.0",
        "grunt-jscs": "~3.0.1",
        "grunt-replace": "~1.0.1",
        "grunt-styledocco": "~0.3.0",
        "grunt-template-jasmine-requirejs": "~0.2.3",
        "grunt-text-replace": "~0.4.0",
        "imagemin-svgo": "~5.2.1",
        "load-grunt-config": "~0.19.2",
        "morgan": "~1.9.0",
        "node-minify": "~2.3.1",
        "path": "~0.12.7",
        "serve-static": "~1.13.1",
        "squirejs": "~0.2.1",
        "strip-json-comments": "~2.0.1",
        "time-grunt": "~1.4.0",
        "underscore": "~1.8.0"
    }
}
```

So, we'll remove the current `package.json`:

```bash
rm package.json
```

then rename the `package.json.sample` to `package.json`:

```bash
mv package.json.sample package.json
```

After that, we'll install the dependencies using NPM:

```
npm install
```

Once the installation is done, you'll have a new node_modules directory with all the dependencies we'll need to run grunt.

The next step would be to set up `Gruntfile.js` which holds the tasks to be run by Grunt. You'll find a `Gruntfile.js.sample` in the root directory, so we'll just have to rename it to `Gruntfile.js`:

```bash
mv Gruntfile.js.sample Gruntfile.js
```

By default in Magento, the following tasks are one of those defined in Grunt:

1. **default**: Just shows a default message in the terminal.
2. **clean**: cleans the directories that hold the cached or generated files.
3. **deploy**: generates static files.
4. **refresh**: cleans cache and refreshes the generated static files.

Usually, in Magento, when making changes in modules or themes to assets like Javascript, LESS, or CSS files, you'll need to run the following command to see the file changes in action:

php bin/magento setup:static-content:deploy -f
Magento claims that this is unnecessary in development, which is why we use -f, however, if you've used Magento you'll know that this is actually not true and you need to run this command whenever you need to see the changes you made.

Actually, prior to running that command, you'll probably also need to remove directories like `var/cache` or `var/page_cache`, or you'll need to run commands that clears and flushes the cache.

Running all these different commands can be such a hassle, and this is where Grunt comes in. You can do all that just with a simple command:

```bash
grunt refresh
```

This command first runs the clean command, which will clear up all the directories holding the cache, then will compile all themes and their assets.

### Compiling Your Custom Theme

Grunt compiles the themes that are declared in `dev/tools/grunt/configs/themes.js`. The declaration for a theme looks something like this:

```js
blank: {
        area: 'frontend',
        name: 'Magento/blank',
        locale: 'en_US',
        files: [
            'css/styles-m',
            'css/styles-l',
            'css/email',
            'css/email-inline'
        ],
        dsl: 'less'
    },
```

As you can see, the key of the object is the name of the theme. It has the following properties:

1. `area`: Where the theme applies. Can be frontend, adminhtml, or doc.
2. `name`: The name of the theme, which is in the format `VENDOR_THEME`.
3. `locale`: The locale of the theme.
4. `files`: These are the files in the theme that should be compiled.
5.`dsl`: stands for dynamic stylesheet language and can be either less or sass

So, to make sure your theme is compiled when running grunt refresh, you'll need to add your theme to this object, with the values for the properties based on your theme.

---

## Adding Custom Tasks

The next part we'll go over is how to create custom tasks in Grunt that will help make our development faster.

One use case we can automate is when we update a module's version. Usually, you have to run 3 different tasks:

```bash
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy -f
```

We'll create a new task called upgrade that will run all these tasks for us.

To create a new task, you need to create a new file in `dev/tools/grunt/tasks`. We'll create a new file there called `upgrade.js`.

The file should export a function that takes grunt as a parameter:

```js
module.exports = function(grunt) {
	//TODO code for the task
};
```

the reason behind this is that `Gruntfile.js` fetches all files in the tasks directory, and passes them the instance of `grunt`.

Next, we'll declare some functions that will be helpful for us:

```js
const exec = require('child_process').execSync,
        log = grunt.log.write,
        ok = grunt.log.ok
```

1. `exec`: it's actually the function `execSync` which allows us to run commands we would run in shell. We'll use it to run the commands mentioned above.
2. `log`: A function that allows us to output information messages.
3. `ok`: A function that allows us to output successful messages.

Next, to register our task, we'll use `grunt.registerTask` which takes two parameters: the name of the task and the function that the task will execute once called.

```js
grunt.registerTask('upgrade', function () {
});
```

The first thing the task should do is run the command `php bin/magento setup:upgrade`. We'll use exec to run it:

```js
log('Running setup:upgrade...')
exec('php bin/magento setup:upgrade', {stdio: 'inherit'})
```

The first parameter is the command to run, and the second parameter is an options object. The option we're passing is `stdio` with the value `inherit`, which means that the output should be printed to the terminal we're calling the task from.

Next, we need to run the command `php bin/magento setup:di:compile`. We'll also use `exec` to do that:

```js
log('Running setup:di:compile')
exec('php bin/magento setup:di:compile', {stdio: 'inherit'})
```

Lastly, we need to run the command `php bin/magento setup:static-content:deploy -f`. Instead of running it through `exec`, we'll run another grunt task which is the `deploy` task since it's already declared and does all the work for us:

```js
log('Running deploy...')
grunt.task.run('deploy')
ok('Upgrade finished!')
```

We run a grunt task with `grunt.task.run` passing it the name of the task. In the end, we're just outputting a successful message to show that our task is done.

That's it! `upgrade.js` should look like this:

```js
module.exports = function(grunt) {
    const exec = require('child_process').execSync,
        log = grunt.log.write,
        ok = grunt.log.ok

    grunt.registerTask('upgrade', function () {
        log('Running setup:upgrade...')
        exec('php bin/magento setup:upgrade', {stdio: 'inherit'})
        log('Running setup:di:compile')
        exec('php bin/magento setup:di:compile', {stdio: 'inherit'})
        log('Running deploy...')
        grunt.task.run('deploy')
        ok('Upgrade finished!')
    });
}
```

Let's test it out. In the terminal run:

```js
grunt upgrade
```

If everything is done correctly, the task will run all 3 commands. This task will make it easier next time you need to upgrade because of a new or updated module!

Now, you can automate any task with the same process. Create a file in `dev/tools/grunt/tasks` that exports a function that takes `grunt` as a parameter. Inside the function, register the task using `grunt.registerTask` and you can add whatever functionality you need inside.

---

## Conclusion
Using Grunt with Magento, you can automate so many tasks that will make your development faster and easier! I suggest that once you start using Grunt with Magento, you keep all the tasks you create for all your projects, as it will be very beneficial.