1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeScript × Vite でハッシュタグ機能を実装

Posted at

はじめに

ハッシュタグは、SNS などで広く利用される、話題やカテゴリを示すキーワードです。

本記事では、Unicode Standard で規定されているハッシュタグの仕様 を元に、ハッシュタグ機能の実装します。この仕様はハッシュタグの共通的な処理を促進する一方で、実装の自由度も一定程度確保しています。

ハッシュタグの仕様

ハッシュタグの仕様について簡単に説明します。

ハッシュタグの構文
<Hashtag-Identifier> := <Start> <Continue>* (<Medial> <Continue>+)*


`:=` : 右辺が左辺の構文を定義
`<>` : 定義された構文
`*`  : 直前の構文が 0 回以上繰り返されることを示す
`+`  : 直前の構文が 1 回以上繰り返されることを示す
`()` : グループ化を表す

Start    : ハッシュタグの開始文字
Continue : 続く文字
Medial   : オプションで使用できる文字

ハッシュタグの仕様には、さまざまなバリエーションが書かれていますが、今回は以下のようにいします。

  • Start 文字は #
  • Continue 文字には XID_Continue プロパティに Extended_Pictographic プロパティ、Emoji_Component プロパティ、_-+ を加えたものを含め、# を除外
  • Medial は空

ハッシュタグを解析する際には、Start 文字の前に Continue 文字がない場合にのみハッシュタグとして認識することが推奨されます。例えば、foo#bar ではハッシュタグは認識されませんが、foo #barfoo.#bar ではハッシュタグとして認識されます。

ハッシュタグの仕様について詳しくは以下の記事で説明しています。

ハッシュタグ機能の実装

Vite による開発環境構築

Vite とは

Vite は、2020 年にリリースされた高速な開発環境を提供するフロントエンド・ビルド・ツールで、オリジナルの開発者は Evan You 氏( Vue.js の作者)です。Vite は、ビルドシステムとしての役割以外にも開発サーバとしての役割も果たします。

互換性について
Vite は Node.js 18+、20+ のバージョンが必要です。ただし、一部のテンプレートではそれ以上のバージョンの Node.js を必要としますので、パッケージマネージャが警告を出した場合はアップグレードしてください。
はじめに | Vite

nodenpm を使用できるか確認してください。

ターミナル
$ node -v
v20.9.0
$ npm -v
10.1.0

開発環境構築

  1. ターミナル開き、デスクトップなどの任意のディレクトリに移動
  2. npm init vite@latest コマンドを実行
    もし、以下のようなテキストが表示された場合は y を入力
    Need to install the following packages:
    create-vite@5.3.0
    Ok to proceed? (y)
    
  3. Project name の項目で任意のプロジェクト名を入力
    今回は、プロジェクト名を hashtag とします。
  4. Select a framework の項目で Vanilla を選択
  5. Select a variant の項目で TypeScript を選択
ターミナル
$ npm init vite@latest
? Project name: › hashtag
? Select a framework: › - Use arrow-keys. Return to submit.
❯   Vanilla
? Select a variant: › - Use arrow-keys. Return to submit.
❯   TypeScript
  1. hashtag ディレクトリに移動
  2. npm install コマンドを実行し、package.json ファイル内に記述されたパッケージ( TypeScript など)を一括でインストール(node_modules ディレクトリ と package-lock.json も同時に作成)
  3. npm run dev コマンドを実行し、開発サーバを起動
  4. Web ブラウザで http://localhost:5173/ にアクセスし、Vite + TypeScriptと書かれたページが表示される確認。
ターミナル
$ cd hashtag
$ npm install
$ npm run dev

以上で、開発環境の構築が完了しました。

実装

foo #bar #foo#foo #b🦰ar foo.#bar と入力すると、

#bar #foo#foo #b🦰ar foo.#bar

のようにハッシュタグとして認識される部分が青色に色付けされる機能を実装します。

index.html

index.html は、Vite + TypeScript で構築されたフロントエンドアプリケーションのエントリポイントとして機能します。

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + TS</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
  • <div id="app"></div>
    body 内の要素はシンプルで、id 属性が app の div タグだけです。
  • <script type="module" src="/src/main.ts"></script>
    Vite は、src ディレクトリにある main.ts ファイルをエントリポイントとして、JavaScript コードをバンドルし、実行します。

main.ts の編集

main.ts ファイルを以下のように編集します。

src/main.ts
import { setupHashtag } from './hashtag.ts'

let app = document.querySelector<HTMLDivElement>('#app')!;
setupHashtag(app);

hashtag.ts の編集

counter.ts ファイルのファイル名を hashtag.ts に変更し、ファイル内のコードを削除します。

型を定義

src/hashtag.ts
type TokenType = 'hashtag' | 'nonHashtag';

type Token = {
    type: TokenType;
    value: string;
};

正規表現を追記

src/hashtag.ts
const Start = /#/;
const Continue = /\p{XID_Continue}|\p{Extended_Pictographic}|\p{Emoji_Component}|[_+-]/u;

setupHashtag 関数を追記

src/hashtag.ts
export function setupHashtag(element: HTMLDivElement) {

    let textarea = document.createElement('textarea');
    element.appendChild(textarea);

    let output = document.createElement('div');
    element.appendChild(output);


    textarea.addEventListener('input', (e) => {
        const { target } = e;

        if (!(target instanceof HTMLTextAreaElement)) {
          return;
        }

        let result: Token[] = tokenize(target.value);

        render(output, result);
    })
}

tokenize 関数を追記

src/hashtag.ts
// 以下の仕様を実装
// - <Hashtag-Identifier> := <Start> <Continue>*
// - Start の前に Continue がない場合にのみハッシュタグとして認識
function tokenize(text: string): Token[] {
    const tokens: Token[] = [];
    let currentToken = '';
    let inHashtag = false;

    function addToken(type: TokenType, value: string) {
        tokens.push({ type, value });
    }

    for (const char of text) {
        if (Start.test(char)) {
            if (inHashtag) {
                addToken('hashtag', currentToken);
                inHashtag = false;
                currentToken = char;
            }else{
                if (currentToken.length > 0) {
                     addToken('nonHashtag', currentToken);
                }
                inHashtag = true;
                currentToken = char;
            }
        } else if (Continue.test(char)) {
            currentToken += char;
        } else {
            if (inHashtag) {
                addToken('hashtag', currentToken);
                inHashtag = false;
                currentToken = char;
            } else{
                currentToken += char;
            }
        }

    }

    // 最後の部分を処理
    if (currentToken) {
        addToken(inHashtag ? 'hashtag' : 'nonHashtag', currentToken);
    }

    return tokens;
}

render 関数を追記

src/hashtag.ts
function render(element: HTMLDivElement, tokens: Token[]) {
    element.innerHTML = '';

    tokens.forEach(token => {
        if (token.type === 'hashtag') {
            const span = document.createElement('span');
            span.style.color = '#blue';
            span.textContent = token.value;
            element.appendChild(span);
        } else {
            const textNode = document.createTextNode(token.value);
            element.appendChild(textNode);
        }
    });
}

動作確認

ブラウザで textarea に foo #bar #foo#foo #b🦰ar foo.#bar と入力し、

#bar #foo#foo #b🦰ar foo.#bar

と表示されるか確認してください。

おわりに

本記事で構築したハッシュタグ機能は、基本的な機能を実装していますが、まだ実装していない機能があります。それは、ハッシュタグの NFKC_CF 形式への変換です。

仕様では、比較やマッチングは NFKC_CF 形式へ変換した後に行う必要があると記述されています。例えば、#MötleyCrüe#MÖTLEYCRÜE と一致する必要があります。

今回は色付けの機能のみを実装しましたが、文字を正規化することでより検索機能を強化することができます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?