Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

はてなブログで tsx コードをシンタックスハイライトする方法

追記: 公式が対応しました!

staff.hatenablog.com

tsx 対応がついに入りました。そのため以下のコードは不要になりました。

追記終わり。以下元々の記事の内容になります。

実際にシンタックスハイライトされてるところ

const Component = () => {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(n => n+1)}>{count} times clicked!</button>
  );
};

↑ こんな感じで、tsx がハイライトされます!!!!

やり方: 以下のコードを貼り付ける

デザイン設定 → ヘッダー → ブログタイトル下 に以下のコードを貼り付ければ OK です

↓貼り付ける位置

↓貼り付けるコード

<style type="text/css">
.entry-content pre,.entry-content code{
    font-family: "Source Code Pro", sans-serif;
    line-height: 1.2;
  }
.entry-content pre:not(.code){
    background: transparent;
}
</style>

<!-- 以下は tsx のシンタックスハイライト用のコード -->
<code class="language-tsx" style="display: none"></code>
<script src="https://unpkg.com/prismjs@1.29.0/components/prism-core.min.js"></script>
<script src="https://unpkg.com/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
<script>
  let count = 0;
  const run = () => {
    if (!(Prism && Prism.languages && 'tsx' in Prism.languages)) {
      ++count;
      if (count < 1e5) {
        setTimeout(run, 5);
      }
      return
    }
    for (const $el of document.querySelectorAll('pre.code.tsx'))
      $el.innerHTML = Prism.highlight($el.innerText, Prism.languages.tsx, 'tsx')
  }
  run();
</script>
<link href="https://unpkg.com/prismjs@1.29.0/themes/prism-tomorrow.css" rel="stylesheet" />
<style>.token.keyword{ color: #cc99cd!important; }</style>

あとは下みたいに↓普通に tsx コードブロックを書けばシンタックスハイライトされます。

これで OK ↓

```tsx
render(<App />)
```

つまり、 Markdown 側に変な小細工をする必要が一切ありません。 zenn など他のサービスで書いた記事を転載しても tsx がハイライトされてくれます。うれしいですね…!!!

解説

上のコードが何をしてるかというと、

  1. prism というシンタックスハイライトのライブラリを読み込む
  2. prism が <code class="language-tsx" style="display: none"></code> をハイライトしようとする
  3. prism が tsx をシンタックスハイライトするために必要なものを動的にダウンロードし始める
  4. ダウンロードが完了するまで待つ (setTimeout(run, 5); あたりのコード)
  5. tsx をシンタックスハイライトする準備ができたら Prism.highlight を使って tsx のコードブロックをハイライトする

という感じです。難しいことはしてません。

テーマを変えたい場合

prism はいくつかテーマを提供しています。

https://unpkg.com/browse/prismjs@1.29.0/themes/

ここから選んで <link href="https://unpkg.com/prismjs@1.29.0/themes/prism-tomorrow.css" rel="stylesheet" /> の部分を差し替えてください。また、<style>.token.keyword{ color: #cc99cd!important; }</style> の部分もテーマに合わせて変えてください。

終わりに

こんなハックをユーザーに強いないで、早く tsx シンタックスハイライトに対応してください はてなさん!!!!!!

MGS (マジで業務で使うシェルスクリプト)

MGS(マジ業務使いのシェルスクリプト)がまあまああるのでまとめる。いま思いついたものだけ書いてるが、思い出したら追記するかも。

説明 コマンド
スペース埋め出力から特定列を抜き出す | awk '{print $1}'
先頭から特定の文字までのみ表示 | cut -f 1 -d :
先頭の x 文字だけ抜き出す | awk '{print substr($0, 0, 30)}'
x文字ごとに改行する | fold -40
特定文字を改行に変える | tr ':' '\n'

以下各コマンドの使用例とコマンド

スペース埋め出力から特定列を抜き出す | awk '{ print $3 }

5       6       npmrc
14      8       tmux.conf
7       2       zsh/alias.zsh

↑こんな感じになってるとき、3列目だけ抜き出すには | awk '{ print $3 } でいけます

git diff --numstat | awk '{print $3}'

先頭から特定の文字までのみ表示 | cut -f 1 -d :

article を含むラインから article_id を含むラインを除いて、ファイル一覧に変換する

rg article | rg -v 'article_id' | cut -f 1 -d : | sort | uniq

先頭の x 文字だけ抜き出す | awk '{print substr($0, 0, 30)}'

minify されたファイルのように1行が膨大なものがマッチしたときに先頭30文字だけ見るときとかに使う(たまにこういうシーンがある)

rg x | awk '{print substr($0, 0, 30)}'

x文字ごとに改行する | fold -40

minify されたファイル同士を diff 取るとき、折り返しておくとうまく diff を取れることがある(取れないこともある)

diff $(cat dist/main.js | fold -40) $(cat dist2/main.js | fold -40)

特定文字を改行に変える | tr ':' '\n'

$PATH が見やすくなる

echo $PATH | tr : \\n

ワンライナーでバージョン文字列を比較する

結論: localeCompare を使う

a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'})

参考: https://stackoverflow.com/a/65687141

localeCompare は IE11 でも使えるので、使えない心配はほぼありません。

しかし、locale とついているためブラウザ実装依存ではないか? と不安になりますよね。今回はそこを調べてみました。

まあ、結論から言うと 実装依存っぽいので自身のサポート環境でしっかり検証してから使う必要があります。

localeCompare は何をするメソッドなのか

まず localeCompare とはどういうメソッドなのか、ecmascript の仕様書 から引用しつつ説明します。

This method returns a Number other than NaN representing the result of an implementation-defined locale-sensitive String comparison of the this value (converted to a String S) with that (converted to a String thatValue).

つまり、localeCompare は呼び出し元(this)と引数(b)を比較して NaN 以外の数値を返すメソッドだそうです。

文字列の順序は実装に依存しています。また、返す値も negative か 0、plus という大雑把な具合にしか決まってなさそうです。仕様を読みましたが、それ以上の説明はありません。結構昔に導入されてブラウザ間の差異が激しくて仕様がフワッとしている…?

環境差異は大丈夫なのか

以下のスクリプトを Node v16.0.0、Chrome112.0.5615.137、Safari 15.6.1 で検証してみましたが全て true になりました。なので おそらく問題ないです (IE で確認したいけど手元の環境にない…)。false になる環境あったら教えてください。

console.log(
  '0.0.0'.localeCompare('0.0.1', undefined, { numeric: true, sensitivity: 'base' }) < 0 &&
  '0.0.1'.localeCompare('0.0.0', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.0'.localeCompare('0.0.0', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.1'.localeCompare('1.0.0', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.0'.localeCompare('0.1.0', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.0'.localeCompare('0.2.3', undefined, { numeric: true, sensitivity: 'base' }) > 0 &&
  '1.0.0'.localeCompare('1.2.3', undefined, { numeric: true, sensitivity: 'base' }) < 0 &&
  '10.0.0'.localeCompare('9.1.2', undefined, { numeric: true, sensitivity: 'base' }) >0
)

↑これくらい満たせていれば流石に大丈夫だと思います。

配列でも使える

this は string へ変換されるので、 apply を使えば配列に対しても使えます。

↓こんな感じ

''.localeCompare.apply([1, 0, 0], [[0, 0, 1], undefined, { numeric: true }])

// bind を使うとこんな感じ
''.localeCompare.bind([1, 0, 0])([0, 0, 1], undefined, { numeric: true })

// bind operator が使えるとこんな感じ
[1, 0, 0]::''.localeCompare([0, 0, 1], undefined, { numeric: true })

dein.vim から packer.nvim に乗り換える

lua でファイル管理したいなぁ…という気分になったので乗り換えることにしました。今のところそんなに違いは感じてないです

(まだ書き換えてる途中なので以下の記述はミスってるかもしれないです)

migration guide

そもそも、 packer.nvim でインストールする方法が書いてあるプラグインが多いので、まず GitHub リポジトリをみましょう。 dein.vim についてはインストール方法書いてないのに packer.nvim はあるとか結構あります。

次に、Lua-guide - Neovim docs を読みましょう。neovim 公式が lua の書き方を解説していてわかりやすいです。日本語資料もあります。この2つを読めば大体かけます。

それでも足りなかった時用に、以下に tips をまとめます

  • hook_add は setup に置き換える
  • hook_source は config に置き換える
  • nnoremap は vim.keymap.set('n', 'key', 'command') に変える
  • let g:hoge =vim.g.hoge = に直す
    • どうしようもない場合は vim.cmd [[ let g:hoge = xxx ]] でいける(他の場合も最悪 vim.cmd [[ ]] で囲めば OK)
  • nmap は vim.keymap.set の第四引数に {remap = true} を渡す以外は nnoremap と同じ
  • on_cmd は cmd に直す
  • on_event は event に直す

たとえば deinlazy.toml にこう書いてあったとする

[[plugins]]
repo = 'nvim-telescope/telescope.nvim'
on_cmd = ['Telescope']
hook_add = '''
nnoremap <leader>ff <cmd>Telescope find_files<cr>
nnoremap <leader>fg <cmd>Telescope live_grep<cr>
nnoremap <leader>fb <cmd>Telescope buffers<cr>
nnoremap <leader>; <cmd>Telescope resume<cr>
'''
lua_source = '''
require'telescope'.setup({
  defaults = {
    mappings = {
      n = {
        q = "close",
      },
    },
  },
})
'''

これはこうなる

return require('packer').startup(function(use)
  use {
    'nvim-telescope/telescope.nvim',
    cmd = {'Telescope'},
    setup = function()
      vim.keymap.set('n', '<leader>ff', ':<C-u>Telescope find_files<CR>')
      vim.keymap.set('n', '<leader>fg', ':<C-u>Telescope live_grep<CR>')
      vim.keymap.set('n', '<leader>fb', ':<C-u>Telescope buffers<CR>')
      vim.keymap.set('n', '<leader>;', ':<C-u>Telescope resume<CR>')
    end,
    config = function()
      require'telescope'.setup{
        defaults = {
          mappings = {
            n = {
              q = "close",
            },
          },
        },
      }
    end,
  }
end)

lua としてそのままシンタックスハイライトとか使えるようになるので便利 (多分 toml の中の lua をシンタックスハイライトつける方法もあると思うんですが、やっぱ lua は lua ファイルに書いたほうが便利だと思う)

Misskey のウィジェットに複数ソースを扱う RSS リーダーが欲しい

公式の RSS リーダーウィジェットだと単一の RSS しか購読できないので、複数の RSS を読めるウィジェットを実装しました。

RSS リーダーウィジェットの追加方法

手っ取り早くウィジェットが欲しい人は以下の手順でどうぞ

  1. AiScript App ウィジェットを追加
  2. AiScript App の設定を開く
  3. script 欄に 複数ソース RSS リーダー のソースを貼り付ける

以上です

RSS リーダーの使い方

まず「購読する RSS を設定する」を押します。そして、RSS の URL を入力します。以上です。ページをリロードすると反映されます。

AiScript 書くときに知ってると便利なもの

AiScript を初めて書いたんですが、かなり苦戦しました。なので参考になったドキュメントとかまとめておきます。

この辺はメチャクチャ読みました。あと、AiScript の playground もめっちゃ使いました。

それから、Misskey Play を使いこなせるとだいぶ開発が早くなります。

Misskey Play を使ったデバッグ方法

  1. Misskey Play で Play を追加する
  2. ソースを編集する
  3. 保存する
  4. 表示する
  5. 更新日時が(たった今)になるまでリロードを連打する (重要)
  6. Play を押して検証する

特に更新日時が(たった今)になるまでリロード連打するのが大事です。なぜか Misskey Play はキャッシュが効いてるっぽくて最新の状態になっているとは限りません(リロードをかけると過去のバージョンに戻ったりする)。なので最新のものが出てくるまでリロードを連打する必要があります。