最小の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 lsmy-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.2UNMET 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 -l5541フォルダサイズ
(New-Object -ComObject Scripting.FileSystemObject).GetFolder('node_modules').sizedu --bytes ./node_modules78565449ファイル数もファイルサイズもなかなかの数値です(これでもwebpackよりは格段にマシですが)。SSDから別ディスクへのコピーや移動はお茶を二口くらい飲めそうですし、HDDにインストールした場合ファイル操作はあまりやりたくないですね。パッケージのダウンロードは圧縮されているとはいえ、モバイルネットワークでnpm iはちょっと避けたくなります。
ちょっとしたアプリを作るのに75MiB使うとすると、npmではプロジェクトごとにインストールすることになるので、14個も作れば1GiBになります。実際には各アプリで使うモジュールを更にインストールするので、ベースとなるサイズは少しでも落としておきたいところです。
しっかり開発する場合はそれでいい(というかそうするべき)のですが、ちょっと触りたい場合にこれではげんなりしますね。・・・それぐらい小さいアプリなら、Reactなんか使わずに生JS書けよってツッコミが入りそうですがw
余談ですが、npm createした後のnpmのキャッシュは230MB程になっていました。
代替を探す
現在の依存関係を知ったところで、代わりになるものを探していきます。
Reactは、本体は依存無し、react-domも少量の依存関係なのでそのまま使っても良さそうですが、preactの方が軽量らしいので、今回はそちらを試してみます。
TSXは、TSX→TypeScript→JavaScriptとトランスパイルしていくことになるので、これらをトランスパイルするツールを探します。
TypeScript→JavaScriptは公式ツールがありますが、TSXやJSXのトランスパイルは、何らかのバンドルツールしか情報が見当たりませんでした。
tsc
TypeScript標準のトランスパイラでnpm i typescriptでインストールするとついてきます。主にTypeScript→JavaScriptにトランスパイルするのに使われています。TSX→TypeScriptもできるらしいのですが、あまり情報がありません。
webpack
言わずと知れたバンドルツールでcreate-react-appで作成したプロジェクトで使われていました。すでに開発は事実上終了していて、後継のTurboPackへ移行が進められているようです。
TSXやTypeScriptのトランスパイルは追加Loaderのインストールが必要です。
開発・本番として必要な機能が一通り揃っており、プラグインでの機能拡張も非常に豊富ですが、とにかくビルドが重いです。
babel
こちらも言わずと知れた、本番向けに自動でフォールバックコードを適用するツールです。TSXやTypeScriptのトランスパイルもプリセットを追加インストールすることで可能です。
esbuild
Go製のバンドルツールです。ネイティブで動作し、Goの得意な並列処理というのもあってか非常に高速です。Viteも開発用ビルドの内部では、これが使われています。
TSXやTypeScriptもネイティブでトランスパイルします。minify機能もありますが、rollupよりはサイズが大きくなる傾向にあるようです。
ちなみにnpmでインストールしなくても、esbuildの実行バイナリ1個があればコマンドラインで単独で動かすことが可能です。また、実行ファイル自体がnpmパッケージに含まれているので、node-gypなどでのビルドも必要ありません。
環境作成
ということで、今回の目的にはesbuildが近そうなので、esbuildで環境を作成してみます。
npm i preact
npm i -D esbuildesbuildは実行時のオプションで色々設定することも可能ですが、ベースとなる設定は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