@effect/printer

中文文档
0.51.0发布于 2026/07/30MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 100.1万

An easy to use, extensible pretty-printer for rendering documents

原始使用文档
文档目录13 节
  1. Installation
  2. @effect/printer
  3. Overview
  4. Terminal Output and Untrusted Text
  5. Simple Example
  6. General Workflow
  7. How the Layout Works
  8. Available Width
  9. Grouping
  10. Things the Pretty Printer Cannot Do
  11. Helpful Tips
  12. Which kind of annotation should I use?
  13. Acknowledgements

Pretty Printer for Effect-TS

Installation

@effect/printer

npm install @effect/printer
pnpm install @effect/printer
yarn add @effect/printer
deno add npm:@effect/printer
bun add @effect/printer

Overview

This module defines a pretty printer to format text in a flexible and convenient way. The idea is to combine a Document out of many small components, then using a layouter to convert it to an easily renderable DocStream, which can then be rendered to a variety of formats.

Terminal Output and Untrusted Text

Leaf constructors preserve control characters verbatim. Rendering returns a string and does not make untrusted text safe for a terminal. Before writing attacker-controlled text to a terminal, apply Doc.sanitize at that input or subtree boundary. Use the existing raw constructors and renderer only when embedded terminal sequences are trusted and intentional.

The document consists of several parts:

  1. Just below is some general information about the library
  2. The actual library with extensive documentation and examples

Simple Example

As a simple demonstration, let's use @effect/printer to pretty-print the following simple Haskell type definition.

example :: Int -> Bool -> Char -> IO ()

First, let's setup the imports we need:

import * as Doc from "@effect/printer/Doc"
import * as Array from "effect/Array"
import { pipe } from "effect/Function"

Next, we intersperse the "->" character between our types and add a leading "::" character:

const prettyTypes = (types: ReadonlyArray<string>): Doc.Doc<never> => {
  const symbolDocuments = pipe(
    Array.makeBy(types.length - 1, () => Doc.text("->")),
    Array.prepend(Doc.text("::"))
  )
  const typeDocuments = types.map(Doc.text)
  const documents = pipe(
    Array.zipWith(
      symbolDocuments,
      typeDocuments,
      (left, right) => Doc.catWithSpace(left, right)
    )
  )
  return Doc.align(Doc.seps(documents))
}

The seps function is one way of concatenating documents, but there are many others (e.g. vsep, cat and fillSep, etc.). In our example, seps is used to space-separate all documents if there is space remaining in the current line, and newlines if the remaining line is too short.

Next, we prepend the name to the type,

const prettyDeclaration = (
  name: string,
  types: ReadonlyArray<string>
): Doc.Doc<never> => Doc.catWithSpace(Doc.text(name), prettyTypes(types))

Now we can define a document that contains some type signature:

const name = "example"
const types = ["Int", "Bool", "Char", "IO ()"]
const doc: Doc.Doc<never> = prettyDeclaration(name, types)

This document can now be printed! And as a bonus, it automatically adapts to available space.

If the page is wide enough (80 characters in this case), the definitions are space-separated.

const rendered = Doc.render(doc, { style: 'pretty' })
console.log(rendered)
// example :: Int -> Bool -> Char -> IO ()

If we narrow the page width to only 20 characters, the same document renders vertically aligned:

const rendered = Doc.render(doc, { style: 'pretty', options: { lineWidth: 20 } })
console.log(rendered)
// example :: Int
//         -> Bool
//         -> Char
//         -> IO ()

Speaking of alignment, had we not used the align combinators, the "->" would be at the beginning of each line, and not beneath the "::".

General Workflow

╔══════════╗
║          ║                         ╭────────────────────╮
║          ║                         │ vsep, pretty, <+>, │
║          ║                         │ nest, align, …     │
║          ║                         ╰─────────┬──────────╯
║          ║                                   │
║  Create  ║                                   │
║          ║                                   │
║          ║                                   ▽
║          ║                         ╭───────────────────╮
║          ║                         │        Doc        │
╠══════════╣                         │  (rich document)  │
║          ║                         ╰─────────┬─────────╯
║          ║                                   │
║          ║                                   │ Layout algorithms
║  Layout  ║                                   │ e.g. Layout.pretty
║          ║                                   ▽
║          ║                         ╭───────────────────╮
║          ║                         │     DocStream     │
╠══════════╣                         │ (simple document) │
║          ║                         ╰─────────┬─────────╯
║          ║                                   │
║          ║                                   ├─────────────────────────────╮
║          ║                                   │                             │ treeForm
║          ║                                   │                             ▽
║          ║                                   │                     ╭───────────────╮
║          ║                                   │                     │    DocTree    │
║  Render  ║                                   │                     ╰───────┬───────╯
║          ║                                   │                             │
║          ║               ╭───────────────────┼─────────────────╮  ╭────────┴────────╮
║          ║               │                   │                 │  │                 │
║          ║               ▽                   ▽                 ▽  ▽                 ▽
║          ║       ╭───────────────╮   ╭───────────────╮   ╭───────────────╮   ╭───────────────╮
║          ║       │ ANSI terminal │   │  Plain Text   │   │ other/custom  │   │     HTML      │
║          ║       ╰───────────────╯   ╰───────────────╯   ╰───────────────╯   ╰───────────────╯
║          ║
╚══════════╝

How the Layout Works

There are two key concepts to laying a document out: the available width, and grouping.

Available Width

The layout algorithm will try to avoid exceeding the maximum width of the Document by inserting line breaks where possible. The available layout combinators make it fairly straightforward to specify where, and under what circumstances, such a line break may be inserted by the layout algorithm (for example via the seps function).

There is also the concept of ribbon width. The ribbon is the part of a line that is printed (i.e. the line length without the leading indentation). The layout algorithms take a ribbon fraction argument, which specifies how much of a line should be filled before trying to break it up. A ribbon width of 0.5 in a document of width 80 will result in the layout algorithm trying to avoid exceeding 0.5 * 80 = 40 (ignoring current indentation depth).

Grouping

A document can be grouped, which tells the layout algorithm that it should attempt to collapse it to a single line. If the result does not fit within the constraints (given by page and ribbon widths), the document is rendered unaltered. This allows fallback renderings, so that we get nice results even when the original document would exceed the layout constraints.

Things the Pretty Printer Cannot Do

Due to how the Wadler/Leijen algorithm is designed, a couple of things are unsupported right now, with a high possibility of having no sensible implementation without significantly changing the layout algorithm. In particular, this includes:

  • Leading symbols instead of just spaces for indentation (as used by the Linux tree tool, for example)
  • Multi-column layouts, in particular tables with multiple cells of equal width adjacent to each other

Helpful Tips

Which kind of annotation should I use?

TL;DR - Use semantic annotations for Doc, and after laying out the `Doc, only then map to backend-specific annotations.

For example, suppose you want to pretty-print some programming language code. If you want keywords to be red, you should annotate the Doc with a type that has a Keyword field (without any notion of color), and then after laying out the document, convert the annotations to map Keyword to Red (using DocStream.reAnnotate). The alternative (which is not recommended) is directly annotating the Doc with Red.

While both versions would work equally well, and would create identical output, the recommended way has two significant advantages: modularity and extensibility.

Modularity: Changing the color of Keywords after laying out a Document means that there is only one modification needed (namely the call to DocStream.reAnnotate) should the color of Keywords need to be changed in the future. If you have directly annotated Documents with the color Red, a full text search/replacement would be required should the color need to be changed.

Extensibility: Adding a different rendering of a Keyword in the recommended version is as simple as adding a variant of DocStream.reAnnotate to convert the Doc annotation to something else. On the other hand, let's assume you have Red as an annotation in the Doc and the backend you would like to implement does not have the notion of color (think of plain text or a website where red doesn’t work well with the rest of the style). You now need to worry what to map redness to, which in this case has no canonical answer. Should it be omitted? What does red mean in the context of the new backend? Additionally, consider the case where keywords and variables have already been annotated as red, but you want to change only the color of variables.

Acknowledgements

This package is a port of https://github.com/quchen/prettyprinter

版本与发布记录

@effect/printer 共发布 305 个版本,首次发布于 2022/05/27;过去 12 个月发布了 4 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.51.0latest2026/07/303 个月前
0.50.02026/07/134 个月前
0.49.02026/03/207 个月前
0.48.02026/03/177 个月前
0.47.02025/11/041 年前
0.46.02025/09/301.1 年前

其他发布通道:snapshot → 0.0.0-snapshot-6ebc752baf28354006ca2a0ae783a5bccf5de9ad。

关于 @effect/printer 的常见问题

@effect/printer 是什么?

An easy to use, extensible pretty-printer for rendering documents

如何安装 @effect/printer?

在项目目录运行 npm install @effect/printer,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:effect(^3.22.1)、@effect/typeclass(^0.41.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@effect/printer 支持 TypeScript 吗?

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

@effect/printer 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@effect/printer 可以商用吗?

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

@effect/printer 的下载量有多少?

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

@effect/printer 还在维护吗?

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