PR13を出しました。
今回の新機能実装
細かいバグフィックス・リファクタリング
前回問題になっていたやつですね。ビルド時にフォントのファイルがコピーされずに取り残されてしまって、 これをホワイトリスト形式で対応していたら数が増えていくばかりなので、ブラックリスト形式に切り替えました。
その前に、設定ファイル or CLIの引数で引数で自由選択したかったのでOption
の見直しをしました。
変数名もopts
だったりoptions
だったりoptions
だったりしたので、この際に調整を行いました。
cliはmeowを使っているのですが、型はおまけ程度のもので、そこまで協力なものありません。なので、any
を扱っているような感じで取り扱います。
設計は次の通りです。
[CLIからの入力] --> [parser] --> [規格化されたパラメーター]
[規格化されたパラメーター]
はインターフェースを切っておきます。
cliの用途は2分できて、開発用、ビルド用の2つです。(もしかしたら、デプロイ用も出来るかもしれません)
interface Options {
develop?: DevelopOption; // 開発用
build?: BuildOption; // ビルド用
}
整理してみると、開発用とビルド用はほとんど同じ設定値を持っていました。
これをCommonOption
として切り出してextends
させておきます。
interface CommonOption {}
interface DevelopOption extends CommonOption {}
interface BuildOption extends CommonOption {}
また、命名において、複数形と単数形の使い方を明確にするようにしました。
I/FのOptions
は開発用とビルド用の2つを持っているため、複数に。
CommonOption
、DevelopOption
、BuildOption
は固有のドメインにおいてのオプションなので単数にしました。
変数名はI/Fの複数、単数に依存するようにし、略称を使わずに記述するようにしました。
// bad
function (opt: BuildOption);
// good
function (option: BuildOption);
// bad
function (opts: Options);
// good
function (options: Options);
他にも組み合わせがあると思いますが、方針はこれで決めです。
外部から指定できるオプションの優先度も決めて置く必要があります。 外から注入できる方法は3パターンあります。
"custom-site": {}
部分config.json
実装はおいおい見直して行こうかなぁ、という気分ですが、優先度と取扱方法は決めておくことにしました。 揮発性の高い書き方、ほとの依存度が高くなる場合は利用の優先度を下げていくようにすると
[CLIの引数] < [package.jsonのcustom-siteパラメータ] < `config.json`
という順番になりました。取り扱い方法は、優先度の高いものが、優先度の低いものをオーバーライドする形式を取りました。 spread operatorを利用するとこれが一瞬ででき、
{
...cliOption,
...pkgJsonOption,
...ConfigJsonOption,
}
といった具合に実装したかったのですが、config.json
は別の所で管理してしまっていてしまったー、という感じです。
config.json
はheadタグのデフォルト設定値などを取り扱っており、別のPRで修正しようと思います。
オプションの整理ができたので、I/Fを信じて実装箇所まで引っ張り回すだけです。 ブラックリストにも、拡張子やファイル名、正規表現が考えられますが、今回は拡張子のみに対応しました。
const isNotBlacklistPattern = (src: string, blacklist: CommonOption["blacklist"]) => {
return !blacklist.extensions.includes(path.extname(src));
};
ブラックリストに該当しない場合のみ、コピー対象とするようにできました。
Github Pagesにデプロイしてみたところ発覚したんですよね。
https://example.com
の要な場合はPRを入れる前の実装でよかったのですが、
https://example.com/a/b/c
のようにデプロイ先のベースパスが異なる場合はcssやjs、画像、aタグのリンクまで壊れてしまうんです。
で、ベースパス対応をするようにしました。これが思った以上に手ごわかったです。
https://example.com/hoge
= https://example.com/hoge/
= https://example.com/hoge/index.html
確認する対象がデバッグサーバー、ビルド後の簡易サーバーの2つで挙動を合わせる必要がありました。 (まだ完全にカバーできていないが...)
ここはまだゴリ押し実装をやっていて、
といった感じです。serverのコードに張り付いてしまっているのでテスタブルになるように分割しないとなー(棒)と思っています(やれ)。
ベースパス対応に伴って自動変換するようにしました。ここに一つのルールを決めておく必要がでてきました。
当初は./
から始まる参照を許容していたのですが、次のような理由から、すべての参照は絶対パスで参照するようにししました。
相対パスは脳内で変換しないといけないので、人間がやると間違える!だからレンダラー側で計算してもらおうという魂胆です。
これを行うにはいくつかのタグ内を書き換える必要があります。
<meta>
、<link>
、<script>
、<a>
が主に利用されているタグですが、
<head>
内にあるか<body>
内にあるかで実装方法が異なっていました。
<head>
内にある参照の書き換え文章を書いているファイルのヘッダーの設定値や、config.json
などに参照が記述されてるので、
それらのファイル読み込み語にベースパスを追加するように実装しました。
[draft.mdx] --> [読み込み] --> [処理] --> [レンダリング]
[config.json] --> [読み込み] --> [処理] --> [レンダリング]
図は楽ですが実装がやや複雑になってきたのでパイプライン構造にしたい。
<body>
内にある参照の書き換えこれはなかなか大変です。が、このジェネレータは簡単です。
カスタムコンポーネント機能を利用することが可能で、MarkdownからHTMLに変換するときに利用するa
タグの変換を自分で定義することが可能です。
const createBodyContent = transformRawStringToHtml({
customComponents: {
a: (props: JSX.IntrinsicElements["a"]): React.Element<any> => { /* 実装 */ };
},
props: {},
});
内部のhref
プロパティに対してベースパスを追加するようにすれば実装が完了です。
ただし、/^https?/
にマッチするようなパターンはそのまま利用するようにしています。
今回追加した実装によって、Markdown内の書き方がかなり楽になります。
index.mdx [1]
article/auto-link.mdx [2]
article/copy-assets-file.mdx [3]
とファイルがあった場合、[2]
から[1]
へ、[2]
から[3]
へリンクしたい場合
aritcle/auto-link.mdx内
[2 --> 1](../)
[2 --> 3](./copy-assets-file)
という書き方でレンダリング時に保管されるようになります。つまり、Markdownのファイルツリーからリンクを使えるようになるので、 エディターの恩恵(パス補完とか)を受けることが可能となります。
実際のリンクはこちら
次はカスタムテンプレート機能の実装をしていこうと思います。
どうやろうかな〜。