0.0.29 • Published 3 years ago

rwjs v0.0.29

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

Codemods

Purpose and Vision

This package contains codemods that automate upgrading a Redwood project.

Package Leads

  • Daniel Choudhury (@dac09)
  • Dominic Saadi (@jtoar)

Usage

Listing available codemods:

npx @redwood/codemods list v0.38.x

Applying a single one:

npx @redwood/codemods add-directives

Contributing

Note that this is a CLI—that is, it's meant to be executed with npx. This means the normal contribution flow (using rwfw) doesn't apply.

You should be familiar with jscodeshift. It's API isn't documented too well so we'll try to explain some of it here.

Like Babel and ESLint, jscodeshift is all about ASTs. The difference is that it's overwriting files. That means things that Babel doesn't care about, like spaces, styling (single quotes or double quotes, etc.), all of a sudden matter a lot. The parser jscodeshift uses, recast, knows how to preserve these details as much as possible.

Structure of this package

The root of the CLI is run from src/codemods.ts, which loads all the available codemods from the src/codemods/* folder.

Codemods are organised by version. For example, for upgrading from v0.37.x -> v0.38.x, the codemods are in the src/codemods/v0.38.x folder.

Each codemod has the following files:

  • README.md—to explain what this codemod does
  • {codemodName}.ts—this is the actual implementation of the codemod. You can export whatever you like here, and use it in the yargs handler
  • {codemodName}.yargs.ts—this is the yargs (CLI) handler that actually invokes your codemod. Each of the yargs handlers should export: command, description and handler at least. More info on how this is handled with yargs commandDir here: Yargs advanced docs
  • {codemodName}.test.ts—all jscodeshift codemods should implement a test. They're fairly simple to write. Have a look at the testing section for more details

Different types of codemods

Codemods are sometimes really simple, e.g. just normal string replace or updating a package.json. But other times we use jscodeshift to change code on a redwood project

Here are a few different examples to help you get familiarised:

  • Rename config in Redwood.toml— Simple string replace on the user's redwood.toml. No ASTs, no complications!

  • Add Directives— Download files from the RedwoodJS template because we've added new files that are needed in a user's project. No ASTs involved

  • Update GraphQL Function— A more complex example, which uses jscodeshift and ASTs to update code in a user's project

The rest of the docs will focus on the more complex cases (the third example).

A Typical Transform

A typical transform looks something like this:

// fooToBar.ts

import type { FileInfo, API } from 'jscodeshift'

export default function transform(file: FileInfo, api: API) {
  const j = api.jscodeshift

  const root = j(file.source)

  return root
    .findVariableDeclarators('foo')
    .renameTo('bar')
    .toSource()
}

You can then run this transform on files via the CLI:

yarn run jscodeshift -t fooToBar.js foo.js

In this way, jscodeshift is similar to Jest in that it's a runner.

💡 Tip

An extremely useful tool to write the actual transform is ASTExplorer. This lets you see how your codemods change input source, in real time!

The API

In the example above, file is the file it's running the transformation on and jscodeshift itself is actually a property of api. Since it's used so much, you'll see this pattern a lot:

const j = api.jscodeshift

j exposes the whole api, but it's also a function—it parses its argument into a Collection, jscodeshift's major type. It's similar to a javascript array and has many of the same methods (forEach, map, etc.). The best way to familiarze yourself with its methods is to either 1) look at a bunch of examples or 2) skim the source.

Writing a transform

When beginning to write a transform, your best bet is to start by pasting the code you want to transform into AST Explorer. Use it to figure out what node you want, and then use one of jscodeshift's find methods to find it:

import type { FileInfo, API } from 'jscodeshift'

export default function transform(file: FileInfo, api: API) {
  const j = api.jscodeshift

  const root = j(file.source)

  /**
   * This finds the line:
   *
   * ```
   * import { ... } from '@redwoodjs/router'
   * ```
   */
  return root.find(j.ImportDeclaration, {
    source: {
      type: 'Literal',
      value: '@redwoodjs/router',
      },
    })
}

Sometimes jscodeshift has a more-specific find method than find, like findVariableDeclarators. Use it when you can—it makes things a lot easier. But note that these find methods aren't on Collection. They're in the extensions:

After you find what you're looking for, you usually want to replace it with something else. Again, use AST Explorer to find out what the AST of that something else is. Then, instead of using a type (like j.ImportDeclaration) to find it, use a builder (like js.importDeclaration—it's just the type lowercased) to make it.

Again, sometimes jscodeshift has a method that makes this trivial, especially for simple operations, like renaming or removing something (just use renameTo or remove). But sometimes you'll just have to use one of the more generic methods: replaceWith, inserterBefore, insertAfter, etc.

Testing

Although JSCodeshift has a built-in way of doing testing, we have a slightly different way of testing.

There's two key test utils you need to be aware of (located in packages/codemods/testUtils/index.ts).

  1. matchTransformSnapshot—this lets you give it a transformName (i.e. the transform you're writing), and a fixtureName. The fixtures should be located in __testfixtures__, and have a {fixtureName}.input.{js,ts} and a `{fixtureName}.output.{js,ts}.

Note that the fixtureName can be anything you want, and you can have multiple fixtures.

describe('Update API Imports', () => {
  it('Updates @redwoodjs/api imports', async () => {
    await matchTransformSnapshot('updateApiImports', 'apiImports')
  })
})
  1. matchInlineTransformSnapshot—very similar to above, but use this in case you want to just provide your fixtures inline
  it('Modifies imports (inline)', async () => {
    await matchInlineTransformSnapshot(
      'updateGraphQLFunction',  // <--- transform name, so we know which transform to apply
      `import {
        createGraphQLHandler,   // <-- input source
        makeMergedSchema,
      } from '@redwoodjs/api'`,
      `import { createGraphQLHandler } from '@redwoodjs/graphql-server'` // <-- expected output
    )
  })

How to run your changes on a test redwood project

  1. Clean all your other packages, and rebuild once:
# root of framework
yarn build:clean
yarn build
  1. Build the codemods package
cd packages/codemods
yarn build
  1. Running the updated CLI

The CLI is meant to be run on a redwood project (i.e. it expects you to be cd'd into a redwood project), but you can provide it as an environment variable too!

RWJS_CWD=/path/to/rw-project node "./packages/codemods/dist/codemods.js" {your-codemod-name}
# ☝️ this is the path to your rw project (not the framework!)

💡 Tip

If you're making changes, and want to watch your source and build on changes, you can use the watch cli

# Assuming in packages/codemods/
watch -p "./src/**/*" -c "yarn build"
0.0.29

3 years ago

0.0.26

3 years ago

0.0.27

3 years ago

0.0.28

3 years ago

0.0.25

3 years ago

0.0.62

3 years ago

0.0.46

3 years ago

0.0.47

3 years ago

0.0.60

3 years ago

0.0.61

3 years ago

0.0.59

3 years ago

0.0.51

3 years ago

0.0.52

3 years ago

0.0.53

3 years ago

0.0.54

3 years ago

0.0.55

3 years ago

0.0.56

3 years ago

0.0.57

3 years ago

0.0.58

3 years ago

0.0.50

3 years ago

0.0.48

3 years ago

0.0.49

3 years ago

0.0.42

3 years ago

0.0.20

3 years ago

0.0.43

3 years ago

0.0.21

3 years ago

0.0.44

3 years ago

0.0.22

3 years ago

0.0.45

3 years ago

0.0.23

3 years ago

0.0.24

3 years ago

0.0.15

3 years ago

0.0.16

3 years ago

0.0.17

3 years ago

0.0.18

3 years ago

0.0.19

3 years ago

0.0.10

3 years ago

0.0.11

3 years ago

0.0.12

3 years ago

0.0.13

3 years ago

0.0.14

3 years ago

0.0.3

3 years ago

0.0.9

3 years ago

0.0.8

3 years ago

0.0.5

3 years ago

0.0.4

3 years ago

0.0.6

3 years ago

0.0.2

3 years ago

0.0.1

3 years ago