こんにちは。MLBお兄さんこと松村です。この記事はオルターブース Advent Calendar 2020の2日目の記事です。
adventar.org
1日目は弊社のマーケティング切り込み隊長のよしざっきーが開幕宣言をしてくれましたね!というかもう12月って早すぎませんかね。
aadojo.alterbooth.com
アドベントカレンダーのネタ探しでは「聞いたことあるけどまだ試したこと無いもの」を取り上げるようにしています。
今年は何を書こうかなーと考えていましたが、 C# の静的サイトジェネレーターである「Statiq」を取り上げてみます。
突然ですが私は .NET Foundation のサイトをたまに見るんですが、実はこのサイトのソースコードは GitHub で公開されています。
で、このリポジトリの README.md を読むとこう書かれています。
This website uses Statiq Web, a flexible and extensible static site generator for .NET.
Statiq というツールがあるのを知ったきっかけですね。
Statiq というのは C# の静的サイトジェネレーターです。記事執筆時点でバージョンが 1.0.0-beta.14
なので、まだ新しいツールですね。
ツールの特徴
1. テンプレートが選べる
最終的には HTML ファイルになりますが、もとのページは Markdown と Razor構文で書くことができます。
Razor で書けるというのが C# 感ありますよね。動的コードがどのように静的コードになるのか、気になるところです。
2. データアジリティ
データアジリティをどう訳すか難しいところですが、 Statiq では YAML, JSON, XML をデータフォーマットとして扱うことができるということです。
現に .NET Foundation のリポジトリでは Markdown ファイルをデータとして扱っていることが分かります。
上記のコードで指定されている projects/data/
には Markdown ファイルが多数あります。
データベースから取得したレコードのように、 Markdown ファイルに記載されているタイトルを取得して出力しています。
3. 柔軟性
Statiq は3つのプロジェクトに分かれています。
- Statiq Web
- HTML の Web ページを生成することができます
- Statiq Docs
- まだこれから開発が行われるようだが、Statiq Web を拡張して API ドキュメントを生成することができるようになるらしいです
- Statiq Framework
- Statiq Web や Statiq Docs のベースになっているフレームワークで、100以上のモジュールがあるということです
4. デプロイメントが組み込まれている
Statiq の機能で Netlify, Azure App Service, GitHub Pages にデプロイができるようです。
Hello world
では Quick Start に沿ってサンプルを作ってみます。
Statiq Web といいつつ、コンソールアプリケーションを作ります。
dotnet new console --name MySite cd MySite dotnet add package Statiq.Web --version 1.0.0-beta.14
Program.cs をこのように書きます。
Statiq.App.Bootstrapper
を使って Web アプリケーションとして実行するというパイプラインを構成します。
using System.Threading.Tasks; using Statiq.App; using Statiq.Web; namespace MySite { public class Program { public static async Task<int> Main(string[] args) => await Bootstrapper .Factory .CreateWeb(args) .RunAsync(); } }
次にページのコンテンツを作ります。
input
フォルダーを作成し、そのなかに Markdown ファイルを作成します。
input/index.md
Title: My First Statiq page --- # Hello World! Hello from my first Statiq page.
Statiq Web を実行して静的ページを出力します。
dotnet run
を実行すると input
フォルダーの HTML ページが output
フォルダーに出力されます。
dotnet run [INFO] Statiq Framework version 1.0.0-beta.29+a415eded36042aa7385b2f5b05cbf3d45ce2b7c7 [INFO] Statiq Web version 1.0.0-beta.14+cdb07e6364c16d1c6d5787f84bd8bc952e03ae3f [INFO] Root path: D:/src/yuta/netcore/statiq/tmp/MySite [INFO] Input path(s): theme/input input [INFO] Output path: output [INFO] Temp path: temp [INFO] ========== Execution ========== [INFO] Executing 10 pipelines (AnalyzeContent, Archives, Assets, Content, Data, DirectoryMetadata, Feeds, Inputs, Redirects, Sitemap) [INFO] Cleaned temp directory: temp [INFO] Cleaned output directory: output [INFO] -> Inputs/Input ≫ Starting Inputs Input phase execution... (0 input document(s), 1 module(s)) [INFO] -> DirectoryMetadata/Input ≫ Starting DirectoryMetadata Input phase execution... (0 input document(s), 1 module(s)) [INFO] <- DirectoryMetadata/Input ≫ Finished DirectoryMetadata Input phase execution (0 output document(s), 38 ms) [INFO] -> DirectoryMetadata/Process ≫ Starting DirectoryMetadata Process phase execution... (0 input document(s), 1 module(s)) [INFO] <- Inputs/Input ≫ Finished Inputs Input phase execution (1 output document(s), 39 ms) [INFO] <- DirectoryMetadata/Process ≫ Finished DirectoryMetadata Process phase execution (0 output document(s), 1 ms) [INFO] -> Inputs/Process ≫ Starting Inputs Process phase execution... (1 input document(s), 9 module(s)) [INFO] <- Inputs/Process ≫ Finished Inputs Process phase execution (1 output document(s), 67 ms) [INFO] -> Assets/Process ≫ Starting Assets Process phase execution... (0 input document(s), 3 module(s)) [INFO] -> Data/Process ≫ Starting Data Process phase execution... (0 input document(s), 5 module(s)) [INFO] <- Assets/Process ≫ Finished Assets Process phase execution (0 output document(s), 4 ms) [INFO] <- Data/Process ≫ Finished Data Process phase execution (0 output document(s), 6 ms) [INFO] -> Content/Process ≫ Starting Content Process phase execution... (0 input document(s), 4 module(s)) [INFO] <- Content/Process ≫ Finished Content Process phase execution (1 output document(s), 175 ms) [INFO] -> Archives/Process ≫ Starting Archives Process phase execution... (0 input document(s), 3 module(s)) [INFO] -> Redirects/Process ≫ Starting Redirects Process phase execution... (0 input document(s), 2 module(s)) [INFO] <- Archives/Process ≫ Finished Archives Process phase execution (0 output document(s), 0 ms) [INFO] -> Feeds/Process ≫ Starting Feeds Process phase execution... (0 input document(s), 3 module(s)) [INFO] <- Feeds/Process ≫ Finished Feeds Process phase execution (0 output document(s), 0 ms) [INFO] <- Redirects/Process ≫ Finished Redirects Process phase execution (0 output document(s), 5 ms) [INFO] -> Redirects/Output ≫ Starting Redirects Output phase execution... (0 input document(s), 1 module(s)) [INFO] -> Data/Output ≫ Starting Data Output phase execution... (0 input document(s), 2 module(s)) [INFO] -> Content/PostProcess ≫ Starting Content PostProcess phase execution... (1 input document(s), 1 module(s)) [INFO] -> Archives/PostProcess ≫ Starting Archives PostProcess phase execution... (0 input document(s), 1 module(s)) [INFO] -> Sitemap/PostProcess ≫ Starting Sitemap PostProcess phase execution... (0 input document(s), 1 module(s)) [INFO] -> Feeds/Output ≫ Starting Feeds Output phase execution... (0 input document(s), 2 module(s)) [INFO] <- Feeds/Output ≫ Finished Feeds Output phase execution (0 output document(s), 2 ms) [INFO] <- Data/Output ≫ Finished Data Output phase execution (0 output document(s), 2 ms) [INFO] <- Redirects/Output ≫ Finished Redirects Output phase execution (0 output document(s), 2 ms) [INFO] -> Assets/Output ≫ Starting Assets Output phase execution... (0 input document(s), 2 module(s)) [INFO] <- Assets/Output ≫ Finished Assets Output phase execution (0 output document(s), 0 ms) [INFO] <- Archives/PostProcess ≫ Finished Archives PostProcess phase execution (0 output document(s), 3 ms) [INFO] -> Archives/Output ≫ Starting Archives Output phase execution... (0 input document(s), 2 module(s)) [INFO] <- Archives/Output ≫ Finished Archives Output phase execution (0 output document(s), 0 ms) [INFO] <- Sitemap/PostProcess ≫ Finished Sitemap PostProcess phase execution (1 output document(s), 4 ms) [INFO] -> Sitemap/Output ≫ Starting Sitemap Output phase execution... (1 input document(s), 1 module(s)) [INFO] <- Sitemap/Output ≫ Finished Sitemap Output phase execution (1 output document(s), 3 ms) [INFO] [Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager] User profile is available. Using 'C:\Users\yuta\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. [INFO] <- Content/PostProcess ≫ Finished Content PostProcess phase execution (1 output document(s), 1769 ms) [INFO] -> Content/Output ≫ Starting Content Output phase execution... (1 input document(s), 2 module(s)) [INFO] <- Content/Output ≫ Finished Content Output phase execution (1 output document(s), 1 ms) [INFO] -> AnalyzeContent/Input ≫ Starting AnalyzeContent Input phase execution... (0 input document(s), 1 module(s)) [INFO] <- AnalyzeContent/Input ≫ Finished AnalyzeContent Input phase execution (1 output document(s), 0 ms) [INFO] AnalyzeContent/Process ≫ Running 3 analyzers (ValidateAbsoluteLinks, FencedCodeBlocksShouldHaveLanguage, ValidateRelativeLinks) [INFO] ========== Execution Summary ========== Number of output documents per pipeline and phase: | Pipeline | Input | Process | PostProcess | Output | Total Time | |----------------------------------------------------------------------------------| | AnalyzeContent | 1 (0 ms) | | | | 0 ms | | Archives | | 0 (0 ms) | 0 (3 ms) | 0 (0 ms) | 3 ms | | Assets | | 0 (4 ms) | | 0 (0 ms) | 4 ms | | Content | | 1 (175 ms) | 1 (1769 ms) | 1 (1 ms) | 1945 ms | | Data | | 0 (6 ms) | | 0 (2 ms) | 8 ms | | DirectoryMetadata | 0 (38 ms) | 0 (1 ms) | | | 39 ms | | Feeds | | 0 (0 ms) | | 0 (2 ms) | 2 ms | | Inputs | 1 (39 ms) | 1 (67 ms) | | | 106 ms | | Redirects | | 0 (5 ms) | | 0 (2 ms) | 7 ms | | Sitemap | | | 1 (4 ms) | 1 (3 ms) | 7 ms | Pipeline phase timeline: | Pipeline | Timeline (2067 total ms) | |----------------------------------------------------------------------------------------------------------| | AnalyzeContent | I | | Archives | PTO | | Assets | P O | | Content | P------T----------------------------------------------------------------------O | | Data | P O | | DirectoryMetadata | I-P | | Feeds | PO | | Inputs | I-P-- | | Redirects | PO | | Sitemap | TO | [INFO] ========== Completed ========== [INFO] Finished execution in 2092 ms [INFO] Cleaned temp directory: temp
dotnet run -- preview
を実行するとサーバーを起動し、静的ページにアクセスすることができます。
プレビュー実行中であればファイル変更を検知して、自動的に再読込してくれます。いわゆるホットリロードですね。
Razor構文のテンプレートも試してみます。
about
フォルダーを作成し、ASP.NET Core Razor Pages で使うレイアウトファイル等を置きます。
input/about/index.cshtml
@{ ViewData["Title"] = "This is about page"; } <div class="text-center"> <h1 class="display-4">Welcome</h1> <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p> </div>
レイアウトファイル + index.cshtml の HTML ファイルが出力されます。
このときページタイトルは index.cshtml にて動的に設定していますが、きちんとコードが実行されたうえでページタイトルになっています。
ViewData["Title"] = "This is about page";
↓
<title>This is about page - MySite</title>
1つのプロジェクトに Markdown 形式と Razor 構文のどちらも含めることができることがわかったので、シンプルな装飾で良ければ Markdown 形式、CSSクラス名を指定したり動的な値設定をしたい場合は Razor 構文にする、といった使い分け方になると思います。