ここ最近は Unity の記事ばかりでしたが、今回は Node.js のお話と言うことで、最近お気に入りのライブラリー、 contentlayer について紹介します。
contentlayer は名前の通り、 Next.js などのコンテンツ周りを良い感じに取得してくれたり、コンパイルしてくれるライブラリです。
これを使うことで、簡単に Markdown などのドキュメントから静的サイトを構築することが出来ます。
ということで、まずは導入方法から。
いつも通り、 yarn 経由で導入します。
$ yarn add contentlayer next-contentlayer
導入したら、 contentlayer.config.ts
という設定ファイルを作成します。
このブログの場合は、以下のような設定になります。
import { ComputedFields, defineDocumentType, defineNestedType, makeSource, } from "contentlayer/source-files"; import { remark } from "remark"; import strip from "strip-markdown"; const getSummarizedText = (markdown: string) => { const text = remark().use(strip).processSync(markdown).toString(); const firstSentence = text.indexOf("。"); if (firstSentence <= 120) { return text.substring(0, firstSentence + 1); } return text.substring(0, 120) + "..."; }; const computedFields: ComputedFields = { summary: { type: "string", resolve: (doc) => { return getSummarizedText(doc.body.raw); }, }, }; const Article = defineDocumentType(() => ({ name: "Article", filePathPattern: "**/*.md", contentType: "markdown", fields: { title: { type: "string", required: true }, date: { type: "string", required: true }, basename: { type: "string", required: true }, categories: { type: "list", default: [], of: { type: "string" } }, }, computedFields, })); const Redirect = defineNestedType(() => ({ name: "Redirect", fields: { from: { type: "string", required: true }, to: { type: "string", required: true }, }, })); const Redirects = defineDocumentType(() => ({ name: "Redirects", filePathPattern: "redirects.json", contentType: "data", fields: { redirects: { type: "list", default: [], of: Redirect }, }, })); const config = makeSource({ contentDirPath: "contents", documentTypes: [Article, Redirects], }); export default config;
defineDocumentType
でドキュメントの形式、 front-matter などの型、その他フィールドを定義します。
こうすることによって、あとで contentlayer
からデータを取得する際に、型が効くようになります。
computedFields
を使うことで、 Markdown などのファイル名や内容から、動的に中身を生成することも可能です。
今回の場合は、 description
用の summary
を動的に生成しています。
そして、 next.config.js
に以下の設定を追記します。
const { withContentLayer } = require("next-contentlayer"); module.exports = withContentLayer()({ // 元の Next.js の設定 // 多分 latest (0.1.x) のバグ webpack: (config) => { config.resolve.alias = { ...config.resolve.alias, "contentlayer/generated": path.join(__dirname, ".contentlayer/generated"), }; return config; }, });
これで準備完了です。
最後に、取得部分は以下のようにします。
import { allArticles, allRedirects } from "contentlayer/generated"; import type { Article as Entry } from "contentlayer/generated"; const getStaticPaths: GetStaticPaths<PathParams> = async () => { const paths = allArticles.map((w) => { const [year, month, day, slug] = w.basename.split("/"); return { params: { slug: [year, month, day, slug] as [string, string, string, string], }, }; }); return { paths, fallback: false, }; };
このように、 all${name に指定した名前}
で簡単に中身を取得することが可能です。
ということで、 contentlayer の紹介でした。
ちなみに、このブログもこの記事が反映されたタイミングで、内部で fs を使って集めるのでは無く、 contentlayer を用いた生成に切り替わっています。