雑記帳

整理しない情報集

更新情報

最小のJSX/TSX構成を作ってみる

公開日:

カテゴリ: JavaScript

React系のJSX/TSXを使ってみたかったのですが、普通にプロジェクトを作成すると大量の依存関係がインストールされてげんなりするので、なるべく依存関係の少ない代替の環境を探してみました。

なお、あくまで趣味レベルの小規模なプロジェクトを想定しているので、大規模なプロジェクトや本番環境対応などは特に考えていません。そのレベルの開発であれば、素直に公式で推奨されているフレームワークを使うのが良いと思います。

TL;DR

  • esbuildが便利
    • JS / TS / JSX / TSX のバンドル・トランスパイルが可能
    • ファイル監視による自動ビルド機能・簡易開発サーバ機能あり
    • npmからインストールせずに、バイナリ1個でも動作する

普通にReact+TSX環境を作ってみる

とりあえずReact公式からチュートリアルを探しに行きます。しかし、探せど探せどフレームワークへの案内しか見当たりません。

どうやら2025/02現在ではフレームワークなしのReactは標準から外れたようです。昔はcreate-react-appがあったような気がしますが、現在は公式ページには影も形もありません。フレームワーク必須のライブラリはどうなのかと思わなくもないですが、今回の趣旨とは関係ないのでひとまず置いておきます。

本項はフレームワークを使うことが目的ではないので、単純にReact+TSX環境を作る方法を探します。Deep Diveとして折りたたんだ先で紹介されているviteでインストールしてみます。

npm create vite@latest my-react-app -- --template react-ts
cd my-react-app
npm i

これ一つで開発環境の構築が完了し、開発に必要/開発体験が向上するツールがセットでインストールされるので、普通に開発する分にはこれで十分でしょう。

さて、依存関係を確認してみます。まずは直接インストールされたパッケージを調べます。

npm ls
my-react-app@0.0.0 FILEPATH/my-react-app
+-- @eslint/js@9.19.0
+-- @types/react-dom@18.3.5
+-- @types/react@18.3.18
+-- @vitejs/plugin-react@4.3.4
+-- eslint-plugin-react-hooks@5.1.0
+-- eslint-plugin-react-refresh@0.4.18
+-- eslint@9.19.0
+-- globals@15.14.0
+-- react-dom@18.3.1
+-- react@18.3.1
+-- typescript-eslint@8.21.0
+-- typescript@5.6.3
`-- vite@6.0.11

次に依存関係の全リストを確認してみます。

npm ls --all

実行結果は長いので畳みます。

my-react-app@0.0.0 FILEPATH/my-react-app
+-- @eslint/js@9.19.0
+-- @types/react-dom@18.3.5
| `-- @types/react@18.3.18 deduped
+-- @types/react@18.3.18
| +-- @types/prop-types@15.7.14
| `-- csstype@3.1.3
+-- @vitejs/plugin-react@4.3.4
| +-- @babel/core@7.26.7
| | +-- @ampproject/remapping@2.3.0
| | | +-- @jridgewell/gen-mapping@0.3.8
| | | | +-- @jridgewell/set-array@1.2.1
| | | | +-- @jridgewell/sourcemap-codec@1.5.0
| | | | `-- @jridgewell/trace-mapping@0.3.25 deduped
| | | `-- @jridgewell/trace-mapping@0.3.25
| | |   +-- @jridgewell/resolve-uri@3.1.2
| | |   `-- @jridgewell/sourcemap-codec@1.5.0 deduped
| | +-- @babel/code-frame@7.26.2
| | | +-- @babel/helper-validator-identifier@7.25.9
| | | +-- js-tokens@4.0.0 deduped
| | | `-- picocolors@1.1.1 deduped
| | +-- @babel/generator@7.26.5
| | | +-- @babel/parser@7.26.7 deduped
| | | +-- @babel/types@7.26.7 deduped
| | | +-- @jridgewell/gen-mapping@0.3.8 deduped
| | | +-- @jridgewell/trace-mapping@0.3.25 deduped
| | | `-- jsesc@3.1.0
| | +-- @babel/helper-compilation-targets@7.26.5
| | | +-- @babel/compat-data@7.26.5
| | | +-- @babel/helper-validator-option@7.25.9
| | | +-- browserslist@4.24.4
| | | | +-- caniuse-lite@1.0.30001695
| | | | +-- electron-to-chromium@1.5.88
| | | | +-- node-releases@2.0.19
| | | | `-- update-browserslist-db@1.1.2
| | | |   +-- browserslist@4.24.4 deduped
| | | |   +-- escalade@3.2.0
| | | |   `-- picocolors@1.1.1 deduped
| | | +-- lru-cache@5.1.1
| | | | `-- yallist@3.1.1
| | | `-- semver@6.3.1 deduped
| | +-- @babel/helper-module-transforms@7.26.0
| | | +-- @babel/core@7.26.7 deduped
| | | +-- @babel/helper-module-imports@7.25.9
| | | | +-- @babel/traverse@7.26.7 deduped
| | | | `-- @babel/types@7.26.7 deduped
| | | +-- @babel/helper-validator-identifier@7.25.9 deduped
| | | `-- @babel/traverse@7.26.7 deduped
| | +-- @babel/helpers@7.26.7
| | | +-- @babel/template@7.25.9 deduped
| | | `-- @babel/types@7.26.7 deduped
| | +-- @babel/parser@7.26.7
| | | `-- @babel/types@7.26.7 deduped
| | +-- @babel/template@7.25.9
| | | +-- @babel/code-frame@7.26.2 deduped
| | | +-- @babel/parser@7.26.7 deduped
| | | `-- @babel/types@7.26.7 deduped
| | +-- @babel/traverse@7.26.7
| | | +-- @babel/code-frame@7.26.2 deduped
| | | +-- @babel/generator@7.26.5 deduped
| | | +-- @babel/parser@7.26.7 deduped
| | | +-- @babel/template@7.25.9 deduped
| | | +-- @babel/types@7.26.7 deduped
| | | +-- debug@4.4.0 deduped
| | | `-- globals@11.12.0
| | +-- @babel/types@7.26.7
| | | +-- @babel/helper-string-parser@7.25.9
| | | `-- @babel/helper-validator-identifier@7.25.9 deduped
| | +-- convert-source-map@2.0.0
| | +-- debug@4.4.0 deduped
| | +-- gensync@1.0.0-beta.2
| | +-- json5@2.2.3
| | `-- semver@6.3.1
| +-- @babel/plugin-transform-react-jsx-self@7.25.9
| | +-- @babel/core@7.26.7 deduped
| | `-- @babel/helper-plugin-utils@7.26.5
| +-- @babel/plugin-transform-react-jsx-source@7.25.9
| | +-- @babel/core@7.26.7 deduped
| | `-- @babel/helper-plugin-utils@7.26.5 deduped
| +-- @types/babel__core@7.20.5
| | +-- @babel/parser@7.26.7 deduped
| | +-- @babel/types@7.26.7 deduped
| | +-- @types/babel__generator@7.6.8
| | | `-- @babel/types@7.26.7 deduped
| | +-- @types/babel__template@7.4.4
| | | +-- @babel/parser@7.26.7 deduped
| | | `-- @babel/types@7.26.7 deduped
| | `-- @types/babel__traverse@7.20.6
| |   `-- @babel/types@7.26.7 deduped
| +-- react-refresh@0.14.2
| `-- vite@6.0.11 deduped
+-- eslint-plugin-react-hooks@5.1.0
| `-- eslint@9.19.0 deduped
+-- eslint-plugin-react-refresh@0.4.18
| `-- eslint@9.19.0 deduped
+-- eslint@9.19.0
| +-- @eslint-community/eslint-utils@4.4.1
| | +-- eslint-visitor-keys@3.4.3
| | `-- eslint@9.19.0 deduped
| +-- @eslint-community/regexpp@4.12.1
| +-- @eslint/config-array@0.19.1
| | +-- @eslint/object-schema@2.1.5
| | +-- debug@4.4.0 deduped
| | `-- minimatch@3.1.2 deduped
| +-- @eslint/core@0.10.0
| | `-- @types/json-schema@7.0.15 deduped
| +-- @eslint/eslintrc@3.2.0
| | +-- ajv@6.12.6 deduped
| | +-- debug@4.4.0 deduped
| | +-- espree@10.3.0 deduped
| | +-- globals@14.0.0
| | +-- ignore@5.3.2 deduped
| | +-- import-fresh@3.3.0
| | | +-- parent-module@1.0.1
| | | | `-- callsites@3.1.0
| | | `-- resolve-from@4.0.0
| | +-- js-yaml@4.1.0
| | | `-- argparse@2.0.1
| | +-- minimatch@3.1.2 deduped
| | `-- strip-json-comments@3.1.1
| +-- @eslint/js@9.19.0 deduped
| +-- @eslint/plugin-kit@0.2.5
| | +-- @eslint/core@0.10.0 deduped
| | `-- levn@0.4.1
| |   +-- prelude-ls@1.2.1 deduped
| |   `-- type-check@0.4.0 deduped
| +-- @humanfs/node@0.16.6
| | +-- @humanfs/core@0.19.1
| | `-- @humanwhocodes/retry@0.3.1
| +-- @humanwhocodes/module-importer@1.0.1
| +-- @humanwhocodes/retry@0.4.1
| +-- @types/estree@1.0.6
| +-- @types/json-schema@7.0.15
| +-- ajv@6.12.6
| | +-- fast-deep-equal@3.1.3 deduped
| | +-- fast-json-stable-stringify@2.1.0
| | +-- json-schema-traverse@0.4.1
| | `-- uri-js@4.4.1
| |   `-- punycode@2.3.1
| +-- chalk@4.1.2
| | +-- ansi-styles@4.3.0
| | | `-- color-convert@2.0.1
| | |   `-- color-name@1.1.4
| | `-- supports-color@7.2.0
| |   `-- has-flag@4.0.0
| +-- cross-spawn@7.0.6
| | +-- path-key@3.1.1
| | +-- shebang-command@2.0.0
| | | `-- shebang-regex@3.0.0
| | `-- which@2.0.2
| |   `-- isexe@2.0.0
| +-- debug@4.4.0
| | `-- ms@2.1.3
| +-- escape-string-regexp@4.0.0
| +-- eslint-scope@8.2.0
| | +-- esrecurse@4.3.0
| | | `-- estraverse@5.3.0 deduped
| | `-- estraverse@5.3.0
| +-- eslint-visitor-keys@4.2.0
| +-- espree@10.3.0
| | +-- acorn-jsx@5.3.2
| | | `-- acorn@8.14.0 deduped
| | +-- acorn@8.14.0
| | `-- eslint-visitor-keys@4.2.0 deduped
| +-- esquery@1.6.0
| | `-- estraverse@5.3.0 deduped
| +-- esutils@2.0.3
| +-- fast-deep-equal@3.1.3
| +-- file-entry-cache@8.0.0
| | `-- flat-cache@4.0.1
| |   +-- flatted@3.3.2
| |   `-- keyv@4.5.4
| |     `-- json-buffer@3.0.1
| +-- find-up@5.0.0
| | +-- locate-path@6.0.0
| | | `-- p-locate@5.0.0
| | |   `-- p-limit@3.1.0
| | |     `-- yocto-queue@0.1.0
| | `-- path-exists@4.0.0
| +-- glob-parent@6.0.2
| | `-- is-glob@4.0.3 deduped
| +-- ignore@5.3.2
| +-- imurmurhash@0.1.4
| +-- is-glob@4.0.3
| | `-- is-extglob@2.1.1
| +-- UNMET OPTIONAL DEPENDENCY jiti@*
| +-- json-stable-stringify-without-jsonify@1.0.1
| +-- lodash.merge@4.6.2
| +-- minimatch@3.1.2
| | `-- brace-expansion@1.1.11
| |   +-- balanced-match@1.0.2
| |   `-- concat-map@0.0.1
| +-- natural-compare@1.4.0
| `-- optionator@0.9.4
|   +-- deep-is@0.1.4
|   +-- fast-levenshtein@2.0.6
|   +-- levn@0.4.1 deduped
|   +-- prelude-ls@1.2.1
|   +-- type-check@0.4.0
|   | `-- prelude-ls@1.2.1 deduped
|   `-- word-wrap@1.2.5
+-- globals@15.14.0
+-- react-dom@18.3.1
| +-- loose-envify@1.4.0
| | `-- js-tokens@4.0.0
| +-- react@18.3.1 deduped
| `-- scheduler@0.23.2
|   `-- loose-envify@1.4.0 deduped
+-- react@18.3.1
| `-- loose-envify@1.4.0 deduped
+-- typescript-eslint@8.21.0
| +-- @typescript-eslint/eslint-plugin@8.21.0
| | +-- @eslint-community/regexpp@4.12.1 deduped
| | +-- @typescript-eslint/parser@8.21.0 deduped
| | +-- @typescript-eslint/scope-manager@8.21.0
| | | +-- @typescript-eslint/types@8.21.0 deduped
| | | `-- @typescript-eslint/visitor-keys@8.21.0 deduped
| | +-- @typescript-eslint/type-utils@8.21.0
| | | +-- @typescript-eslint/typescript-estree@8.21.0 deduped
| | | +-- @typescript-eslint/utils@8.21.0 deduped
| | | +-- debug@4.4.0 deduped
| | | +-- eslint@9.19.0 deduped
| | | +-- ts-api-utils@2.0.0 deduped
| | | `-- typescript@5.6.3 deduped
| | +-- @typescript-eslint/utils@8.21.0 deduped
| | +-- @typescript-eslint/visitor-keys@8.21.0
| | | +-- @typescript-eslint/types@8.21.0 deduped
| | | `-- eslint-visitor-keys@4.2.0 deduped
| | +-- eslint@9.19.0 deduped
| | +-- graphemer@1.4.0
| | +-- ignore@5.3.2 deduped
| | +-- natural-compare@1.4.0 deduped
| | +-- ts-api-utils@2.0.0
| | | `-- typescript@5.6.3 deduped
| | `-- typescript@5.6.3 deduped
| +-- @typescript-eslint/parser@8.21.0
| | +-- @typescript-eslint/scope-manager@8.21.0 deduped
| | +-- @typescript-eslint/types@8.21.0
| | +-- @typescript-eslint/typescript-estree@8.21.0
| | | +-- @typescript-eslint/types@8.21.0 deduped
| | | +-- @typescript-eslint/visitor-keys@8.21.0 deduped
| | | +-- debug@4.4.0 deduped
| | | +-- fast-glob@3.3.3
| | | | +-- @nodelib/fs.stat@2.0.5
| | | | +-- @nodelib/fs.walk@1.2.8
| | | | | +-- @nodelib/fs.scandir@2.1.5
| | | | | | +-- @nodelib/fs.stat@2.0.5 deduped
| | | | | | `-- run-parallel@1.2.0
| | | | | |   `-- queue-microtask@1.2.3
| | | | | `-- fastq@1.18.0
| | | | |   `-- reusify@1.0.4
| | | | +-- glob-parent@5.1.2
| | | | | `-- is-glob@4.0.3 deduped
| | | | +-- merge2@1.4.1
| | | | `-- micromatch@4.0.8
| | | |   +-- braces@3.0.3
| | | |   | `-- fill-range@7.1.1
| | | |   |   `-- to-regex-range@5.0.1
| | | |   |     `-- is-number@7.0.0
| | | |   `-- picomatch@2.3.1
| | | +-- is-glob@4.0.3 deduped
| | | +-- minimatch@9.0.5
| | | | `-- brace-expansion@2.0.1
| | | |   `-- balanced-match@1.0.2 deduped
| | | +-- semver@7.6.3
| | | +-- ts-api-utils@2.0.0 deduped
| | | `-- typescript@5.6.3 deduped
| | +-- @typescript-eslint/visitor-keys@8.21.0 deduped
| | +-- debug@4.4.0 deduped
| | +-- eslint@9.19.0 deduped
| | `-- typescript@5.6.3 deduped
| +-- @typescript-eslint/utils@8.21.0
| | +-- @eslint-community/eslint-utils@4.4.1 deduped
| | +-- @typescript-eslint/scope-manager@8.21.0 deduped
| | +-- @typescript-eslint/types@8.21.0 deduped
| | +-- @typescript-eslint/typescript-estree@8.21.0 deduped
| | +-- eslint@9.19.0 deduped
| | `-- typescript@5.6.3 deduped
| +-- eslint@9.19.0 deduped
| `-- typescript@5.6.3 deduped
+-- typescript@5.6.3
`-- vite@6.0.11
  +-- UNMET OPTIONAL DEPENDENCY @types/node@^18.0.0 || ^20.0.0 || >=22.0.0
  +-- esbuild@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/aix-ppc64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/android-arm@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/android-arm64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/android-x64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/darwin-arm64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/darwin-x64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/freebsd-arm64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/freebsd-x64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/linux-arm@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/linux-arm64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/linux-ia32@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/linux-loong64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/linux-mips64el@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/linux-ppc64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/linux-riscv64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/linux-s390x@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/linux-x64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/netbsd-arm64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/netbsd-x64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/openbsd-arm64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/openbsd-x64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/sunos-x64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/win32-arm64@0.24.2
  | +-- UNMET OPTIONAL DEPENDENCY @esbuild/win32-ia32@0.24.2
  | `-- @esbuild/win32-x64@0.24.2
  +-- UNMET OPTIONAL DEPENDENCY fsevents@~2.3.3
  +-- UNMET OPTIONAL DEPENDENCY jiti@>=1.21.0
  +-- UNMET OPTIONAL DEPENDENCY less@*
  +-- UNMET OPTIONAL DEPENDENCY lightningcss@^1.21.0
  +-- postcss@8.5.1
  | +-- nanoid@3.3.8
  | +-- picocolors@1.1.1
  | `-- source-map-js@1.2.1
  +-- rollup@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-android-arm-eabi@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-android-arm64@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-darwin-arm64@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-darwin-x64@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-freebsd-arm64@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-freebsd-x64@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-linux-arm-gnueabihf@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-linux-arm-musleabihf@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-linux-arm64-gnu@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-linux-arm64-musl@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-linux-loongarch64-gnu@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-linux-powerpc64le-gnu@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-linux-riscv64-gnu@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-linux-s390x-gnu@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-linux-x64-gnu@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-linux-x64-musl@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-win32-arm64-msvc@4.32.0
  | +-- UNMET OPTIONAL DEPENDENCY @rollup/rollup-win32-ia32-msvc@4.32.0
  | +-- @rollup/rollup-win32-x64-msvc@4.32.0
  | +-- @types/estree@1.0.6 deduped
  | `-- UNMET OPTIONAL DEPENDENCY fsevents@~2.3.2
  +-- UNMET OPTIONAL DEPENDENCY sass-embedded@*
  +-- UNMET OPTIONAL DEPENDENCY sass@*
  +-- UNMET OPTIONAL DEPENDENCY stylus@*
  +-- UNMET OPTIONAL DEPENDENCY sugarss@*
  +-- UNMET OPTIONAL DEPENDENCY terser@^5.16.0
  +-- UNMET OPTIONAL DEPENDENCY tsx@^4.8.1
  `-- UNMET OPTIONAL DEPENDENCY yaml@^2.4.2

UNMET OPTIONAL DEPENDENCYと出力されている行は、他の環境向けのパッケージなので実際にはインストールされていません(今回はWindows環境での実行結果です)。dedupedと出力されている行は重複するため除外されたものです。

最終的にインストールされたものは以下のようになります。

npm ls --all | findstr /v "UNMET OPTIONAL DEPENDENCY" | findstr /v "deduped"
npm ls --all | grep -v -e "UNMET OPTIONAL DEPENDENCY" -e "deduped"
my-react-app@0.0.0 FILEPATH/my-react-app
+-- @eslint/js@9.19.0
+-- @types/react-dom@18.3.5
+-- @types/react@18.3.18
| +-- @types/prop-types@15.7.14
| `-- csstype@3.1.3
+-- @vitejs/plugin-react@4.3.4
| +-- @babel/core@7.26.7
| | +-- @ampproject/remapping@2.3.0
| | | +-- @jridgewell/gen-mapping@0.3.8
| | | | +-- @jridgewell/set-array@1.2.1
| | | | +-- @jridgewell/sourcemap-codec@1.5.0
| | | `-- @jridgewell/trace-mapping@0.3.25
| | |   +-- @jridgewell/resolve-uri@3.1.2
| | +-- @babel/code-frame@7.26.2
| | | +-- @babel/helper-validator-identifier@7.25.9
| | +-- @babel/generator@7.26.5
| | | `-- jsesc@3.1.0
| | +-- @babel/helper-compilation-targets@7.26.5
| | | +-- @babel/compat-data@7.26.5
| | | +-- @babel/helper-validator-option@7.25.9
| | | +-- browserslist@4.24.4
| | | | +-- caniuse-lite@1.0.30001695
| | | | +-- electron-to-chromium@1.5.88
| | | | +-- node-releases@2.0.19
| | | | `-- update-browserslist-db@1.1.2
| | | |   +-- escalade@3.2.0
| | | +-- lru-cache@5.1.1
| | | | `-- yallist@3.1.1
| | +-- @babel/helper-module-transforms@7.26.0
| | | +-- @babel/helper-module-imports@7.25.9
| | +-- @babel/helpers@7.26.7
| | +-- @babel/parser@7.26.7
| | +-- @babel/template@7.25.9
| | +-- @babel/traverse@7.26.7
| | | `-- globals@11.12.0
| | +-- @babel/types@7.26.7
| | | +-- @babel/helper-string-parser@7.25.9
| | +-- convert-source-map@2.0.0
| | +-- gensync@1.0.0-beta.2
| | +-- json5@2.2.3
| | `-- semver@6.3.1
| +-- @babel/plugin-transform-react-jsx-self@7.25.9
| | `-- @babel/helper-plugin-utils@7.26.5
| +-- @babel/plugin-transform-react-jsx-source@7.25.9
| +-- @types/babel__core@7.20.5
| | +-- @types/babel__generator@7.6.8
| | +-- @types/babel__template@7.4.4
| | `-- @types/babel__traverse@7.20.6
| +-- react-refresh@0.14.2
+-- eslint-plugin-react-hooks@5.1.0
+-- eslint-plugin-react-refresh@0.4.18
+-- eslint@9.19.0
| +-- @eslint-community/eslint-utils@4.4.1
| | +-- eslint-visitor-keys@3.4.3
| +-- @eslint-community/regexpp@4.12.1
| +-- @eslint/config-array@0.19.1
| | +-- @eslint/object-schema@2.1.5
| +-- @eslint/core@0.10.0
| +-- @eslint/eslintrc@3.2.0
| | +-- globals@14.0.0
| | +-- import-fresh@3.3.0
| | | +-- parent-module@1.0.1
| | | | `-- callsites@3.1.0
| | | `-- resolve-from@4.0.0
| | +-- js-yaml@4.1.0
| | | `-- argparse@2.0.1
| | `-- strip-json-comments@3.1.1
| +-- @eslint/plugin-kit@0.2.5
| | `-- levn@0.4.1
| +-- @humanfs/node@0.16.6
| | +-- @humanfs/core@0.19.1
| | `-- @humanwhocodes/retry@0.3.1
| +-- @humanwhocodes/module-importer@1.0.1
| +-- @humanwhocodes/retry@0.4.1
| +-- @types/estree@1.0.6
| +-- @types/json-schema@7.0.15
| +-- ajv@6.12.6
| | +-- fast-json-stable-stringify@2.1.0
| | +-- json-schema-traverse@0.4.1
| | `-- uri-js@4.4.1
| |   `-- punycode@2.3.1
| +-- chalk@4.1.2
| | +-- ansi-styles@4.3.0
| | | `-- color-convert@2.0.1
| | |   `-- color-name@1.1.4
| | `-- supports-color@7.2.0
| |   `-- has-flag@4.0.0
| +-- cross-spawn@7.0.6
| | +-- path-key@3.1.1
| | +-- shebang-command@2.0.0
| | | `-- shebang-regex@3.0.0
| | `-- which@2.0.2
| |   `-- isexe@2.0.0
| +-- debug@4.4.0
| | `-- ms@2.1.3
| +-- escape-string-regexp@4.0.0
| +-- eslint-scope@8.2.0
| | +-- esrecurse@4.3.0
| | `-- estraverse@5.3.0
| +-- eslint-visitor-keys@4.2.0
| +-- espree@10.3.0
| | +-- acorn-jsx@5.3.2
| | +-- acorn@8.14.0
| +-- esquery@1.6.0
| +-- esutils@2.0.3
| +-- fast-deep-equal@3.1.3
| +-- file-entry-cache@8.0.0
| | `-- flat-cache@4.0.1
| |   +-- flatted@3.3.2
| |   `-- keyv@4.5.4
| |     `-- json-buffer@3.0.1
| +-- find-up@5.0.0
| | +-- locate-path@6.0.0
| | | `-- p-locate@5.0.0
| | |   `-- p-limit@3.1.0
| | |     `-- yocto-queue@0.1.0
| | `-- path-exists@4.0.0
| +-- glob-parent@6.0.2
| +-- ignore@5.3.2
| +-- imurmurhash@0.1.4
| +-- is-glob@4.0.3
| | `-- is-extglob@2.1.1
| +-- json-stable-stringify-without-jsonify@1.0.1
| +-- lodash.merge@4.6.2
| +-- minimatch@3.1.2
| | `-- brace-expansion@1.1.11
| |   +-- balanced-match@1.0.2
| |   `-- concat-map@0.0.1
| +-- natural-compare@1.4.0
| `-- optionator@0.9.4
|   +-- deep-is@0.1.4
|   +-- fast-levenshtein@2.0.6
|   +-- prelude-ls@1.2.1
|   +-- type-check@0.4.0
|   `-- word-wrap@1.2.5
+-- globals@15.14.0
+-- react-dom@18.3.1
| +-- loose-envify@1.4.0
| | `-- js-tokens@4.0.0
| `-- scheduler@0.23.2
+-- react@18.3.1
+-- typescript-eslint@8.21.0
| +-- @typescript-eslint/eslint-plugin@8.21.0
| | +-- @typescript-eslint/scope-manager@8.21.0
| | +-- @typescript-eslint/type-utils@8.21.0
| | +-- @typescript-eslint/visitor-keys@8.21.0
| | +-- graphemer@1.4.0
| | +-- ts-api-utils@2.0.0
| +-- @typescript-eslint/parser@8.21.0
| | +-- @typescript-eslint/types@8.21.0
| | +-- @typescript-eslint/typescript-estree@8.21.0
| | | +-- fast-glob@3.3.3
| | | | +-- @nodelib/fs.stat@2.0.5
| | | | +-- @nodelib/fs.walk@1.2.8
| | | | | +-- @nodelib/fs.scandir@2.1.5
| | | | | | `-- run-parallel@1.2.0
| | | | | |   `-- queue-microtask@1.2.3
| | | | | `-- fastq@1.18.0
| | | | |   `-- reusify@1.0.4
| | | | +-- glob-parent@5.1.2
| | | | +-- merge2@1.4.1
| | | | `-- micromatch@4.0.8
| | | |   +-- braces@3.0.3
| | | |   | `-- fill-range@7.1.1
| | | |   |   `-- to-regex-range@5.0.1
| | | |   |     `-- is-number@7.0.0
| | | |   `-- picomatch@2.3.1
| | | +-- minimatch@9.0.5
| | | | `-- brace-expansion@2.0.1
| | | +-- semver@7.6.3
| +-- @typescript-eslint/utils@8.21.0
+-- typescript@5.6.3
`-- vite@6.0.11
  +-- esbuild@0.24.2
  | `-- @esbuild/win32-x64@0.24.2
  +-- postcss@8.5.1
  | +-- nanoid@3.3.8
  | +-- picocolors@1.1.1
  | `-- source-map-js@1.2.1
  +-- rollup@4.32.0
  | +-- @rollup/rollup-win32-x64-msvc@4.32.0

次に、ファイル数やフォルダサイズを見ていきます。

ファイル数

dir node_modules /A-D /S /B | find /c /v ""
ls ./node_modules -F | grep -v / | wc -l
5541

フォルダサイズ

(New-Object -ComObject Scripting.FileSystemObject).GetFolder('node_modules').size
du --bytes ./node_modules
78565449

ファイル数もファイルサイズもなかなかの数値です(これでもwebpackよりは格段にマシですが)。SSDから別ディスクへのコピーや移動はお茶を二口くらい飲めそうですし、HDDにインストールした場合ファイル操作はあまりやりたくないですね。パッケージのダウンロードは圧縮されているとはいえ、モバイルネットワークでnpm iはちょっと避けたくなります。

ちょっとしたアプリを作るのに75MiB使うとすると、npmではプロジェクトごとにインストールすることになるので、14個も作れば1GiBになります。実際には各アプリで使うモジュールを更にインストールするので、ベースとなるサイズは少しでも落としておきたいところです。

しっかり開発する場合はそれでいい(というかそうするべき)のですが、ちょっと触りたい場合にこれではげんなりしますね。・・・それぐらい小さいアプリなら、Reactなんか使わずに生JS書けよってツッコミが入りそうですがw

余談ですが、npm createした後のnpmのキャッシュは230MB程になっていました。

代替を探す

現在の依存関係を知ったところで、代わりになるものを探していきます。

Reactは、本体は依存無し、react-domも少量の依存関係なのでそのまま使っても良さそうですが、preactの方が軽量らしいので、今回はそちらを試してみます。

TSXは、TSXTypeScriptJavaScriptとトランスパイルしていくことになるので、これらをトランスパイルするツールを探します。

TypeScriptJavaScriptは公式ツールがありますが、TSXJSXのトランスパイルは、何らかのバンドルツールしか情報が見当たりませんでした。

tsc

TypeScript標準のトランスパイラでnpm i typescriptでインストールするとついてきます。主にTypeScriptJavaScriptにトランスパイルするのに使われています。TSXTypeScriptもできるらしいのですが、あまり情報がありません。

webpack

言わずと知れたバンドルツールでcreate-react-appで作成したプロジェクトで使われていました。すでに開発は事実上終了していて、後継のTurboPackへ移行が進められているようです。

TSXTypeScriptのトランスパイルは追加Loaderのインストールが必要です。

開発・本番として必要な機能が一通り揃っており、プラグインでの機能拡張も非常に豊富ですが、とにかくビルドが重いです。

babel

こちらも言わずと知れた、本番向けに自動でフォールバックコードを適用するツールです。TSXTypeScriptのトランスパイルもプリセットを追加インストールすることで可能です。

esbuild

Go製のバンドルツールです。ネイティブで動作し、Goの得意な並列処理というのもあってか非常に高速です。Viteも開発用ビルドの内部では、これが使われています。

TSXTypeScriptもネイティブでトランスパイルします。minify機能もありますが、rollupよりはサイズが大きくなる傾向にあるようです。

ちなみにnpmでインストールしなくても、esbuildの実行バイナリ1個があればコマンドラインで単独で動かすことが可能です。また、実行ファイル自体がnpmパッケージに含まれているので、node-gypなどでのビルドも必要ありません。

環境作成

ということで、今回の目的にはesbuildが近そうなので、esbuildで環境を作成してみます。

npm i preact
npm i -D esbuild

esbuildは実行時のオプションで色々設定することも可能ですが、ベースとなる設定はtsconfig.jsonで指定します。tsconfig.jsonは自動で読み取ってくれます。

以下のサンプルは、とりあえず必要最小限のみ記載しています。

{
	"compilerOptions": {
		"jsx": "react-jsx",
		"jsxImportSource": "preact"
	}
}

esbuildではwatch機能や簡易サーバ機能も内蔵されています。Viteでは本番向けはesbuildではなくrollupを使いますが、今回はesbuildで本番用もビルドします。

開発向け

import { context } from "esbuild";

const ctx = await context({
	entryPoints: ["./src/main.tsx"],
	outdir: "./dest",
	bundle: true,
	minify: true
});

await ctx.watch(); // watch機能
await ctx.serve({ servedir: "./dest" }); // 簡易サーバ機能

※コマンドラインで実行する場合

esbuild ./src/main.tsx --outdir=dest --bundle --minify --watch --servedir=dest

本番向け

import { build } from "esbuild";

await build({
	entryPoints: ["./src/main.tsx"],
	outdir: "./dest",
	bundle: true,
	minify: true
});

特に設定を変えない場合は、実質的にcontextを使うかbuildを使うかの違いしかありません。

※コマンドラインで実行する場合

esbuild ./src/main.tsx --outdir=dest --bundle --minify

なお、いずれの場合もHTMLファイルは自動でoutDirへコピーされないので、ビルドスクリプトにコピーするスクリプトを組むか、手動でコピーします。

動作確認

適当にコードを書いて動作確認します。

ちなみにesbuildはhtmlファイルのminify機能はありません。CSSファイルはTSX内でimportすると自動で1つにまとめたCSSファイルとして出力する機能があります(<link>タグや<style>タグを自動挿入する機能は無し)。

<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Example</title>
		<script src="./main.js"></script>
	</head>
	<body>
		<div id="root"></div>
	</body>
</html>
import { render } from "preact";

const App = () => {
	return (<p>Hello, preact!</p>);
};

document.addEventListener("DOMContentLoaded", () => {
	const root = document.getElementById("root");
	if (root) {
		render(<App />, root);
	}
});

Hello, preact! が画面に表示されれば成功です。

おまけ

今回動かしたソースは以下のURLにあります。

https://github.com/Raintensity/JunkBox/tree/main/template/project/preact-minimal

カテゴリ: JavaScript