Panda Noir

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

Vite所感

ここ最近 Vite で軽くアプリ作ってたので感想。

Vite とは?

4行で書くと

  • webアプリ開発に特化したビルドツール
  • バンドラではない(バンドル"も"できる)
  • 超高速 dev server & HMR
  • もちろんプロダクションビルドもできる

こんな感じ。ビルドツールなので、webpack や Rollup と似た役割。特徴は ES Module を前提とした dev server。これのおかげでノーバンドルを実現してる。

Good default だから環境構築が楽

webアプリ開発の速度が上がる。デフォルトのままで CSS や JSON、画像まで import できる。画像を読み込めるの、ヤバすぎない?まさにGood default。web アプリ開発のユースケースをしっかりと押さえてる。

さらに、インストールもカンタン。インストールは質問に3つ答えるだけで完了。とてもスッキリ。環境構築コストがかなり低いので、開発に集中できる。

ディレクトリ構成が自由

不必要にディレクトリ構成を縛られたりしない。ディレクトリ名を指定されたりもしない。vite.config.js と index.html を置く以外は何もしなくていい。

webpack から vite に移行したときも、vite.config.js と index.html を追加して package.json 直すだけで行けた。ディレクトリ構成を制限されないのは結構嬉しい。

Dev server が爆速

早い。とにかく早い。Dev server がほんと1秒未満で起動する。最高。これですよ、我々が求めてたのは…という感じ。

適材適所。ライブラリには向いてない

ライブラリ作るときにはあまり向かない。てか使う必要がない。ライブラリ作るときにHMRとか要らんし。ライブラリ開発なら素直にバンドラを使う方が良さそう。

React の新しい JSX transform は未対応

React の新しい JSX 変換にはまだ対応してない(将来的にはするらしい)。ここは惜しい。早く対応して欲しいな。

Backend と組み合わせようとすると難しいかも

Backend と組み合わせて使うこともできる(つまり Laravel とかでも使える)。かなりエレガントな手法を提供してくれてる。

けど、フロント側のリポジトリとサーバーのリポジトリを分けている場合は難しいかも。というのも、ビルド時に生成される manifest.json というファイルを参照してスクリプトを読み込まなければならない。これをフロント側のリポジトリからどうやってサーバー側に共有するかが課題になりそう。

でも、実際に試した訳じゃないので、エレガントな解決策があるかも。

【Docker】 fallback 機能付きで静的コンテンツを手軽に配信する

goStatic がオススメ

goStaticの特徴はこちらです。

  • 設定ファイル不要
  • Go言語で書かれている
  • 軽量コンテナ

たとえば、以下のコマンド一発で fallback 機能を使いつつ静的コンテンツを配信できます。

docker run -d -p 80:8043 -v $PWD/dist:/srv/http pierrezemb/gostatic -fallback index.html

Dockerfile を書くときも結構シンプルにできます。

# ビルド環境
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 本番環境
FROM pierrezemb/gostatic as production-stage
COPY --from=build-stage /app/dist /srv/http
CMD ["-fallback", "index.html"]
# port を変えたいのであればこちら
# CMD ["-port", "8080", "-fallback", "index.html"]

僕はViteでプロダクションビルドした結果をgoStaticで配信する形を取りました。

個人的には node イメージで servor をインストールするよりオーバーヘッドが小さく、それでいてnginx イメージを使うよりも楽にできたなという印象です。 goStatic、使ってみてはいかがでしょうか。

git branch を fzf で簡単に選択する in zsh

setopt no_flow_control
__fbr() {
  local item
  git branch | fzf +s +m -e --ansi --reverse --height 40% | sed -e 's/^ *//' -e 's/^\* //' | while read item; do
    echo -n "${(q)item} "
  done
  local ret=$?
  echo
  return $ret
}
fzf-branch-widget() {
  LBUFFER="${LBUFFER}$(__fbr)"
  local ret=$?
  zle reset-prompt
  return $ret
}
zle     -N   fzf-branch-widget
bindkey '^S' fzf-branch-widget

これで、Ctrl-sを押すとブランチ選択ウィンドウが立ち上がるようになります。かなり開発効率上がるのでオススメです。

(fzfのkey-bindings.zshを参考に書いただけで詳しい動作はあまり理解してないけど問題ないはず)

eslint-plugin-import を使ってディレクトリ単位でアクセス制限を敷く

TypeScript を使っていて、「この関数、他のディレクトリからはアクセスして欲しくないんだけどテストのために export しなきゃ行けないな…」みたいなケースありませんか?実は、ESLint でうまく設定してやると解決できます!今回はその方法を紹介します。

お急ぎ開け口

  1. eslint-plugin-import をインストール
  2. print-allowed-dir.sh を書く
  3. print-allowed-dir.sh を使って eslint の設定を書く

詳しくは以下のデモリポジトリを参照ください。

github.com

eslint-plugin-import とは?

eslint-plugin-import は外部モジュールへのアクセスの仕方を設定するプラグインです。(外部モジュールとは、同一階層でないディレクトリとだいたい同義です)

たとえば、以下のように eslint-plugin-import の設定をすると、今回やりたいことが実現できます。

// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:import/typescript',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint', 'import'],
  rules: {
    // アクセス可能なファイルを glob を使って列挙する
    'import/no-internal-modules': [
      'error',
      {
        allow: ['**/dir/*', '**/dir-including-index/index.ts'],
      },
    ],
  },
};
src/
  - dir/
    - reachable.ts
  - dir-including-index/
    - index.ts
    - internal.ts

上の eslint 設定の場合、このディレクトリ構成に対して以下のようなアクセス制限がなされます。

  • dir/reachable.ts にはどこからでもアクセス可能
  • dir-including-index/internal.ts は dir-including-index/index.ts のみアクセス可能(同一階層のため)
  • dir-including-index/index.ts にはどこからでもアクセス可能

うまくやりたいことが実現できました。しかし、いちいち各ディレクトリへのアクセス制限を手動で書くのは馬鹿馬鹿しいですよね。というわけで自動化します。

ディレクトリ列挙を自動化する

いちいち index.ts が含まれたフォルダを探して設定をしていたのでは面倒なので、Shell Script を書いて列挙を自動化しましょう。

以下の Shell Script を print-allowed-dir.sh と名付けて保存してください。実行権限の付与をお忘れなく。

この shell script は以下のことをしています

  1. ディレクトリを再帰的に全て列挙し、そのうちindexファイルが直下にあるものを除外
  2. index ファイルを列挙
  3. 列挙したファイル、ディレクトリを整形
#!/bin/bash
list_directories() {
  find ./src -type d
}
list_index_files() {
  find ./src -type f -name 'index\.*'
}
subtract() {
  diff "$1" "$2" | grep '^< ' | sed -e 's/< //'
}

(
  subtract <(list_directories) <(list_index_files | xargs -I{} dirname {} | uniq) | sed -e 's/$/\/*/'
  list_index_files
)  | sed -e 's/\.\/src/**/' | sed -e '/\*\*\/\*/d'

あとはこれを使って設定できるように .eslintrc.js を書き換えます。

// print-allowed-dir.sh を実行すると各ディレクトリに対して
//  - **/dir-with-index/index.ts
//  - **/dir-without-index/*
// が出力されるので、eslint-plugin-import へ渡す
const { execSync } = require('child_process');
const allowList = `${execSync('./print-allowed-dir.sh')}`
  .replace(/\n$/, '')
  .split('\n');

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:import/typescript', // eslint-plugin-import の設定を読み込む
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint', 'import'], // eslint-plugin-import を読み込む
  rules: {
    'import/no-internal-modules': [
      'error',
      {
        allow: allowList,
      },
    ],
  },
};

(この記事は同様のことをしてくれるESLintプラグインが見当たらなくて書いたので、同じことをしてくれるプラグインがあれば紹介してほしいです)

おまけ: testディレクトリからはすべてのファイルにアクセス可能とする

ESLint なので、overrides をうまく使うことで様々なことができます。一例として、srcディレクトリ以下のファイルに対しては上の制限を敷くが、testディレクトリには敷かないということをやってみます。

const { execSync } = require('child_process');
const allowList = `${execSync('./print-allowed-dir.sh')}`
  .replace(/\n$/, '')
  .split('\n');

module.exports = {
  // (省略)
  overrides: [
    {
      files: ['src/**/*'],
      rules: {
        'import/no-internal-modules': [
          'error',
          {
            allow: allowList,
          },
        ],
      },
    },
    {
      files: ['test/**/*'],
      rules: {
        'import/no-internal-modules': 'off',
      },
    },
  ],
};

これで、src以下のファイルたちにはアクセス制限を掛けられつつ、test からは自由に private 関数へアクセスできます。

物理キーボードに対するムカつき

最初に書いておきますがただのポエムです。

物理キーボードは Bad UX

  • 物理キーボードは入力はしやすい。が、ctrlキーやaltキーなどを押しても、アルファベットキーたちにインタラクションが何もない。これはBad UX。
  • 逆にソフトウェアキーボードはユーザーアクションに対してリアクションがある。とても良いUX。たとえば、唯一のメタキーであるシフトキーを押すと、キー表示が大文字になる。これによってユーザーは直感的な操作が可能になる。また、メタキーがシフトキー以外にない。そのため、「メタキーを押してもリアクションがない」という問題を抱えていない。
  • ソフトウェアキーボードでは日本語入力と英語入力の切り替えを「キーボードそのものを入れ替えて」実現している。物理キーボードでこれは無理。配列ごとまるっと変わるので。ソフトウェアキーボードの利点のひとつといえる。
  • けど、ソフトウェアキーボード最大の弱点は入力がしづらいこと。連続した入力をしづらい、両手10本の指を動員しづらい、指のサイズに対してキートップのサイズが小さいことなどが原因だ。また、物理的にキートップ間の境界がわからないため、タッチタイピングにも向いていない。

折衷案「タブレット型キーボード」

  • ソフトウェアキーボード、物理キーボードどちらの特性も盛り込みつつ本気で課題を解消するなら「タブレット型のキーボード端末」になるのでは?
    • 画面を大きくすることでキーサイズを保障する
    • iPhoneのホームボタンのように振動を使って疑似的にボタンを押した感じを出す
    • 「ある程度は」ソフトウェアキーボードの入力のしづらさを解消できるはず
    • ソフトウェアキーボードのように配列を入力ごとにまるっと切り替えられる
  • 本気で普及を目指すのであれば、タブレット型キーボードはノートPCにデフォルトで付けるしかない。Macあたりがやってほしい(そういえば Mac の Touch Bar ってこれの先駆け感あるな。もっとぐいぐい行ってくれ)。そして、タブレット型キーボードはキーショートカット学習用、外部キーボードは腱鞘炎対策となるのではないか。

タブレット型キーボードの課題

  • ある程度はこの分業で良い。サービス触りたては学習用を触ればいいし、触らずともショートカット表として使える。でも、できたらタブレット型キーボードに統一したい。新しいソフトウェアを触るときに毎回タブレット型キーボードへスイッチングするコストが高い。結局タブレット型キーボードを使わなくなるのがありありと想像できる。
  • 絶対、タブレット型キーボードならショートカットキーの学習速度が現在とは段違いになるはずだし、みんなにとって恩恵がある。やらない理由はないんですよ…
  • もっと言うならメタキー自体廃止したい。いや、そこまでやるとさすがに過激なんだけど、あるソフトウェアのときはデフォルトでショートカットキー用の表示にするくらいはしたい。たとえばPhotoshopとかはメタキーなしのキーボードショートカットを採用しているし、Photoshopを立ち上げた瞬間にキーボードショートカット専用キーボードになるとうれしいよね。
  • そもそも、「立ち上げたソフトごとにキーボードショートカットを表示する」ためにはそのソフトのショートカット情報をキーボードに送信しなければならない。そのためのフォーマット・プロトコルを作る必要があるし、ソフトウェア側にショートカット情報を提供してもらう形にすると、普及に時間がかかる。どんなに早くて3年くらいかかるのではないだろうか。長い道のりだ…