はじめに
RSSリーダーやポッドキャストアプリ、ブログの更新チェッカーを作ろうとして、フィードの解析処理で挫折しかけたことはありませんか?世の中のフィードにはRSS 2.0、Atom、RDF、JSON Feedと複数のフォーマットが混在しており、しかも仕様に厳密に従っていないフィードも少なくありません。「RSSはこのライブラリ、Atomは別のライブラリ、生成はまた別の……」とツールを継ぎ足していくうちに、コードはどんどん複雑になっていきます。
そんな悩みを一気に解決してくれるのが、今回紹介するFeedsmithです。パースも生成もこれひとつ。しかもJavaScript製フィードパーサーの中でも最速クラスという、頼もしいライブラリです。
Feedsmithとは
Feedsmithは「Fast, all-in-one JavaScript feed parser and generator」を掲げる、オールインワンのフィード処理ライブラリです。RSS・Atom・RDF・JSON Feedといった主要なフィード形式に加え、フィード購読リストの標準形式であるOPMLまで、解析と生成の両方をサポートしています。
TypeScriptで100%実装されており、2,000以上のテストとカバレッジ99%という品質の高さも魅力です。
主な特徴
- 幅広いフォーマット対応 - RSS (0.9x, 2.0)、Atom (0.3, 1.0)、RDF (0.9, 1.0)、JSON Feed (1.0, 1.1)、OPML (1.0, 2.0) をカバーします
- 30以上の名前空間サポート - Dublin Core、iTunes、Podcast Index、Media RSSなど、ポッドキャストやメディア系フィードで使われる拡張名前空間を型付きで扱えます
- 寛容なパース処理 - 大文字小文字の揺れやレガシーな要素、多少壊れたフィードも正規化して読み込んでくれます
- 高速 - JavaScriptフィードパーサーの中でも最速クラスのパフォーマンスを誇ります
- 完全な型定義 - TypeScriptの型が隅々まで用意されており、エディタの補完だけでフィードの構造を把握できます
インストール
npm、yarn、pnpmのいずれでもインストールできます。
# npm
npm install feedsmith
# yarn
yarn add feedsmith
# pnpm
pnpm add feedsmith
Node.js 14.0.0以降とモダンブラウザで動作し、CommonJSとESモジュールの両方に対応しています。
基本的な使い方
フォーマットを自動判定してパースする
一番手軽なのが parseFeed 関数です。フィードの形式を自動判定し、判定結果とパース済みデータの両方を返してくれます。
import { parseFeed } from 'feedsmith'
const response = await fetch('https://example.com/feed.xml')
const content = await response.text()
const { format, feed } = parseFeed(content)
console.log('フィード形式:', format) // 'rss' | 'atom' | 'json' | 'rdf'
console.log('タイトル:', feed.title)
取得先のフィードがRSSなのかAtomなのか事前にわからないケースでも、この関数ひとつで対応できます。
形式別のパーサーを使う
扱う形式が決まっている場合は、専用のパーサーを使うとより厳密な型でデータを受け取れます。
import { parseRssFeed, parseAtomFeed, parseJsonFeed } from 'feedsmith'
const rssFeed = parseRssFeed(rssContent)
// 型付きでプロパティにアクセスできます
console.log(rssFeed.title)
console.log(rssFeed.items?.[0]?.title)
// Dublin Coreなどの名前空間も型付きで扱えます
console.log(rssFeed.dc?.creator)
名前空間のデータが dc のような専用プロパティに正規化されている点がポイントです。フィードごとにプレフィックスがバラバラでも、Feedsmithが標準形式に揃えてくれます。
フィードを生成する
Feedsmithはパースだけでなく生成もできます。ブログやポッドキャストのフィード配信を自前で実装するときに便利です。
import { generateRssFeed } from 'feedsmith'
const rss = generateRssFeed({
title: '私の技術ブログ',
link: 'https://example.com',
description: 'Web開発の記録',
items: [
{
title: 'Feedsmithを使ってみた',
link: 'https://example.com/posts/feedsmith',
description: 'フィード処理が驚くほど簡単になりました。',
pubDate: new Date(),
},
],
})
console.log(rss) // RSS 2.0のXML文字列が出力されます
Atomなら generateAtomFeed、JSON Feedなら generateJsonFeed、OPMLなら generateOpml と、形式ごとに対応する関数が用意されています。
エラーハンドリング
認識できないコンテンツを渡すと例外が投げられるので、try...catch で囲んでおきましょう。
import { parseFeed } from 'feedsmith'
try {
const { format, feed } = parseFeed(content)
console.log(`${format}形式のフィードを読み込みました`)
} catch (error) {
console.error('フィードとして解析できませんでした:', error)
}
実践的なユースケース
複数ブログの新着記事をまとめて取得する
RSSリーダーの核となる処理を書いてみましょう。形式の異なる複数のフィードから新着記事を集め、日付順に並べる例です。
import { parseFeed } from 'feedsmith'
type Article = {
source: string
title: string
link: string
publishedAt?: string
}
async function fetchArticles(feedUrls: string[]): Promise<Article[]> {
const results = await Promise.allSettled(
feedUrls.map(async (url) => {
const response = await fetch(url)
const content = await response.text()
const { format, feed } = parseFeed(content)
const items =
format === 'json'
? feed.items?.map((item) => ({
source: feed.title ?? url,
title: item.title ?? '(無題)',
link: item.url ?? '',
publishedAt: item.date_published,
}))
: feed.items?.map((item) => ({
source: feed.title ?? url,
title: item.title ?? '(無題)',
link: item.link ?? '',
publishedAt: item.pubDate,
}))
return items ?? []
})
)
return results
.filter((r): r is PromiseFulfilledResult<Article[]> => r.status === 'fulfilled')
.flatMap((r) => r.value)
.sort((a, b) => (b.publishedAt ?? '').localeCompare(a.publishedAt ?? ''))
}
const articles = await fetchArticles([
'https://blog-a.example.com/rss.xml',
'https://blog-b.example.com/atom.xml',
])
console.log(articles.slice(0, 10))
Promise.allSettled を使うことで、一部のフィードが落ちていても残りの取得は続行されます。寛容なパーサーであるFeedsmithとの組み合わせで、現実の「行儀の悪いフィード」にも耐えるコードになります。
OPMLで購読リストをインポートする
RSSリーダーの購読リストは、OPMLという形式でエクスポート・インポートするのが慣例です。FeedsmithならOPMLの読み書きもそのまま書けます。
import { parseOpml, generateOpml } from 'feedsmith'
// 他のリーダーからエクスポートしたOPMLを読み込む
const opml = parseOpml(opmlContent)
const subscriptions = opml.body?.outlines?.map((outline) => ({
title: outline.text,
feedUrl: outline.xmlUrl,
})) ?? []
console.log(`${subscriptions.length}件の購読をインポートしました`)
// 逆に、購読リストをOPMLとしてエクスポートする
const exported = generateOpml({
head: { title: '私の購読リスト' },
body: {
outlines: subscriptions.map((sub) => ({
text: sub.title ?? '',
xmlUrl: sub.feedUrl,
})),
},
})
パースと生成が同じライブラリで完結するので、インポート機能とエクスポート機能を対称に実装できるのが気持ち良いところです。
まとめ
Feedsmithの特徴と使い方を紹介しました。ポイントを振り返ります。
- RSS・Atom・RDF・JSON Feed・OPMLを1つのライブラリでパース・生成できます
parseFeedによる形式の自動判定で、雑多なフィードをまとめて処理できます- 30以上の名前空間を型付きで扱え、ポッドキャストなど拡張フィードにも強いです
- 寛容なパース処理と最速クラスのパフォーマンスを両立しています
フィード処理は「枯れた技術」に見えて、実際に触ると形式の乱立と仕様のゆるさに悩まされる領域です。Feedsmithはその面倒な部分をまるごと引き受けてくれます。RSSリーダーやポッドキャストツール、ブログのフィード配信を作る機会があれば、まずFeedsmithを候補に入れてみてください。
