Today’s JavaScript language standards iterate very quickly, which is a good thing, but it also accelerates code corruption in many older front-end projects. I believe those of you who have taken over the old project with a history can relate to this. So can we automate and painlessly migrate old code to the new standard? The answer is yes.

Here’s a step-by-step guide to automated refactoring of old code:

  • Simple processing
  • ESLint
  • Regular match
  • Codemod

Simple processing

The most basic and naive treatment of history code falls into two general categories:

  • Continue to fill holes in the historical code.
  • To write a requirement somewhere, change the code there to a new one.

Both of these approaches work, of course, but they come with their own problems: if the one based entirely on old code continues to fill holes, maintainability will decline as the specification progresses; If you change as you encounter it, you will probably encounter the following problem:

  • Some parts of the code repository use the new syntax, some use the old syntax, and such inconsistencies can cause some confusion.
  • In commit history, changes that change old code are mixed with true functional changes, increasing the cost of code review.

Therefore, the practice of tinkering with code style while developing new features is hardly optimal on medium – to large-scale projects. We need more batch, automated ways to update our code base. Let’s start with an off-the-shelf ESLint:

ESLint

For new front-end projects, ESLint is almost standard for basic quality assurance. Instead of going over how to install and use this tool, I’ll briefly share its role in updating the code style of older projects.

As long as you have ESLint installed in your old project, you can easily use it with the general NPX command. This gives us the opportunity to automate the adjustment of old code from non-uniform styles to consistent styles:

npx eslint --fix
Copy the code

This isn’t a complicated operation, but ESLint’s constraints on code style can help keep the code base stable in future changes. It is therefore highly recommended to use ESLint as a base aid when making bulk changes mentioned below.

Regular match

Those familiar with ESLint should know that the normal PRESET rule does not restrict us to using the ES5 or ES6 style. So the off-the-shelf ESLint –fix doesn’t come in handy directly when migrating a code base from ES5 to ES6, and some other means are needed. Let’s start with regular matching.

The simplest re matching is actually string substitution. Don’t underestimate string substitution, which is easiest to use in some cases. For example, when migrating from ES5 to ES6 in Vue, there is a common way to migrate:

// old
methods: {
  foo: function () { / *... * /}}// new
methods: {
  foo() { / *... * /}}Copy the code

At this point we can automatically update the code style in an editor like VSCode by replacing: function (with (in full. But for slightly more complex rules, simple string matching is clearly not enough. At this point, we can write Node scripts to change the batch processing code files for optimization. For example, we recently implemented an optimization like this for LoDash loading on demand:

// old
import lodash from 'lodash'
lodash.merge(/ *... * /)

// new
import { merge } from 'lodash'
merge(/ *... * /)
Copy the code

At first glance, such an update requires changing the syntax of import and function call statements in the syntax tree, which seems like a difficult optimization to automate. In practice, however, it is not difficult to implement an algorithm based on regex, as long as we notice that the function calls we need to match are of the form lodash.xxx:

  1. Use all of the codelodash.xxxDeclare a match.
  2. Using the matchedmerge / cloneAnd so on, replace itimportStatements.
  3. Will the originallodash.xxxReplace.

This allows us to automate code style updates. As an example, the Node script that implements the above steps is actually not very long:

const glob = require('glob')
const fs = require('fs-extra')

const filePath = process.argv[2]

// Usage
// node index.js .. /foo/\*.js
glob(filePath, (err, files) => {
  if (err) throw err

  files.forEach(file= > {
    fs.readFile(file, 'utf8').then(code= > {
      const re = /lodash\.(.) +? \(/g
      const matchResults = code.match(re)

      if(! matchResults)return
      console.log('mod', file)

      const methodNames = matchResults.map(
        result= > result.replace('lodash.'.' ').replace('('.' '))const filteredNames = Array.from(new Set(methodNames))
      let modCode = code.replace(
        `import lodash from 'lodash'`.`import { ${filteredNames.join(', ')} } from 'lodash'`
      )
      matchResults.forEach((result, i) = > {
        // eslint-disable-next-line
        const re = result.replace('. '.'\ \.).replace('('.'\ \ (')
        modCode = modCode.replace(new RegExp(re, 'g'), methodNames[i] + '(')
      })
      fs.writeFile(file, modCode, 'utf8')})})})Copy the code

With just one key line /lodash\.(.) +? \(/g regular, we can use familiar tools to change the code style in batches, doesn’t that sound a bit high? You might question the practical effect of this fiddling with less important code styles. In terms of performance optimization, the above script automatically reconstructs more than 200 calls to LoDash when applied to our editor code repository. After refactoring, the volume of loDash in uncompressed code was reduced from 526K to 162K (for comparison, the uncompressed volume of the Vue Runtime is 204K) with the optimized babel-plugin-import module loading statement. That’s almost a quarter of our uncompressed volume. That is, we were able to reduce the package size by 25% without doing any complex refactoring to the business logic. This effect is satisfactory to us.

Of course, purely regular-based refactoring runs the risk of breaking the code structure. For example, replacing lodash.merge with merge is problematic if your scope already declares a merge function. The good news is that if you have ESLint configured, this behavior can be detected and corrected in time so that code behaves consistently without repeated smoke tests. This is why Lint takes precedence in the previous article.

Codemod

In fact, from ES5 to ES6, it’s clear that there are many stylistic shifts in syntax that are beyond the expressive power of regular expressions. For example, for var to let and const upgrades, we need to consider whether the variable scope has re-assign behavior. For example, if we want to mass-migrate function to a more concise arrow function, we need to consider whether there are references to this inside the function that might be broken… For these types of code style updates, we can introduce Codemod as a more precise tool.

Currently, the Jscodeshift tool in the JS community can help automate the style transitions mentioned above. The tool executes Codemod scripts developed based on it to automate the migration of code styles. We can use it this way:

  1. Jscodeshift is installed globally
  2. Clone Js-Codemod repository outside the repository that you want to change
  3. Perform Codemod

After the tool is installed, if we want to use the migration of var automation to let and const, simply use it as follows:

jscodeshift -t js-codemod/transforms/no-vars.js your-old-repo
Copy the code

The community also provides a number of ready-made Codemod scripts that we can help ourselves to 🙂

summary

In addition to making project code more consistent and tidy, automating code styles can reap additional performance gains. In this paper, although we have introduced a variety of enforcement of such modification, but because of the more complex, the more expressive means often means more cost of development and debugging, so we are still more recommended in actual engineering practice, according to the actual demand analysis, with off-the-shelf tools or minimum cost to solve practical engineering problems, quantifiable. Don’t worry about whitespace, indentation, and line breaks; let your tools automate them for you