@effected/markdown

中文文档
0.29.0发布于 2026/11/10MITTypeScriptES Module构建来源已验证近 30 天下载 8942

常见马克和GFM 标记下解析、编辑和转换效果计划

中文使用文档
文档目录14 节
  1. 为什么 @ 受影响/ markdown
  2. 安装
  3. 快开始
  4. 在不重写文档的情况下编辑
  5. 前题
  6. 和树一起工作
  7. mdast 接口
  8. MDX 树
  9. 犬形结构稳定
  10. 文字文本
  11. 在没有解析器的情况下忽略标记
  12. 特征
  13. 遵约
  14. 许可证

@ 受影响/ 标记下

npm License: MIT Node.js %3E%3D24.11.0 TypeScript 7.0

CommonMark 0.31.2和GFM 解析,编辑和变换,以效果计和纯函数表示. 将标记降为带字节偏移的mdast形状节点,导航标题、分节和链接,计算外科偏移相接编辑,规范标记,将前题解码为经验证的域名图案,为表达式生态系统进行项目往返,并按文档行走 Stream。 。 。 .

预1.0.0。 。 。 . 此软件包是 @effected/* 在稳定基础上建造的工具包 效应v4(effect ~4.1.0) 并仍在 0.x 发展。 稳定效果 制作一个工具箱 1.0.0 可能,不是自动的。 为了保持你的 effect 财务报告和审定财务报表 @effect/* 该工具包在线上构建和测试、安装 @effected/pnpm-plugin-effected。 。 。 .

稳定:不稳定. 此软件包的 API 表面尚未考虑 完成并可能改变 0.x 释放 定一个确切的版本——甚至 标记软件包 稳定 在此之前 1.0.0 可以引入突破性更改 和精确的针 变成一个类型检查错误 而不是一个 跑步时间惊喜。 完全政策: 释放策略。 。 。 .

为什么 @ 受影响/ markdown

Markdown是文档,更改日志,知识库和AI代理上下文文件实际存在的地方——文件人手编辑并期望能活过一个触摸到它们的工具. 评论生态系统是 JavaScript 默认, 但它的序列器 重构设计: 读出一个文档并写回它, 你得到评论的想法 马克下,而不是你的。 生态系统中没有人会失去科技委员会, 而言论维护者自己则指向位置分层.

这个软件包将这个建议作为架构。 每个节点带字节与非站点行/列位置相抵消,所以编辑是针对原始出处的相接而来,而不是树的再串行化:改变一个标题和空白行,HTML评论和手调相接相接的相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相. MarkdownEdit 同字段为 JsoncEdit, (中文(简体) ), YamlEdit 财务报告和审定财务报表 TomlEdit,因此,相同的编辑词汇跨越了工具包中的每一种格式.

该引擎是一个销售商加硬化的普通马克.js端口——由 spec 作者维护的参考解析器——重组为每个模块在方言键入的登记册后建造. 这意味着没有运行时间解析器依赖性,也没有插件表面来解释: commonmark 财务报告和审定财务报表 gfm 是两种方言, gfm 是默认的, 两者都由上游的相容体所支撑。 解析是近乎全面的,因为CommonMark没有语法错误:被打出的错误通道只携带了硬化-护卫出行,从不"malform mark down".

节点的形状是mdast的准确类型名称和字段形状,因此树已经是说出生态系统的形状 Mdast.toMdast 剥去这个包的忠心字段, 交给你普通的 spec -valid mdast JSON。 Frontmatter——Mdast完全没有解析的故事——通过独立的每个格式的解码器,在解码器后被捕获,在不拖出三个格式引擎的情况下,将打入一个需要一个格式的捆绑器的灰色物质等同化.

标记到 HTML 是故意超出范围。 此包解析、 编辑并变换下标值; 渲染属于您已经拥有的任何渲染器, 通过 mdast 投影到达 .

安装

npm install @effected/markdown effect
pnpm add @effected/markdown effect

需要节点.js~~24.11.0. effect v4 是一个对等依赖; 软件包的一次运行依赖是无依赖的 @effected/source,其 LineIndex 它的再出口.

@effected/yaml, (中文(简体) ), @effected/toml 财务报告和审定财务报表 @effected/jsonc 为: 选项 等同位素,仅需要您导入的前置编码模块。 一个消费者解码的 yaml 前题安装 @effected/yaml 一个从未接触过前置物品的消费者.

npm install @effected/yaml

全体 @effected/* 软件包仅为无害环境管理:只公布出口地图 import 条件,所以 require() 包括解析 CJS 模式的工具 —— 与节点失败 ERR_PACKAGE_PATH_NOT_EXPORTED 而不是装入不存在的 CJS 建筑。 从ES模块导入.

快开始

MarkdownDocument.parse 以一个值给出源、树、定义索引和导航访问器:

import { MarkdownDocument } from "@effected/markdown";
import { Effect } from "effect";

const source = `# Release notes

## Fixed

- Tables no longer ~~drop~~ trailing cells.
`;

const program = Effect.gen(function* () {
  const doc = yield* MarkdownDocument.parse(source);
  return doc.headings.map((heading) => `${"#".repeat(heading.depth)} ${heading.text}`);
});

Effect.runPromise(program).then(console.log);
// [ "# Release notes", "## Fixed" ]

方言默认为 gfm,所以击出 上面的画像 delete 没有配置的节点 。 过 MarkdownParseOptions.make({ dialect: "commonmark" }) 为了严格的普通马克.

每个剖面都有一个同步的双胞胎—— MarkdownDocument.parseResult 财务报告和审定财务报表 Markdown.parseResult 返回 a Result ——所以一个构建脚本,Vite插件或语言服务器勾选可以调用,而不需要效果运行时间.

在不重写文档的情况下编辑

MarkdownFormat.modify 计算 a MarkdownEdit 与已解析的文档相对的阵列; modifyToString 应用它一步。 目标是从文档自己的树上取出一个节点,与身份相匹配,而编辑时没有覆盖的一切都活过字节:

import { MarkdownDocument, MarkdownFormat } from "@effected/markdown";
import { Effect, Option } from "effect";

const source = `# Release notes

See the [changelog](./CHANGELOG.md).
`;

const program = Effect.gen(function* () {
  const doc = yield* MarkdownDocument.parse(source);
  const label = Option.getOrThrow(doc.find("text"));
  return yield* MarkdownFormat.modifyToString(doc, label, "Release notes (2026)");
});

Effect.runPromise(program).then(console.log);
// # Release notes (2026)
//
// See the [changelog](./CHANGELOG.md).

find 在文档预排序中行走树并返回文档中自己的节点 Option —— 说 Option.none() 当没有匹配时——所以匹配的种子 modify 以身份证明 替换是字形的字符串或节点片段,两者都通过条形字符串来渲染出,所以一个被修改的文档通过构造可以清晰地分拆出——你不能在原始标记下相接来重开栅栏或打碎桌子.

MarkdownFormat.format 处理另一半: 保守的标记正常化, 绝不重写内容 。 它转换了标题样式,弹出字符,强调标记,栅栏字符,主题破解字符和代码块样式,并跳过任何不会安全而非巧妙尝试的转换:

import { MarkdownFormat, MarkdownFormattingOptions } from "@effected/markdown";

const source = `Setext heading
==============

* one
* two
`;

const options = MarkdownFormattingOptions.make({ headingStyle: "atx", bulletChar: "-" });

console.log(MarkdownFormat.formatToString(source, undefined, options));
// # Setext heading
//
// - one
// - two

console.log(MarkdownFormat.format(source, undefined, options));
// [
//   { offset: 0, length: 29, content: "# Setext heading" },
//   { offset: 31, length: 1, content: "-" },
//   { offset: 37, length: 1, content: "-" }
// ]

format 纯度和总度,编辑是非模拟数据——将其交给 MarkdownEdit.applyAll,或把它们作为文本编辑器的有效载荷发送给编辑器.

codeBlockStyle 是一个选项, 可以在您击出默认之前知道 : 没有它, 一个没有语言的代码块会保留它已有的拼写, 一个没有语言的块 Code 无节点 fenceChar 序列化为 缩进 块. "fenced" 将缩成的地块改为围栏, "indented" 以相反的方式,结果可以证明是相同的,两个方向仅适用于根级的冲出-左块。 一个有语言的区块没有缩进的拼写,从未被触碰.

前题

Frontmatter 抓取是选入, 因为启用它会改变文档打开方式 --- 剖析: 普通马克读取 ---\ntitle: x\n--- 作为主题课间休息和设定的副标题, 和光谱相符合的读取保持, 除非你要求别的东西。 打开它,并用一个编码器来编曲您的计划,用于打印出灰色物质的等同性:

import { MarkdownDocument, MarkdownFrontmatter, MarkdownParseOptions, YamlFrontmatter } from "@effected/markdown";
import { Effect, Schema } from "effect";

const source = `---
title: Release notes
draft: false
---

# Release notes
`;

const Post = Schema.Struct({ title: Schema.String, draft: Schema.Boolean });
const decodePost = MarkdownFrontmatter.schema(Post, YamlFrontmatter);

const program = Effect.gen(function* () {
  const doc = yield* MarkdownDocument.parse(source, MarkdownParseOptions.make({ frontmatter: true }));
  return yield* decodePost(doc);
});

Effect.runPromise(program).then(console.log);
// { title: "Release notes", draft: false }

每个阶段都打出并分开:没有抓取是 FrontmatterMissingError (抓取可选语义的标记),一个被解码器递错的栅栏是一个 FrontmatterFormatMismatchError,不可解析的内容是 FrontmatterDecodeError 搭载格式包自身定位的故障结构,而schema-无效数据是一个 FrontmatterValidationError 带有结构化的问题树,而不是字符串渲染.

"无前事"有三义 FrontmatterMissingError.reason 告诉他们: "absent" 当使者没有障碍的时候, "captureDisabled" 当它用一个打开,但被解析为切换——其中的修补是一个解析选项,而不是文档编辑——和 "unclosed" 当它打开一个没有关闭栅栏的栅栏时 非封闭的栅栏不是前题(语法规则,和评论前题),所以没有什么被抓住,但作者几乎肯定是指一块地. MarkdownDocument.hasFrontmatterBlock 财务报告和审定财务报表 MarkdownDocument.frontmatterFence (单位:千美元)"absent", (中文(简体) ), "unclosed" 或者说 "closed")直接从源头回答同样的问题,因此访问者和错误永远不能有分歧.

要针对整个文件报告YAML、TOML或JSON诊断,请添加解析 Frontmatter 节点 valueStart 价值开始时,在打开围栏线及其终止者之后,无论它们是什么,均抵消价值内的格式解析报告。 没有自己的栅栏算术:

import { MarkdownDocument, MarkdownParseOptions } from "@effected/markdown";
import { Yaml } from "@effected/yaml";
import { Option, Result } from "effect";

const source = "---\r\ntitle: [unclosed\r\n---\r\n# Body\r\n";
const doc = Result.getOrThrow(MarkdownDocument.parseResult(source, MarkdownParseOptions.make({ frontmatter: true })));
const node = Option.getOrThrow(doc.frontmatter);
const yaml = Yaml.parseResult(node.value);
if (Result.isFailure(yaml)) {
  const inFile = (node.valueStart ?? 0) + (yaml.failure.diagnostics[0]?.offset ?? 0);
  console.log(doc.lineIndex.positionAt(inFile)); // the diagnostic's line and character in the file
}

三个解码器.. YamlFrontmatter (单位:千美元)---, (中文(简体) ), TomlFrontmatter (单位:千美元)+++和 JsonFrontmatter (单位:千美元)---json)——是独立命名的输出,每个模块一个,刻意从不收集到命名空间物体. 命名一个解码器是它的格式引擎的拉动,所以一个JSON-frontmatter的消费者从不支付"活塞"(yaml parser).

Frontmatter 块也可以描述它们自己的计划. SchemaResolver.classify 有点 $schema 值为标记的工会 ByUrl, (中文(简体) ), ByPath, (中文(简体) ), Inline 财务报告和审定财务报表 ByName - 和 SchemaResolver.fromRegistry 决定 ByName 诸如 [email protected] 以你们的计谋盟誓, URL、路径和内入式文档作为数据载出,从未取出:这是一个纯软件包,它不执行IO.

当身体根本不应该通过普通马克引擎时——一个MDX页面,一个模板,一个需要字节精确边界的快照-hash合同—— FrontmatterSource.split 使用相同的封闭栅栏语法 在生弦上,没有别的。 总计: 它返回区块格式, 栅栏线之间的精确字节, 以及源代码中的相抵(valueStart, (中文(简体) ), valueEnd)和遗体残存(body 总是如此 source.slice(bodyOffset)),不存在可代表的结果而不是错误. fence 报告 "absent", (中文(简体) ), "unclosed" 或者说 "closed",以及 fenceFormat 打开的栅栏所声明的格式,所以未关闭 --- 与没有任何围栏是不同的. FrontmatterSource.join 序列化部件返回,复制原字节用于未修改的回转。 两个边缘正常化而不是复制:一个关闭的栅栏结束文档而无线终止者,则获得一个;而栅栏线与不匹配的终止者(例如: ---\r\n … ---\n)再用开口栅栏取出. 值和体字节在每种情况下都能够逐字存活,而且从来没有在这个级别解析过——解码会保留代码模块的业务.

和树一起工作

MarkdownDocument 从树上取出它的导航器,所以他们绝不是反对的. links 收集每个包含 URL 的节点并传递 url 与书面内容完全一致——捆绑-相亲的外壳从未正常化:

import { MarkdownDocument } from "@effected/markdown";
import { Effect } from "effect";

const source = `# Release notes

See the [changelog](./CHANGELOG.md) and the [docs](https://example.com/docs).
`;

const program = Effect.gen(function* () {
  const doc = yield* MarkdownDocument.parse(source);
  return doc.links.map((link) => link.url);
});

Effect.runPromise(program).then(console.log);
// [ "./CHANGELOG.md", "https://example.com/docs" ]

headings 列出其所坐位置的每个标题,包括块状参数和项目. headingSlugs 与它平行: GitHub 风格的锚, 使路条脱落 github-slugger 实际数(dup, dup, dup-1, dup 给 dup, dup-1, dup-1-1, dup-2),以及 MarkdownDocument.slug(text) 是一个不重复的原始人。 平等是与 github-slugger而不是与GitHub本身. sections 仅由根级标题来划分,每个区域的范围会覆盖其分节,因此,编辑层可以在一次编辑中将整个区域相接出. firstSection 财务报告和审定财务报表 sectionByHeading 找到一个 DocumentSection 不扫描 sections 自己——在精确修剪后的标题字符串上的第二个匹配,a RegExp,或上游,两者均接受 { depth } 限制在一个标题级别。 两个都回 Option,就像每一次检查 在包里做的 frontmatter 访问器和 find 电话:

import { MarkdownDocument } from "@effected/markdown";
import { Effect, Option } from "effect";

const source = `# Changelog

## 1.2.3

Fixed a bug.

## 1.2.2

Initial release.
`;

const program = Effect.gen(function* () {
  const doc = yield* MarkdownDocument.parse(source);
  return Option.match(doc.sectionByHeading("1.2.3", { depth: 2 }), {
    onNone: () => "no such release",
    onSome: (section) => section.body.trim(),
  });
});

Effect.runPromise(program).then(console.log);
// Fixed a bug.

DocumentSection.body 是没有标题的一节,它被刻意地解开——就是字节 bodyRange 描述——所以呼叫者写 .body.trim() 当他们想要干净的表格.

unresolvedFootnoteReferences 列出每个 GFM 脚注标记 - [^label] ——没有相匹配的定义,与其原始 label,其案件卷 identifier 联 合 国 position。只有在定义存在时,才使用参考形式,所以未定义的标记保留文字,与邻居合并,以及 findAll("footnoteReference") 无法看见它;剖析器在它拒绝的时候,各记录一个。 标出解析器从未读取的——在代码跨度和代码块,自动链接,HTML,背斜后,或形成链接的括号对内——不报告,节点树不变:

import { MarkdownDocument } from "@effected/markdown";
import { Result } from "effect";

const doc = Result.getOrThrow(MarkdownDocument.parseResult("See [^a] and [^b], not `[^c]`.\n\n[^a]: Defined.\n"));
console.log(doc.unresolvedFootnoteReferences.map((marker) => [marker.label, marker.position.start.offset]));
// [ [ "b", 13 ] ]

对于任何进入者没有覆盖, find 财务报告和审定财务报表 findAll 在文档预排序中将整个树行走。 匹配节点的字符串选择器 type 并缩小结果—— doc.findAll("heading") 实值 ReadonlyArray<Heading>, (中文(简体) ), doc.find("table") 实值 Option<Table> - 和类型护卫的上方 缩小相同的方式。 节点通过身份回归,所以 doc.findAll("heading")[1] 将第二个标题移至 MarkdownFormat.modify 没有生孩子索引.

MarkdownVisitor.visit 流出与行走相同 Enter编号 :Exit 带有节点、其子索引路径及其深度的事件:

import { MarkdownDocument, MarkdownVisitor } from "@effected/markdown";
import { Effect, Stream } from "effect";

const program = Effect.gen(function* () {
  const doc = yield* MarkdownDocument.parse("# Title\n\nA *b* c\n");
  const events = yield* Stream.runCollect(MarkdownVisitor.visit(doc.root));
  return Array.from(events).map((event) => `${event._tag}:${"node" in event ? event.node.type : "?"}`);
});

Effect.runPromise(program).then(console.log);
// [ "Enter:root", "Enter:heading", "Enter:text", "Exit:text", "Exit:heading",
//   "Enter:paragraph", "Enter:text", "Exit:text", "Enter:emphasis", "Enter:text",
//   "Exit:text", "Exit:emphasis", "Enter:text", "Exit:text", "Exit:paragraph", "Exit:root" ]

MarkdownVisitor.walk 是一个同步而懒惰的行走 Iterable - 原始的 - 原始的 visit 定义方式是——对于不能被暂停的代码,例如lint规则一个主机同步调用. 每个迭代都从根部重新走出 for…of 那个 break停止走动:

import { Markdown, MarkdownVisitor } from "@effected/markdown";
import { Result } from "effect";

const root = Result.getOrThrow(Markdown.parseResult("# Title\n\nA *b* c\n"));
const headings: Array<number> = [];
for (const event of MarkdownVisitor.walk(root)) {
  if (event._tag === "Enter" && event.node.type === "heading") headings.push(event.node.depth);
}
console.log(headings);
// [ 1 ]

MarkdownVisitor.traverse 开同样的车走过去 enter编号 :exit 引导它的回调: 返回 MarkdownVisitor.SKIP 从 enter 使节点的子孙不受影响 exit 仍在运行),或 MarkdownVisitor.EXIT 停下来。 每个回调也得到节点 path, (中文(简体) ), depth 财务报告和审定财务报表 ancestors。给它一个节点类型,或者一组,只有匹配的节点才能到达回调,按节点类键——行走仍然会通过其他所有方式下降:

import { Markdown, MarkdownVisitor } from "@effected/markdown";
import { Result } from "effect";

const root = Result.getOrThrow(Markdown.parseResult("# A\n\n> # B\n\n## C\n"));
const depths: Array<number> = [];
MarkdownVisitor.traverse(root, ["heading", "blockquote"], {
  enter: (node) => {
    if (node.type === "blockquote") return MarkdownVisitor.SKIP; // headings quoted inside are not visited
    depths.push(node.depth);
    return undefined;
  },
});
console.log(depths);
// [ 1, 2 ]

traverse 返回时 Option.none(),或 NestingDepthExceeded 当一棵人工建造的树筑起后, 深度会结束行走; 一棵被剖开的树永远不会.

mdast 接口

节点类已经使用 mdast 的类型名称和字段形状 ; Mdast.toMdast 剥去此套件所添加的忠义字段——弹出字符、栅栏样式、ATX-对文本拼写(只保留一个) Text 节点 escapeStyle 设定时,见 文字文本)并表明,该言论的生态系统直接消耗,包括 mdast-util-to-hast 如果你想急着:

import { Markdown, Mdast } from "@effected/markdown";
import { Result } from "effect";

const parsed = Markdown.parseResult("A *b* c\n");
if (Result.isSuccess(parsed)) {
  console.log(JSON.stringify(Mdast.toMdast(parsed.success)).slice(0, 62));
  // {"type":"root","children":[{"type":"paragraph","children":[{"t
}

Mdast.fromMdast 以另一种方式作为检查的准入边界:它验证一棵外国树,并在一个没有或不完整的地方合成零宽哨位。 树也承认了 树级的工作流程和犬科 stringify; 偏移- 相片编辑需要真实位置, 仅需要解析结果 .

MDX 树

MDX节点词汇 - MdxJsxFlowElement 财务报告和审定财务报表 MdxJsxTextElement (与 MdxJsxAttribute, (中文(简体) ), MdxJsxExpressionAttribute 财务报告和审定财务报表 MdxJsxAttributeValueExpression (d) 作为其责任人, MdxFlowExpression, (中文(简体) ), MdxTextExpression 财务报告和审定财务报表 MdxjsEsm - 形状完全与 mdast-util-mdx 和所有其他节点类一样 解析器不读取 MDX 语法; 这些节点是存在的 已合成 树可以携带 JSX 并串行到有效的 MDX 而不是加入字符串 :

import { Markdown, MdxJsxAttribute, MdxJsxAttributeValueExpression, MdxJsxFlowElement, Root } from "@effected/markdown";
import { Result } from "effect";

const page = Root.make({
  children: [
    MdxJsxFlowElement.make({
      name: "ApiSignature",
      attributes: [
        MdxJsxAttribute.make({
          name: "code",
          value: MdxJsxAttributeValueExpression.make({ value: JSON.stringify({ lang: "ts" }) }),
        }),
      ],
      children: [],
    }),
  ],
});

const mdx = Markdown.stringifyResult(page);
if (Result.isSuccess(mdx)) {
  console.log(mdx.success); // <ApiSignature code={{"lang":"ts"}} />
}

序列化匹配 mdxJsxToMarkdown默认 " 引用, 空格自闭 <a />, (中文(简体) ), <></> 碎片,流出的儿童 缩入两个空间 每个JSX祖先, {expr} 表达式、 无害环境管理值逐字—— 和包含任何 MDX 节点的树 { mDX 所需的文本中,而一个没有字节的树会按顺序将字节与下表相同。 没有MDX拼写的形状是无法构造的,而不是序列化错误:碎片不能携带属性,属性需要非空名,拒绝在 make 在解码处.

犬形结构稳定

Markdown.stringify 发出一克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克 更改其中的任何一个是此套件的破解更改而不是补丁。因此,一个测试可能对这些字节进行断言,一个需要稳定的被转换成标记的管道应该通过这里进行序列化,而不是一个第三方字符串,其默认值可以在发布之间自由移动.

默认为节点的忠义字段( O)bulletChar, (中文(简体) ), headingStyle, (中文(简体) ), markerChar, (中文(简体) ), fenceChar, (中文(简体) ), delimiter)赢了,所以解析 * a 列表或设置ext 标题以写入形式重命名。 放下他们,通过 { canonical: true } 改为 stringify 或者说 stringifyResult或称纯洁 MarkdownTransform.canonicalize(tree) 以获取已清除的树拷贝(输入从未变异)。 既给出相同的输出,又替换 Mdast.toMdast 编号 : Mdast.fromMdastResult 来回旅行.

对于一个没有忠贞场的节点:

构造条形
标题ATX (中文(简体) )## x在每一个深度
专题间断***
符号列表标记-
排序列表分隔符.,翻转到 ) 以分离一个相邻的兄弟姐妹列表
强调/强* 编号 : **
代码块当节点带有一个 lang 或一个 fenceChar,因为栅栏已经长出任何内部反向跑道;否则会缩入-除非凹入不会重新作为代码块被分割出,这迫使栅栏
区块分离完全一个空白行
文档单行后新行

MDX 节点是同一承诺的一部分: MDX 树 被同一种方式钉住,只有真正带MDX节点的树才能额外支付 { 逃脱.

可代表性胜于表. 单词形式从不发出文本,这些文本会像其他东西一样重新分解,所以一行产生两个冲突的地方. 到达消费者的病例是缩进的代码块:列表被吸收为列表内容后直接缩入的块,所以一个 Code 没有节点 lang 也没有 fenceChar 在那个位置上排出栅栏 并缩入其它地方 因此,对合成代码块的字节级断言取决于前面的兄弟.

使这成为一个非问题的立场——如果建立树木并坚持其字节,则采取的立场是: fenceChar 每页 Code 既无带节点 lang 也没有一个已经,在 后解码行走 到达巢节点。 然后选择让发射器完全离开,任何输出都不要依赖节点的邻居.

须在 已解码 树: Mdast.fromMdast 承认spec mdast 并剥去它之外的一切,所以 fenceChar 入院前被安放在一棵平地上 唯一的例外是 escapeStyle 编号 text 节点,下一个描述.

带忠贞字段的节点会覆盖相匹配行—— headingStyle, (中文(简体) ), markerChar, (中文(简体) ), fenceChar, (中文(简体) ), delimiter —— 解析出的文件如何在作者的拼写中重新序列化. 该表描述了一个被合成的节点,这是关于生成的标记降值的测试所实际坚持的.

文字文本

文本逃出默认为空 : ~0.2.1 在表格单元格中以 \~0.2.1A级 Text 带节点 escapeStyle: "literal" 选择退出——来电者保证该值已经是安全标记了,发送者逐字写出,没有以内线语法为目标的逃出. 该诉讼产生了依赖表等内容,其中: ~0.2.1, (中文(简体) ), ^1.0.0 财务报告和审定财务报表 @scope/pkg 改为:

import { Markdown, Mdast } from "@effected/markdown";
import { Result } from "effect";

const cell = (value: string) => ({ type: "tableCell", children: [{ type: "text", value, escapeStyle: "literal" }] });
const tree = Mdast.fromMdastResult({
  type: "root",
  children: [{ type: "table", children: [{ type: "tableRow", children: [cell("Range"), cell("~0.2.1 | ^1.0.0")] }] }],
});
if (Result.isSuccess(tree)) {
  console.log(Result.getOrThrow(Markdown.stringifyResult(tree.success)));
  // | Range | ~0.2.1 \| ^1.0.0 |
  // | --- | --- |
}

保护周围的逃亡 块 仍然适用,因为放下它们会腐蚀文档,而不是添加格式:

  • 在表格单元格中,每个 | 还没有反弹- 逃脱变成 \|· ;
  • 在任何容器中,一个最终值 \ 当更多的内容跟随它时,它就会加倍,因此它无法逃脱该内容的第一个字符;
  • 在表格单元格或标题中,新行变为空格;
  • 在一个方向,一个线索 # 将读取为关闭序列的运行;
  • 在其他地方,将形成一行空白的新行变为: &#10;,在一行起步时引导白空间成为字符参考,可以打开块的一行起步被逃脱——a -, (中文(简体) ), + 或者说 * 弹出或命令列表标记,然后是空格、标签或行尾,1至6 # 之后是同样的, >,3个倒推或平板的运行,一个主题断开或设置ext运行,a --- 或者说 +++ 前题栏杆,仅一行 -, (中文(简体) ), :, (中文(简体) ), | 和白空间,保守地说,任何 <, (中文(简体) ), [, (中文(简体) ), | 或者说 : - 文本不能,例如: ~0.2.1, (中文(简体) ), 1.0.0, (中文(简体) ), -x 或者说 #x,按原样写;
  • 在树上挂着MDX节点, { 财务报告和审定财务报表 < 继续逃跑.

以平分表示的字面值不回转数字 : *a* 以逐字记录和重分部分作为重点。 保持无内入语法的值是呼叫者的承诺;发送者不检查. 解析器从未设定 escapeStyle,以及 Mdast.fromMdast 承认了 text 节点——跨越该边界的一个忠心场,因为它是对发件人的指示,而不是源拼音记录——所以一棵平地的mdast树可以直接携带入去. 选出是添加剂:一棵从未将它序列化的树将字节-等同于上面的犬形.

改为: 常态 用于不同选择、使用的现有文档 MarkdownFormat 与 MarkdownFormattingOptions. 这是可布置的地表;这是故意的.

在没有解析器的情况下忽略标记

一种仅能建立树木并将其序列化的捆绑——一种将内容渲染成标记的工人,比如说,从 @effected/markdown/stringify该入口输出节点类别, stringify 财务报告和审定财务报表 stringifyResult, (中文(简体) ), MarkdownStringifyError 财务报告和审定财务报表 MarkdownDiagnostic,而且它没有达到加载块通过,内接通过或HTML实体表,所以一个捆绑器将所有三个放出:

import { Heading, Root, Text, stringifyResult } from "@effected/markdown/stringify";
import { Result } from "effect";

const tree = Root.make({ children: [Heading.make({ depth: 2, children: [Text.make({ value: "Usage" })] })] });
console.log(Result.getOrThrow(stringifyResult(tree)));
// ## Usage

字节正是 Markdown.stringify's——这些静态代表这些函数——根入按身份输出相同的函数和类别,因此任意导入的任意函数树摇. 将解析器放入捆绑的 是通过 Markdown 类: 捆绑者不能丢弃未使用的静态成员.

特征

  • Markdown —— 说 parse编号 :stringify 作为 Effects 有输入 MarkdownParseError编号 :MarkdownStringifyError 频道,纯度 parseResult编号 :stringifyResult 双胞胎 同步呼叫者 MarkdownFromString 双向编解码器.
  • stringify编号 :stringifyResult ——作为自由函数的序列器,在根入和解析器上 @effected/markdown/stringify 节点类旁边的入口 .
  • Markdown.parsePhrasing编号 :parsePhrasingResult ——将散文片段分析为一段落的内行内容,没有完整的文档解析和段落相接:空白行保持内行,引用永远不形成(没有引用上下文),相对于输入字符串,位置是正确的.
  • MarkdownDocument 源、树、诊断和链接定义指数,加上衍生的 headings, (中文(简体) ), headingSlugs, (中文(简体) ), sections 财务报告和审定财务报表 links 访问者 firstSection编号 :sectionByHeading 发现者在一个 DocumentSection (标题、深度、范围、身体) find编号 :findAll 对被收缩的选取者进行树质查询, unresolvedFootnoteReferences 页:1 [^label] 没有定义的标记,以及 frontmatter 抓取为 hasFrontmatterBlock 财务报告和审定财务报表 frontmatterFence 告诉一个没有被抓住或没有关闭的街区.
  • Mdast-形状的节点类——普通马克类型外加GFM delete, (中文(简体) ), table, (中文(简体) ), tableRow, (中文(简体) ), tableCell, (中文(简体) ), footnoteDefinition, (中文(简体) ), footnoteReference 和任务列表 checked,每个携带有字节相抵的无主位置 和这个软件包的忠诚域. position 默认为零宽的合成哨点,所以一个替换片段在一行中构造—— Text.make({ value: "shipped" })。 。 。 .
  • MDX节点词汇 - MdxJsxFlowElement编号 :MdxJsxTextElement 以他们的配主发誓, MdxFlowExpression编号 :MdxTextExpression 财务报告和审定财务报表 MdxjsEsm,形状为 mdast-util-mdx 建筑和系列化合同(解析器读作没有MDX语法).
  • FrontmatterSource ——字符串级别前题 split编号 :join 同一封闭的栅栏语法、字节精确的边界和数值抵消,a FrontmatterFenceState 显示没有关闭的栅栏,没有显示价值或身体;对于身体,通用马克引擎不应触碰.
  • MarkdownEdit 编号 : MarkdownRange (与 applyAll) ——非修改文本编辑词汇,字段与 @effected/jsonc因为, @effected/yaml还有.. @effected/toml对.
  • MarkdownFormat —— 说 format编号 :formatToString 计算保守标记-规范化编辑; modify编号 :modifyToString 以身份取而代之的节点通过犬形字符串.
  • MarkdownVisitor ——走一棵被剖开的树作: Enter编号 :Exit 有儿童索引路径和深度的事件:作为懒惰者同步 Iterable (单位:千美元)walk),作为a Stream (单位:千美元)visit),或通过 enter编号 :exit 调用 SKIP编号 :EXIT 信号、祖先和受类型限制的选定者traverse) (中文(简体) ).
  • Mdast —— 说 toMdast (一) 向普通JSON项目; fromMdast编号 :fromMdastResult 承认一棵外国的木头树.
  • MarkdownFrontmatter.schema 加减 YamlFrontmatter, (中文(简体) ), TomlFrontmatter 财务报告和审定财务报表 JsonFrontmatter 编解码器——在可选的每个格式的同位素上键入前事解码,有四个可被接取失败模式和一个 FrontmatterMissingReason 在缺一个.
  • SchemaResolver - 分类前题 $schema 声明进入 ByUrl编号 :ByPath编号 :Inline编号 :ByName 并且用一个没有独立组织、没有依附关系的登记簿来决定名称.
  • MarkdownDiagnostic 结构化诊断code, (中文(简体) ), message, (中文(简体) ), offset, (中文(简体) ), length, (中文(简体) ), line, (中文(简体) ), character每个打出的错误都带有,形状与兄弟姐妹的包相同'.

遵约

652 CommonMark 0.31.2 spec 例都用空跳图运行,整体在两种方言下再次运行,并明确宣称有差异列表. GFM扩展子公司从cmark-gfm——光谱扩展段和 extensions.txt,唯一正式的脚注-运行完整,27平方 mdast-util-from-markdown 程序,它声称AST 财务报告和审定财务报表 通过 Mdast 而不是仅仅匹配输出.

两次独立检查 备份这一点。 差异属性套件对照 commonmark npm包横跨实体加数以万计生成的文件,cmark的病理套件将线性时间的保证用校正的预算来钉住——Markdown的DoS向量是四重强调并链接起爆,而分界线-stack算法就是击败它的原因. 递归地表搭载了256个深层筑巢盖,因此筑巢炸弹通过打字错误通道失效而不会溢出堆放. 每个神谕和神职人员都只依靠依赖性;没有一个人到达你的运行时间.

许可证

麻省理工学院

版本与发布记录

@effected/markdown 共发布 47 个版本,首次发布于 2026/07/20;过去 12 个月发布了 47 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.29.0latest2026/11/102 天前
0.28.02026/11/094 天前
0.27.02026/11/067 天前
0.26.02026/11/0310 天前
0.25.02026/11/0112 天前
0.24.02026/10/2915 天前

关于 @effected/markdown 的常见问题

@effected/markdown 是什么?

常见马克和GFM 标记下解析、编辑和转换效果计划

如何安装 @effected/markdown?

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

@effected/markdown 支持 TypeScript 吗?

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

@effected/markdown 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@effected/markdown 需要什么版本的 Node.js?

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

@effected/markdown 可以商用吗?

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

@effected/markdown 的下载量有多少?

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

@effected/markdown 还在维护吗?

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