TypeScript で Word 文書を読み書き・編集する

.docx をゼロから作成することも、既存のファイルを開いて書き換えることもできます。段落、リスト、表、画像、ヘッダー、コメント、変更履歴は、どれも型付きのシンプルな関数で扱えます。ライブラリがモデル化していない部分は、手を加えずにそのまま書き戻します。Node でもブラウザーでも動作します。

はじめる プレイグラウンドを開く

ドキュメントと、それを生成したコード

hero-document.ts
export function buildHeroDocument(): Docx {
  const doc = createDocx({ paragraphs: [] });
  setPageSize(doc, PAGE_SIZE_A4);
  setPageMargins(doc, MARGINS_NORMAL);
  ensureHeadingStyles(doc);

  appendHeading(doc, "Quarterly review", 1);
  const intro = appendParagraph(doc, "Revenue grew 23% on last quarter. ");
  appendTextRun(intro, "Churn hit a record low.", { bold: true });

  appendHeading(doc, "Highlights", 2);
  addBulletList(doc, [
    "Shipped the self-serve onboarding flow",
    "Cut median support response time to 2 hours",
    "Opened the Osaka office",
  ]);

  appendHeading(doc, "Numbers", 2);
  const table = addTable(doc, [
    ["Metric", "Q2", "Q3"],
    ["Revenue", "$1.20M", "$1.48M"],
    ["Customers", "412", "497"],
    ["Churn", "3.1%", "2.4%"],
  ]);
  setTableBorders(table, { color: "BFC5D2" });
  const header = table.rows[0]!;
  setTableRowAsHeader(header);
  for (const cell of header.cells) setTableCellShading(cell, { fill: "E9EFFC" });

  return doc;
}

Quarterly review

Revenue grew 23% on last quarter. Churn hit a record low.

Highlights

  • Shipped the self-serve onboarding flow
  • Cut median support response time to 2 hours
  • Opened the Osaka office

Numbers

MetricQ2Q3
Revenue$1.20M$1.48M
Customers412497
Churn3.1%2.4%

このページは実際の出力です。隣のコードでドキュメントを生成し、このサイトがそれを .docx のバイト列として保存したうえで openDocx で開き直し、読み取った内容を HTML として描画しています。ファイルをダウンロードして Word で開くと、見出しはスタイル、リストはリスト、表は表として作られていることがわかります。

  • createDocx()

    空のドキュメントから始める

    createDocx() はスタイル パーツとリレーションシップを設定済みのドキュメントを返すため、テンプレート ファイルを同梱する必要はありません。見出し、段落、箇条書きと番号付きリスト、表、インライン画像、ヘッダー、フッター、脚注、目次を追加できます。

    REPL で書いてみる
  • openDocx(bytes)

    手元のファイルを編集する

    .docx を開き、本文、ヘッダー、フッター、脚注、文末脚注、コメントにまたがってテキストを置換したり、代替テキストを手がかりに画像を差し替えたり、変更履歴の挿入と削除を承諾・却下したりしてから保存できます。ライブラリがモデル化していない部分は、元の状態のままファイルに戻されます。

    レシピを見る
  • previewToDOM(doc, element)

    ページ上にドキュメントを表示する

    姉妹パッケージの @office-kit/docx-preview は、Docx またはバイト列を DOM 要素に描画します。オープンソースの docx-preview レンダラーを 1 つの関数でラップしたものです。ブラウザー内で動作するため、ファイルがアップロードされることはありません。

    プレイグラウンドで試す

テンプレートは Word でデザインし、中身はコードで埋める

レターヘッド、契約書、フォントまで整えたレポート。大切な文書の多くは、誰かが手をかけた .docx としてすでに存在しています。それを開いて変わる部分だけを置き換えれば、それ以外はデザインしたとおりに残ります。

Word は {{name}} のようなプレースホルダーを複数のランに分割して保存することがよくあります。照合は XML ではなく段落のテキストに対して行うため、分割されていても見つかります。また replaceTextEverywhere は本文だけでなく、ヘッダー、フッター、脚注・文末脚注、コメントも置換の対象にします。

template-fill.ts
import { readFile, writeFile } from "node:fs/promises";
import { openDocx, replaceTextEverywhere, toUint8Array } from "@office-kit/docx";

const values: Record<string, string> = { name: "Ada Lovelace" };

const doc = openDocx(await readFile("letter-template.docx"));

const replaced = replaceTextEverywhere(
  doc,
  /\{\{(\w+)\}\}/g,
  (m) => values[m.captures[0] ?? ""] ?? "",
);
console.log(`Filled ${replaced} placeholders.`);

await writeFile("letter.docx", toUint8Array(doc));

ラウンドトリップを機械で検証

他人の文書を編集するライブラリにとって、何より大切なのは文書を壊さないことです。それを裏付ける仕組みを紹介します。

実際の Word ファイルを読み込み、同じ内容のまま書き出します。
テストスイートは、mammoth.js と python-docx がテストに使っている .docx のコーパスを開き、すべてのファイルを保存してから開き直して、段落とテキストを比較します。CI はプルリクエストごとに、これを Node 22 と Node 24 で実行します。
理解できないものには手を触れません。
モデル化していない要素は元の位置にパススルー ノードとして保持され、読み取らないパーツもパッケージ内にそのまま残ります。コンテンツ コントロールやカスタム XML パーツも、編集によって失われることはありません。
validate() は、Word が修復を求める原因になる問題を検出します。
存在しないパーツを指すリレーションシップ、参照先のないコメント・脚注・文末脚注の参照、対になっていないブックマーク、欠落したメディアなどです。テストスイートでは、20 通りの機能の組み合わせで問題が出ないことを確認しています。
バンドルに含まれるのは、インポートした関数だけです。
副作用のないエクスポートが 120 個あり、クラスはありません。createDocx、appendParagraph、toUint8Array だけなら minify 後 42 KB、API 全体でも 133 KB です。この最小構成のバンドルが 50 KB を超えると CI が失敗します。
ランタイム依存は 1 つだけです。
ZIP 処理のための fflate だけです。XML パーサーとシリアライザーは自前で実装しており、ライブラリ内に Node の組み込みモジュールをインポートしている箇所はありません。

docx パッケージとの違い

docx パッケージは JavaScript で Word ファイルを生成する定番の手段で、このライブラリではまだ作成できないものも作れます。両者の違いは方向性です。docx パッケージは新しい文書の生成に特化しているのに対し、このライブラリは手元にある文書の読み取りと編集にも対応しています。比較対象は docx 9.7.1 です。

機能@office-kit/docxdocx (npm)
既存の .docx を開いて編集対応。ファイルをモデルとして読み込み、任意の段落、ラン、表を変更可能プレースホルダーのみ。patchDocument で {{tokens}} を新しい内容に差し替え
ファイルの内容の読み取りテキスト、アウトライン、統計情報、画像、フィールド、ハイパーリンク、ブックマーク非対応。patchDetector で取得できるのはプレースホルダーの一覧のみ
プレースホルダーの置換先テキストテキスト、段落、表、画像
変更履歴ファイル内の変更履歴を承諾・却下できるが、新規作成は不可挿入と削除を作成できるが、既存の変更履歴の解決は不可
セルの結合、フローティング画像、テキストボックス未対応対応
API の形プレーンなデータを扱う、ツリーシェイキング可能な関数群。ESM のみクラス インスタンスのツリー。ESM、CommonJS、script タグ用のバンドルを提供
ランタイム依存1 つ(fflate)6 つ(jszip、xml-js など)

新しい文書を生成するだけで、セルの結合、フローティング画像、テキストボックス、CommonJS ビルドが必要なら、docx パッケージを選んでください。すでにある文書を読み取ったり、変更したり、検証したりする必要があるなら、このライブラリが適しています。

現在できること

このライブラリはまだ 1.0 未満です。現時点で動作するものと、まだ対応していないものを以下にまとめます。すべての関数は API リファレンスに掲載しています。

テキスト
段落、見出し、ラン(太字、斜体、下線、文字の色、蛍光ペン、サイズ、フォント)。配置、インデント、間隔、罫線、網かけ。
リストとスタイル
箇条書きと番号付きリスト。スタイルの追加、削除、検索と、デザイン済みテンプレートからのスタイル定義一式の取り込み。
表
行、セルのテキスト、罫線、セルの網かけ、垂直方向の配置、行の高さ、タイトル行の繰り返し。表を解除して段落に戻す操作。
画像
PNG、JPEG、GIF、BMP、TIFF、SVG のインライン画像。ファイル内の画像の一覧取得と、パーツ名または代替テキストによる差し替え。
ページ設定
用紙サイズ、余白、印刷の向き、セクション区切り。標準ページ・先頭ページ・偶数ページ用のヘッダーとフッター、ページ番号。
校閲
コメント、脚注、文末脚注、ブックマーク、外部・内部へのハイパーリンク。変更履歴の挿入と削除のすべての承諾・却下。
フィールド
目次、差し込みフィールド、PAGE・NUMPAGES・DATE フィールド。
ドキュメント
コア プロパティとアプリケーション プロパティ、すべてのパーツを対象にした検索と置換、アウトライン、統計情報、プレーンテキストの抽出、validate()。

未対応

  • 表のセルの結合と入れ子の表
  • フローティング画像、テキストボックス、グラフ(既存のファイルに含まれるものは保持されます)
  • コンテンツ コントロール用の型付き API(既存のファイルに含まれるものは保持されます)
  • ISO/IEC 29500 Strict 形式のファイル
  • PDF へのレンダリング(今後も対応する予定はありません)

AI エージェントにも読みやすく

AI エージェントが文書を生成する場面が増えているため、ドキュメントはモデルが直接取得できる形式でも公開しています。掲載しているコードスニペットはすべて、サイトのビルド時にライブラリに対して型チェックされるファイルです。そのため、エージェントにもう存在しない API を教えてしまうことはありません。

  • /llms.txt モデルが参照先を選ぶための、ドキュメントの目次(英語)。
  • /llms-full.txt ガイド、レシピ、API 一覧を 1 つにまとめた Markdown ファイル(英語)。

1 つのキットで 3 つのファイル形式に対応

Office Kit は、共通のルールに沿って作られたライブラリ群です。ECMA-376 仕様を唯一の拠り所とすること、読み込んだファイルを損なわずに書き出すこと、そして 1 つの ESM ビルドがどこでも動作することです。