Skip to main content

knitwork vs magicast vs recast: JavaScript Code Generation and Modification (2026)

·PkgPulse Team

TL;DR

knitwork is the UnJS code string generation utility — simple functions to generate JavaScript code strings (ESM imports/exports, JSON, raw code), no AST needed, used by Nuxt for code generation. magicast is the UnJS code modification tool — programmatically modify JavaScript/TypeScript files while preserving formatting, uses recast under the hood, designed for config file modification. recast is the AST-preserving code transformer — parse JavaScript into an AST, modify it, and print it back preserving original formatting, used by jscodeshift and many codemods. In 2026: knitwork for simple code string generation, magicast for config file modification, recast for complex AST-level code transforms.

Key Takeaways

  • knitwork: ~2M weekly downloads — UnJS, string-based code gen, ESM/JSON helpers
  • magicast: ~5M weekly downloads — UnJS, modify JS files preserving formatting, config files
  • recast: ~15M weekly downloads — AST parser/printer, preserves formatting, codemods
  • knitwork generates code strings (no parsing); magicast and recast modify existing code
  • magicast provides a higher-level API than recast for common operations
  • recast is the most powerful for complex AST transformations

knitwork

knitwork — code string generation:

ESM code generation

import {
  genImport, genExport, genDynamicImport,
  genObjectFromRaw, genArrayFromRaw,
  genString, genSafeVariableName,
} from "knitwork"

// Generate import statements:
genImport("express", "express")
// → import express from "express"

genImport("react", ["useState", "useEffect"])
// → import { useState, useEffect } from "react"

genImport("vue", { name: "ref", as: "vueRef" })
// → import { ref as vueRef } from "vue"

genImport("./styles.css")
// → import "./styles.css"

// Generate export statements:
genExport("./utils", ["formatDate", "parseURL"])
// → export { formatDate, parseURL } from "./utils"

genExport("express", "default")
// → export { default } from "express"

// Dynamic imports:
genDynamicImport("./heavy-module.js")
// → import("./heavy-module.js")

Object and array generation

import { genObjectFromRaw, genArrayFromRaw } from "knitwork"

// Generate object (values are raw code, not strings):
genObjectFromRaw({
  name: '"pkgpulse"',
  version: '"1.0.0"',
  port: "3000",
  isDev: "process.env.NODE_ENV === 'development'",
  handler: "() => console.log('ready')",
})
// → {
// →   name: "pkgpulse",
// →   version: "1.0.0",
// →   port: 3000,
// →   isDev: process.env.NODE_ENV === 'development',
// →   handler: () => console.log('ready')
// → }

// Generate array:
genArrayFromRaw(['"a"', '"b"', "myVar", "fn()"])
// → ["a", "b", myVar, fn()]

How Nuxt uses knitwork

import { genImport, genExport, genObjectFromRaw } from "knitwork"

// Nuxt generates virtual modules at build time:
function generatePluginsModule(plugins: string[]) {
  const imports = plugins.map((p, i) =>
    genImport(p, `plugin_${i}`)
  ).join("\n")

  const exports = genExport(undefined, {
    default: genArrayFromRaw(
      plugins.map((_, i) => `plugin_${i}`)
    ),
  })

  return `${imports}\n\n${exports}`
}

// Output:
// import plugin_0 from "~/plugins/auth"
// import plugin_1 from "~/plugins/analytics"
//
// export default [plugin_0, plugin_1]

Safe variable names

import { genSafeVariableName, genString } from "knitwork"

// Convert any string to a safe JS variable name:
genSafeVariableName("my-package")    // → "myPackage"
genSafeVariableName("@scope/pkg")    // → "scopePkg"
genSafeVariableName("123-invalid")   // → "_123Invalid"
genSafeVariableName("hello world")   // → "helloWorld"

// Safely generate a string literal:
genString('He said "hello"')
// → "He said \"hello\""

genString("Line 1\nLine 2")
// → "Line 1\\nLine 2"

magicast

magicast — code modification:

Modify config files

import { parseModule, generateCode } from "magicast"

// Parse existing config file:
const mod = parseModule(`
  export default {
    server: {
      port: 3000,
      host: "localhost",
    },
    plugins: ["react"],
  }
`)

// Modify values:
mod.exports.default.server.port = 8080
mod.exports.default.server.host = "0.0.0.0"

// Add new properties:
mod.exports.default.server.https = true

// Modify arrays:
mod.exports.default.plugins.push("vue")

// Generate updated code (preserves formatting!):
const { code } = generateCode(mod)
console.log(code)
// → export default {
// →   server: {
// →     port: 8080,
// →     host: "0.0.0.0",
// →     https: true,
// →   },
// →   plugins: ["react", "vue"],
// → }

Add imports

import { parseModule, generateCode } from "magicast"
import { addVitePlugin } from "magicast/helpers"

const mod = parseModule(`
  import { defineConfig } from "vite"
  import react from "@vitejs/plugin-react"

  export default defineConfig({
    plugins: [react()],
  })
`)

// Add a new Vite plugin:
addVitePlugin(mod, {
  from: "vite-plugin-pwa",
  imported: "VitePWA",
  constructor: "VitePWA",
  options: {
    registerType: "autoUpdate",
  },
})

const { code } = generateCode(mod)
// → import { defineConfig } from "vite"
// → import react from "@vitejs/plugin-react"
// → import { VitePWA } from "vite-plugin-pwa"
// →
// → export default defineConfig({
// →   plugins: [react(), VitePWA({ registerType: "autoUpdate" })],
// → })

Nuxt config modification

import { parseModule, generateCode } from "magicast"
import { addNuxtModule } from "magicast/helpers"

const mod = parseModule(`
  export default defineNuxtConfig({
    modules: ["@nuxt/content"],
  })
`)

// Add a Nuxt module:
addNuxtModule(mod, "@nuxt/image")
addNuxtModule(mod, "@pinia/nuxt")

const { code } = generateCode(mod)
// → export default defineNuxtConfig({
// →   modules: ["@nuxt/content", "@nuxt/image", "@pinia/nuxt"],
// → })

Modify package.json scripts

import { parseModule, generateCode } from "magicast"

// magicast can also handle JSON-like structures:
const mod = parseModule(`
  export default {
    scripts: {
      dev: "vite",
      build: "vite build",
    },
  }
`)

// Add a new script:
mod.exports.default.scripts.test = "vitest"
mod.exports.default.scripts.lint = "eslint ."

const { code } = generateCode(mod)

recast

recast — AST-preserving transformer:

Basic parsing and printing

import * as recast from "recast"

const code = `
// Important comment
const greeting = "Hello"
function sayHello(name) {
  return greeting + ", " + name + "!"
}
`

const ast = recast.parse(code)

// Print back — preserves original formatting:
const output = recast.print(ast)
console.log(output.code)
// → Identical to input (comments, whitespace preserved)

AST manipulation

import * as recast from "recast"
const b = recast.types.builders

const code = 'const x = 1 + 2'
const ast = recast.parse(code)

// Find and replace nodes:
recast.visit(ast, {
  visitBinaryExpression(path) {
    const { left, right, operator } = path.node
    if (
      operator === "+" &&
      left.type === "Literal" &&
      right.type === "Literal"
    ) {
      // Replace 1 + 2 with 3:
      path.replace(b.literal(left.value + right.value))
    }
    return false
  },
})

console.log(recast.print(ast).code)
// → const x = 3

Add imports

import * as recast from "recast"
const b = recast.types.builders

const code = `
import express from "express"

const app = express()
`

const ast = recast.parse(code)

// Create new import: import cors from "cors"
const newImport = b.importDeclaration(
  [b.importDefaultSpecifier(b.identifier("cors"))],
  b.literal("cors")
)

// Insert after first import:
ast.program.body.splice(1, 0, newImport)

console.log(recast.print(ast).code)
// → import express from "express"
// → import cors from "cors"
// →
// → const app = express()

Transform with TypeScript

import * as recast from "recast"
import * as tsParser from "recast/parsers/typescript"

const code = `
interface User {
  name: string
  email: string
}

function greet(user: User): string {
  return "Hello, " + user.name
}
`

// Parse with TypeScript support:
const ast = recast.parse(code, { parser: tsParser })

// Find all interface declarations:
recast.visit(ast, {
  visitTSInterfaceDeclaration(path) {
    console.log("Found interface:", path.node.id.name)
    // → "Found interface: User"
    this.traverse(path)
  },
})

// Modify and print (formatting preserved):
const output = recast.print(ast)

Building codemods

import * as recast from "recast"
const b = recast.types.builders

// Codemod: Convert var to const/let
function varToConstLet(code: string): string {
  const ast = recast.parse(code)

  recast.visit(ast, {
    visitVariableDeclaration(path) {
      if (path.node.kind === "var") {
        // Check if variable is reassigned:
        const name = path.node.declarations[0].id.name
        const isReassigned = hasReassignment(path.scope, name)
        path.node.kind = isReassigned ? "let" : "const"
      }
      this.traverse(path)
    },
  })

  return recast.print(ast).code
}

// Input:  var x = 1; var y = 2; y = 3;
// Output: const x = 1; let y = 2; y = 3;

Feature Comparison

Featureknitworkmagicastrecast
PurposeGenerate code stringsModify existing codeAST code transforms
ApproachString concatenationHigh-level proxy APIAST manipulation
Parsing needed✅ (built-in)
Preserves formattingN/A
TypeScript supportN/A✅ (with parser)
Config file helpers✅ (Vite, Nuxt)
Import generation✅ (AST builders)
Complex transforms
Learning curveLowLowHigh
Used byNuxt codegenNuxt CLI, module toolsjscodeshift, codemods
Weekly downloads~2M~5M~15M

When to Use Each

Use knitwork if:

  • Generating code strings from scratch (virtual modules, templates)
  • Need ESM import/export generation helpers
  • Building a framework that generates code files
  • Want the simplest approach (no AST, just strings)

Use magicast if:

  • Modifying config files programmatically (vite.config, nuxt.config)
  • Adding plugins, modules, or options to existing configs
  • Need to preserve original formatting and comments
  • Building CLI tools that modify user's config files

Use recast if:

  • Building codemods (automated code migrations)
  • Need complex AST-level transformations
  • Want to visit/modify specific node types
  • Building code analysis or transformation tools

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on knitwork v1.x, magicast v0.3.x, and recast v0.23.x.

Compare code generation and AST tooling on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.