DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

How to Improve the Performance of ESLint in Gulp

Learn how to improve the performance of ESLint, which checks JavaScript code quality.

Bogdan Nechyporenko user avatar by
Bogdan Nechyporenko
CORE ·
Dec. 20, 18 · Tutorial
Like (6)
Save
Tweet
Share
6.15K Views

Join the DZone community and get the full member experience.

Join For Free

As you may already know, GulpJS is a JavaScript task runner that lets you automate several tasks during development and ESLint checks the code quality based on defined rules. A quite popular library which helps to combines these two tools is gulp-eslint. 

The default configuration to make linting work with help of gulp is quite trivial:

const {src, task} = require('gulp');
const eslint = require('gulp-eslint');

task('default', () => {
    return src(['scripts/*.js'])
        // eslint() attaches the lint output to the "eslint" property
        // of the file object so it can be used by other modules.
        .pipe(eslint())
        // eslint.format() outputs the lint results to the console.
        // Alternatively use eslint.formatEach() (see Docs).
        .pipe(eslint.format())
        // To have the process exit with an error code (1) on
        // lint error, return the stream and pipe to failAfterError last.
        .pipe(eslint.failAfterError());
});

In the beginning of a project, it works really well with a reasonable performance. The problems appear when the code base becomes massive and to check the code costs not seconds anymore but minutes. It becomes really annoying if you want to have a fast feedback and don't want to switch to another activity during this waiting time or especially when you are under time pressure to deliver the story as soon as possible. 

ESLint has a good feature to turn on a cache to overcome this problem and verify only the updated files. The issue that the passing this property through a configuration in gulp-eslint doesn't work, because of the way how it is integrated. That branch of code where the caching is happening even not executed. 

One of the solutions to this problem could be to create a small wrapper around it to mimic a caching logic. Let's first see how our gulp can look like:

import cache from 'cache'; // this file we will create

const lintStream = (stream) => stream
    .pipe(cache.cacheAndFilter({filename: '.eslintstore'})) // extra line
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failOnError())
    .pipe(cache.updateCache()); // extra line

gulp.task('lint', () => lintStream(gulp.src(`${paths.webSrcDir}/**/*.js`)));

On line 4, we will collect information about all files and filter out already verified files on next lint run. And line 8 will persist/update the cache of modified and passed the verification by lint files.

First, we need to scan all file paths for the given file stream with respect to the last modified timestamp. To simplify work with streams we can use through2 module.

import through from 'through2';

const files = {};

const cacheAndFilter = () => {
    const collect = (file, enc, cb) => {
        files[file.path] = file.stat.mtime;
        cb(null, file);
    };

    return through.obj(collect);
};

export default {
    cacheAndFilter
}

The logic is only on line 7, where we collect the information about each file. Line 8 says that the received file stream will be passed to the next pipe without any modification. For example, to filter that file stream out will require such implementation

cb(null, null);

Once we collected all information about files, we can start persisting the timestamps of validated files having no linting issues. When ESLint process each file it modifies it and creates another file object contacting additional data. In our case, we care about `file.eslint.errorCount`, If it is not 0 than we don't update timestamp (line 24).

import through from 'through2';
import PluginError from 'plugin-error';
import fs from 'fs';

let cachedFiles = {};
let options;
const files = {};

const cacheAndFilter = (opt) => {
    if (!opt.filename) {
        return cb(new PluginError('gulp-eslint-cache', 'Provide filename for caching'));
    }
    options = opt;

    const collectAndFilter = (file, enc, cb) => {
        files[file.path] = file.stat.mtime;
        cb(null, file);
    };

    return through.obj(collectAndFilter);
};

const updateCache = () => through.obj((file, enc, cb) => {
    if (!file.eslint.errorCount) {
        fs.writeFile(options.filename, JSON.stringify({...cachedFiles, ...files}), 'utf8', (err) =>
            cb(err ? new PluginError('gulp-eslint-cache', err) : null, file));
    } else {
        cb(null, file);
    }
});

export default {
    cacheAndFilter,
    updateCache
}

And the last missing part is to actually apply our caching knowledge and filter the files which we would like to send to ESLint. As this part is the performance bottleneck which we are solving.

The added code between lines 22 and 27 does it by looking up only modified files. It compares the current timestamp of each file with the timestamp saved in .eslintstore in the previous run. If the file is new, it won't be in .eslintstore so then we have to lint it. Or if the timestamp of this file from the previous run is older than actual file timestamp then we have also to lint this file again.

import through from 'through2';
import PluginError from 'plugin-error';
import fs from 'fs';

let cachedFiles = {};
let options;
const files = {};

const cacheAndFilter = (opt) => {
    if (!opt.filename) {
        return cb(new PluginError('gulp-eslint-cache', 'Provide filename for caching'));
    }
    options = opt;

    if (fs.existsSync(options.filename)) {
        cachedFiles = JSON.parse(fs.readFileSync(options.filename, 'utf8'));
    }

    const collectAndFilter = (file, enc, cb) => {
        files[file.path] = file.stat.mtime;

        const currentTimestamp = cachedFiles[file.path];
        if (!currentTimestamp || currentTimestamp && new Date(currentTimestamp).getTime() < file.stat.mtime.getTime()) {
            cb(null, file);
        } else {
            cb(null, null);
        }
    };

    return through.obj(collectAndFilter);
};

const updateCache = () => through.obj((file, enc, cb) => {
    if (!file.eslint.errorCount) {
        fs.writeFile(options.filename, JSON.stringify({...cachedFiles, ...files}), 'utf8', (err) =>
            cb(err ? new PluginError('gulp-eslint-cache', err) : null, file));
    } else {
        cb(null, file);
    }
});

export default {
    cacheAndFilter,
    updateCache
}

I didn't want to create yet another npm package for that. First, because the logic is quite compact and each project has own specifics that this implementation might not fit exactly project's needs. But having this as a base and if necessary tweaking some parts can significantly improve your experience with linting. Especially if you already use gulp-eslint in your project and didn't make any performance improvements so far.

ESLint

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Beginners’ Guide to Run a Linux Server Securely
  • Mr. Over, the Engineer [Comic]
  • Fraud Detection With Apache Kafka, KSQL, and Apache Flink
  • Educating the Next Generation of Cloud Engineers With Google Cloud

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: