Migrating our scripts to Node.js v16 using codemod
Eduardo Maciel
Posted on November 11, 2021
At Entria we have a lot of Node.js scripts to automate common tasks
we also make a script for any migration and to make it easy to run a single migration for test purposes and each script starts with anonymous async functions that is auto executed.
Like this template
const run = async () => {
//migration code
}
(async () => {
try {
await run();
} catch (err) {
// eslint-disable-next-line
console.log('err: ', err);
process.exit(1);
}
process.exit(0);
})();
This works well, but not enough for our use case. Because we create tests for our scripts and migrations if they are used in production.
If you import the run function in your test files it will run the async functions in your tests, which is not the behavior you wanna. So we have a check async function that is only auto executed when we are running directly.
To make this check, we used module.parent
propriety, but it will be deprecated on Node v16.
Context of Node v14 and v16
In October 26th, 2021 Node.js v16 replaced v14 as the LTS release.
And with these changes we on Entria bring to us breaking changes into our codebase on Entria, like a module.parent.
module.parent
has used on Node v14 to locate if script is a module or executable, like:
if (!module.parent) {
// running like `node myfile.js`
} else {
// running like require('./myfile.js')
}
We had 206 files that use module.parent
And we want changes all occurrences of module.parent
to require.main
, that allows we check the same thing of module.parent
.
if (require.main === module) {
/// running like `node myfile.js`
} else {
// running like require('./myfile.js')
}
To change all occurrences of module.parent
we used a codemod, with jscodeshift. Codemod is a tool/library to assist our with large-scale codebase refactors that can be partially automated.
But Eduardo, why you not use find and replace of your IDE?
R: Because this require a lot of attention and time of our developers, and if we not used codemod can't sure that can exists more module.parent on the future.
Time of code
We want change
if(!module.parent) {
}
to
if(require.main === module){
}
How ?
We used jscodeshift to codemod.
First you should add jscodeshift in your node_modules with
npm install -g jscodeshift
After this, you should create an archive that contains our codemod, in this case, replace-module-parent.js
.
First, we should create a function that is used in all files of the folder that we select, and pass two arguments, fileInfo
and api
.
The fileInfo
argument represents information of the currently processed file, and api
is object that exposes the jscodeshift
library and helper functions from the runner.
// replace-module-parent.js
function transform (fileInfo, api) {
};
module.exports = transform;
Now we want to get jscodeshift helpers, from api.jscodeshift
and transform our code to AST (Abstract System Types).
And you can explore more of our AST here AST Explorer.
const j = api.jscodeshift;
const root = j(fileInfo.source)
Now, we want find all occurrences of if(!module.parent)
, and replace to if(require.main === module)
// finding all ocurrences of if(!module.parent)
root
.find(j.IfStatement, {
type : 'IfStatement',
test : {
type : 'UnaryExpression',
operator : '!',
argument : {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'module'
},
property: {
type: 'Identifier',
name: 'parent'
}
}
}
})
.filter((path) => {
if (path.node.test.type !== 'UnaryExpression') {
return false;
}
return true;
})
Replacing all to require.main
.forEach((path) => {
const requireMain = j.ifStatement(
j.binaryExpression(
'===',
j.memberExpression(
j.identifier('require'),
j.identifier('main')
),
j.identifier('module')
),
path.node.consequent,
path.node.alternate
)
j(path).replaceWith(requireMain)
});
return root.toSource();
And on final our codemod is
function transform (fileInfo, api) {
const j = api.jscodeshift;
const root = j(fileInfo.source)
root
.find(j.IfStatement, {
type : 'IfStatement',
test : {
type : 'UnaryExpression',
operator : '!',
argument : {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'module'
},
property: {
type: 'Identifier',
name: 'parent'
}
}
}
})
.filter((path) => {
if (path.node.test.type !== 'UnaryExpression') {
return false;
}
return true;
})
.forEach((path) => {
const requireMain = j.ifStatement(
j.binaryExpression(
'===',
j.memberExpression(
j.identifier('require'),
j.identifier('main')
),
j.identifier('module')
),
path.node.consequent,
path.node.alternate
)
j(path).replaceWith(requireMain)
});
return root.toSource();
};
module.exports = transform;
module.exports.parser = 'ts';
To run this code you can use this on your terminal:
jscodeshift -t replace-module-parent.js [your-input-files] -d -p
Posted on November 11, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.