@elg/tscodegen

中文文档
0.5.0发布于 2026/04/23MITTypeScriptCommonJS近 30 天下载 1.4万

TypeScript string-based code generation tool, with hashed files and manually editable sections

原始使用文档
文档目录10 节
  1. Key Features
  2. Installation
  3. Usage
  4. Sample
  5. Codegen script
  6. Output
  7. Generating non-TypeScript files
  8. Generating indentation-sensitive languages (Python, YAML, M…
  9. Supported file types by CommentSyntax
  10. Known limitations

tscodegen

CircleCI codecov Maintainability

tscodegen is a minimal TypeScript port of Facebook's Hack Codegen. It provides a fluent API that allows you to build human-readable TypeScript source files from strings of code, with optional manually editable sections and tamper detection.

Key Features

  • Manually editable sections within generated files. This behavior is nearly identical to Hack Codegen's amazing partially generated files. Unlike most other code generation tools, tscodegen (and Hack Codegen) is able to produce editable files, allowing your codegen output to be easily customized and extended without monkeypatching or subclassing. These edits will be preserved when the code is regenerated.

  • String-based code generation. We sidestep dealing with an AST (e.g. with ts-morph or the TypeScript compiler API) in favor of string manipulation. This lets you write your generated code naturally, without having to know the details of TypeScript's AST.

  • Codelock tamper detection. A hash is added to the docblock of every generated file to make it easy to detect if a file has been changed outside the generated manual sections. In the future, this can be enforced with an ESLint rule (PRs welcome!).

  • No template files. tscodegen uses template literals and TypeScript method calls, allowing you to use loops and conditionals without having to learn another templating language.

  • Auto formatting with Prettier, using your project's Prettier config. Generated code has rarely been so readable.

  • Clean, fluent, minimal API.

Installation

npm install @elg/tscodegen prettier # NPM
yarn add @elg/tscodegen prettier # Yarn

Usage

  1. import { CodeFile } from '@elg/tscodegen';
  2. Instantiate CodeFile.
  3. Call build to build the file with a CodeBuilder instance.
  4. Write the built file to disk with saveToFile.

Sample

Codegen script

new CodeFile("./file.ts")
  .build((builder) =>
    builder
      .addLine("import path from 'path';")
      .addLine("import fs from 'fs'")
      .addLine()
      .addManualSection("custom_imports", (builder) => builder)
      .addLine()
      .addBlock("class Steam extends Water", (builder) =>
        builder
          .addBlock("constructor()", (builder) =>
            builder.addLine("this.boil();"),
          )
          .addLine()
          .addBlock("boil()", (b) =>
            b.addManualSection("boil_body", (builder) =>
              builder.add("this.temp = 100;"),
            ),
          ),
      )
      .format(),
  )
  .lock()
  .saveToFile();

Output

/**
 * This file is generated with manually editable sections. Only make
 * modifications between BEGIN MANUAL SECTION and END MANUAL SECTION
 * designators.
 *
 * @generated-editable Codelock<<jF8gPj9IVq16NXBAtEzJj0rrD9HR7Q6V>>
 */

import path from "path";
import fs from "fs";

/* BEGIN MANUAL SECTION custom_imports */
/* END MANUAL SECTION */

class Steam extends Water {
  constructor() {
    this.boil();
  }

  boil() {
    /* BEGIN MANUAL SECTION boil_body */
    new Magician().magic(); // This line was retrieved from the original file.ts
    /* END MANUAL SECTION */
  }
}

Generating non-TypeScript files

By default, CodeFile emits JSDoc/C-style docblocks and manual section markers. Pass a commentSyntax option to target file formats that only support line comments (e.g. .gitattributes with # comments, shell scripts, or a .ts file where you prefer // docblocks):

export type CommentSyntax =
  | { kind: "jsdoc" } // default — JSDoc/C-style
  | { kind: "line"; prefix: string }; // e.g. "# " or "// "

Example .gitattributes generator:

new CodeFile(".gitattributes", {
  commentSyntax: { kind: "line", prefix: "# " },
})
  .build((b) =>
    b
      .addManualSection("manual", (m) => m.addLine("# add custom rules here"))
      .addLine("path/to/generated.ts linguist-generated=true"),
  )
  .lock("\nTo update this file, run: npm run generate:gitattributes\n")
  .saveToFile();

Sample output:

# This file is generated with manually editable sections. Only make
# modifications between BEGIN MANUAL SECTION and END MANUAL SECTION
# designators.
#
# To update this file, run: npm run generate:gitattributes
#
# @generated-editable Codelock<<...>>

# BEGIN MANUAL SECTION manual
# add custom rules here
# END MANUAL SECTION
path/to/generated.ts linguist-generated=true

Everything else works the same: verify() detects tampering outside the manual sections, lock() adds the hash, and saveToFile() writes the result. Manual-section keys still must be non-empty and whitespace-free.

Generating indentation-sensitive languages (Python, YAML, Makefile, …)

For languages where indentation is syntactic, use CodeBuilder.indent(amount, fn) to open a nested scope in which every emitted line (including manual-section markers and bodies) is prefixed with amount. Indent scopes compose additively: indent(" ", b => b.indent(" ", bb => …)) produces four spaces of indent.

new CodeFile("compute.py", { commentSyntax: { kind: "line", prefix: "# " } })
  .build((b) =>
    b
      .addLine("def compute(x: int) -> int:")
      .indent("    ", (fn) =>
        fn
          .addLine("result = x + 1")
          .addManualSection("postprocess", (m) => m.addLine("return result")),
      ),
  )
  .lock()
  .saveToFile();

Output:

# This file is generated with manually editable sections. Only make
# modifications between BEGIN MANUAL SECTION and END MANUAL SECTION
# designators.
#
# @generated-editable Codelock<<...>>

def compute(x: int) -> int:
    result = x + 1
    # BEGIN MANUAL SECTION postprocess
    return result
    # END MANUAL SECTION

Manual sections round-trip cleanly across regenerations: the stored body is semantic (column-0 content), and the builder reapplies the ambient indent on the way out. So a human who edits the section and adds lines at the matching indent:

    # BEGIN MANUAL SECTION postprocess
    if result > 100:
        raise ValueError(result)
    return result
    # END MANUAL SECTION

will see their edits preserved verbatim after regeneration, with no re-shifting of nested indents (the if/raise pair keeps its 4-space relative indent).

indent() accepts any string — typically spaces or "\t" — so it also covers Makefile recipes, YAML mappings, Terraform/HCL blocks, INI-like formats, or any other layout where column position matters.

Supported file types by CommentSyntax

tscodegen treats the comment prefix as an opaque string, so any language whose comments fit one of the supported shapes works out of the box. The table below lists the configurations for common target file types.

File typeCommentSyntax
TypeScript / JavaScript / TSX / JSX{ kind: "jsdoc" } (default)
Go, Rust, Swift, Kotlin, Java{ kind: "jsdoc" }
C, C++{ kind: "jsdoc" }
CSS / SCSS / LESS{ kind: "jsdoc" }
Protobuf{ kind: "jsdoc" }
TS/JS (line-comment preferred){ kind: "line", prefix: "// " }
Python, Ruby{ kind: "line", prefix: "# " }
Shell / Bash / zsh{ kind: "line", prefix: "# " }
YAML, TOML{ kind: "line", prefix: "# " }
Dockerfile, .env{ kind: "line", prefix: "# " }
Terraform / HCL, GraphQL{ kind: "line", prefix: "# " }
Makefile{ kind: "line", prefix: "# " } + tab indent
.gitattributes, .gitignore{ kind: "line", prefix: "# " }
nginx.conf, systemd unit{ kind: "line", prefix: "# " }
SQL{ kind: "line", prefix: "-- " }

The Python/YAML/Terraform/Makefile/SQL snapshots in src/__snapshots__/integration.test.ts.snap are a living catalogue of what generated output looks like for each of these.

Known limitations

  • JSON has no comment syntax, so it cannot be locked. If you need to generate JSON alongside other files, either emit a companion metadata file or use JSONC ({ kind: "line", prefix: "// " }).
  • XML / HTML / SVG / Markdown / Vue SFCs / MDX use <!-- ... --> wrapping comments, which is not expressible in CommentSyntax.
  • Shebangs must be line 1. lock() prepends its docblock, so a locked shell script's #!/usr/bin/env bash ends up below the docblock. For scripts invoked as executables, either prepend the shebang yourself after locking or invoke them via the interpreter directly.

版本与发布记录

@elg/tscodegen 共发布 6 个版本,首次发布于 2020/05/28;过去 12 个月发布了 3 个版本。

版本发布日期距今
0.5.0latest2026/04/236 个月前
0.4.02026/03/038 个月前
0.3.02026/03/038 个月前
0.2.02020/06/086 年前
0.1.12020/05/286 年前
0.1.02020/05/286 年前

关于 @elg/tscodegen 的常见问题

@elg/tscodegen 是什么?

TypeScript string-based code generation tool, with hashed files and manually editable sections

如何安装 @elg/tscodegen?

在项目目录运行 npm install @elg/tscodegen,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:prettier(3.x)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@elg/tscodegen 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@elg/tscodegen 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@elg/tscodegen 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.18.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@elg/tscodegen 可以商用吗?

@elg/tscodegen 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@elg/tscodegen 的下载量有多少?

根据 npm 官方下载统计,@elg/tscodegen 在 2026/10/14 – 2026/11/12 这 30 天内被下载 14,159 次,日均约 472 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@elg/tscodegen 还在维护吗?

最新版本 0.5.0 发布于 2026/04/23(6 个月前);至今共发布 6 个版本,其中过去 12 个月发布了 3 个;首次发布于 2020/05/28。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。