Eleventy テンプレートエラーのデバッグ情報を改善
ドキュメントビルド中にテンプレートエラーが発生した際、これまで undefined ✖ Error while writing the docs. という無意味なメッセージしか表示されなかった問題が修正されました。エラーの実際のファイルパス・行番号・パーサーメッセージが正しく表示されるようになります。
背景
Eleventyはテンプレートのレンダリング失敗を TemplateContentRenderError でラップして報告しますが、有用な詳細情報(ファイルパス・行番号・列番号)は originalError プロパティに格納されています。しかし、scripts/docs.js のエラーハンドラは error.cause を参照していたため、ほぼ常に undefined が表示されていました。
この問題はウォッチモードの再ビルドハンドラ(scripts/build.js)にも存在しており、開発中にテンプレートを誤った状態で保存した際も同様に無意味なエラー出力しか得られませんでした。
技術的な変更
formatError 関数を scripts/utils.js に新設し、エラーオブジェクトの構造を考慮した表示フォーマットを一元化しました。
新設された formatError 関数は、originalError・cause・message・stack の優先順位でエラー情報を取り出します:
export function formatError(err) {
const inner = err?.originalError || err?.cause;
const innerStr = inner?.message || inner?.stack;
const outer = err?.message;
return innerStr && outer && !innerStr.includes(outer)
? `${outer}\n\n${innerStr}`
: innerStr || err?.stack || outer || String(err);
}
originalError が存在する場合、外側のエラーメッセージと内側の詳細情報を改行で区切って両方表示します。内側の文字列がすでに外側のメッセージを含んでいる場合は重複を避けるため内側のみを表示します。スタックトレースより .message を優先するのは、Eleventy のエラーメッセージにはすでにファイルパスと行番号が含まれており、内部フレームの羅列を表示しても開発者の役に立たないためです。
scripts/docs.js では、エラーハンドラを以下のように変更しました:
変更前:
} catch (error) {
console.warn = originalWarn;
console.error('\n\n' + chalk.red(error.cause) + '\n');
変更後:
} catch (error) {
if (outputs.warn.length > 0) {
console.error(chalk.yellow('\n11ty warnings captured during build:'));
for (const args of outputs.warn) {
console.error(chalk.yellow(' ' + args.map(a => a?.message || a?.stack || String(a)).join(' ')));
}
}
console.error('\n\n' + chalk.red(formatError(error)) + '\n');
} finally {
restoreConsole();
あわせて、ビルド中に Eleventy が出力した警告を outputs.warn に蓄積して表示する処理も追加されました。従来はビルドスクリプトが console.warn をインターセプトして出力を抑制していましたが、エラー発生時には蓄積した警告を遡って表示することで、問題の手掛かりとなる情報が失われなくなります。また、console.warn の復元を catch ブロックから finally ブロック に移したことで、正常終了時にもインターセプトが確実に解除されるようになりました。
stubConsole 関数の引数の展開方法も修正されています:
変更前:
console[key] = function (...args) {
outputs[key].push(...args);
};
変更後:
console[key] = function (...args) {
outputs[key].push(args);
};
...args でスプレッドしていた従来の実装では、後から各引数を個別に参照できず警告の整形が困難でした。args をそのまま配列として格納することで、後続の args.map(a => a?.message || ...) によるフォーマット処理が成立します。
設計判断
エラー整形ロジックを scripts/utils.js に集約する 方針が採られました。docs.js と build.js の両方が同じ問題を抱えていたため、formatError を共有ユーティリティとして切り出すことで重複を排除し、将来のビルドスクリプト追加時にも一貫したエラー表示が得られます。
PR説明にも明示されているとおり、すべての変更はエラーパスに閉じており、正常なビルドの挙動には影響しません。エラーメッセージの改善という局所的な目的に対して変更範囲を最小限に抑えた判断といえます。
まとめ
この変更は、開発者がドキュメントビルドのエラーに直面した際に失っていた「なぜ失敗したか」という情報を取り戻します。formatError による Eleventy 固有のエラー構造への対応と finally による確実なコンソール復元は、DX 改善における堅牢性の高い実装パターンを示しています。