ドラフトを可能にするために
ドラフト(下書き・草案、設計図等)は完成前の準備段階を指す言葉です。
しかし、どうすれば非公開にできるかを考えると、そういう仕組みが11tyにあるわけではありません。それは11tyの作者がそういうシンプルにやるという考え方に基づいていて、
- 制限を設けるのではなく、
- そういった部分も個人の(書き方としての)自由を奪わず、
- TipsとしてJavaScriptの
filterを使用して記事を除外してね
というスタンスでいるからです。おそらくそういう機能も追加できるのでしょうが敢えてシンプルに作られています。現在進行中のバージョン4においてもそういう機能が公式に入るようになるということはありません。
個人の自由とは、例えばフロントマターで、
- ある人は、draft: trueとして設定するかも知れない
- ある人は、status: "archived"かも知れないし
- ある人は published: false を使うこともあるだろう
そういうのを邪魔しないということです。自分で「好きに指定」して、「好きに除外」すればいいやんということですね。
どうやって実装するか
最初にしないといけないのは、11tyがどういう状態であるかを判別する事です。
const isServe = process.env.ELEVENTY_RUN_MODE === "serve";
こう宣言しておけば、RUN_MODEがserve、つまり開発時にはisServe = trueになります。開発時以外はfalseですからこれを利用します。
11tyの設定ファイルのeleventy.config.jsに、次のような書き方をします。
export default function (eleventyConfig) {
// 各種設定
}
例えば、このexport default function (eleventyConfig) {}スコープの外、つまり
const isServe = process.env.ELEVENTY_RUN_MODE === "serve";
export default function (eleventyConfig) {
// 各種設定
}
このように書けば、グローバルスコープとなってこのファイルの中でどこでもisServeが使えます。isServeを使うことはあまりないでしょうが、上記のように書いておくのが良いかと思います。
コレクションについて
次に、11tyのどこでも使うコレクション(collection)についてですが、この機能は集めたいデータだけをまとめるものです。blog記事を集めたい場合、
const isServe = process.env.ELEVENTY_RUN_MODE === "serve";
export default function (eleventyConfig) {
// 各種設定
// コレクション
eleventyConfig.addCollection("posts", (api) => {
return api.getFilteredByGlob("src/posts/**/*.md");
});
}
と言うような感じに書けて、postsという変数に、src/posts/の中にあるMarkdownファイル(記事)を集める(コレクションする)という意味の書き方です。src/posts/の他にsrc/blog/や他のディレクトリからでも集められますし、getFilteredByGlobとあるように、src/{posts,blog}/**/*.mdなどとしても集められます。
スコープとは
{}こういったもので囲まれている部分です。function() {}はカッコ内の処理を実行する関数に当たります。{}の中で定義したものはカッコの中でしか扱えないローカル変数など{}内だけの値になり、{}の外で定義したものはグローバルな値になります。
上記のようにも書けるわけですが、古いもの順なので、ブログの記事などであれば、
return api.getFilteredByGlob("src/posts/**/*.md").reverse();
と書きたくなるでしょう。つまりreverse()を使用して新着順にするということです。新しい記事が先頭に来るという書き方になります。
しかし、公式のドキュメントにも書いてありますが、reverse()は元のデータを逆順に書き換えてしまいます。書き換えられてしまった元々のデータを別で使用した時、並び方が違うなどという問題がでてしまうわけです。
.toReversed()を使うか、.filter(entry => entry).reverse()- あるいはテンプレートの
reverseフィルターなど
を使いましょうとあるわけです。
これらは元々のデータを書き換えずにコピーしてからコピーしたものを逆順にします。なので元のデータは汚染されることがありません。
これらをまず念頭に置いて、draftの件に繋げると、まずdraft記事以外を集めるコードを書いてみます、
eleventyConfig.addCollection("posts", (api) => {
const posts = api.getFilteredByGlob("src/posts/**/*.md");
if (!isServe) { // RUN_MODE が serve ではない時
// post.data.draft が trueでないものを返す
// └> フロントマターに draft: true とあるものを取り除いて返す
return posts.filter(post => !post.data.draft);
}
// RUN_MODE が serve の時はposts全部返す
return posts;
});
このようにも書けます。こういうコレクションは例えばフロントマターについている他のタグでコレクションしたり、日付でコレクションしたりなど色々なコレクションを作ることができますので、そのたび毎にその記事からdraft記事を判定するのは記述も増えますし、reverse()の件もあるので、次のような関数をひとつ作っておきます。
draft記事を取り除いた記事を返す関数
const isServe = process.env.ELEVENTY_RUN_MODE === "serve";
// 11ty設定
export default function (eleventyConfig) {
// 各種設定
// ----------------------------
// ドラフト記事を除外する関数
// ----------------------------
const noDraft = (items) => {
return isServe ? [...items] : items.filter(item => !item.data.draft);
};
// ----------------------------
// コレクション
// ----------------------------
// 元のコード
// eleventyConfig.addCollection("posts", (api) => {
// return api.getFilteredByGlob("src/posts/**/*.md");
// });
// 元のコードにnoDraftを適用したコード
eleventyConfig.addCollection("posts", (api) => {
return noDraft(api.getFilteredByGlob("src/posts/**/*.md")).reverse();
});
}
もう答えを全体的に書いてますが、その名もズバリnoDraft()。.filter()を用いて、集めたコレクションの中からフロントマターにdraft: trueとある記事を除外した新しい配列を作って返します。.filter()は元のデータをコピーしてから操作します。これにより元のデータを汚染せずコピーしたものを加工できるようになります。オリジナルのデータはそのままに、コピーしたデータを操作して返すという便利なメソッドです。draft: true以外を(!item.data.draft)という条件で集めて返す関数になっています。
オリジナルの破壊を起こさないので、.reverse()による逆順にして新しいもの順にしても何も問題はありません
説明忘れ
説明忘れてましたがreturn isServe ? trueの時 : falseの時 ;は、isServe(serveの時、つまり開発時)が、
- 開発時なら true
- それ以外は false
ということです。三項演算子(Ternary operatorまたは、Ternaly)と言います。.sort()や、.reverse()は使いますからデータの汚染を防ぐことからもコピー後に使用するようにしています。
noDraftにisServe ? [...item] とありますが、これはシャローコピーをしています。何から何まで複製するディープコピーの反対で、配列やオブジェクトなどのデータ構造を複製する際、参照のみをコピーして実体の複製は作らない方式のことを言います。
.reverse()や.sort()は配列自体の並び順を書き換えてしまいますが、そのためだけにファイルのクローンが必要になることはなく、どういう並びでそれがあるかがコピーできればよいわけです。.sort(),.reverse())メソッドを使用すれば、元のデータを破壊せずに済みますし、複製を作る手間(メモリ使用量・CPUパワー)が不要と言うことにもなります。
もっと別で喩えると、GIMPやIllustratorあるいはwebではSVGにおけるUseとか、実体を複製してわざわざ名前を変えて使用しなくても、画像のリンクで配置表示した方が速いでしょう?
フロントマターとは
---
title: blog記事のタイトル
description: 記事の説明
tags:
- blog
layout: posts.njk
draft: true
---
## Markdownで記事を書く
記事本文
これまでから何度と無く書いていましたが、こういうMarkdownの先頭にその記事はどういうものかを書いておく部分をフロントマターといいます。上記にも書いてありますが、draft: true部分、これは11tyではitem.data.draftとしてテンプレートやjsで利用できます。
item.data.draftのitemはその時々によって変わります。const posts = ~としてコレクションした場合は、それらをループで一つずつ取り出して、~ => (post ...)となることがあるかと思います。その場合はpost.data.draftとその記事のデータにあるdraftというような意味になります。
最初の方で書いた「ある人は published: false を使うこともあるだろう」という場合は、フロントマターにfalseが設定されているので、publishedがfalse以外は集めるという書き方になります。true|falseの違いはあれど、それはドラフト記事にしたいと言えますから、結果的にはそれらが付いていない記事だけを集めるようにするとシンプルです。そう考えれば、
const noDraft = (items) => {
return isServe
? [...items]
: items.filter(item =>
!('draft' in item.data) && !('published' in item.data)
);
};
こうも書けると思います。draft: trueと書いているのでオリジナルのnoDraftで問題ないのですが、人間が手動で書く・消すをするため、
---
title: タイトル
draft
---
と値を記載しない場合や、間違った理解から(間違わないと思いますが)、draft: falseと、trueが下書きであるから、falseと書けば公開ってなりそうですが、ちょっとややこしくはないでしょうか?
true|falseでは条件が人間の思考と反対なので条件式がややこしくなります。published: falseだと公開するのはfalseということですが、draft: trueは下書きですと言ってることとなるので、これらは単純には反対の意味を持っています。unpublidhed: trueだと同じかも知れませんが書くのが面倒くさい。
なのでドラフト記事(下書き)としての考え方としては、
- フロントマターには
draft: trueを書く。が第一条件としてあり、 - draftの
true|false問わず、書いてあれば非公開、なければ公開とする- 人為的ミスを防ぐ(書き間違い、消し忘れ等も含めて)
- 一度公開されてしまってそれがrssの元データであればFeedly等にデジタルタトゥーとして残ってしまいます。そのため「ドラフト記事にしたい(下書きである)」と意識された何かしらの記述があれば、非公開にしておくべきだ。不要なら書かなければ良いそれの方が楽とも言えます
- draft以外の下書き用と思われる記述がフロントマターにある場合は、同様にする
- なぜならそれを書くということはドラフト記事にしたいから
こういう考えです。draft :trueをフロントマターに用いて運用すれば、コードは、
const noDraft = (items) => {
return isServe ? [...items] : items.filter(item => !item.data.draft);
};
と、シンプルかつ美しく書けますから、誰かと記事を書いたりでその書き方が自由な場合、11ty側で吸収するとすれば上記のin等を使用したコードのように工夫は必要ですが、個人なら、
- ドラフトにする時は
draft: trueと書く - それを弾く関数は上記の通り(オリジナルのnoDraft)
- これは
isServe が true|falseの場合でも、コピーした配列で返すので元のデータを汚染しない
- これは
まとめ
Draftにするのはここまでで説明したように設定を書くわけですが、実際の所、記事をローカルに置いておけば良いだけのことではあります。しかしながら何かと操作を間違ったりして公開してしまった時の防波堤としても予め準備はしておくほうが良いです。
.sort()や.reverse()はオリジナルの配列の並びを変更してしまうのでそれらをコピーしてから使用するのが大事ということです。
今回のnoDraft関数を使用せずとも、Node.jsのtoReversed()を使用すれば普通にも書けますし、やり方は人それぞれです。どちらも同じようにコピーしたものを逆順にするということで効率自体もほとんど変わりません。noDraft(~)とあれば何をしているのかが一目瞭然だろうと思います。Node.jsのtoReversed()を知っていればその意味も自ずと知っているはずですがそれらが明確でない場合でも、そのコードで何をしているのかがわかると良いですよね。
noDraftでコピーしているのに更にtoReversed()をするとまたコピーすることにもなると思うので、それらは避けるようにして下さい。仮にしたとしても問題はないはずですがとにかく後で見て何をしているのかがわかりやすくシンプルで十分な書き方をしているのが良いかと思います。