- 10年目のJavaScript開発者が
package.jsonを読む場面を苦痛の試練として描き、1つの設定ファイルにエコシステムの蓄積された複雑さがどれほど詰め込まれているかを示している
main、browser、type: moduleのようなフィールドは、Isomorphic JavaScript、ブラウザバンドル、CommonJSからESMへの移行における古いマイグレーションの傷跡を思い起こさせる
scripts、dependencies、devDependenciesには、lintの派生形、watchman、gulp、left-pad、moment.js、resolutionsのように、古いツールとパッケージ管理の問題が複雑に絡み合っている
eslint、postcss、jestといったビルド・テストツールは、厳格なルール、プラットフォームごとのインストールエラー、ESM・TypeScript設定の難しさを通じて、開発環境の維持コストを押し上げている
- Nodeの
enginesを見ながらもBunやDenoへ移行しない理由は、苦痛が消えるどころかさらに大きくなると確信しているからであり、開発者はファイルを閉じてひとまず生き延びる
package.jsonを読む試練
- JavaScriptを10年間使ってきた開発者が、コンピュータの前で
package.jsonファイルを開き、最後まで読まなければならない状況から話が始まる
- このファイルは単なる設定ファイルではなく、JavaScriptエコシステムで経験した記憶と苦痛を一気によみがえらせる対象である
versionはまだ1.0に達しておらず、SemVerはずっと以前から無視する術を覚えた不快な記憶として残っている
nameは、すでに7年前に誰かが確保したまま更新していないパッケージのせいで、望む名前を使えない状況を示している
モジュールとバンドリングの古傷
mainとbrowserフィールドが共存していることで、Isomorphic JavaScriptの痕跡が浮かび上がる
- ブラウザバンドルで
fsモジュールをrequireしようとした記憶と、それを動かすために使ったハックが不快な体験として残っている
type: moduleは、requireからimportへ移るマイグレーションと結びついている
- CommonJSとESMモジュールを相互にimportしようとした経験は、大きな苦痛として残っている
スクリプトと依存関係の混乱
scriptsは、熱く苦しい混乱の中心である
lint、lintall、lintfast、lintdiffのような派生形がある
- 並列実行、不明瞭な引数、JSON形式の引数に対する二重エスケープが登場する
- yarnやpnpmに移行した後でも、一部のサブコマンドは依然としてnpmを呼び出す
- 一部のスクリプトは今でも
watchmanを使っている
- symlinkをサポートしないため使うべきではないことを覚えておかなければならない
- 関連イシューは2015年から開かれたままである
- gulpベースのスクリプトも残っており、もっと現代的なものに置き換えたいが、誰も交換する勇気を出せない
パッケージ一覧が呼び起こすトラウマ
dependenciesには非常に多くのパッケージがあり、それぞれが独自のトラウマの記憶を呼び起こす
left-padは小さなパッケージだが、「インターネット全体を壊した」伝説的なパッケージとして残っている
- 依存関係の一覧を修正するたびに、一部のパッケージはコンソールに寄付のお願いやbreaking changeの警告を長々と出力する
- 開発者は、悪意あるパッケージが秘密を盗んだりコンピュータを壊したりしないことだけを願う状態になる
- moment.jsは良いAPIを持つライブラリとして記憶されているが、インターネットはそれをmutableすぎて大きく、treeshakingも効かないと判断し、いまやdate-fnsへ移行しなければならない状況である
- パッケージのバージョンは現在の基準から大きく遅れており、アップグレードが必要だが、過去のアップグレード体験のせいで大きな苦痛が予想される
resolutionsは、パッケージのアップグレードを避けるために選んだ、もう一つの苦痛として残っている
開発ツールが生んだもう一つの複雑さ
devDependenciesを見ながら、non-dev dependencyだけで済んでいた時代を思い出すのは難しい
eslintの設定は厳しすぎるほどになり、小さなミスでも赤い下線が現れ、CIはlintの問題をまるで世界の終わりのように扱う
- チームのジュニアエンジニアには誤った安心感を与える
- どのルールを有効にするかを巡って何度も議論があった
- コードベース各所の
eslint-ignoreの数だけ苦痛が増す
postcssは自分で直接使っているわけではないが、依存関係の依存関係が要求するパッケージとして存在している
- 新しいプラットフォームで
npm installを試すたびに、不明瞭なC++コンパイルエラーを引き起こす
jestは高速なテストランナーとして始まったが、今では大きく重くなり、一部のBabelパッケージに依存している
- アプリケーションの残りの部分はesbuildとswcの組み合わせでトランスパイルされる
- ESMとTypeScriptに合わせて正しく設定する作業は、「PhDレベルの科学プロジェクト」のように難しい
- コードベースには、TypeScript、esbuild、swc、babel、eslint、prettier、jest、webpack、rollup、terserなど、多くのツールとパーサーが関わっている
enginesにはnodeが明記されており、BunやDenoに移行しても苦痛は止まらず、むしろ悪化すると見ている
- 最後の閉じ中括弧まで読み終えたあと、タブを閉じて息を整え、手と脳が無事なまま、ひとまず生き延びる
1件のコメント
Hacker News の意見
かなり笑える。18年ほど JavaScript を書いて、3年くらい前にやめたけど、このリストにある悩みの大半はこの10年くらいのものだ。
2013年に書かれていたなら必ず入っていたはずの ポリフィル が抜けているのが特に目立つ。2013年の JavaScript の悪夢で、今ではほとんど消えたものは他に何があるだろう?もちろん、この笑えるリストに置き換わったわけだけど。
一番気に入ったのは、コードベースで動いているツールやパーサーを数えながら TypeScript、esbuild、swc、babel、eslint、prettier、jest、webpack、rollup、terser を思い浮かべ、他にも抜けがあるかどうかすら知りたくなくなるほどつらい、という部分。
undefined、with、プライベートフィールドのまねのためのクロージャ、変更されたプロトタイプ、デフォルトのグローバル変数、呼び出し位置によって変わるthis、比較時の暗黙の型変換……そう、JavaScript の10年以上はこんなふうに過ぎてきた。
SomeFunction.prototypeをいじって作った 偽クラス だった。それを即時実行の無名関数で包んでオブジェクトとして返し、
SomeFunctionがクラスであるかのようにnew SomeFunction()やSomeFunction.actualFunction()を呼ばせていた。アロー関数がなかったので、イベントをスコープに結び付けるためにFunction.prototype自体をいじって.apply()を使ったり、イベントコールバックごとに終わりのない.bind(this)地獄に落ちたりしたのも大きい。今日も、まだ Promise も宣言ファイルもなしに素の JavaScript を使っている会社のテスト課題を書くという不運に遭ったけど、2012年に初めて Node を触ったころに戻った感じだった。JavaScript を使わなければならないという要件は JSDoc で回避したが、記憶よりずっとひどかった。
新しい JavaScript、正確には TypeScript プロジェクト を始めるたびに、いつも同じ手順を踏む。
たいていフロントエンドなので React を選ぶ。Svelte も試してみたい気持ちはある。バンドラーは以前なら create-react-app や Webpack のラッパーだったが、今は設定が少なく主要フレームワークをすべてサポートする Vite が最善だ。
TypeScript コンパイラをインストールして設定するが、たいていテンプレートからコピペする。最新の安定版 JavaScript と ESM モジュールをターゲットにし、eslint と prettier をそろえて設定し、TypeScript や React などのプラグインも追加する。コミット前に Prettier と eslint を走らせるフックも入れる。
IDE 拡張もインストールし、
.devcontainerや推奨拡張機能の一覧を作っておく。スタイル自体には興味がなく、コードの一貫性とミスの検出が重要なので、eslint はたいてい既成の設定を使う。その次に jest や vitest のような単体テストを設定し、TypeScript 関連の設定と eslint ルールがまた付いてくる。CSS フレームワークは tailwind が好きなので styled-components と twin.macro までインストールして React とうまく合うようにし、Google Fonts プラグインやフォントライブラリも入れる。
こうして丸一日くらい経ってようやくプログラミングを始められる。なぜこんなにやることが多いのか? Vite + TypeScript + React/Vue/Svelte + eslint/prettier + tailwind/bootstrap + jest/vitest の組み合わせを使う人はこれほど多いのに、90% のユースケースにそのまま合う 設定不要ライブラリ はなぜないのだろう?
ツールチェーンをどうにか立ち上げても、自分がコントロールしている感じがまったくなく、ごく小さな揺らぎでも崩れそうで、直すこともできなさそうだった。
最近は、この JavaScript スタックの大半を自分がやりたいことの 障害物 と見ることにして、昔のように Flask と Jinja2 ベースのサーバーサイドレンダリングアプリを作った。最新風にきらびやかではなく、CSS もないので今はかなり不格好だが、素のモダン CSS で行ってみるといいと助言された。それでも、そのスタックで機能をどれほど速く作れるかには驚くほどだ。
package.jsonの名前だけ変える。もちろん自分はバックエンドだけだけど、フロントエンドでもそういう 個人用テンプレート を作れるのでは?npm createがやってくれないの?npm create vueを実行すると、設定の大半がすでに済んだ、かなり良くて動作する Vue 構成が出てくる。ボイラープレートや細々した遊びが多いことには同意するが、今われわれが Web で作るアプリも昔よりずっと大きくなっている。ESModules だけでツールなしに JavaScript を使うこともできるが、それらのツールが実際に何かをしてくれるから、あまりそうしないのだ。最もきれいな方法ではないが、とにかく動く。新しいプロジェクトを始めるときにクローンして、何か所か名前だけ変えれば準備完了になるはず。
これは、人々が JavaScript と言っているものの、実際には「Node エコシステムのごちゃごちゃ」を意味している、また別の例です。
最初から
package.jsonファイルを開いて読む必要があると言っていて、その後もずっとその流れです。言及されているもののうち、JavaScript 自体に関係するものはほとんどありません。JavaScript と NodeJS が同じではないのはその通りですが、ほとんどの人は筆者が何を意味していたか理解したはずです。もっと正確に書ければよかったとは思いますが、完全に間違っているとも言いにくいです。
package.jsonを使うバンドラーなしで、一度本当に JavaScript を書いてみてください。ECMA が公式仕様にパッケージマネージャーやバンドラーを入れるまでは、package.jsonは事実上 JavaScript の一部です。ふざけた調子がいいですね。皮膚と神経終末が何層も焼け落ちた後では、こういう痛点を思い出すのは有益です。
逆に言えば、やる気のあるジュニア開発者に新しい MacBook を渡して深い水に放り込んでも、1日以内に生産性を出せます。コンテナ、.NET、nginx、よく使うコマンドラインツール100個を知らなくてもよく、問題が起きたらただ検索すればいいのです。
直接手で触れさえすれば数日で船全体を作れると分かっているのに、渡された粗悪な道具でボトルシップを直すために、不必要な複雑性の層と戦わされる。JavaScript、HTML、CSS だけを使う個人プロジェクトを素早く進めていたところから、現代のフロントエンド業務に投入されると、まさにそんな感じです。
それを作るのに何カ月もかかり、問題の一部がフレームワーク過多であるため翌日には時代遅れになる、という事実を素朴に分かっていません。
「説明どおりにやったら、grunt がインストールされていないと言われた。私はもう年寄りなので grunt が何なのかよく分からず、自分の grunt ファイルも何か合っていなかった。そこで少し検索して、grunt が何なのか分かった。Grunt とは……今でも実は何なのかよく分からない。」
--Joe Armstrong, https://www.youtube.com/watch?v=lKXe3HUG2l4
数日前に
gem install wayback_machine_downloaderを実行しました。Ruby という言語を、そのエコシステムに入るほど好きになったことはありませんが、外から見る限り、そのエコシステムは驚くほど安定して維持されているように見えます。gem は今でも使われているツールで、Rails もまだあります。複雑なフロントエンドが必要で、JavaScript が0〜30行を超えるような新しいプロジェクトを待っています。そのときは、最近いちばん人気の巨大フレームワークよりも HTMX のほうが幸せになれそうです。
これは JavaScript ではなく npm についての記事です。
こういうごちゃごちゃのせいで npm が嫌いで、できるだけ避けています。個人的には Deno を使い、職場では npm を不要にしてくれる monorepo ベースの bazel 風のものを使っています。
ただ、JavaScript/TypeScript はプログラミングしやすい言語だと感じていて、何を作るにしても最初に選ぶ言語です。人々が「JavaScript はひどい」と言うとき、実際には npm で React アプリをビルドするのがひどい、という意味であることがあって悲しくなります。現代の JavaScript/TypeScript のコーディングは本当に楽しいです。
JavaScript とそのエコシステムが私たち全員に PTSD を残したせいで、この記事のコメディを楽しめなくしているように感じます。
面白い文章をめぐって「真面目な」議論が多すぎます。好きなだけ解剖してもいいですが、本当の意図はユーモアだということを覚えておくとよいでしょう。
正直、最初に触れたエコシステムが Node で、後から学んだものにもそれぞれ十分に狂った問題があったと思っています。
Haskell は複数のマシンで使うのが非常に難しく、2019年に MacOS にインストールするのも苦痛で、エコシステム管理も大変でした。マクロと拡張はかなり混乱を招き、言語の後方互換性もよくありません。一方で、2010年の JavaScript はたいてい2023年でもそのまま動くと期待できます。
Docker イメージや codespace/gitpod のような環境をきちんと作ることさえ難しく、GitHub が提供するデフォルトイメージもいまいちで問題があります。
C はほとんどのプラットフォームでおおむね問題ありませんが、言語自体は正直複雑で、システム全般に関する膨大な知識が必要です。その結果、一般的なプログラミングの多くの用途には合わない、移植性の低いコードになりがちです。
もっと言うこともできますが、複数の言語を使ったことがある人なら、どのエコシステムにも複数の欠陥があり、私たちは皆ずっと苦しみ続けていることを知っています。PHP ではプロジェクト設定を数日以内に終えられたことがなく、Elm は苦痛が少ないものの、善意の独裁者状態と開発速度という独自の問題があります。
複数のプログラミングパラダイムや言語、フロントエンドとバックエンドのどちらにも慣れていると思っている多言語開発者
package.jsonが20行を超えているのを見るたびに、ほぼ正確にこういう反応になるpackage.jsonが200行を超えている悪魔を追い払おうとするかのように声に出して言う。「恐れてはならない。恐怖は精神を殺すものだ。恐怖は完全な消滅をもたらす小さな死だ。私は自分の恐怖と向き合う。それを私の中を通り過ぎさせる。通り過ぎた後には、心の目でその道筋を見る。恐怖が通り過ぎた場所には何もないだろう。ただ私だけが残る」
泣いている
昔ながらの JavaScript 開発者が記事を読みながら PTSD とフラッシュバックに苦しんでいる場面
恐れてはならない。恐怖は精神を殺すものだ。恐怖は完全な消滅をもたらす小さな死だ
私は自分の恐怖と向き合う。それを私の中を通り過ぎさせる。通り過ぎた後には、心の目でその道筋を見る
恐怖が通り過ぎた場所には何もないだろう。ただ私だけが残る
記事を読み終えて生き延びたことに気づくが、すでに汗だくになっている