Migrating our scripts to Node.js v16 using codemod
Tags: javascript, jscodeshift, refactorit, programming
Published: 2021-11-11 14:25:17

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
```js
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:
```js
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`.
```js
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

```js
if(!module.parent) {

}
```

to

```js
if(require.main === module){

}
```

# How ?
We used [jscodeshift](https://github.com/facebook/jscodeshift) to codemod.

First you should add jscodeshift in your node_modules with
```npm
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.

```js
// 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](https://astexplorer.net/#/gist/a1bb66ce659eaa0b728b75f92773cb64/fe344255725c8510a90943c62d6a58ab1a9506ac).
```js
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)`

```js
// 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`
```js
.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 
```js
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:

```terminal
jscodeshift -t replace-module-parent.js [your-input-files] -d -p
```