Yuku

Codegen

The codegen prints a tree back to source in one walk, so its output is always valid and faithful to the tree.

JavaScript

npm install yuku-codegen
import { generate } from "yuku-codegen";
import { parse } from "yuku-parser";

const { code, map, diagnostics } = generate(parse("const x = 1 + 2;").program);

The same printer in plain JavaScript, for any ESTree AST, with byte-identical output, source maps included. It runs 2.6× faster than @babel/generator, or 3× with source maps on. See yuku-codegen on npm for the full API.

Zig

var tree = try parser.parse(allocator, "const x: number = 1;", .{ .lang = .ts });
defer tree.deinit();

const result = try parser.codegen.generate(allocator, &tree, .{ .strip = true });
defer result.deinit(allocator);

std.debug.print("{s}\n", .{result.code}); // const x = 1;

generate leaves the tree unchanged and returns the code, its diagnostics as ast.Diagnostic, and the source map, owned by the allocator it was given and freed with result.deinit. It fails only with error.OutOfMemory.

Options

FieldDefaultDescription
stripfalseDrop TypeScript-only syntax, see Type stripping
minifyfalseSize-reducing syntax rewrites, see Minification
format.pretty.compact emits only the whitespace the grammar requires
indent2Spaces per level
quotes.preserve.preserve, .double, .single, or .shortest
comments.someWhich attached comments to print, .some for legal headers, JSDoc, and annotations, or .all, .none, .line, .block
source_mapnullEmit a Source Map V3, see Source maps

Every option is independent, so they compose freely. Comments print from the nodes they are attached to, so parse with .comments = .attached or .both to keep them.

Type stripping

strip prints TypeScript as JavaScript in the same walk, skipping the nodes and fields the parser already marked as TypeScript. Types, interfaces, generics, assertions, satisfies, and declare strip cleanly. Features that emit runtime code, such as enum, namespace, and parameter properties, are reported in diagnostics and left out, since converting them is transpilation, not stripping.

Minification

minify rewrites the syntax, and combined with .compact and .shortest it is full minification.

const result = try parser.codegen.generate(allocator, &tree, .{
    .minify = true,
    .format = .compact,
    .quotes = .shortest,
});
// const enabled = true; const size = 1000000;
// prints as const enabled=!0;const size=1e6
  • true and false become !0 and !1.
  • Numbers take their shortest form, so 1000000 becomes 1e6 and 0.5 becomes .5.
  • obj["foo"] becomes obj.foo, and { "foo": x } becomes { foo: x }.
  • </script, <!--, and --> are escaped, so the output is safe inside a <script> tag.

Source maps

const result = try parser.codegen.generate(allocator, &tree, .{
    .source_map = .{
        .source = source,           // required for a map
        .file = "out.js",
        .source_file_name = "in.js",
        .source_root = null,
        .sources_content = source,
    },
});
defer result.deinit(allocator);

const map = result.map.?; // version, file, source_root, sources, sources_content, names, mappings

Mappings come from the same walk that prints the code, with columns in UTF-16 code units, the convention of browser devtools and source map libraries.

A build that never needs maps can compile them out, for a smaller binary. map is then always null.

const yuku = b.dependency("yuku", .{ .@"codegen-source-maps" = false });