Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import mdx from "@astrojs/mdx";
import react from "@astrojs/react";
import sitemap from "@astrojs/sitemap";
import { defineConfig } from "astro/config";
import remarkPrefixBaseUrl from "./plugins/remark/prefixBaseUrl";

const site = process.env.NETLIFY
? process.env.DEPLOY_URL
Expand All @@ -12,6 +13,9 @@ export default defineConfig({

markdown: {
syntaxHighlight: false, // 使い方の途中とかに小さいコードブロックがあるだけなのでハイライトは無い方が良い
remarkPlugins: [
[remarkPrefixBaseUrl, { baseUrl: site }],
],
},

integrations: [
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,10 @@
"esbuild",
"sharp"
]
},
"devDependencies": {
"@types/mdast": "^4.0.4",
"unified": "^11.0.5",
"unist-util-visit": "^5.0.0"
}
}
102 changes: 102 additions & 0 deletions plugins/remark/prefixBaseUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import type { Root, Link, LinkReference, Definition } from "mdast";
import type { Plugin } from "unified";
import { visit } from "unist-util-visit";

/**
* プラグインオプション
* - baseUrl: 前置するベースURL(例: https://example.com)
*/
export interface PrefixBaseUrlOptions {
baseUrl: string;
}

/* ==================================================================
URL helpers(純関数)
================================================================== */
/**
* 前置のスキップ対象を判定
* - スキーム付き: http:, https:, mailto:, tel:, data: など
* - プロトコル相対: //example.com
* - ハッシュのみ: #section
* - クエリのみ: ?foo=bar
*/
const shouldSkipPrefixing = (href: string): boolean =>
/^(?:[a-zA-Z][\w+.-]*:|\/\/|#|\?)/.test(href);

/**
* base の末尾スラッシュを落として正規化
*/
const normalizeBase = (base: string): string => base.replace(/\/+$/, "");

/**
* 正規化済み base と相対/ルート相対 href を結合
* - new URL(href, base + '/') による相対解決
* - 失敗時はフォールバックで単純結合
*/
const resolveWithBase = (href: string, normalizedBase: string): string => {
try {
return new URL(href, normalizedBase + "/").toString();
} catch {
return `${normalizedBase}/${String(href).replace(/^\/+/, "")}`;
}
};

/* ==================================================================
共通ロジック:Link / Definition 用
================================================================== */
// 後述のprefixUrl()に使用
type UrlNode = Link | Definition;

/**
* url を base で前置する
* - 型は Link | Definition に限定
*/
const prefixUrl = (node: UrlNode, normalizedBase: string): void => {
const href = node.url.trim();
if (href && !shouldSkipPrefixing(href)) {
node.url = resolveWithBase(href, normalizedBase);
}
};

/* ==================================================================
プラグイン本体
================================================================== */
/**
* 1) インラインの Link を前置
* 2) 本文に出現した LinkReference の identifier を収集
* 3) その identifier と一致する Definition だけ前置(= 参照画像の Definition は変更しない)
*/
const remarkPrefixBaseUrl: Plugin<[PrefixBaseUrlOptions], Root> = (options) => {
const rawBase = options.baseUrl?.trim();
if (!rawBase) throw new Error("baseUrl must be a non-empty string");

const normalizedBase = normalizeBase(rawBase);

return (tree: Root) => {
// 1) インライン記法のリンク([text](href))
visit(tree, "link", (node: Link) => {
prefixUrl(node, normalizedBase);
});

// 2) 本文で使われている LinkReference の id を収集
const linkIds = new Set<string>();
visit(tree, "linkReference", (ref: LinkReference) => {
// identifier は mdast 側で正規化済み(大小文字/空白)
linkIds.add(ref.identifier);
});

// 3) 参照リンクに使われる Definition だけ処理(参照画像の Definition はスルー)
if (linkIds.size > 0) {
visit(tree, "definition", (def: Definition) => {
if (linkIds.has(def.identifier)) {
prefixUrl(def, normalizedBase);
}
});
}
};
};

export default remarkPrefixBaseUrl;

// ユニットテストで利用するため公開
export { normalizeBase, resolveWithBase, shouldSkipPrefixing, prefixUrl };
Loading
Loading