Using a custom fixable ESLint rule to reduce your application’s bundle size
In this article, I explained how to quickly create a custom ESLint rule with TypeScript. Let’s go one step further and see how we can implement a fixable rule that will fix your code automatically when you save your file in VSCode.
Here, our example will be the import of lodash functions in a frontend app.
As told here, it’s better to import lodash functions with import range from 'lodash/range'
instead of import { range } from 'lodash'
if you want to avoid importing the full lodash library in your application’s bundle.
Sadly, the VS Code auto import uses the latter and if you don’t fix it, you’ll have a bigger frontend bundle than you need to.
There are many ways to fix this issue (see what lodash suggests here for example) but it’s something that can easily be automatically fixed by ESLint and it’s a good example to learn how to implement the fix part of a custom rule. Let’s discover this now!
Spotting the import issues (no fix yet)
Let’s start by building the rule that will alert on bad imports. For the sake of simplicity, I will ignore the imports like import * as lodash from 'lodash'
or import lodash from 'lodash'
as they can’t be fixed but a real rule should alert on these too.
Let’s write the tests first:
import { RuleTester } from '@typescript-eslint/rule-tester';
import lodashImportRule from './lodash-import-rule';
const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser'
});
ruleTester.run('lodash-import-rule', lodashImportRule, {
valid: [
'import range from "lodash/range"',
'import lodashRange from "lodash/range"',
`import range from "lodash/range";
import uniq from "lodash/uniq";`,
],
invalid: [
{
code: 'import { range } from "lodash"',
errors: [
{
messageId: 'defaultMessage',
},
],
},
{
code: 'import { range, uniq } from "lodash"',
errors: [
{
messageId: 'defaultMessage',
},
],
},
],
});
And here’s the code for the rule:
import { TSESLint, AST_NODE_TYPES } from '@typescript-eslint/utils';
const lodashImportRule: TSESLint.RuleModule<'defaultMessage'> = {
defaultOptions: [],
meta: {
type: 'problem',
messages: {
defaultMessage: "Don't import from full lodash module",
},
schema: [], // no options
},
create: context => ({
ImportDeclaration: node => {
if (node.source.value !== 'lodash') {
return;
}
const importedMethods = node.specifiers
.map(specifier => {
return specifier.type === AST_NODE_TYPES.ImportSpecifier
? specifier.imported.name
: null;
})
.filter((specifier): specifier is string => specifier !== null);
if (importedMethods.length === 0) {
return;
}
return context.report({
node: node.source,
messageId: 'defaultMessage'
});
},
}),
};
export default lodashImportRule;
Let’s focus quickly on the importedMethods
variable. Each import specifier can be of one of these three types:
ImportDefaultSpecifier
:import lodash from 'lodash'
ImportNamespaceSpecifier
:import * as lodash from 'lodash'
ImportSpecifier
:import { omit } from 'lodash'
We only want to handle the ImportSpecifier
type in this article, hence the importedMethods
variable and the check on its length. In the real world, you’ll probably want to report an error for the other cases too.
Fixing the code
Now that we are alerted on bad imports, let’s see how to make ESLint fix the code automatically.
To implement the fix part of a rule, we have to use the fix
property of the object passed to context.report
. This property accepts a function whose parameter is an object allowing us to replace, remove or add code. See the official documentation for a more detailed explanation.
Here, we’ll want to replace completely the import so we’ll use fixer.replaceText
. Let’s modify the return part of our rule with this:
return context.report({
node: node.source,
messageId: 'defaultMessage',
fix: fixer => {
return fixer.replaceText(
node,
importedMethods
.map(name => `import ${name} from "lodash/${name}";`)
.join('\n'),
);
},
});
If you try to run the test without modifying them, you’ll have this error displayed: AssertionError: the rule fixed the code. Please add 'output' property
. ESLint wants us to verify that the fixed code is matching what we expect. Let’s adapt the invalid cases of our tests:
invalid: [
{
code: 'import { range } from "lodash"',
errors: [
{
messageId: 'defaultMessage',
},
],
output: 'import range from "lodash/range";',
},
{
code: 'import { range, uniq } from "lodash"',
errors: [
{
messageId: 'defaultMessage',
},
],
output: `import range from "lodash/range";\nimport uniq from "lodash/uniq";`,
},
],
And there it is! If you run ESLint with the --fix
option on your code, the code will be modified to remove the bad imports.
Going further
- If you are using VS Code, add this
.vscode/setting.json
on your repository to make ESLint fix your code when you save a file:
{
"editor.codeActionsOnSave": {
"source.fixAll": true
}
}
- As written above, in order to really prevent having the full library in the application’s bundle, we need the rule to report an error when using the default or namespace imports.
- Our code only fixes the issue when you import from
lodash
but some other lodash methods can also be imported fromlodash/fp
. Importing the fulllodash/fp
module has the same impact on the application’s bundle so we should modify our rule to fix imports for this too.
As in the previous article, you can find an implementation of what we’ve seen in this article in this repository (make sure you are on the lodash-fix-rule branch)
I hope you’ve learned a few things in this article and that you feel confident writing ESLint rules to fix your code when it’s possible!
Feel free to ask your questions in the comments!