magicast

repository·main·Indexed 25 days ago

https://github.com/unjs/magicast

A library for programmatically modifying JavaScript and TypeScript source code using a simplified, JSON-like syntax. Magicast abstracts AST manipulation to allow reading, modifying, and writing back JS/TS files while preserving original code formatting. It includes utilities like loadFile and writeFile for filesystem operations, parseModule and generateCode for string manipulation, and a builders API for constructing AST nodes such as function calls and binary expressions.

Tokens
5.2K
Snippets
13
Records
36
Agent score
82%

What's inside magicast

  1. Handle function calls like `defineConfig()`

    main

    Magicast allows you to easily manipulate arguments passed to function calls. When a module export is a function call (e.g., export default defineConfig({ ... })), you can access the arguments via the $args property and check the $type property.

    import { parseModule, generateCode } from "magicast";
    
    const mod = parseModule(`export default defineConfig({ foo: 'bar' })`);
    
    // Support for both bare object export and `defineConfig` wrapper
    const options =
      mod.exports.default.$type === "function-call"
        ? mod.exports.default.$args[0]
        : mod.exports.default;
    
    console.log(options.foo); // bar
  2. Install Magicast

    main

    You can install Magicast using your preferred package manager. It is recommended to install it as a development dependency.

    yarn add --dev magicast
    
    npm install -D magicast
    
    pnpm add -D magicast
    npm install -D magicast
  3. Use Magicast in Browser or Worker environments

    main

    The default entry point for magicast includes Node.js filesystem utilities. To use the core AST manipulation logic in a browser or Web Worker, import from magicast/core instead.

    import { parseModule } from "magicast/core";
  4. Modify JavaScript/TypeScript files using loadFile and writeFile

    main

    Magicast provides high-level filesystem utilities to read, manipulate, and write back JS/TS files using a JSON-like syntax. This is the easiest way to programmatically update configuration files.

    1. Use loadFile(path) to get a module object.
    2. Access properties via mod.exports using familiar dot notation.
    3. Use writeFile(mod, path) to save the changes back to the file.
    import { loadFile, writeFile } from "magicast";
    
    const mod = await loadFile("config.js");
    
    // Append 'b' to the 'foo' property of the default export
    mod.exports.default.foo.push("b");
    
    await writeFile(mod, "config.js");
  5. Understand the structure of Proxified values

    main

    Magicast uses a proxy mechanism to represent Abstract Syntax Tree (AST) nodes as interactive JavaScript objects. Every proxified entity (objects, arrays, functions, etc.) inherits from ProxyBase and includes a $ast property containing the underlying ASTNode.

    To identify the type of a proxified node at runtime, check the $type property. Common $type values include:

    • object
    • array
    • identifier
    • function-call
    • new-expression
    • binaryExpression
    • module
    • imports
  6. Configure ESLint with eslint-config-unjs

    main

    To use the standard UNJS ESLint configuration in your project, import unjs from eslint-config-unjs in your eslint.config.js file. The unjs() function accepts two main arguments:

    1. Configuration Object: An object containing standard ESLint configuration properties, such as rules. This allows you to override or disable specific rules provided by the base configuration.
    2. Global Ignores Object: An object containing an ignores array to specify files or patterns that should be excluded from linting globally.

    This pattern allows you to inherit the UNJS linting standards while maintaining control over specific rule behaviors and file exclusions.

    import unjs from "eslint-config-unjs";
    
    export default unjs(
      {
        rules: {
          "no-useless-constructor": 0,
          "unicorn/empty-brace-spaces": 0,
          "unicorn/expiring-todo-comments": 0,
        },
      },
      {
        ignores: ["vendor/**/*"],
      },
    );
  7. Handle potential errors during code modification

    main

    Because JavaScript is highly dynamic, Magicast's simplified syntax cannot cover every possible edge case. Accessing properties on nodes might throw errors if the input code doesn't match the expected structure. It is highly recommended to wrap your modification logic in a try/catch block.

    import { loadFile, writeFile } from "magicast";
    
    function updateConfig() {
      try {
        const mod = await loadFile("config.js");
    
        mod.exports.default.foo.push("b");
    
        await writeFile(mod);
      } catch {
        console.error("Unable to update config.js");
        // handle error
      }
    }
  8. Create new function calls with builders

    main

    Use the builders utility to programmatically construct new AST nodes, such as function calls, and assign them to your module structure.

    import { parseModule, generateCode, builders } from "magicast";
    
    const mod = parseModule(`export default {}`);
    
    // Create a function call: create([1, 2, 3])
    const options = (mod.exports.default.list = builders.functionCall(
      "create",
      [1, 2, 3],
    ));
    
    console.log(mod.generateCode()); // export default { list: create([1, 2, 3]) }
  9. Use high-level helpers for common tasks

    main

    Magicast provides experimental high-level helpers for common configuration tasks (like adding Nuxt modules or Vite plugins). These are located in magicast/helpers.

    Note: These helpers are experimental and may move to a separate package in the future.

    import {
      deepMergeObject,
      addNuxtModule,
      addVitePlugin,
      // ...
    } from "magicast/helpers";
  10. Parse and generate code from strings

    main

    If you are working with code strings rather than files, use parseModule to create a module object and generateCode to turn it back into a string.

    Note that generateCode returns an object containing both the code and a map (for source maps).

    import { parseModule, generateCode } from "magicast";
    
    // Parse to AST
    const mod = parseModule(`export default { }`);
    
    // Manipulate using familiar syntax
    mod.exports.default.foo ||= [];
    mod.exports.default.foo.push("b");
    mod.exports.default.foo.unshift("a");
    
    // Generate code
    const { code, map } = generateCode(mod);
  11. Access the underlying AST

    main

    If Magicast's simplified syntax is insufficient for a specific transformation, you can access the raw AST node directly via the $ast property on any Magicast node.

    import { parseModule, generateCode } from "magicast";
    
    const mod = parseModule(`export default { }`);
    const ast = mod.exports.default.$ast;
    // Perform direct AST manipulation on 'ast'
  12. Load a file for AST transformation with loadFile

    main
    Use loadFile to read a file from the file system and parse its contents into a ProxifiedModule. This is the starting point for performing AST transformations on existing files. The function accepts a filename and an optional recast ParseOptions object. If options.sourceFileName is not provided, it defaults to the provided filename.