Boost Frontend Speed: Incremental ESLint Checks and Build with Git Diff
This article explains how to apply incremental thinking to ESLint code checks and webpack builds by using Git diff to detect changed files, running targeted linting and bundling, and demonstrates significant time savings in large front‑end projects.
Introduction
In Linux, tools like rsync achieve faster synchronization by transferring only the differences (incremental sync) instead of the whole data set. The same principle can be applied to front‑end engineering to speed up code linting and build processes.
Incremental Code Linting
Front‑end projects often run ESLint automatically before commits and before merge requests. When the codebase is large, a full lint run can take minutes, slowing down continuous integration. Incremental linting focuses only on the files that have changed.
2.1 Find Modified Files
The script uses git diff --cached --diff-filter=ACMR --name-only to list added, copied, modified, or renamed files (excluding deletions). The Node.js child_process.exec function runs the command, and the output is parsed to obtain a JavaScript file list.
const exec = require('child_process').exec;
const GITDIFF = 'git diff --cached --diff-filter=ACMR --name-only';
exec(GITDIFF, (error, stdout) => {
if (error) {
console.error(`exec error: ${error}`);
}
const diffFileArray = stdout.split('
').filter(diffFile => /(\.js|\.jsx)(
|$)/gi.test(diffFile));
console.log('Files to lint:', diffFileArray);
});2.2 Lint the Modified Files
ESLint’s Node.js API ( new ESLint() or CLIEngine for older versions) is used to lint the file list. The results array contains error and warning counts per file.
const { ESLint } = require('eslint');
const linter = new ESLint();
let errorCount = 0;
let warningCount = 0;
if (diffFileArray.length > 0) {
const eslintResults = linter.lintFiles(diffFileArray).results;
eslintResults.forEach(result => {
errorCount += result.errorCount;
warningCount += result.warningCount;
if (result.messages && result.messages.length) {
console.log(`ESLint found problems in file: ${result.filePath}`);
result.messages.forEach(msg => {
if (msg.severity === 2) {
console.log(`Error: ${msg.message} at Line ${msg.line} Column ${msg.column}`);
} else {
console.log(`Warning: ${msg.message} at Line ${msg.line} Column ${msg.column}`);
}
});
}
});
}2.3 Friendly Output and Exit Codes
Print a clear pass/fail message in the terminal.
If any errors exist, exit with a non‑zero status so Git aborts the commit.
if (errorCount >= 1) {
console.log('\x1b[31m', 'ESLint failed');
console.log('\x1b[31m', `✖ ${errorCount + warningCount} problems (${errorCount} error, ${warningCount} warning)`);
process.exit(1);
} else if (warningCount >= 1) {
console.log('\x1b[32m', 'ESLint passed, but needs improvement.');
process.exit(0);
} else {
console.log('\x1b[32m', 'ESLint passed');
process.exit(0);
}Integrate the script into package.json and run it via a pre‑commit hook. Tools like lint‑staged and husky can simplify the setup.
2.4 Result Comparison
In a project with 460 JavaScript files, a full lint run took 38 seconds, while the incremental approach took only 2 seconds when a single file changed.
Incremental Build with Webpack
Large multi‑page applications (MPA) often spend dozens of minutes on a full build. When only a few pages or components change, rebuilding the entire bundle is wasteful.
3.1 Find Modified Files
Use git diff origin/master --name-only to list files changed between the current branch and master. The list is normalized with Node’s path.posix.
const execSync = require('child_process').execSync;
const path = require('path').posix;
const GITDIFF = 'git diff origin/master --name-only';
const diffFiles = execSync(GITDIFF, { encoding: 'utf8' })
.split('
')
.filter(item => item)
.map(filePath => path.normalize(filePath));3.2 Compute Incremental Entry Points
Build a dependency tree for each page entry (e.g., using Madge ). If the tree contains any of the modified files, that page’s entry is added to the incremental build list.
// Example dependency trees (simplified)
const relyTree = {
// demo/index.js tree
{
'demo/a.jsx': ['util/fetch.js'],
'demo/b.js': [],
'demo/index.js': ['demo/a.jsx', 'demo/b.js'],
'util/fetch.js': []
},
// demo/buy.js tree
{
'util/env.js': [],
'demo/buy.js': ['demo/c.js', 'demo/d.js'],
'demo/c.js': ['util/env.js'],
'demo/d.js': []
}
};
function intersection(arr1, arr2) {
return arr1.some(e => arr2.includes(e));
}
const incrementEntries = [];
for (const i in relyTree) {
for (const j in relyTree[i]) {
if (intersection(relyTree[i][j], diffFiles)) {
incrementEntries.push(i);
}
}
}For example, if util/fetch.js changed, only demo/index needs rebuilding, so webpack receives a single entry and finishes much faster.
3.3 Boundary Cases
If git diff shows changes in package.json (dependency upgrades), the safest approach is to trigger a full build because such changes affect the entire dependency graph.
3.4 Result Comparison
In a 50‑page MPA, a full build took about 7 minutes, while an incremental build that rebuilt only two changed pages completed in 50 seconds.
Conclusion
The article demonstrates how the incremental mindset—identifying only the changed parts of a codebase—can dramatically reduce the time spent on ESLint checks and webpack builds in front‑end projects. While the exact scripts may need adaptation, the underlying principle can be applied to many other front‑end engineering tasks.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
