How to change require statements inline using codemod (jscodeshift)?

531 views Asked by At

Hi I'm trying to write a codemod which moves my require statement from top of the file to inside class constructor function.

const moduleA = require('moduleA');
const moduleB = require('../moduleB');

class Example {
  constructor(context) {
    this.lazy(
      "moduleA",
      () => new moduleA(context)
    );
    this.lazy(
      "moduleB",
      () => new moduleB(context)
    );
  }
  
  lazy() {
  
  }

  async callThis() {
     this.moduleA.callThatMethod();
  }
}

module.exports = Example;

These require statements on top of the file taking long time, which is only used if that API is called at-least once. So as the require is being cached by Node.js at process level anyway. I'm trying to move the require statement inside the arrow function.

Like Below

class Example {
  constructor(context) {
    super(context);
    this.lazy("moduleA", () => { 
       const moduleA = require('moduleA');
       return new moduleA() 
    }
    this.lazy("moduleB", () => {
      const moduleB = require('../moduleB');
      return new moduleB() 
    }
  }

  async callThis() {
     this.moduleA.callThatMethod();
  }
}

I'm having trouble achieving this, because i dunno how to select the "lazy" function defined and then move the top require.

Any help is much appreciated Thanks

1

There are 1 answers

0
coderaiser On

You don't need jscodeshift for such a simple transformation. With my tool Putout you can easily remove top-level require, and add it to lazy function:

const isTopLevel = (vars, {parentPath}) => parentPath.isProgram();

export const match = () => ({
    'const moduleA = require("moduleA")': isTopLevel,
    'const moduleB = require("../moduleB")': isTopLevel,
});

export const replace = () => ({
    'const moduleA = require("moduleA")': '',
    'const moduleB = require("../moduleB")': '',
    '() => new moduleA(context)': `{
        const moduleA = require("moduleA");
        return new moduleA();
    }`,
    '() => new moduleB(context)': `{
        const moduleA = require("../moduleB");
        return new moduleB();
    }`,
});

Try it

putout editor

If you need to filter out this.lazy and have a lot of different modules, you can use PutoutScript with template values __a and __b used to determine and replace Identifier from VariableDeclarator id and StringLiteral from CallExpression callee inside VariableDeclarator init in const moduleA = require('moduleA') VariableDeclaration:

☝️ This language is what you HAVE TO deal with while using jscodeshift all the time

const isTopLevel = ({parentPath}) => parentPath.isProgram();

export const match = () => ({
    // filter out cases where require path contains name
    'const __a = require(__b)': ({__a, __b}, path) => {
        
        if (!isTopLevel(path))
            return false;
        
        // ☝️ __a: Identifier & {name: string}
        // ☝️ __b: StringLiteral & {value: string};
        return __b.value.includes(__a.name);
    },
    
    // filter out 'this.lazy'
    '() => new __a(context)': ({__a}, path) => {
        // ☝️ parentPath: CallExpression & {callee: MemberExpression}
        return compare(path.parentPath.node.callee, 'this.lazy');
    }
});

export const replace = () => ({
    // remove top-level require according to 'match'-condition'
    'const __a = require(__b)': '',
    
    // insert 'require'
    '() => new __a(context)': ({__a}) => {
        return `() => {
            const __a = require("${__a.name}");
            return new __a();
        }`
    },
});

Try it

putout editor