VS CodeとDockerである程度快適なZennの執筆環境を構築する
これはなに
VS CodeとDockerでZennの記事の執筆環境を構築したときのメモ。
つくった環境
- zenn cliを利用して、ローカルでプレビューしながら記事を執筆する
- textlintで内容を校正する
- zennのためのスニペットを使えるようにする
- GitHubにpushしてZennに記事を公開する
- 以上の環境をDockerで構築する
ホスト環境
- Visual Studio Code (VS Code)
- Windows 11 + WSL2 (Ubuntu)
- Docker Desktop for Windows
VS CodeとDockerが使えれば、上記の環境でなくても動くと思われる。
前提条件
- VS Codeをインストールしている
- Dockerを使えるようにしている
VS Codeに拡張機能を入れる
VS CodeにDev Containersという拡張機能を入れる。
Dockerコンテナをつくるための準備をする
拡張機能Dev Containersを利用して、Dockerfileをビルドしアタッチする。
まず、.devcontainer
ディレクトリを作成し、その下にDockerfile
とdevcontainer.json
ファイルを作成する。
.devcontainer
├── Dockerfile
└── devcontainer.json
Dockerfile
次に、作成したDockerfile
に以下を記述する。Dockerfile
では、localeとtimezoneを日本に設定し、git、zenn cli、textlint(+ルール)をインストールする。
ARG NPM_VERSION="9.7.1"
ARG ZENN_CLI_VERSION="0.1.143"
FROM node:18-bullseye-slim
EXPOSE 8000
WORKDIR /workspaces/
# Set environment variables for locale and timezone
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ Asia/Tokyo
# Install packages, set locale and timezone in a single RUN command
RUN apt update -y \
&& apt install -y --no-install-recommends \
locales \
git \
ca-certificates \
&& sed -i -e 's/# \(ja_JP.UTF-8\)/\1/' /etc/locale.gen \
&& locale-gen \
&& ln -sf /usr/share/zoneinfo/$TZ /etc/localtime \
&& rm -rf /var/lib/apt/lists/*
# Install zenn-cli
RUN npm install -g npm@${NPM_VERSION} \
&& npm init --yes \
&& npm install zenn-cli@${ZENN_CLI_VERSION}
# Install textlint
RUN npm install --save-dev \
textlint \
textlint-rule-preset-ja-technical-writing \
textlint-rule-no-mixed-zenkaku-and-hankaku-alphabet \
textlint-rule-prefer-tari-tari \
@textlint-ja/textlint-rule-no-insert-dropping-sa \
textlint-rule-ja-no-orthographic-variants \
textlint-rule-preset-ja-spacing \
textlint-rule-ja-no-inappropriate-words \
@textlint-ja/textlint-rule-no-dropping-i \
@textlint-ja/textlint-rule-no-insert-re \
textlint-rule-no-doubled-conjunction \
textlint-rule-ja-hiragana-keishikimeishi \
textlint-rule-ja-hiragana-fukushi \
textlint-rule-ja-hiragana-hojodoushi \
@textlint-ja/textlint-rule-no-synonyms sudachi-synonyms-dictionary \
textlint-filter-rule-allowlist
ENV PATH $PATH:/workspaces/zenn-docs/scripts/
USER node
Dockerfile
において、node
、npm
、zenn cli
のバージョンは、それぞれ最新のものにするとよい。
devcontainer.json
続けて、作成したdevcontainer.json
に以下の内容を記述する。
{
"name": "zenn-docs",
"build": {
"dockerfile": "./Dockerfile",
"context": ".."
},
"forwardPorts": [
8000
],
"postCreateCommand": "chmod -R +x scripts",
"customizations": {
"vscode": {
"extensions": [
"taichi.vscode-textlint"
]
}
}
}
執筆環境に追加するVS Codeの拡張機能を指定する
devcontainer.json
のextensions
でVS Codeの拡張機能を指定すると、その拡張機能が必ず自動でDockerコンテナへインストールされる。
{
// ...
"customizations": {
"vscode": {
"extensions": [
"taichi.vscode-textlint"
]
}
}
}
また、.vscode/extensions.json
に以下のように拡張機能を指定すると、コンテナへアタッチした際に推奨される拡張機能(@recommended
)として認識される。
{
"recommendations": [
"mohammadbaqer.better-folding",
"johnharrison.bongocat-buddy",
"streetsidesoftware.code-spell-checker",
"igorsbitnev.error-gutters",
"saikou9901.evilinspector",
"mhutchie.git-graph",
"donjayamanne.githistory",
"fabiospampinato.vscode-highlight",
"oderwat.indent-rainbow",
"yzhang.markdown-all-in-one",
"davidanson.vscode-markdownlint",
"gera2ld.markmap-vscode",
"christian-kohler.path-intellisense",
"gruntfuggly.todo-tree",
"shardulm94.trailing-spaces",
"tonybaloney.vscode-pets"
]
}
これらのうち好きな方法で必要な拡張機能を指定する[1]。
textlintの設定ファイルを追加する
.textlintrc
ファイルを作成し、textlint
を実行するためのルールの設定を記述する。
ところどころZenn独自の記述のためのルールを追加している。
{
"filters": {
"allowlist": {
"allow": [
"/:*details (.*?)/"
]
}
},
"rules": {
"preset-ja-technical-writing": {
"no-exclamation-question-mark": false,
"ja-no-mixed-period": {
"allowPeriodMarks": [
":::",
"::::"
],
"checkFootnote": true
},
"sentence-length":{
"skipPatterns":[
"/:{3,}message/",
"/:{3,}message alert/",
"/:{3,}details/",
"/:{3,}",
"/\\[(.*?)\\]\\((.*?)\\)/"
]
}
},
"no-mixed-zenkaku-and-hankaku-alphabet": true,
"prefer-tari-tari": true,
"@textlint-ja/textlint-rule-no-insert-dropping-sa": true,
"ja-no-orthographic-variants": true,
"preset-ja-spacing": true,
"ja-no-inappropriate-words": true,
"@textlint-ja/textlint-rule-no-dropping-i": true,
"@textlint-ja/textlint-rule-no-insert-re": true,
"no-doubled-conjunction": true,
"ja-hiragana-keishikimeishi": true,
"ja-hiragana-fukushi": {
"rulePath": "./fukushi.yml"
},
"ja-hiragana-hojodoushi": true,
"@textlint-ja/no-synonyms": true
}
}
新規作成のためのスクリプトを用意する
zenn cliでは新規作成のコマンドが用意されているが、個人的なこだわりを反映できるスクリプトを別途用意する。
scripts
ディレクトリを作成し、そこにスクリプトファイルを作成していく。
新規記事作成スクリプト
zenn cliでは、新規記事は次のnpx
コマンドで生成できる。
npx zenn new:article --slug my-first-article --title タイトル --type tech
しかし、個人的にslugは英数字とハイフンのみ(アンダーバーを許可しない)ようにしたかったため、以下のarticle
スクリプトを作成した。
#!/bin/bash
# 関数定義
set -euo pipefail
error() {
local exit_code="${1:-1}" # Default exit status 1
shift
printf "\033[31;1m%s\033[0m\n" "!!!ERROR!!! $@" >&2
exit "$exit_code"
}
# 引数をチェックする
set -eo pipefail
if [ -z "${1-}" ]; then
error 1 "引数が1つ必要です。"
fi
set -euo pipefail
readonly SLUG=$1
# 引数がパターンに一致するかチェックする
# 小文字の半角英数字(a-z0-9)またはハイフン(-)の12〜50字の組み合わせ
readonly PATTERN="^[a-z0-9-]{12,50}$"
if ! [[ "${SLUG}" =~ $PATTERN ]]; then
error 2 "slugの値(${SLUG})が不正です。小文字の半角英数字(a-z0-9)またはハイフン(-)の12〜50字の組み合わせにしてください。"
fi
# すでに存在するかチェック
readonly FILE_PATH=articles/"${SLUG}.md"
if [ -e "$FILE_PATH" ]; then
error 3 "そのsulgの記事はすでに存在しています。"
fi
# 記事を作成する
npx zenn new:article --slug "${SLUG}" --title タイトル --type tech
次のように利用する。
article orig-slug
新規本作成スクリプト
zenn cliでは、新規本は次のnpx
コマンドで生成できる。
npx zenn new:book --slug my-first-book
しかし、個人的にslugは英数字とハイフンのみ(アンダーバーを許可しない)ようにしたいのと、デフォルトの構成をカスタマイズしたかったため、以下のbook
スクリプトを作成した。
#!/bin/bash
# 関数定義
set -euo pipefail
error() {
local exit_code="${1:-1}" # Default exit status 1
shift
printf "\033[31;1m%s\033[0m\n" "!!!ERROR!!! $@" >&2
exit "$exit_code"
}
# 引数をチェックする
set -eo pipefail
if [ -z "${1-}" ]; then
error 1 "引数が1つ必要です。"
fi
set -euo pipefail
readonly SLUG=$1
# 引数がパターンに一致するかチェックする
# 小文字の半角英数字(a-z0-9)またはハイフン(-)の12〜50字の組み合わせ
readonly PATTERN="^[a-z0-9-]{12,50}$"
if ! [[ "${SLUG}" =~ $PATTERN ]]; then
error 2 "slugの値(${SLUG})が不正です。小文字の半角英数字(a-z0-9)またはハイフン(-)の12〜50字の組み合わせにしてください。"
fi
# すでに存在するかチェック
readonly BOOK_DIR_PATH=books/"${SLUG}"
if [ -d "$BOOK_DIR_PATH" ]; then
error 3 "そのsulgの本はすでに存在しています。"
fi
# templatesから本を生成する
if ! mkdir "${BOOK_DIR_PATH}"; then
error 4 "ディレクトリの作成に失敗しました : ${BOOK_DIR_PATH}"
fi
if ! cp -r templates/book/* "${BOOK_DIR_PATH}"/; then
error 5 "ファイルのコピーに失敗しました : templates/book/* to ${BOOK_DIR_PATH}/"
fi
本のテンプレートファイルは以下の構造で作成した。
.
├── scripts
│ └── book
└── templates
└── book
├── config.yaml
├── about.md
└── references.md
title: ""
summary: ""
topics: [] # (5つまで)
published: false
toc_depth: 2
price: 0 # 有料の場合200〜5000で100円単位
chapters:
- about
- references
---
title: "はじめに | これはなに"
free: true
---
---
title: "参考文献・URL"
free: true
---
次のように利用する。
book orig-slug
プレビュー表示スクリプト
zenn cliでは、新規本は次のnpx
コマンドでプレビューを表示できる。
npx zenn preview
scripts
ディレクトリにpreview
スクリプトを作成すると、npx zenn preview
の代わりにpreview
だけでプレビューを表示できるので楽である。
#!/bin/bash
set -euo pipefail
npx zenn preview
次のように利用する。
preview
記事のためのスニペットを作成する
Zenn独自のMarkdown記法をスニペットとして登録しておく。
作成したスニペット(長いのでたたむ)
今回はワークスペースにだけスニペットを登録するようにした。
{
"zenn_image": {
"scope": "markdown",
"prefix": "zenn_img",
"body": [
"![$1]($2 =450x)",
"*$1*",
],
"description": "Image in zenn.",
},
"zenn_image-url": {
"scope": "markdown",
"prefix": "zenn_img-url",
"body": [
"![$1]($2 =450x)(link)",
"*$1*",
],
"description": "Image with link in zenn.",
},
"zenn_codeblock": {
"scope": "markdown",
"prefix": "zenn_codeblock",
"body": [
"```$1:$2",
"$3",
"```",
],
"description": "Code block with filename in zenn.",
},
"zenn_codeblock-diff": {
"scope": "markdown",
"prefix": "zenn_codeblock-diff",
"body": [
"```diff $1:$2",
"$3",
"```",
],
"description": "Code block with filename and diff in zenn.",
},
"zenn_message_warning": {
"scope": "markdown",
"prefix": "zenn_message_warning",
"body": [
":::message",
"$1",
":::",
],
"description": "Warning message in zenn.",
},
"zenn_message_alert": {
"scope": "markdown",
"prefix": "zenn_message_alert",
"body": [
":::message alert",
"$1",
":::",
],
"description": "Alert message in zenn.",
},
"zenn_details": {
"scope": "markdown",
"prefix": "zenn_details",
"body": [
":::details $1",
"$2",
":::",
],
"description": "Details in zenn.",
},
"zenn_url_card": {
"scope": "markdown",
"prefix": "zenn_url_card",
"body": [
"@[card]($1)",
],
"description": "Url card in zenn.",
},
"zenn_github": {
"scope": "markdown",
"prefix": "zenn_github",
"body": [
"$1#L1-L3",
],
"description": "GitHub text in zenn.",
},
"zenn_title": {
"scope": "markdown",
"prefix": "zenn_title",
"body": [
"---",
"title: \"$1\"",
"---",
"",
"# $2",
],
"description": "Article title in zenn.",
},
"zenn_published_at": {
"scope": "yaml",
"prefix": "zenn_published_at",
"body": [
"published_at: $CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE 08:00",
],
"description": "Add time of publication to zenn article.",
},
}
Dockerコンテナをビルドしアタッチする
VS Codeで"Ctrl+p"を押してコマンドパレットを開き、Dev Containers: Open Folder in Container
を実行する。これにより、devcontainer.json
に従って自動でDockerfile
がビルドされ、作成されたコンテナにVS Codeがアタッチされる。
Zenn cliのセットアップを実行する
最初だけ、zenn cliのセットアップコマンドを実行する。
npx zenn init
すると、articles
やbooks
など必要なファイル群が生成される。
動作確認
記事を作る
article my-first-article
本を作る
book my-first-book
プレビューする
preview
http://localhost:8000/へアクセスすると、プレビューが表示される。
GitHubとZennを連携する
Zenn公式の出している記事に従って、GitHubにリポジトリを作成し、Zennと連携しておく。
連携に成功すると以下の画面になる。
GitHubとZennの連携に成功した画面
GitHubにpushする
ここまで作成したローカルプロジェクトをgit管理下に置く。
git init
これまでの変更をadd
してcommit
する。下記は例。
git add .
git commit -m ":tada: Initial commit"
リモートリポジトリを先ほどZennと連携したリポジトリに設定し、pushする。
git remote add origin https://github.com/NakuRei/zenn-docs.git
git branch -M main
git push -u origin main
初回以降はgit push
だけでリモートリポジトリにpush
できる。
新しい記事を作成してpush
し、Zennにデプロイされていれば成功。
参考文献・URL
-
ちなみに、筆者は環境に必須の拡張機能だけ
devcontainer.json
のextensions
に記述し、任意の拡張機能は.vscode/extensions.json
に記述している。 ↩︎
Discussion