Tuesday, June 21, 2016

Web Audio API: Why Compose When You Can Code?

The Web Audio API provides a powerful and versatile system for controlling audio on the Web. It allows developers to choose audio sources, add effects to audio, create audio visualizations, apply spatial effects.

The Web Audio API involves handling audio operations inside an audio context and has been designed to allow modular routing. Basic audio operations are performed with audio nodes, which are linked together to form an audio routing graph. Several sources, with different types of channel layout, are supported even within a single context.
The very first draft for a web audio API appeared in W3C during 2011. Although audio in webpages has been supported for a long time, a proper way of generating audio from the web browser was not available until quite recently. I personally attribute this to Google Chrome, because as for the interest of Google, the browser started becoming the most important part of a computer. You may recall, the realm of the web browser didn’t start to change much until Google Chrome appeared. If you used sound in a webpage in this time, it would have been a poor design decision. But since the idea of web experiments appeared, web audio started to make sense again. Web browsers nowadays are another tool for artistic expression, and video and audio in the web browser plays a vital role in it.
Web Audio API: Why Compose When You Can Code?
Web Audio API can be quite hard to use for some purposes, as it is still under development, but a number of JavaScript libraries already exist to make things easier. In this case I am going to show you how to get started with the Web Audio API using a library called Tone.js. With this, you will be able to cover most of your browser sound needs from only learning the basics.

Hello Web Audio API

Getting Started

We will begin without using the library. Our first experiment is going to involve making three sine waves. As this will be a simple example, we will create just one file named hello.html, a bare HTML file with a small amount of markup.
<!DOCTYPE html>
 <html>
   <head>
<meta charset="utf‐8">
<title> Hello web audio </title> </head>
<body>
   </body>
   <script>
   </script>
</html>
The core of Web Audio API is the audio context. The audio context is an object that will contain everything related to web audio. It’s not considered a good practice to have more than one audio context in a single project. We will begin by instantiating an audio context following the recommendations given by Mozilla’s Web Audio API documentation.
var audioCtx = new (window.AudioContext || window.webkitAudioContext);

Making an Oscillator

With an audio context instantiated, you already have an audio component: the audioCtx.destination. This is like your speaker. To make a sound, you have to connect it to audioCtx.destination. Now to produce some sound, let’s create an oscillator:
var sine = audioCtx.createOscillator();
Great, but not enough. It also needs to be started and connected to our audioCtx.destination:
sine.start();
sine.connect(audioCtx.destination);
With these four lines, you will have a pretty annoying webpage that plays a sine sound, but now you understand how modules can connect with one another. In the following script, there will be three sine shaped tone, connected to the output, each with a different tone. The code is very self-explanatory:
//create the context for the web audio
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
//create, tune, start and connect each oscillator sinea, sineb and sinec
var sinea = audioCtx.createOscillator();
sinea.frequency.value = 440;
sinea.type = "sine";
sinea.start();
sinea.connect(audioCtx.destination);
var sineb = audioCtx.createOscillator();
sineb.frequency.value = 523.25;
sineb.type = "sine";
sineb.start();
sineb.connect(audioCtx.destination);
var sinec = audioCtx.createOscillator();
sinec.frequency.value = 698.46;
sinec.type = "sine";
sinec.start();
sinec.connect(audioCtx.destination);
Oscillators are not restricted to sine waves, but also can be triangles, sawtooth, square and custom shaped,as stated in the MDN.

Patching Logic of Web Audio

Next, we will add a gain module to our orchestra of Web Audio components. This module allows us to change the amplitude of our sounds. It is akin to a volume knob. We have already used the connect function to connect an oscillator to the audio output. We can use the same connect function to connect any audio component. If you are using Firefox, and you take a look at the web audio console, you will see the following:
If we want to change the volume, our patch should look like:
Which means that the oscillators are no longer connected to the audio destination, but instead to a Gain module, and that gain module is connected to the destination. It’s good to always imagine that you do this with guitar pedals and cables. The code will look like this:
var audioCtx = new (window.AudioContext || window.webkitAudioContext)
// we create the gain module, named as volume, and connect it to our
var volume = audioCtx.createGain();
volume.connect(audioCtx.destination);
//these sines are the same, exept for the last connect statement.
//Now they are connected to the volume gain module and not to the au
var sinea = audioCtx.createOscillator();
sinea.frequency.value = 440;
sinea.type = "sine";
sinea.start();
sinea.connect(volume);
var sineb = audioCtx.createOscillator();
sineb.frequency.value = 523.25;
sineb.type = "sine";
sineb.start();
sineb.connect(volume);
var sinec = audioCtx.createOscillator();
sinec.frequency.value = 698.46;
sinec.type = "sine";
sinec.start();
sinec.connect(volume);
volume.gain.value=0.2;
You can find the solution at https://github.com/autotel/simple-webaudioapi/.
GainNode is the most basic effect unit, but there is also a delay, a convolver, a biquadratic filter, a stereo panner, a wave shaper, and many others. You can grab new effects from libraries such as Tone.js.
Storing one of these sound patches in objects of their own will allow you to reuse them as needed, and create more complex orchestrations with less code. This could be a topic for a future post.

Making Things Easier with Tone.js

Now that we have taken a brief look how the vanilla Web Audio modules work, let us take a look at the awesome Web Audio framework: Tone.js. With this (and NexusUI for user interface components), we can very easily build more interesting synths and sounds. To try things out, let us make a sampler and apply some user interactive effects to it, and then we will add some simple controls for this sample.

Tone.js Sampler

We can start by creating a simple project structure:
simpleSampler
|-- js
    |-- nexusUI.js
    |-- Tone.js
|-- noisecollector_hit4.wav
|-- sampler.html
Our JavaScript libraries will reside in the js directory. For the purposes of this demo, we can useNoiseCollector’s hit4.wav file that can be downloaded from Freesound.org.
Tone.js provides its functionalities through Player objects. The basic capability of the object is to load a sample, and to play it either in a loop or once. Our first step here is to create a player object in a “sampler” var, inside the sampler.html file:
<!doctype html>
<html>
    <head>
        <title> Sampler </title>
        <script type="text/javascript" src="js/nexusUI.js" ></script>
        <script type="text/javascript" src="js/Tone.js" ></script>
        <script>
            var sampler = new Tone.Player("noisecollector_hit4.wav", function() {
                console.log("samples loaded");
            });
        </script>
    </head>
    <body>
    </body>
</html>
Note that the first parameter of the player constructor is the name of the WAV file, and the second is a callback function. WAV is not the only supported file type, and the compatibility depends on the web browser more than the library. The callback function will run when the player has finished loading the sample into its buffer.
We also have to connect our sampler to the output. The Tone.js way of doing this is:
sampler.toMaster();
… where sampler is a Tone.Player object, after line 10. The toMaster function is shorthand for connect(Tone.Master).
If you open your web browser with the developer console open, you should see the “samples loaded” message, indicating that the player was created correctly. At this point you may want to hear the sample. To do that, we need to add a button to the webpage, and program it to play the sample once pressed. We are going to use a NexusUI button in the body:
<canvas nx="button"></canvas>
You should now see a rounded button being rendered in the document. To program it to play our sample, we add a NexusUI listener, which looks like this:
button1.on('*',function(data) {
    console.log("button pressed!");
})
Something outstanding about NexusUI is that it creates a global variable for each NexusUI element. You can set NexusUI to not do that, and instead have these variables only in nx.widgets[] by setting nx.globalWidgets to false. Here we are going to create just a couple of elements, so we’ll just stick to this behaviour.
Same as in jQuery, we can put these .on events, and the first argument will be the event name. Here we are just assigning a function to whatever is done to the button. This whatever is written as “*”. You can learn more about events for each element in the NexusUI API. To play the sample instead of logging messages when we press the button, we should run the start function of our sampler.
nx.onload = function() {
    button1.on('*',function(data) {
    console.log("button pressed!");
        sampler.start();
    });
}
Also notice that the listener goes inside an onload callback. NexusUI elements are drawn in canvas, and you can’t refer to them until nx calls the onload function. Just as you would do with DOM elements in jQuery.
The event is triggered on mouse down and on release. If you want it to be triggered only on press, you have to evaluate whether event.press equals one.
With this, you should have a button that plays the sample on each press. If you set sampler.retrigger to true, it will allow you to play the sample regardless of whether it is playing or not. Otherwise, you have to wait until the sample finishes to retrigger it.

Applying Effects

With Tone.js, we can easily create a delay:
var delay= new Tone.FeedbackDelay("16n",0.5).toMaster();
The first argument is the delay time, which can be written in musical notation as shown here. The second is the wet level, which means the mixture between the original sound and the sound that has an effect on it. For delays you don’t usually want a 100% wet, because delays are interesting with respect to the original sound, and the wet alone is not very appealing as both together.
The next step is to unplug our sampler from master and plug it instead to the delay. Tweak the line where sampler is connected to master:
sampler.connect(delay);
Now try the button again and see the difference.
Next, we will add two dials to the body of our document:
<canvas nx="dial"></canvas>
<canvas nx="dial"></canvas>
And we apply the dials’ values to the delay effect using the NexusUIlistener:
dial1.on('*',function(data) {
    delay.delayTime.value=data.value;
})
dial2.on('*',function(data) {
    delay.feedback.value=data.value;
})
The parameters that you can tweak on each event can be found in Tone.js documentations. For delay, it ishere. Now you are ready to try the example and tweak the delay parameters with the NexusUI dials. This process can be easily done with each NexusUI element, not limited only to effects. For instance, also try adding another dial, and adding its listener as follows:
dial3.on('*',function(data) {
    sampler.playbackRate=data.value;
})
You can find these files at github.com/autotel/simpleSampler

Conclusion

When I went through these APIs, I started feeling overwhelmed by all the possibilities and ideas that started to come to my mind. The big difference between this implementation of audio and the traditional implementations of digital audio is not in the audio itself, but in the context. There are no new methods of making synthesis here. Rather the innovation is that audio and music making are now meeting web technologies.
I am personally involved in electronic music making, and this area has always had this paradox of the ambiguity between actually performing music and just pressing play to a recorded track. If you want to really make live electronic music, you must be able to create your own performative tools or “music-making robots” for live improvisation. But if the performance of electronic music becomes simply tweaking parameters in pre-prepared music making algorithms, then the audience can also be involved in this process. I have been working on little experiments regarding this integration of web and audio for crowdsourced music, and perhaps soon we will be attending parties where the music comes from the audience through their smartphones. After all, it’s not that different from rhythmic jams we might have enjoyed in the cave ages.
This article was written by JOAQUÍN ALDUNATE, a Toptal HTML developer.

Tuesday, June 14, 2016

Write Code to Rewrite Your Code: jscodeshift

Codemods with jscodeshift

How many times have you used the find-and-replace functionality across a directory to make changes to JavaScript source files? If you’re good, you’ve gotten fancy and used regular expressions with capturing groups, because it’s worth the effort if your code base is sizable. Regex has limits, though. For non-trivial changes you need a developer who understands the code in context and is also willing to take on the long, tedious, and error-prone process.
This is where “codemods” come in.
Codemods are scripts used to rewrite other scripts. Think of them as a find and replace functionality that can read and write code. You can use them to update source code to fit a team’s coding conventions, make widespread changes when an API is modified, or even auto-fix existing code when your public package makes a breaking change.
The jscodeshift toolkit is great for working with codemods.Think of codemods as a scripted find and replace functionality that can read and write code.

In this article, we’re going to explore a toolkit for codemods called “jscodeshift” while creating three codemods of increasing complexity. By the end you will have broad exposure to the important aspects ofjscodeshift and will be ready to start writing your own codemods. We will go through three exercises that cover some basic, but awesome, uses of codemods, and you can view the source code for these exercises on my github project.

What Is jscodeshift?

The jscodeshift toolkit allows you to pump a bunch of source files through a transform and replace them with what comes out the other end. Inside the transform, you parse the source into an abstract syntax tree (AST), poke around to make your changes, then regenerate the source from the altered AST.
The interface that jscodeshift provides is a wrapper around recast and ast-types packages. recasthandles the conversion from source to AST and back while ast-types handles the low-level interaction with the AST nodes.

Setup

To get started, install jscodeshift globally from npm.
npm i -g jscodeshift
There are runner options you can use and an opinionated test setup that makes running a suite of tests via Jest (an open source JavaScript testing framework) really easy, but we’re going to bypass that for now in favor of simplicity:
jscodeshift -t some-transform.js input-file.js -d -p
This will run input-file.js through the transform some-transform.js and print the results without altering the file.
Before jumping in, though, it is important to understand three main object types that the jscodeshift API deals with: nodes, node-paths, and collections.

Nodes

Nodes are the basic building blocks of the AST, often referred to as “AST nodes.” These are what you see when exploring your code with AST Explorer. They are simple objects and do not provide any methods.

Node-paths

Node-paths are wrappers around an AST node provided by ast-types as a way to traverse the abstract syntax tree (AST, remember?). In isolation, nodes do not have any information about their parent or scope, so node-paths take care of that. You can access the wrapped node via the node property and there are several methods available to change the underlying node. node-paths are often referred to as just “paths.”

Collections

Collections are groups of zero or more node-paths that the jscodeshift API returns when you query the AST. They have all sorts of useful methods, some of which we will explore.
Collections contain node-paths, node-paths contain nodes, and nodes are what the AST is made of. Keep that in mind and it will be easy to understand the jscodeshift query API.
It can be tough to keep track of the differences between these objects and their respective API capabilities, so there’s a nifty tool called jscodeshift-helper that logs the object type and provides other key information.
Knowing the difference between nodes, node-paths, and collections is important.Knowing the difference between nodes, node-paths, and collections is important.

Exercise 1 - Remove Calls To console

To get our feet wet, let’s start with removing calls to all console methods in our code base. While you can do this with find and replace and a little regex, it starts to get tricky with multiline statements, template literals, and more complex calls, so it’s an ideal example to start with.
First, create two files, remove-consoles.js and remove-consoles.input.js:
//remove-consoles.js

export default (fileInfo, api) => {
};
//remove-consoles.input.js

export const sum = (a, b) => {
  console.log('calling sum with', arguments);
  return a + b;
};
  
export const multiply = (a, b) => {
  console.warn('calling multiply with',
    arguments);
  return a * b;
};

export const divide = (a, b) => {
  console.error(`calling divide with ${ arguments }`);
  return a / b;
};

export const average = (a, b) => {
  console.log('calling average with ' + arguments);
  return divide(sum(a, b), 2);
};
Here’s the command we’ll be using in the terminal to push it through jscodeshift:
jscodeshift -t remove-consoles.js remove-consoles.input.js -d -p
If everything’s set up correctly, when you run it you should see something like this.
Processing 1 files... 
Spawning 1 workers...
Running in dry mode, no files will be written! 
Sending 1 files to free worker...
All done. 
Results: 
0 errors
0 unmodified
1 skipped
0 ok
Time elapsed: 0.514seconds
OK, that was a bit anticlimactic since our transform doesn’t actually do anything yet, but at least we know it’s all working. If it doesn’t run at all, make sure you installed jscodeshift globally. If the command to run the transform is incorrect, you’ll either see an “ERROR Transform file … does not exist” message or “TypeError: path must be a string or Buffer” if the input file cannot be found. If you’ve fat-fingered something, it should be easy to spot with the very descriptive transformation errors.
Our end goal though, after a successful transform, is to see this source:
export const sum = (a, b) => {
  return a + b;
};
  
export const multiply = (a, b) => {
  return a * b;
};

export const divide = (a, b) => {
  return a / b;
};

export const average = (a, b) => {
  return divide(sum(a, b), 2);
};
To get there, we need to convert the source into an AST, find the consoles, remove them, and then convert the altered AST back into source. The first and last steps are easy, it’s just:
remove-consoles.js

export default (fileInfo, api) => {
  const j = api.jscodeshift;
  const root = j(fileInfo.source);

  return root.toSource();
};
But how do we find the consoles and remove them? Unless you have some exceptional knowledge of the Mozilla Parser API, you’ll probably need a tool to help understand what the AST looks like. For that you can use the AST Explorer. Paste the contents of remove-consoles.input.js into it and you’ll see the AST. There is a lot of data even in the simplest code, so it helps to hide location data and methods. You can toggle the visibility of properties in AST Explorer with the checkboxes above the tree.
We can see that calls to console methods are referred to as CallExpressions, so how do we find them in our transform? We use jscodeshift’s queries, remembering our earlier discussion on the differences between Collections, node-paths and nodes themselves:
//remove-consoles.js

export default (fileInfo, api) => {
  const j = api.jscodeshift;
  const root = j(fileInfo.source);

  return root.toSource();
};
The line const root = j(fileInfo.source); returns a collection of one node-path, which wraps the root AST node. We can use the collection’s find method to search for descendant nodes of a certain type, like so:
const callExpressions = root.find(j.CallExpression);
This returns another collection of node-paths containing just the nodes that are CallExpressions. At first blush, this seems like what we want, but it is too broad. We might end up running hundreds or thousands of files through our transforms, so we have to be precise to have any confidence that it will work as intended. The naive find above would not just find the console CallExpressions, it would find every CallExpression in the source, including
require('foo')
bar()
setTimeout(() => {}, 0)
To force greater specificity, we provide a second argument to .find: An object of additional parameters, each node needs to be included in the results. We can look at the AST Explorer to see that our console.* calls have the form of:
{
  "type": "CallExpression",
  "callee": {
    "type": "MemberExpression",
    "object": {
      "type": "Identifier",
      "name": "console"
    }
  }
}
With that knowledge, we know to refine our query with a specifier that will return only the type of CallExpressions we’re interested in:
const callExpressions = root.find(j.CallExpression, {
  callee: {
    type: 'MemberExpression',
    object: { type: 'Identifier', name: 'console' },
  },
});
Now that we’ve got an accurate collection of the call sites, let’s remove them from the AST. Conveniently, the collection object type has a remove method that will do just that. Our remove-consoles.js file will now look like this:
//remove-consoles.js

export default (fileInfo, api) => {
  const j = api.jscodeshift;

  const root = j(fileInfo.source)

  const callExpressions = root.find(j.CallExpression, {
      callee: {
        type: 'MemberExpression',
        object: { type: 'Identifier', name: 'console' },
      },
    }
  );

  callExpressions.remove();

  return root.toSource();
};
Now, if we run our transform from the command line using jscodeshift -t remove-consoles.js remove-consoles.input.js -d -p, we should see:
Processing 1 files... 
Spawning 1 workers...
Running in dry mode, no files will be written! 
Sending 1 files to free worker...

export const sum = (a, b) => {
  return a + b;
};
  
export const multiply = (a, b) => {
  return a * b;
};

export const divide = (a, b) => {
  return a / b;
};

export const average = (a, b) => {
  return divide(sum(a, b), 2);
};

All done. 
Results: 
0 errors
0 unmodified
0 skipped
1 ok
Time elapsed: 0.604seconds
It looks good. Now that our transform alters the underlying AST, using .toSource() generates a different string from the original. The -p option from our command displays the result, and a tally of dispositions for each file processed is shown at the bottom. Removing the -d option from our command, would replace the content of remove-consoles.input.js with the output from the transform.
Our first exercise is complete… almost. The code is bizarre looking and probably very offensive to any functional purists out there, and so to make transform code flow better, jscodeshift has made most things chainable. This allows us to rewrite our transform like so:
// remove-consoles.js

export default (fileInfo, api) => {
  const j = api.jscodeshift;

  return j(fileInfo.source)
    .find(j.CallExpression, {
        callee: {
          type: 'MemberExpression',
          object: { type: 'Identifier', name: 'console' },
        },
      }
    )
    .remove()
    .toSource();
};
Much better. To recap exercise 1, we wrapped the source, queried for a collection of node-paths, change the AST, and then regenerated that source. We’ve gotten our feet wet with a pretty simple example and touched on the most important aspects. Now, let’s do something more interesting.

Exercise 2 - Replacing Imported Method Calls

For this scenario, we’ve got a “geometry” module with a method named “circleArea” that we’ve deprecated in favor of “getCircleArea.” We could easily find and replace these with /geometry\.circleArea/g, but what if the user has imported the module and assigned it a different name? For example:
import g from 'geometry';
const area = g.circleArea(radius);
How would we know to replace g.circleArea instead of geometry.circleArea? We certainly cannot assume that all circleArea calls are the ones we’re looking for, we need some context. This is where codemods start showing their value. Let’s start by making two files, deprecated.js and deprecated.input.js.
//deprecated.js

export default (fileInfo, api) => {
  const j = api.jscodeshift;
  const root = j(fileInfo.source);

  return root.toSource();
};
deprecated.input.js

import g from 'geometry';
import otherModule from 'otherModule';

const radius = 20;
const area = g.circleArea(radius);

console.log(area === Math.pow(g.getPi(), 2) * radius);
console.log(area === otherModule.circleArea(radius));
Now run this command to run the codemod.
jscodeshift -t ./deprecated.js ./deprecated.input.js -d -p
You should see output indicating the transform ran, but has not changed anything yet.
Processing 1 files... 
Spawning 1 workers...
Running in dry mode, no files will be written! 
Sending 1 files to free worker...
All done. 
Results: 
0 errors
1 unmodified
0 skipped
0 ok
Time elapsed: 0.892seconds
We need to know what our geometry module has been imported as. Let’s look at the AST Explorer and figure out what we’re looking for. Our import takes this form.
{
  "type": "ImportDeclaration",
  "specifiers": [
    {
      "type": "ImportDefaultSpecifier",
      "local": {
        "type": "Identifier",
        "name": "g"
      }
    }
  ],
  "source": {
    "type": "Literal",
    "value": "geometry"
  }
}
We can specify an object type to find a collection of nodes like this:
const importDeclaration = root.find(j.ImportDeclaration, {
    source: {
      type: 'Literal',
      value: 'geometry',
    },
  });
This gets us the ImportDeclaration used to import “geometry”. From there, dig down to find the local name used to hold the imported module. Since this is the first time we’ve done it, let’s point out an important and confusing point when first starting.
Note: It’s important to know that root.find() returns a collection of node-paths. From there, the .get(n)method returns the node-path at index n in that collection, and to get the actual node, we use .node. The node is basically what we see in AST Explorer. Remember, the node-path is mostly information about the scope and relationships of the node, not the node itself.
// find the Identifiers
const identifierCollection = importDeclaration.find(j.Identifier);

// get the first NodePath from the Collection
const nodePath = identifierCollection.get(0);

// get the Node in the NodePath and grab its "name"
const localName = nodePath.node.name;

This allows us to figure out dynamically what our geometry module has been imported as. Next, we find the places it is being used and change them. By looking at AST Explorer, we can see that we need to find MemberExpressions that look like this:
{
  "type": "MemberExpression",
  "object": {
    "name": "geometry"
  },
  "property": {
    "name": "circleArea"
  }
}
Remember, though, that our module may have been imported with a different name, so we have to account for that by making our query look like this instead:
j.MemberExpression, {
  object: {
    name: localName,
  },
  property: {
    name: "circleArea",
  },
})
Now that we have a query, we can get a collection of all the call sites to our old method and then use the collection’s replaceWith() method to swap them out. The replaceWith() method iterates through the collection, passing each node-path to a callback function. The AST Node is then replaced with whatever Node you return from the callback.
Codemods allow you to script 'intelligent' considerations for refactoring.Again, understanding the difference between collections, node-paths and nodes is necessary for this to make sense.
Once we’re done with the replacement, we generate the source as usual. Here’s our finished transform:
//deprecated.js
export default (fileInfo, api) => {
  const j = api.jscodeshift;
  const root = j(fileInfo.source);

  // find declaration for "geometry" import
  const importDeclaration = root.find(j.ImportDeclaration, {
    source: {
      type: 'Literal',
      value: 'geometry',
    },
  });

  // get the local name for the imported module
  const localName =
    // find the Identifiers
    importDeclaration.find(j.Identifier)
    // get the first NodePath from the Collection
    .get(0)
    // get the Node in the NodePath and grab its "name"
    .node.name;

  return root.find(j.MemberExpression, {
      object: {
        name: localName,
      },
      property: {
        name: 'circleArea',
      },
    })

    .replaceWith(nodePath => {
      // get the underlying Node
      const { node } = nodePath;
      // change to our new prop
      node.property.name = 'getCircleArea';
      // replaceWith should return a Node, not a NodePath
      return node;
    })

    .toSource();
};
When we run the source through the transform, we see that the call to the deprecated method in the geometry module was changed, but the rest was left unaltered, like so:
import g from 'geometry';
import otherModule from 'otherModule';

const radius = 20;
const area = g.getCircleArea(radius);

console.log(area === Math.pow(g.getPi(), 2) * radius);
console.log(area === otherModule.circleArea(radius));

Exercise 3 - Changing A Method Signature

In the previous exercises we covered querying collections for specific types of nodes, removing nodes, and altering nodes, but what about creating altogether new nodes? That’s what we’ll tackle in this exercise.
In this scenario, we’ve got a method signature that’s gotten out of control with individual arguments as the software has grown, and so it has been decided it would be better to accept an object containing those arguments instead.
Instead of car.factory('white', 'Kia', 'Sorento', 2010, 50000, null, true);
we’d like to see
const suv = car.factory({
  color: 'white',
  make: 'Kia',
  model: 'Sorento',
  year: 2010,
  miles: 50000,
  bedliner: null,
  alarm: true,
});
Let’s start by making the transform and an input file to test with:
//signature-change.js

export default (fileInfo, api) => {
  const j = api.jscodeshift;
  const root = j(fileInfo.source);

  return root.toSource();
};
//signature-change.input.js

import car from 'car';

const suv = car.factory('white', 'Kia', 'Sorento', 2010, 50000, null, true);
const truck = car.factory('silver', 'Toyota', 'Tacoma', 2006, 100000, true, true);
Our command to run the transform will be jscodeshift -t signature-change.js signature-change.input.js -d -p and the steps we need to perform this transformation are:
  • Find the local name for the imported module
  • Find all call sites to the .factory method
  • Read all arguments being passed in
  • Replace that call with a single argument which contains an object with the original values
Using the AST Explorer and the process we used in the previous exercises, the first two steps are easy:
//signature-change.js

export default (fileInfo, api) => {
  const j = api.jscodeshift;
  const root = j(fileInfo.source);

  // find declaration for "car" import
  const importDeclaration = root.find(j.ImportDeclaration, {
    source: {
      type: 'Literal',
      value: 'car',
    },
  });

  // get the local name for the imported module
  const localName =
    importDeclaration.find(j.Identifier)
    .get(0)
    .node.name;

  // find where `.factory` is being called
  return root.find(j.CallExpression, {
      callee: {
        type: 'MemberExpression',
        object: {
          name: localName,
        },
        property: {
          name: 'factory',
        },
      }
    })
    .toSource();
};
For reading all the arguments currently being passed in, we use thereplaceWith() method on our collection of CallExpressions to swap each of the nodes. The new nodes will replace node.arguments with a new single argument, an object.
Easily swap method arguments with jscodeshift!Change method signatures with 'replacewith()' and swap out entire nodes.
Let’s give it a try with a simple object to make sure we know how this works before we use the proper values:
    .replaceWith(nodePath => {
      const { node } = nodePath;
      node.arguments = [{ foo: 'bar' }];
      return node;
    })
When we run this (jscodeshift -t signature-change.js signature-change.input.js -d -p), the transform will blow up with:
 ERR signature-change.input.js Transformation error
Error: {foo: bar} does not match type Printable
Turns out we can’t just jam plain objects into our AST nodes. Instead, we need to use builders to create proper nodes.
Node Builders
Builders allow us to create new nodes properly; they are provided by ast-types and surfaced through jscodeshift. They rigidly check that the different types of nodes are created correctly, which can be frustrating when you’re hacking away on a roll, but ultimately, this is a good thing. To understand how to use builders, there are two things you should keep in mind:
All of the available AST node types are defined in the deffolder of the ast-types github project, mostly in core.js There are builders for all the AST node types, but they use camel-cased version of the node type, notpascal-case. (This isn’t explicitly stated, but you can see this is the case in the ast-types source
If we use AST Explorer with an example of what we want the result to be, we can piece this together pretty easily. In our case, we want the new single argument to be an ObjectExpression with a bunch of properties. Looking at the type definitions mentioned above, we can see what this entails:
def("ObjectExpression")
    .bases("Expression")
    .build("properties")
    .field("properties", [def("Property")]);

def("Property")
    .bases("Node")
    .build("kind", "key", "value")
    .field("kind", or("init", "get", "set"))
    .field("key", or(def("Literal"), def("Identifier")))
    .field("value", def("Expression"));
So, the code to build an AST node for { foo: ‘bar’ } would look like:
j.objectExpression([
  j.property(
    'init',
    j.identifier('foo'),
    j.literal('bar')
  )  
]);
Take that code and plug it into our transform like so:
.replaceWith(nodePath => {
      const { node } = nodePath;
      const object = j.objectExpression([
        j.property(
          'init',
          j.identifier('foo'),
          j.literal('bar')
        )
      ]);
      node.arguments = [object];
      return node;
    })
Running this gets us the result:
import car from 'car';

const suv = car.factory({
  foo: "bar"
});
const truck = car.factory({
  foo: "bar"
});
Now that we know how to create a proper AST node, it’s easy to loop through the old arguments and generate a new object to use, instead. Here’s what our signature-change.js file looks like now:
//signature-change.js

export default (fileInfo, api) => {
  const j = api.jscodeshift;
  const root = j(fileInfo.source);

  // find declaration for "car" import
  const importDeclaration = root.find(j.ImportDeclaration, {
    source: {
      type: 'Literal',
      value: 'car',
    },
  });

  // get the local name for the imported module
  const localName =
    importDeclaration.find(j.Identifier)
    .get(0)
    .node.name;

  // current order of arguments
  const argKeys = [
    'color',
    'make',
    'model',
    'year',
    'miles',
    'bedliner',
    'alarm',
  ];

  // find where `.factory` is being called
  return root.find(j.CallExpression, {
      callee: {
        type: 'MemberExpression',
        object: {
          name: localName,
        },
        property: {
          name: 'factory',
        },
      }
    })
    .replaceWith(nodePath => {
      const { node } = nodePath;

      // use a builder to create the ObjectExpression
      const argumentsAsObject = j.objectExpression(

        // map the arguments to an Array of Property Nodes
        node.arguments.map((arg, i) =>
          j.property(
            'init',
            j.identifier(argKeys[i]),
            j.literal(arg.value)
          )
        )
      );

      // replace the arguments with our new ObjectExpression
      node.arguments = [argumentsAsObject];

      return node;
    })

    // specify print options for recast
    .toSource({ quote: 'single', trailingComma: true });
};
Run the transform (jscodeshift -t signature-change.js signature-change.input.js -d -p) and we’ll see the signatures have been updated as expected:
import car from 'car';

const suv = car.factory({
  color: 'white',
  make: 'Kia',
  model: 'Sorento',
  year: 2010,
  miles: 50000,
  bedliner: null,
  alarm: true,
});
const truck = car.factory({
  color: 'silver',
  make: 'Toyota',
  model: 'Tacoma',
  year: 2006,
  miles: 100000,
  bedliner: true,
  alarm: true,
});

Codemods With jscodeshift Recap

It took a little time and effort to get to this point, but the benefits are huge when faced with mass refactoring. Distributing groups of files to different processes and running them in parallel is something jscodeshift excels at, allowing you to run complex transformations across a huge codebase in seconds. As you become more proficient with codemods, you’ll start repurposing existing scripts (such as the react-codemod github repository or writing your own for all sorts of tasks, and that will make you, your team, and your package-users more efficient.
This article was written by Jeremy Greer, a Toptal JavaScript developer.

Navigating Portfolio Management Services (PMS): Evaluation, Strategy, and Return Dynamics.

  What is Portfolio Management Services (PMS)? Portfolio Management Services (PMS) are customized, professional investment solutions designe...