"リモートワーク"の認識ズレ解消のためにリモートワークスタイルチェックを作ってみた
ふと思い立って "リモートワークスタイルチェック" というものを作ってみた。
ここ最近やった何件かのカジュアル面談を通して、"リモートワーク" のスタイルの認識に企業・個人によって大きく差がある印象を受けたので、ミスマッチを防ぐためにチェックリスト的なものを作ってみました(こういう観点もあるよ〜とかあれば知りたい)https://t.co/1faTEwNzKB
— mugi (@mugi_uno) 2022年3月22日
"リモートワーク" という言葉の認識の齟齬
最近、Meetyを使ってカジュアル面談をちょいちょいやっていて、いろんな方とたくさん話をさせてもらった。その中で、自分が地方に住んでるのもあってか、リモートワークが話題に上がることが多かったのだが、人によって "リモートワーク" の認識が大きく異なっている印象を受けた。
「雑談が減っちゃう」という意見もあれば、「静かで集中できる」という方もいた。「通勤が無くて楽」という方もいたし「たまにはオフィス行きたい」という話も聞いた。
同じような環境だとしても、人によって捉え方は大きく違ってた。
また、これは個人だけの話だけではなく、企業でも同じことが言えると思う。1年ほど前に転職活動をしたときに、さまざまな会社と話す機会があり、似たようなギャップを感じることがあった。
これは一例だが、とある「フルリモート可能です」という会社との面談の中で色々と掘り下げて聞いていった結果、実際には「いまはコロナ禍もありフルリモート可能だが、将来的に制度が変更されて出社必須になる可能性はある」ということが後から解った、といったことがあった。途中で制度が変わることはあまり想定していなかったので、認識に大きな齟齬があったことになる。
誤解を招かないように補足しておくと、"後から言いやがって〜" などとその会社のことをどうこう言うつもりはない。その会社にとっては "リモートワーク" はオプショナルな働き方のひとつでしかなかったが、自分にとっては "リモートワーク" は人生に関わる部分なのでかなり重要な要素だった、というだけなので、どちらが正しい・間違っているという話ではない。
ともあれ、これらを踏まえ、あまりにも "リモートワーク" という言葉に情報を含めすぎているなぁという印象を受けた。
リモートワークにはさまざまなスタイルがあり、企業・チームによってスタンスが大きく異なる。リモートワークに全振りしてる会社もあれば、社会情勢を鑑みて限定的に許可しているケースもある。
個人側の "リモートワーク" に求めるものも多種多様で、自分のように地方在住なので基本的にフルリモートで1年中働きたい人もいれば、普段は出社で良いが家庭事情による突発的な在宅勤務を許可してくれればOK、という人もいるはず。
ただ、これらの結果で一番怖いのが、"認識の齟齬があるまま入社してしまった" という可能性が潜んでいる点だと思う。お互いに気づかずに採用が確定してしまうと、
- 「リモートで静かに仕事できると思ってたのに、毎日オンラインMTGだらけじゃん!」
- 「定期的に出社必須とか聞いてないんだけど〜」
- 「入社して1年したらリモートワークがなくなったんだけど...」
といったおぞましい結果が待ってる恐れがある。
リモートワークスタイルの確認観点がほしい
リスクを減らすためにどうしたらいいか?と考えた時に、リモートワークについて確認すべき観点がどこかに列挙されており一般化されている必要があるな〜という結論に至った。
作ってみたチェック観点
実際に用意してみたチェック観点は次の通り。
※なお内容については、自分と同じように地方から長くリモートワークを経験している Toyama.rb の @kunitoo にも意見をいただいた。感謝
比重度と文化
まず大きく "比重度" と "文化" の2つに大きく分けている。
比重度は、どの程度リモートワークに重きを置いて業務を回しているかを判断する。
比重が大きいほどにリモートワークを前提とする社内制度が充実したり、何か課題があった場合も全員の課題となり解決しやすくなる。代わりに、常にリモートワーク独自の問題と向き合い続ける必要も強まったり、"オフラインのほうが手っ取り早いよね" という解決策を取りにくくなる。
スタイルチェック内にも書いたが、あくまでも「比重」を見るものであり、優劣ではない。より積極的にリモートワークを導入してるから良い、そうでないから悪い、という話ではない。 企業の属性や何を重要視するかなど、価値観はそれぞれである。リモートワーク全振りになることでむしろ働きづらく感じる人もいると思う。
一方で文化のほうは、リモートワークをどのようなポリシーで導入しているかを見る。
主にコミュニケーションに関わる項目が多く、オンライン上での会話量や、時間を合わせた同期的な仕事をどの程度必要とするかなどが対象。
「ある程度は直接人と話さないとつらいです」という人はビデオ会話が多い方が良いはずだし、「静かにもくもくと自分のペースで仕事したい」という人は真逆になると思う。
比重度の項目
リモートワークの導入期間
どの程度リモートワークを導入しているか。
シンプルに、期間が長い方が組織としてリモートワークへのノウハウが蓄積されているはず。また、長期的な実施ならでは課題にも向き合う必要が出ていると思われる。
スポット的な導入というケースもありうるため、期間が継続 or 累積によって判断を分けている。継続的に導入している方が比重が高いと判断している。
リモートワークの導入ポリシー
リモートワークを一時的なものとして導入しているか、恒久的に取り扱うか。 また、内容の変更がどの程度ありうるか。
「もう会社の制度としてがっつり組み込まれているので廃止とかは絶対ないです!」という導入なのか、「社会情勢を鑑みて一時的に使えるようになってます」という導入なのかを見る。
リモートワークのマジョリティ度合い
全メンバーのうちどの程度がリモートワークに関わっているか。 リモートワークの課題が全員のものとなるか、「リモートワークのひと」の課題となるかの違いがある。
リモートワークを全員で実施してるか、一部のメンバーだけが実施しているかで大きく扱いに違いが出てくる。「会議室のマイクの音質が絶望的に悪い...」みたいなケースも、リモートワークをするメンバーが多数派であれば全員の課題になりやすい。
申請・許可の必要性
リモートワークを行うために何らかの手続きが発生するかどうか。 また、申請が必要な場合にはどの程度の頻度で申請を行う必要があるか。
高頻度で申請が必要なほどにリモートワークがオプショナルな選択肢である割合が強くなり、リモートワークを実施するために何らかのコストが発生することになる。また、申請が必要=却下の可能性も出てくる。
リモートワークの適用範囲
リモートワークが利用可能なのは誰か。
利用のために何らかの条件が設けられているかどうか。
誰でも自由に利用できるケースと、出社可能なメンバーは出社必須なケースでは大きな隔たりがある。 また、社員は利用できるが業務委託の人はダメです、みたいなケースでも、実際に仕事をする上では不都合が出てくる可能性がある。
出社義務
業務上必須とされる出社がどの程度発生するか。 「義務」としての出社のみ数える。出社が伴うイベントだとしても個々の判断で出社せずにオンライン参加が可能なケースなどは含めない。
出社の頻度。頻度が高ければ高いほど居住地などにも制約が出てくる。 また、突発的な出社が発生する可能性がある場合、それは出社待機状態と変わらないため、考慮が必要。(@imunolionさんにご意見頂きました、ありがとうございます!)
リモートワークで必要なサービス・アプリケーションの取り扱い
リモートワーク・非リモートワークで利用するサービス・アプリケーションに差異があるか。
リモートワーク向けにサービス・アプリケーションを最適化しているかどうか。 クラウド移行などが全く実施されていない場合には、日常業務で支障が出る可能性もある。また、将来的に移行する見込みがあるかどうかも大事。
情報のアクセス範囲
リモートワーク時と非リモートワーク時に、アクセス可能な情報量にどの程度差があるか。 また、差がある場合にはそれをカバーする手段が用意されているかどうか。
リモートワーク時に手に入らない情報がどの程度あるか。 すべて同条件で手に入るか、リモートワーク向けに専用で議事録を用意してあげる必要があるか、まったくアクセスできないか、など。「オフィスでしかアクセスできない...」みたいなケースが多いほど出社頻度や、非リモート側の共有コストが生じる。
その他
その他リモートワークに関する制度や特徴的な情報など
段階的なものではなく、YES/NOで判断できるようなリモートワークに関する特記事項。
役員が自ら実施しているかや、手当があるかどうか、など。
文化の項目
同期・非同期コミュニーションの割合
コミュニケーションが伴う業務において、同期・非同期のコミュニケーションはどういった割合か。
同期的(=複数人が同じ時間でのやり取り)と、非同期的(=複数人でタイムラグを許容するやり取り)なコミュニケーションの割合。
同期的な割合が高いほど、働く時間が束縛されることが多くなるが、リアルタイムな反応を得られることになるため、スムーズに進めやすいように感じるかもしれない。 逆に非同期的な割合が高いと、自分のペースで仕事を進めやすくなりやすい。ただし、相手からのレスポンスも遅れることを許容する必要があり、タイムゾーンレベルで違いがある場合には、大きい工夫が必要となる。
テキストコミュニケーションの割合
リモートワークでのコミュニケーション方法において、テキストが占める割合はどの程度か
Slack・Teamsなどのチャットツールやメールのようなテキストベースでのコミュニケーションと、Zoom・Google Meets のようなビデオ・音声会話ツールでのコミュニケーションの割合。
テキストが主体であれば、必然的に文章に会話が残るため後から記録が追いやすくなるが、会話よりも情報量が減りやすく、工夫を伴ったコミュニケーションも重要になる。ビデオ・音声の場合は、テキストにはない表情や雰囲気といった情報を得やすく、心理的な面でのフォローもしやすいが、議事に残らず失われていく情報も多くなる。
コミュニケーション頻度
リモートワーク時に、どの程度他のメンバーとコミュニケーションを取るか (テキスト・ビデオなどの手段は問いません)
コミュニケーションの活発具合。
リモートワークはオフィスワークに比べて雑談が減りがちといった課題がある。それをストレスに感じてしまう場合、コミュニケーション頻度は重要になる。
逆に、余計な雑談などを省いて仕事をしたいと感じている場合には、成果物のみでのやり取りなど最低限のコミュニケーションスタイルのほうがマッチするかもしれない。
働く時間
リモートワークで働くにあたって、どういった時間帯に仕事をするか。 これによって同期・非同期の割合が決まったり、非同期の場合もレスポンス速度が変わってくる。
働く時間がどの程度マッチするのか。
働く時間が特に定められていない場合は自由度が増すが、同期的なコミュニケーションを必要とする場合には、事前調整が必要になるといったコストが発生する。 一方でコアタイムや定時などが定まっている場合には、ある程度で束縛されることになるが、同期的なやりとりは捗りやすい。(生活リズムが整いやすい、みたいなメリットもありそう)
というわけで、リモートワークスタイルチェックを作ってみたという話でした。
リモートワークが世の中に広く普及してきたものの、こういった差分を明確に文章化されたものが見当たらなかったため、悲劇が起きる前に書いておいた方がいいかなぁと思って頑張って作ってみた。
これで完璧な内容だとは思っていないので、TwitterでもGistコメントでもいいので、意見をもらえたら反映するかもしれません。
リモートワーク、むずかしいね!
おわり
Viteのコードを読む - createServer(ViteDevServer生成まで)
前回: Viteのコードを読む - CLI経由でのコア実行の流れ - memo_md
viteパッケージ内部 / サーバー起動部分をよむ
vite dev vite serve などで起動する packages/vite/src/node/server をよむ
ファイル一覧
. ├── __tests__ ├── hmr.ts ├── index.ts ├── middlewares │ ├── base.ts │ ├── error.ts │ ├── indexHtml.ts │ ├── proxy.ts │ ├── spaFallback.ts │ ├── static.ts │ ├── time.ts │ └── transform.ts ├── moduleGraph.ts ├── openBrowser.ts ├── pluginContainer.ts ├── searchRoot.ts ├── send.ts ├── sourcemap.ts ├── transformRequest.ts └── ws.ts
CLI からの起動は ↓
const { createServer } = await import('./server') try { const server = await createServer({ root, base: options.base, mode: options.mode, configFile: options.config, logLevel: options.logLevel, clearScreen: options.clearScreen, server: cleanOptions(options) }) ...
vite/packages/vite/src/node/server/index.ts # createServer が該当
createServer
コンフィグの読み込みとサーバー生成
const config = await resolveConfig(inlineConfig, 'serve', 'development')
- inlineConfig は CLI の実行時引数などから生成したオブジェクト
resolveConfigでResolvedConfig型の値を返す- inlineConfig の
configFileがfalseでなければloadConfigFromFileで読む (明示的に "使いません!!" としない限りロードする) - 次の順番でロードを試みる
configFileでファイル指定がある場合はそれをロードvite.config.jsvite.config.mjsvite.config.tsvite.config.cjs
- ESM の場合 (package.json の type が
module、または設定ファイルが.mjs・.ts)はbundleConfigFile- esbuildでビルド
- Pluginとして
externalize-depsとreplace-import-metaを適用している - そういうプラグインパッケージがあるわけじゃなく、ただ名前つけてるだけぽい
externalize-deps→ 外部パッケージをマークreplace-import-meta→import.meta.url,__dirname,__filenameを実体に置き換え
- inlineConfig の
const httpsOptions = await resolveHttpsConfig( config.server.https, config.cacheDir )
vite/packages/vite/src/node/http.ts#resolveHttpsConfig- https接続用の ca,cert,key,pfx ファイルをロード
const middlewares = connect() as Connect.Server const httpServer = middlewareMode ? null : await resolveHttpServer(serverConfig, middlewares, httpsOptions) const ws = createWebSocketServer(httpServer, config, httpsOptions)
- connect でサーバのMiddlewareを作成
- server設定で
middlewareModeが設定されていなければresolveHttpServer- https://ja.vitejs.dev/config/#server-middlewaremode
index.htmlの配布を誰が責務を持つかの話- ドキュメント上では
ssrorhtmlが設定に渡せるようだが、実際はtruefalseもいける
- https/proxyの設定に応じて、
httporhttpsorhttp2モジュールでcreateServer(connectで作られたMiddlewareを渡す)
- https://ja.vitejs.dev/config/#server-middlewaremode
createWebSocketServer: HMR用のWebSocketサーバーの準備。基本的に↑で使っているサーバーをそのまま使う- Midlewareモードだったり
server.htr.serverの設定などによってはサーバーを作る - 先に作ったサーバーを利用する場合は、
upgrade要求で WebSocket コネクションを確立する
- Midlewareモードだったり
createPluginContainer
const container = await createPluginContainer(config, moduleGraph, watcher)
- そもそも
PluginContainerとは何なのかを知らないと読めなさそう - https://ja.vitejs.dev/guide/api-javascript.html#vitedevserver
指定したファイル上でプラグインフックを実行できる Rollup プラグインコンテナ。とある
- https://ja.vitejs.dev/guide/api-plugin.html#%E5%85%B1%E9%80%9A%E3%81%AE%E3%83%95%E3%83%83%E3%82%AF
開発中、Vite 開発サーバは、Rollup が行うのと同じ方法で Rollup ビルドフックを呼び出すプラグインコンテナを作成します。
- 適宜 Rollup ビルドを行うためのコンテナ
// we should create a new context for each async hook pipeline so that the // active plugin in that pipeline can be tracked in a concurrency-safe manner. // using a class to make creating new contexts more efficient class Context implements PluginContext { ... }
次のようなものを持つ(特徴的ぽい一部だけ抜粋)
parse: acornでコードをparseresolve: ID(ファイルのURLやパス)を解決addWatchFile: chokiderの監視対象にファイルを乗せる
都度生成しているケースもあるし、TransformContext のように継承しているケースもある。
最終的に返される PluginContainer は次のメソッドを持つ
buildStart: すべてのプラグインを対象にbuildStartを呼ぶresolveId: ID(ファイルのURLやパス)を解決load: すべてのプラグインを対象に id を引数にloadを呼ぶtransform: すべてのプラグインを対象に id を引数にtransformを呼ぶclose: すべてのプラグインを対象にbuildEndとcloseBundleを呼ぶ
サーバー停止用ハンドラの生成
const closeHttpServer = createServerCloseFn(httpServer)
connectionのたびに全てをopenSocketsとして保持- 全ての接続に対して
destroyで破棄
ViteDevServer の生成
ここまで作ってきた全てを一通り保持する ViteDevServer オブジェクトを作る
詳細は次回以降。
感想
createServerだけでもやってることめっちゃ多かった- vite.config 書くときに「tsでも書けて便利〜〜」とか思ってたけど、裏で普通にesbuildしててそりゃそうだよなってなった
- RollUp用の
PluginContainerで複雑なことをやってんだなってのはわかった- 実際プラグイン書いてみないと真髄はわからなさそう
次回
createServer 読み終わってないので、続きをよむ
Viteのコードを読む - CLI経由でのコア実行の流れ
前回: Viteのコードを読む - リポジトリ直下のファイル群 - memo_md
コアのファイルをよむ
packages/viteを見ていく- とりあえずディレクトリ構造を眺めてみる
ディレクトリ構造
. ├── bin ├── dist │ ├── client │ └── node │ └── chunks ├── scripts ├── src │ ├── client │ └── node │ ├── __tests__ │ │ ├── packages │ │ │ ├── name │ │ │ └── noname │ │ └── plugins │ ├── optimizer │ ├── plugins │ ├── server │ │ ├── __tests__ │ │ │ └── fixtures │ │ │ ├── lerna │ │ │ │ └── nested │ │ │ ├── none │ │ │ │ └── nested │ │ │ ├── pnpm │ │ │ │ └── nested │ │ │ └── yarn │ │ │ └── nested │ │ └── middlewares │ └── ssr │ └── __tests__ └── types
CLI経由でのコマンド実行の流れ
- bin/vite.js
- dist/node/cli
- コマンドに応じた振り分け
bin/vite.js
- source-map-supportのインストール (StackTraceから追えるようにぽい)
--profileオプションに応じた挙動の切り替えrequire('../dist/node/cli')… 実行本体- 実際には
packages/vite/src/node/cli.ts
- 実際には
dist/node/cli (packages/vite/src/node/cli.ts)
- CLIは cac ベース。読みやすい
- CLIに限らず、出力全体的に picocolors を使ってた。便利そう
- 次のコマンドを登録してる
[root](alias → vite serve / vite dev)buildoptimizepreview
コマンドに応じた振り分け
[root]
- vite起動本体。ViteDevServerが立ち上がる(実体は https://nodejs.org/api/http.html)
packages/vite/src/node/server/index.ts#createServer- CLIオプションを
inlineConfigとして渡す
build
- 本番用build
packages/vite/src/node/build.ts#build
optimize
packages/vite/src/node/optimizer.ts#optimizeDeps- https://vitejs.dev/guide/dep-pre-bundling.html
- 通称
dependency pre-bundlingの実行 - 一部依存を事前解決しておくことで、vite serve 時のパフォーマンス向上に繋がるぽい
preview
packages/vite/src/node/preview.ts#preview- https://vitejs.dev/guide/static-deploy.html#testing-the-app-locally
buildでビルドした成果物の確認用distに吐かれたものをサーバ起動し確認- サーバは
serve同様httpが内部的に使われる
- サーバは
- 依存しているライブラリ (cac とか picocolors とか) は自分でも使えそう
- CLI経由の場合オプションはそれほどない。まだ読めるって感じする
- コア(root)の中に入るとそれなりに大きい。そこからが本番って感じ
次 → 振り分けている各コマンドの詳細を見ていく
Viteのコードを読む - リポジトリ直下のファイル群
前回: Viteのコードを読む - ディレクトリ構造とテスト実行 - memo_md
リポジトリ直下のファイル群をみてみる
チェックアウトしたリポジトリの直下にあるファイル群。 主に設定ファイルとかそういう系を眺めてみる。
一覧
- package.json
- LICENSE
- CODE_OF_CONDUCT.md
- CONTRIBUTING.md
- jest.config.ts
- netlify.toml
- pnpm-lock.yaml
- pnpm-workspace.yaml
- README.md
LICENCEとかそういうのは省略。必要そうなのを見ていく。
package.json
- node ≥ 12.2.0
- scripts
preinstallでnpx only-allow pnpm- https://github.com/pnpm/only-allow
- 利用可能なパッケージマネージャを強制するツールらしい。こんなのあったのか..
- 試しに
yarn installとかするとUse "pnpm install" for installation in this project.とエラーにしてくれる
formatやlintはよくある感じ。eslintやprettierを実行してる。- 他は jest実行系, vitepressによるドキュメント生成系、viteやpluginのビルド系、などがある
- lint-staged
- https://github.com/okonet/lint-staged
- gitでstageされたファイルを対象にlint,prettierを実行
- prettierは全ファイル。eslintはtsファイルのみ対象に実行している
- simple-git-hooks
- https://github.com/toplenboren/simple-git-hooks
husky的なものpre-commit:lint-stagedを実行してるcommit-msg: https://git-scm.com/docs/githooks#_commit_msg- コミットやマージのタイミングで
scripts/verifyCommit.tsを実行 - コミットメッセージの内容が規則に沿ってるかチェックしてる
feat|fix|docs|dx|refactor|perf|test|workflow|build|ci|chore|types|wip|release|depsで始まらないと弾かれる
- コミットやマージのタイミングで
pnpm-workspace.yaml / pnpm-lock.yaml
以下をmonorepos対象にしてる
packages/*packages/playground/**
jest.config.ts
- ts-jest利用
- 意外とテスト実行時に型チェックしてるんだなという感想
VITE_TEST_BUILD指定時は実行対象をplayground配下に絞ってる- npm script の
test-buildtest-serveの呼び分けで使う - 開発サーバモードでテストするか、ビルドモードでテストするかの違い
CONTRIBUTING.mdにそのあたりの説明がある
- npm script の
globalSetup: './scripts/jestGlobalSetup.cjs'playwright-chromiumで browser を起動packages/tempに playground のファイルをコピーしてる?
globalTeardown: './scripts/jestGlobalTeardown.cjs'global.__BROWSER_SERVER__にサーバーがあれば閉じるpackages/tempを消して後片付け
testEnvironment: './scripts/jestEnv.cjs'- 独自のEnvironmentを実装して適用
setupwsEndpointを参照して、ブラウザで接続global.pageに新しく開いたブラウザのページを保持
teerdown- ブラウザを閉じてる
setupFilesAfterEnv: ['./scripts/jestPerTestSetup.ts']- 各 Playground のテストディレクトリにある
serve.jsがあれば実行する- ビルドやサーバ起動を行ってる
- requireしてみて
serveorpreServeがあれば実行という感じ
isBuildTest(VITE_TEST_BUILDで決まる)の値に応じてpageから繋ぐ接続先のURLを切り替えてるisBuildTestがtruthyならビルドされる- Viteのサーバではなく、ビルド結果を使って
httpパッケージによる配信を行う
- 各 Playground のテストディレクトリにある
CODE_OF_CONDUCT.md
健全なOSS運営についてのポリシーが書いてある。
大事。
CONTRIBUTING.md
- リポジトリのセットアップ方法
- pnpm 周りの導入あたりがわかる
- テスト実行周り
- playwright使ってますよ〜という話
- 開発サーバモードとビルドモードの違い
- テストの書き方
- デバッグログの出力方法
- PRのガイドライン
- Dependencies周りの注意点
- 小さく保つためRollupでバンドルするのが前提で、基本的にdevDependenciesになる
- requireは使えないので dynamic import を使う必要がある
- 依存関係が大きく、機能のわりにサイズが大きいものは使わない
- 結構厳しい感じ。自力で作れるものは作りましょうって感じかもしれん
- 型付けを完璧に維持してますという話
- オプション追加、ホントに必要かよく考えろよという話
- 価値があるのか、他に方法(別オプションやプラグインでの解決)が無いのか、など
- 翻訳や Vite Land (https://chat.vitejs.dev) の話
感想
- JestのEnvironment独自で作ってるのはナルホドって感じだった
- 依存関係気軽に足せないのは大変そう
lint-stagedとか普通に知らんかった..、便利そうなので使っていきたい
次→Viteのコアコードを少しずつ読んでく
Viteのコードを読む - ディレクトリ構造とテスト実行
(全然ブログ書いてなかった...)
Viteのコードを読んでみようという試み
Viteが盛り上がっていて個人的にも使っているので中身を少しでも把握したい。
大変だろうけど、バージョンが上がりまくって機能追加されてからだと中身追うのも無理になりそうなので、今のうちに見れるだけ見てみるという試み。心が折れたらやめるかもしれない。
読む対象
- v2.8.3
- hash: e2349569cf96e506e0d5fff1d043727a77fdad70
ファイル一覧を眺める
とりあえず何も考えずに tree コマンドで列挙してみた
見てみる(クリックで展開)
. ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs │ ├── blog │ │ └── announcing-vite2.md │ ├── config │ │ └── index.md │ ├── guide │ │ ├── api-hmr.md │ │ ├── api-javascript.md │ │ ├── api-plugin.md │ │ ├── assets.md │ │ ├── backend-integration.md │ │ ├── build.md │ │ ├── comparisons.md │ │ ├── dep-pre-bundling.md │ │ ├── env-and-mode.md │ │ ├── features.md │ │ ├── index.md │ │ ├── migration.md │ │ ├── ssr.md │ │ ├── static-deploy.md │ │ ├── using-plugins.md │ │ └── why.md │ ├── images │ │ ├── bundler.png │ │ ├── esm.png │ │ ├── graph.png │ │ ├── vercel-configuration.png │ │ └── vite-plugin-inspect.png │ ├── index.md │ ├── plugins │ │ └── index.md │ └── public │ ├── _headers │ ├── cypress.svg │ ├── divriots.png │ ├── logo.svg │ ├── mux.svg │ ├── plaid.svg │ ├── stackblitz.svg │ ├── tailwind-labs.svg │ ├── vite.mp3 │ ├── voice.svg │ └── vuejobs.png ├── jest.config.ts ├── netlify.toml ├── package.json ├── packages │ ├── create-vite │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── __tests__ │ │ │ └── cli.spec.ts │ │ ├── index.js │ │ ├── package.json │ │ ├── template-lit │ │ │ ├── _gitignore │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── favicon.svg │ │ │ │ └── my-element.js │ │ │ └── vite.config.js │ │ ├── template-lit-ts │ │ │ ├── _gitignore │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── favicon.svg │ │ │ │ ├── my-element.ts │ │ │ │ └── vite-env.d.ts │ │ │ ├── tsconfig.json │ │ │ ├── tsconfig.node.json │ │ │ └── vite.config.ts │ │ ├── template-preact │ │ │ ├── _gitignore │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── app.jsx │ │ │ │ ├── favicon.svg │ │ │ │ ├── index.css │ │ │ │ ├── logo.jsx │ │ │ │ └── main.jsx │ │ │ └── vite.config.js │ │ ├── template-preact-ts │ │ │ ├── _gitignore │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── app.tsx │ │ │ │ ├── favicon.svg │ │ │ │ ├── index.css │ │ │ │ ├── logo.tsx │ │ │ │ ├── main.tsx │ │ │ │ ├── preact.d.ts │ │ │ │ └── vite-env.d.ts │ │ │ ├── tsconfig.json │ │ │ ├── tsconfig.node.json │ │ │ └── vite.config.ts │ │ ├── template-react │ │ │ ├── _gitignore │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── App.css │ │ │ │ ├── App.jsx │ │ │ │ ├── favicon.svg │ │ │ │ ├── index.css │ │ │ │ ├── logo.svg │ │ │ │ └── main.jsx │ │ │ └── vite.config.js │ │ ├── template-react-ts │ │ │ ├── _gitignore │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── App.css │ │ │ │ ├── App.tsx │ │ │ │ ├── favicon.svg │ │ │ │ ├── index.css │ │ │ │ ├── logo.svg │ │ │ │ ├── main.tsx │ │ │ │ └── vite-env.d.ts │ │ │ ├── tsconfig.json │ │ │ ├── tsconfig.node.json │ │ │ └── vite.config.ts │ │ ├── template-svelte │ │ │ ├── README.md │ │ │ ├── _gitignore │ │ │ ├── index.html │ │ │ ├── jsconfig.json │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ └── favicon.ico │ │ │ ├── src │ │ │ │ ├── App.svelte │ │ │ │ ├── assets │ │ │ │ │ └── svelte.png │ │ │ │ ├── lib │ │ │ │ │ └── Counter.svelte │ │ │ │ ├── main.js │ │ │ │ └── vite-env.d.ts │ │ │ └── vite.config.js │ │ ├── template-svelte-ts │ │ │ ├── README.md │ │ │ ├── _gitignore │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ └── favicon.ico │ │ │ ├── src │ │ │ │ ├── App.svelte │ │ │ │ ├── assets │ │ │ │ │ └── svelte.png │ │ │ │ ├── lib │ │ │ │ │ └── Counter.svelte │ │ │ │ ├── main.ts │ │ │ │ └── vite-env.d.ts │ │ │ ├── svelte.config.js │ │ │ ├── tsconfig.json │ │ │ ├── tsconfig.node.json │ │ │ └── vite.config.ts │ │ ├── template-vanilla │ │ │ ├── _gitignore │ │ │ ├── favicon.svg │ │ │ ├── index.html │ │ │ ├── main.js │ │ │ ├── package.json │ │ │ └── style.css │ │ ├── template-vanilla-ts │ │ │ ├── _gitignore │ │ │ ├── favicon.svg │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── main.ts │ │ │ │ ├── style.css │ │ │ │ └── vite-env.d.ts │ │ │ └── tsconfig.json │ │ ├── template-vue │ │ │ ├── README.md │ │ │ ├── _gitignore │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ └── favicon.ico │ │ │ ├── src │ │ │ │ ├── App.vue │ │ │ │ ├── assets │ │ │ │ │ └── logo.png │ │ │ │ ├── components │ │ │ │ │ └── HelloWorld.vue │ │ │ │ └── main.js │ │ │ └── vite.config.js │ │ └── template-vue-ts │ │ ├── README.md │ │ ├── _gitignore │ │ ├── index.html │ │ ├── package.json │ │ ├── public │ │ │ └── favicon.ico │ │ ├── src │ │ │ ├── App.vue │ │ │ ├── assets │ │ │ │ └── logo.png │ │ │ ├── components │ │ │ │ └── HelloWorld.vue │ │ │ ├── env.d.ts │ │ │ └── main.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ └── vite.config.ts │ ├── playground │ │ ├── alias │ │ │ ├── __tests__ │ │ │ │ └── alias.spec.ts │ │ │ ├── customResolver.js │ │ │ ├── dir │ │ │ │ ├── from-script-src.js │ │ │ │ ├── module │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ │ ├── test.css │ │ │ │ └── test.js │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── test.js │ │ │ └── vite.config.js │ │ ├── assets │ │ │ ├── __tests__ │ │ │ │ └── assets.spec.ts │ │ │ ├── css │ │ │ │ ├── css-url.css │ │ │ │ ├── fonts.css │ │ │ │ ├── icons.css │ │ │ │ ├── import.css │ │ │ │ └── nested │ │ │ │ └── at-imported-css-url.css │ │ │ ├── fonts │ │ │ │ ├── Inter-Italic.woff │ │ │ │ └── Inter-Italic.woff2 │ │ │ ├── foo.js │ │ │ ├── index.html │ │ │ ├── nested │ │ │ │ ├── asset.png │ │ │ │ ├── fragment-bg.svg │ │ │ │ ├── fragment.svg │ │ │ │ ├── icon.png │ │ │ │ └── �\203\206�\202��\203\210-測試-white\ space.png │ │ │ ├── package.json │ │ │ ├── static │ │ │ │ ├── icon.png │ │ │ │ ├── import-expression.js │ │ │ │ ├── raw.css │ │ │ │ └── raw.js │ │ │ ├── vite.config.js │ │ │ └── �\203\206�\202��\203\210-測試-white\ space.js │ │ ├── backend-integration │ │ │ ├── __tests__ │ │ │ │ └── backend-integration.spec.ts │ │ │ ├── frontend │ │ │ │ ├── entrypoints │ │ │ │ │ ├── global.css │ │ │ │ │ ├── index.html │ │ │ │ │ └── main.ts │ │ │ │ ├── images │ │ │ │ │ └── logo.png │ │ │ │ └── styles │ │ │ │ ├── background.css │ │ │ │ └── tailwind.css │ │ │ ├── package.json │ │ │ ├── postcss.config.js │ │ │ ├── references.css │ │ │ ├── tailwind.config.js │ │ │ └── vite.config.js │ │ ├── cli │ │ │ ├── __tests__ │ │ │ │ ├── cli.spec.ts │ │ │ │ └── serve.js │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ └── vite.config.js │ │ ├── cli-module │ │ │ ├── __tests__ │ │ │ │ ├── cli-module.spec.ts │ │ │ │ └── serve.js │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ └── vite.config.js │ │ ├── css │ │ │ ├── __tests__ │ │ │ │ └── css.spec.ts │ │ │ ├── async-treeshaken.css │ │ │ ├── async-treeshaken.js │ │ │ ├── async.css │ │ │ ├── async.js │ │ │ ├── composed.module.css │ │ │ ├── composed.module.less │ │ │ ├── composed.module.scss │ │ │ ├── composes-path-resolving.module.css │ │ │ ├── css-dep │ │ │ │ ├── index.css │ │ │ │ ├── index.js │ │ │ │ ├── index.scss │ │ │ │ ├── index.styl │ │ │ │ └── package.json │ │ │ ├── dep.css │ │ │ ├── glob-dep │ │ │ │ ├── bar.css │ │ │ │ └── foo.css │ │ │ ├── glob-dep.css │ │ │ ├── imported-at-import.css │ │ │ ├── imported.css │ │ │ ├── index.html │ │ │ ├── inlined.css │ │ │ ├── less.less │ │ │ ├── linked-at-import.css │ │ │ ├── linked.css │ │ │ ├── main.js │ │ │ ├── minify.css │ │ │ ├── mod.module.css │ │ │ ├── mod.module.scss │ │ │ ├── nested │ │ │ │ ├── _index.scss │ │ │ │ ├── _partial.scss │ │ │ │ ├── icon.png │ │ │ │ ├── nested.less │ │ │ │ └── nested.styl │ │ │ ├── ok.png │ │ │ ├── options │ │ │ │ ├── absolute-import.styl │ │ │ │ └── relative-import.styl │ │ │ ├── package.json │ │ │ ├── pkg-dep │ │ │ │ ├── _index.scss │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── postcss-caching │ │ │ │ ├── blue-app │ │ │ │ │ ├── imported.css │ │ │ │ │ ├── index.html │ │ │ │ │ ├── main.js │ │ │ │ │ ├── package.json │ │ │ │ │ └── postcss.config.js │ │ │ │ ├── css.spec.ts │ │ │ │ └── green-app │ │ │ │ ├── imported.css │ │ │ │ ├── index.html │ │ │ │ ├── main.js │ │ │ │ ├── package.json │ │ │ │ └── postcss.config.js │ │ │ ├── postcss.config.js │ │ │ ├── sass.scss │ │ │ ├── stylus.styl │ │ │ └── vite.config.js │ │ ├── css-codesplit │ │ │ ├── __tests__ │ │ │ │ └── css-codesplit.spec.ts │ │ │ ├── index.html │ │ │ ├── main.css │ │ │ ├── main.js │ │ │ ├── other.js │ │ │ ├── package.json │ │ │ ├── style.css │ │ │ └── vite.config.js │ │ ├── css-codesplit-cjs │ │ │ ├── __tests__ │ │ │ │ └── css-codesplit.spec.ts │ │ │ ├── index.html │ │ │ ├── main.css │ │ │ ├── main.js │ │ │ ├── other.js │ │ │ ├── package.json │ │ │ ├── style.css │ │ │ └── vite.config.js │ │ ├── data-uri │ │ │ ├── __tests__ │ │ │ │ └── data-uri.spec.ts │ │ │ ├── index.html │ │ │ └── package.json │ │ ├── define │ │ │ ├── __tests__ │ │ │ │ └── define.spec.ts │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ └── vite.config.js │ │ ├── dynamic-import │ │ │ ├── __tests__ │ │ │ │ └── dynamic-import.spec.ts │ │ │ ├── css │ │ │ │ └── index.css │ │ │ ├── index.html │ │ │ ├── mxd.js │ │ │ ├── mxd.json │ │ │ ├── nested │ │ │ │ ├── index.js │ │ │ │ └── shared.js │ │ │ ├── package.json │ │ │ ├── qux.js │ │ │ ├── views │ │ │ │ ├── bar.js │ │ │ │ ├── baz.js │ │ │ │ └── foo.js │ │ │ └── vite.config.js │ │ ├── env │ │ │ ├── __tests__ │ │ │ │ └── env.spec.ts │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ └── vite.config.js │ │ ├── extensions │ │ │ ├── __tests__ │ │ │ │ └── extensions.spec.ts │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ └── vite.config.js │ │ ├── file-delete-restore │ │ │ ├── App.jsx │ │ │ ├── Child.jsx │ │ │ ├── __tests__ │ │ │ │ └── file-delete-restore.spec.ts │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ └── vite.config.js │ │ ├── fs-serve │ │ │ ├── __tests__ │ │ │ │ └── fs-serve.spec.ts │ │ │ ├── entry.js │ │ │ ├── nested │ │ │ │ └── foo.js │ │ │ ├── package.json │ │ │ ├── root │ │ │ │ ├── src │ │ │ │ │ ├── index.html │ │ │ │ │ └── safe.txt │ │ │ │ ├── unsafe.txt │ │ │ │ └── vite.config.js │ │ │ ├── safe.json │ │ │ └── unsafe.json │ │ ├── glob-import │ │ │ ├── __tests__ │ │ │ │ └── glob-import.spec.ts │ │ │ ├── dir │ │ │ │ ├── baz.json │ │ │ │ ├── foo.js │ │ │ │ ├── index.js │ │ │ │ ├── nested │ │ │ │ │ └── bar.js │ │ │ │ └── node_modules │ │ │ │ └── hoge.js │ │ │ ├── index.html │ │ │ └── package.json │ │ ├── hmr │ │ │ ├── __tests__ │ │ │ │ └── hmr.spec.ts │ │ │ ├── customFile.js │ │ │ ├── global.css │ │ │ ├── hmr.js │ │ │ ├── hmrDep.js │ │ │ ├── hmrNestedDep.js │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── unicode-path │ │ │ │ └── 中�\226\207-�\201��\201��\202\223�\201\224-�\225\234�\200-�\237\214\225�\237\214\226�\237\214\227 │ │ │ │ └── index.html │ │ │ └── vite.config.js │ │ ├── html │ │ │ ├── __tests__ │ │ │ │ └── html.spec.ts │ │ │ ├── common.css │ │ │ ├── emptyAttr.html │ │ │ ├── foo.html │ │ │ ├── index.html │ │ │ ├── inline │ │ │ │ ├── common.js │ │ │ │ ├── dep1.js │ │ │ │ ├── dep2.js │ │ │ │ ├── dep3.js │ │ │ │ ├── module-graph.dot │ │ │ │ ├── shared-1.html │ │ │ │ ├── shared-2.html │ │ │ │ ├── shared.js │ │ │ │ ├── unique.html │ │ │ │ └── unique.js │ │ │ ├── invalid.html │ │ │ ├── link.html │ │ │ ├── main.css │ │ │ ├── main.js │ │ │ ├── nested │ │ │ │ ├── index.html │ │ │ │ ├── nested.css │ │ │ │ └── nested.js │ │ │ ├── noBody.html │ │ │ ├── noHead.html │ │ │ ├── package.json │ │ │ ├── scriptAsync.html │ │ │ ├── scriptMixed.html │ │ │ ├── shared.js │ │ │ ├── unicode-path │ │ │ │ └── 中�\226\207-�\201��\201��\202\223�\201\224-�\225\234�\200-�\237\214\225�\237\214\226�\237\214\227 │ │ │ │ └── index.html │ │ │ ├── vite.config.js │ │ │ └── zeroJS.html │ │ ├── json │ │ │ ├── __tests__ │ │ │ │ └── json.spec.ts │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ └── public.json │ │ │ └── test.json │ │ ├── legacy │ │ │ ├── __tests__ │ │ │ │ ├── legacy.spec.ts │ │ │ │ └── ssr │ │ │ │ ├── legacy-ssr.spec.ts │ │ │ │ └── serve.js │ │ │ ├── async.js │ │ │ ├── entry-server.js │ │ │ ├── immutable-chunk.js │ │ │ ├── index.html │ │ │ ├── main.js │ │ │ ├── package.json │ │ │ ├── style.css │ │ │ ├── vite.config-custom-filename.js │ │ │ └── vite.config.js │ │ ├── lib │ │ │ ├── __tests__ │ │ │ │ ├── lib.spec.ts │ │ │ │ └── serve.js │ │ │ ├── index.dist.html │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── main.js │ │ │ │ ├── main2.js │ │ │ │ └── message.js │ │ │ ├── vite.config.js │ │ │ └── vite.dyimport.config.js │ │ ├── multiple-entrypoints │ │ │ ├── __tests__ │ │ │ │ └── multiple-entrypoints.spec.ts │ │ │ ├── deps.json │ │ │ ├── dynamic-a.js │ │ │ ├── dynamic-b.js │ │ │ ├── entrypoints │ │ │ │ ├── a0.js │ │ │ │ ├── a1.js │ │ │ │ ├── a10.js │ │ │ │ ├── a11.js │ │ │ │ ├── a12.js │ │ │ │ ├── a13.js │ │ │ │ ├── a14.js │ │ │ │ ├── a15.js │ │ │ │ ├── a16.js │ │ │ │ ├── a17.js │ │ │ │ ├── a18.js │ │ │ │ ├── a19.js │ │ │ │ ├── a2.js │ │ │ │ ├── a20.js │ │ │ │ ├── a21.js │ │ │ │ ├── a22.js │ │ │ │ ├── a23.js │ │ │ │ ├── a24.js │ │ │ │ ├── a3.js │ │ │ │ ├── a4.js │ │ │ │ ├── a5.js │ │ │ │ ├── a6.js │ │ │ │ ├── a7.js │ │ │ │ ├── a8.js │ │ │ │ └── a9.js │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ ├── reference.js │ │ │ ├── reference.scss │ │ │ └── vite.config.js │ │ ├── nested-deps │ │ │ ├── __tests__ │ │ │ │ └── nested-deps.spec.ts │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── test-package-a │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── test-package-b │ │ │ │ ├── index.js │ │ │ │ ├── node_modules │ │ │ │ │ └── test-package-a │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ │ └── package.json │ │ │ ├── test-package-c │ │ │ │ ├── index-es.js │ │ │ │ ├── index.js │ │ │ │ ├── package.json │ │ │ │ └── side.js │ │ │ ├── test-package-d │ │ │ │ ├── index.js │ │ │ │ ├── package.json │ │ │ │ └── test-package-d-nested │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── test-package-e │ │ │ │ ├── index.js │ │ │ │ ├── package.json │ │ │ │ ├── test-package-e-excluded │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ │ └── test-package-e-included │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ └── vite.config.js │ │ ├── optimize-deps │ │ │ ├── __tests__ │ │ │ │ └── optimize-deps.spec.ts │ │ │ ├── cjs-dynamic.js │ │ │ ├── cjs.js │ │ │ ├── dedupe.js │ │ │ ├── dep-cjs-compiled-from-cjs │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── dep-cjs-compiled-from-esm │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── dep-esbuild-plugin-transform │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── dep-linked │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── dep-linked-include │ │ │ │ ├── Test.vue │ │ │ │ ├── foo.js │ │ │ │ ├── index.mjs │ │ │ │ ├── package.json │ │ │ │ └── test.css │ │ │ ├── glob │ │ │ │ └── foo.js │ │ │ ├── index.html │ │ │ ├── nested-exclude │ │ │ │ ├── index.js │ │ │ │ ├── nested-include │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ │ └── package.json │ │ │ ├── package.json │ │ │ └── vite.config.js │ │ ├── optimize-missing-deps │ │ │ ├── __test__ │ │ │ │ ├── optimize-missing-deps.spec.ts │ │ │ │ └── serve.js │ │ │ ├── index.html │ │ │ ├── main.js │ │ │ ├── missing-dep │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── multi-entry-dep │ │ │ │ ├── index.browser.js │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── package.json │ │ │ └── server.js │ │ ├── package.json │ │ ├── preload │ │ │ ├── __tests__ │ │ │ │ └── preload.spec.ts │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── router.js │ │ │ ├── src │ │ │ │ ├── App.vue │ │ │ │ └── components │ │ │ │ ├── About.vue │ │ │ │ ├── Hello.vue │ │ │ │ └── Home.vue │ │ │ └── vite.config.js │ │ ├── preserve-symlinks │ │ │ ├── __tests__ │ │ │ │ └── preserve-symlinks.spec.ts │ │ │ ├── index.html │ │ │ ├── moduleA │ │ │ │ ├── linked.js -> ./src/index.js │ │ │ │ ├── package.json │ │ │ │ └── src │ │ │ │ ├── data.js │ │ │ │ └── index.js │ │ │ ├── package.json │ │ │ └── src │ │ │ └── main.js │ │ ├── react │ │ │ ├── App.jsx │ │ │ ├── __tests__ │ │ │ │ └── react.spec.ts │ │ │ ├── components │ │ │ │ └── Dummy.jsx │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ └── vite.config.ts │ │ ├── react-emotion │ │ │ ├── App.jsx │ │ │ ├── __tests__ │ │ │ │ └── react.spec.ts │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ └── vite.config.ts │ │ ├── resolve │ │ │ ├── __tests__ │ │ │ │ └── resolve.spec.ts │ │ │ ├── browser-field │ │ │ │ ├── multiple.dot.path.js │ │ │ │ ├── no-ext-index │ │ │ │ │ └── index.js │ │ │ │ ├── no-ext.js │ │ │ │ ├── not-browser.js │ │ │ │ ├── out │ │ │ │ │ ├── cjs.node.js │ │ │ │ │ └── esm.browser.js │ │ │ │ ├── package.json │ │ │ │ └── relative.js │ │ │ ├── config-dep.js │ │ │ ├── custom-condition │ │ │ │ ├── index.custom.js │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── custom-ext.es │ │ │ ├── custom-main-field │ │ │ │ ├── index.custom.js │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── dir │ │ │ │ └── index.js │ │ │ ├── dir-with-ext │ │ │ │ └── index.js │ │ │ ├── dir-with-ext.js │ │ │ │ └── empty │ │ │ ├── dir.js │ │ │ ├── exact-extension │ │ │ │ ├── file.js │ │ │ │ └── file.js.js │ │ │ ├── exports-env │ │ │ │ ├── browser.js │ │ │ │ ├── browser.mjs │ │ │ │ ├── browser.prod.mjs │ │ │ │ ├── fallback.umd.js │ │ │ │ └── package.json │ │ │ ├── exports-path │ │ │ │ ├── cjs.js │ │ │ │ ├── deep.js │ │ │ │ ├── dir │ │ │ │ │ └── dir.js │ │ │ │ ├── main.js │ │ │ │ └── package.json │ │ │ ├── index.html │ │ │ ├── inline-package │ │ │ │ ├── inline.js │ │ │ │ └── package.json │ │ │ ├── package.json │ │ │ ├── ts-extension │ │ │ │ ├── hello.ts │ │ │ │ └── index.ts │ │ │ ├── utf8-bom │ │ │ │ └── main.js │ │ │ ├── util │ │ │ │ ├── bar.util.js │ │ │ │ └── index.js │ │ │ └── vite.config.js │ │ ├── resolve-config │ │ │ ├── __tests__ │ │ │ │ ├── resolve-config.spec.ts │ │ │ │ └── serve.js │ │ │ ├── package.json │ │ │ └── root │ │ │ ├── index.js │ │ │ └── vite.config.js │ │ ├── resolve-linked │ │ │ ├── dep.js │ │ │ ├── package.json │ │ │ └── src │ │ │ └── index.js │ │ ├── shims.d.ts │ │ ├── ssr-deps │ │ │ ├── __tests__ │ │ │ │ ├── serve.js │ │ │ │ └── ssr-deps.spec.ts │ │ │ ├── define-properties-exports │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── define-property-exports │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── forwarded-export │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── index.html │ │ │ ├── message │ │ │ ├── object-assigned-exports │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── only-object-assigned-exports │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── package.json │ │ │ ├── primitive-export │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── read-file-content │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── require-absolute │ │ │ │ ├── foo.js │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── server.js │ │ │ ├── src │ │ │ │ └── app.js │ │ │ └── ts-transpiled-exports │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── ssr-html │ │ │ ├── __tests__ │ │ │ │ ├── serve.js │ │ │ │ └── ssr-html.spec.ts │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── server.js │ │ │ └── src │ │ │ └── app.js │ │ ├── ssr-pug │ │ │ ├── __tests__ │ │ │ │ ├── serve.js │ │ │ │ └── ssr-pug.spec.ts │ │ │ ├── index.pug │ │ │ ├── package.json │ │ │ ├── server.js │ │ │ └── src │ │ │ └── app.js │ │ ├── ssr-react │ │ │ ├── __tests__ │ │ │ │ ├── serve.js │ │ │ │ └── ssr-react.spec.ts │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── prerender.js │ │ │ ├── server.js │ │ │ ├── src │ │ │ │ ├── App.jsx │ │ │ │ ├── add.js │ │ │ │ ├── circular-dep-init │ │ │ │ │ ├── README.md │ │ │ │ │ ├── circular-dep-init.js │ │ │ │ │ ├── module-a.js │ │ │ │ │ └── module-b.js │ │ │ │ ├── entry-client.jsx │ │ │ │ ├── entry-server.jsx │ │ │ │ ├── forked-deadlock │ │ │ │ │ ├── README.md │ │ │ │ │ ├── common-module.js │ │ │ │ │ ├── deadlock-fuse-module.js │ │ │ │ │ ├── fuse-stuck-bridge-module.js │ │ │ │ │ ├── middle-module.js │ │ │ │ │ └── stuck-module.js │ │ │ │ ├── multiply.js │ │ │ │ └── pages │ │ │ │ ├── About.jsx │ │ │ │ ├── Env.jsx │ │ │ │ └── Home.jsx │ │ │ └── vite.config.js │ │ ├── ssr-vue │ │ │ ├── __tests__ │ │ │ │ ├── serve.js │ │ │ │ └── ssr-vue.spec.ts │ │ │ ├── dep-import-type │ │ │ │ ├── deep │ │ │ │ │ └── index.d.ts │ │ │ │ └── package.json │ │ │ ├── example-external-component │ │ │ │ ├── ExampleExternalComponent.vue │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── prerender.js │ │ │ ├── server.js │ │ │ ├── src │ │ │ │ ├── App.vue │ │ │ │ ├── assets │ │ │ │ │ ├── button.css │ │ │ │ │ ├── fonts │ │ │ │ │ │ ├── Inter-Italic.woff │ │ │ │ │ │ └── Inter-Italic.woff2 │ │ │ │ │ └── logo.png │ │ │ │ ├── components │ │ │ │ │ ├── Foo.jsx │ │ │ │ │ ├── ImportType.vue │ │ │ │ │ ├── button.js │ │ │ │ │ └── foo.css │ │ │ │ ├── entry-client.js │ │ │ │ ├── entry-server.js │ │ │ │ ├── main.js │ │ │ │ ├── pages │ │ │ │ │ ├── About.vue │ │ │ │ │ ├── External.vue │ │ │ │ │ ├── Home.vue │ │ │ │ │ └── Store.vue │ │ │ │ └── router.js │ │ │ ├── vite.config.js │ │ │ └── vite.config.noexternal.js │ │ ├── ssr-webworker │ │ │ ├── __tests__ │ │ │ │ ├── serve.js │ │ │ │ └── ssr-webworker.spec.ts │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ └── entry-worker.jsx │ │ │ ├── vite.config.js │ │ │ └── worker.js │ │ ├── tailwind │ │ │ ├── __test__ │ │ │ │ └── tailwind.spec.ts │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── postcss.config.js │ │ │ ├── public │ │ │ │ └── favicon.ico │ │ │ ├── src │ │ │ │ ├── App.vue │ │ │ │ ├── assets │ │ │ │ │ └── logo.png │ │ │ │ ├── components │ │ │ │ │ └── HelloWorld.vue │ │ │ │ ├── main.js │ │ │ │ ├── router.ts │ │ │ │ └── views │ │ │ │ └── Page.vue │ │ │ ├── tailwind.config.js │ │ │ └── vite.config.ts │ │ ├── testUtils.ts │ │ ├── tsconfig-json │ │ │ ├── __tests__ │ │ │ │ └── tsconfig-json.spec.ts │ │ │ ├── index.html │ │ │ ├── nested │ │ │ │ ├── main.ts │ │ │ │ ├── not-used-type.ts │ │ │ │ └── tsconfig.json │ │ │ ├── nested-with-extends │ │ │ │ ├── main.ts │ │ │ │ ├── not-used-type.ts │ │ │ │ └── tsconfig.json │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── main.ts │ │ │ │ └── not-used-type.ts │ │ │ └── tsconfig.json │ │ ├── tsconfig-json-load-error │ │ │ ├── __tests__ │ │ │ │ └── tsconfig-json-load-error.spec.ts │ │ │ ├── has-error │ │ │ │ ├── main.ts │ │ │ │ └── tsconfig.json │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ └── main.ts │ │ │ └── tsconfig.json │ │ ├── tsconfig.json │ │ ├── vue │ │ │ ├── Assets.vue │ │ │ ├── AsyncComponent.vue │ │ │ ├── CssModules.vue │ │ │ ├── CustomBlock.vue │ │ │ ├── CustomBlockPlugin.ts │ │ │ ├── CustomElement.ce.vue │ │ │ ├── Hmr.vue │ │ │ ├── Main.vue │ │ │ ├── Node.vue │ │ │ ├── PreProcessors.vue │ │ │ ├── ReactivityTransform.vue │ │ │ ├── ScanDep.vue │ │ │ ├── Slotted.vue │ │ │ ├── Syntax.vue │ │ │ ├── __tests__ │ │ │ │ └── vue.spec.ts │ │ │ ├── assets │ │ │ │ ├── asset.png │ │ │ │ └── fragment.svg │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ └── icon.png │ │ │ ├── setup-import-template │ │ │ │ ├── SetupImportTemplate.vue │ │ │ │ └── template.html │ │ │ ├── src-import │ │ │ │ ├── SrcImport.vue │ │ │ │ ├── script.ts │ │ │ │ ├── srcImportStyle.vue │ │ │ │ ├── srcImportStyle2.vue │ │ │ │ ├── style.css │ │ │ │ ├── style2.css │ │ │ │ └── template.html │ │ │ └── vite.config.ts │ │ ├── vue-jsx │ │ │ ├── Comp.tsx │ │ │ ├── Comps.jsx │ │ │ ├── OtherExt.tesx │ │ │ ├── Script.vue │ │ │ ├── SrcImport.jsx │ │ │ ├── SrcImport.vue │ │ │ ├── __tests__ │ │ │ │ └── vue-jsx.spec.ts │ │ │ ├── index.html │ │ │ ├── main.jsx │ │ │ ├── package.json │ │ │ └── vite.config.js │ │ ├── vue-lib │ │ │ ├── __tests__ │ │ │ │ ├── serve.js │ │ │ │ └── vue-lib.spec.ts │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src-consumer │ │ │ │ └── index.ts │ │ │ ├── src-lib │ │ │ │ ├── CompA.vue │ │ │ │ ├── CompB.vue │ │ │ │ └── index.ts │ │ │ ├── vite.config.consumer.ts │ │ │ └── vite.config.lib.ts │ │ ├── wasm │ │ │ ├── __tests__ │ │ │ │ └── wasm.spec.ts │ │ │ ├── heavy.wasm │ │ │ ├── heavy.wasm.map │ │ │ ├── index.html │ │ │ ├── light.wasm │ │ │ └── package.json │ │ └── worker │ │ ├── __tests__ │ │ │ └── worker.spec.ts │ │ ├── index.html │ │ ├── my-shared-worker.ts │ │ ├── my-worker.ts │ │ ├── newUrl │ │ │ ├── module.js │ │ │ ├── url-shared-worker.js │ │ │ └── url-worker.js │ │ ├── package.json │ │ ├── possible-ts-output-worker.mjs │ │ ├── test-plugin.tsx │ │ ├── vite.config.ts │ │ └── workerImport.js │ ├── plugin-legacy │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── index.d.ts │ │ ├── index.js │ │ └── package.json │ ├── plugin-react │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── api-extractor.json │ │ ├── package.json │ │ ├── src │ │ │ ├── babel.d.ts │ │ │ ├── fast-refresh.ts │ │ │ ├── index.ts │ │ │ └── jsx-runtime │ │ │ ├── babel-import-to-require.ts │ │ │ ├── babel-restore-jsx.spec.ts │ │ │ ├── babel-restore-jsx.ts │ │ │ └── restore-jsx.ts │ │ └── tsconfig.json │ ├── plugin-vue │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── api-extractor.json │ │ ├── package.json │ │ ├── src │ │ │ ├── compiler.ts │ │ │ ├── handleHotUpdate.ts │ │ │ ├── helper.ts │ │ │ ├── index.ts │ │ │ ├── main.ts │ │ │ ├── script.ts │ │ │ ├── style.ts │ │ │ ├── template.ts │ │ │ └── utils │ │ │ ├── descriptorCache.ts │ │ │ ├── error.ts │ │ │ └── query.ts │ │ └── tsconfig.json │ ├── plugin-vue-jsx │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.d.ts │ │ ├── index.js │ │ └── package.json │ └── vite │ ├── CHANGELOG.md │ ├── LICENSE.md │ ├── README.md │ ├── api-extractor.json │ ├── bin │ │ ├── openChrome.applescript │ │ └── vite.js │ ├── client.d.ts │ ├── package.json │ ├── rollup.config.js │ ├── scripts │ │ └── patchTypes.ts │ ├── src │ │ ├── client │ │ │ ├── client.ts │ │ │ ├── env.ts │ │ │ ├── overlay.ts │ │ │ └── tsconfig.json │ │ └── node │ │ ├── __tests__ │ │ │ ├── asset.spec.ts │ │ │ ├── build.spec.ts │ │ │ ├── config.spec.ts │ │ │ ├── dev.spec.ts │ │ │ ├── packages │ │ │ │ ├── name │ │ │ │ │ └── package.json │ │ │ │ └── noname │ │ │ │ └── package.json │ │ │ ├── plugins │ │ │ │ ├── css.spec.ts │ │ │ │ └── import.spec.ts │ │ │ ├── scan.spec.ts │ │ │ └── utils.spec.ts │ │ ├── build.ts │ │ ├── certificate.ts │ │ ├── cli.ts │ │ ├── config.ts │ │ ├── constants.ts │ │ ├── http.ts │ │ ├── importGlob.ts │ │ ├── index.ts │ │ ├── logger.ts │ │ ├── optimizer │ │ │ ├── esbuildDepPlugin.ts │ │ │ ├── index.ts │ │ │ ├── registerMissing.ts │ │ │ └── scan.ts │ │ ├── packages.ts │ │ ├── plugin.ts │ │ ├── plugins │ │ │ ├── asset.ts │ │ │ ├── assetImportMetaUrl.ts │ │ │ ├── clientInjections.ts │ │ │ ├── css.ts │ │ │ ├── dataUri.ts │ │ │ ├── define.ts │ │ │ ├── esbuild.ts │ │ │ ├── html.ts │ │ │ ├── importAnalysis.ts │ │ │ ├── importAnalysisBuild.ts │ │ │ ├── index.ts │ │ │ ├── json.ts │ │ │ ├── loadFallback.ts │ │ │ ├── manifest.ts │ │ │ ├── modulePreloadPolyfill.ts │ │ │ ├── preAlias.ts │ │ │ ├── reporter.ts │ │ │ ├── resolve.ts │ │ │ ├── ssrRequireHook.ts │ │ │ ├── terser.ts │ │ │ ├── wasm.ts │ │ │ ├── worker.ts │ │ │ └── workerImportMetaUrl.ts │ │ ├── preview.ts │ │ ├── server │ │ │ ├── __tests__ │ │ │ │ ├── fixtures │ │ │ │ │ ├── lerna │ │ │ │ │ │ ├── lerna.json │ │ │ │ │ │ └── nested │ │ │ │ │ │ └── package.json │ │ │ │ │ ├── none │ │ │ │ │ │ └── nested │ │ │ │ │ │ └── package.json │ │ │ │ │ ├── pnpm │ │ │ │ │ │ ├── nested │ │ │ │ │ │ │ └── package.json │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ └── pnpm-workspace.yaml │ │ │ │ │ └── yarn │ │ │ │ │ ├── nested │ │ │ │ │ │ └── package.json │ │ │ │ │ └── package.json │ │ │ │ ├── pluginContainer.spec.ts │ │ │ │ └── search-root.spec.ts │ │ │ ├── hmr.ts │ │ │ ├── index.ts │ │ │ ├── middlewares │ │ │ │ ├── base.ts │ │ │ │ ├── error.ts │ │ │ │ ├── indexHtml.ts │ │ │ │ ├── proxy.ts │ │ │ │ ├── spaFallback.ts │ │ │ │ ├── static.ts │ │ │ │ ├── time.ts │ │ │ │ └── transform.ts │ │ │ ├── moduleGraph.ts │ │ │ ├── openBrowser.ts │ │ │ ├── pluginContainer.ts │ │ │ ├── searchRoot.ts │ │ │ ├── send.ts │ │ │ ├── sourcemap.ts │ │ │ ├── transformRequest.ts │ │ │ └── ws.ts │ │ ├── ssr │ │ │ ├── __tests__ │ │ │ │ └── ssrTransform.spec.ts │ │ │ ├── ssrExternal.ts │ │ │ ├── ssrManifestPlugin.ts │ │ │ ├── ssrModuleLoader.ts │ │ │ ├── ssrStacktrace.ts │ │ │ └── ssrTransform.ts │ │ ├── tsconfig.json │ │ └── utils.ts │ ├── tsconfig.base.json │ └── types │ ├── alias.d.ts │ ├── anymatch.d.ts │ ├── chokidar.d.ts │ ├── commonjs.d.ts │ ├── connect.d.ts │ ├── customEvent.d.ts │ ├── dynamicImportVars.d.ts │ ├── hmrPayload.d.ts │ ├── http-proxy.d.ts │ ├── importMeta.d.ts │ ├── package.json │ ├── shims.d.ts │ ├── terser.d.ts │ └── ws.d.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts │ ├── jestEnv.cjs │ ├── jestGlobalSetup.cjs │ ├── jestGlobalTeardown.cjs │ ├── jestPerTestSetup.ts │ ├── patchEsbuildDist.ts │ ├── patchFileDeps.ts │ ├── publishCI.ts │ ├── release.ts │ ├── releaseUtils.ts │ ├── tsconfig.json │ └── verifyCommit.ts └── tree.txt 298 directories, 969 files
- 298 directories, 969 files
packages/playground配下が大きい- 215 directories, 628 files を占める
- 様々な環境でViteを利用するミニマムセットで、全てテストを含んでいる
playgroundでgrepしてみると、 CONTRIBUTING.md がヒットし、テストに関する記述などが出てくる- jestの設定に含まれており、
playwright-chromium経由で Playwright を起動してブラウザ上でテストが実行される
packages/viteがvite本体ぽい- 27 directories, 114 files
- コア部分は意外と小さい
ディレクトリだけ一覧して眺める
ファイル一覧だと Playground 配下が巨大すぎてアレなので、ディレクトリだけで一覧してみる。
tree -dplaygroundnode_modulesは除去
という感じで出力してみた。
. ├── docs │ ├── blog │ ├── config │ ├── guide │ ├── images │ ├── plugins │ └── public ├── packages │ ├── create-vite │ │ ├── __tests__ │ │ ├── template-lit │ │ │ └── src │ │ ├── template-lit-ts │ │ │ └── src │ │ ├── template-preact │ │ │ └── src │ │ ├── template-preact-ts │ │ │ └── src │ │ ├── template-react │ │ │ └── src │ │ ├── template-react-ts │ │ │ └── src │ │ ├── template-svelte │ │ │ ├── public │ │ │ └── src │ │ │ ├── assets │ │ │ └── lib │ │ ├── template-svelte-ts │ │ │ ├── public │ │ │ └── src │ │ │ ├── assets │ │ │ └── lib │ │ ├── template-vanilla │ │ ├── template-vanilla-ts │ │ │ └── src │ │ ├── template-vue │ │ │ ├── public │ │ │ └── src │ │ │ ├── assets │ │ │ └── components │ │ └── template-vue-ts │ │ ├── public │ │ └── src │ │ ├── assets │ │ └── components │ ├── playground │ │ └── (※省略) │ ├── plugin-legacy │ ├── plugin-react │ │ ├── dist │ │ └── src │ │ └── jsx-runtime │ ├── plugin-vue │ │ ├── dist │ │ └── src │ │ └── utils │ ├── plugin-vue-jsx │ └── vite │ ├── bin │ ├── dist │ │ ├── client │ │ └── node │ │ └── chunks │ ├── scripts │ ├── src │ │ ├── client │ │ └── node │ │ ├── __tests__ │ │ │ ├── packages │ │ │ │ ├── name │ │ │ │ └── noname │ │ │ └── plugins │ │ ├── optimizer │ │ ├── plugins │ │ ├── server │ │ │ ├── __tests__ │ │ │ │ └── fixtures │ │ │ │ ├── lerna │ │ │ │ │ └── nested │ │ │ │ ├── none │ │ │ │ │ └── nested │ │ │ │ ├── pnpm │ │ │ │ │ └── nested │ │ │ │ └── yarn │ │ │ │ └── nested │ │ │ └── middlewares │ │ └── ssr │ │ └── __tests__ │ └── types └── scripts
- そこまで階層が深いわけでもなく、わりとシンプルなディレクトリ構造な印象
create-viteviteの2つがコアって感じ
テストを動かしてみる
pnpm i pnpm test
- 普通に大量に落ちる。なんでや!
Target page, context or browser has been closed- https://github.com/vitejs/vite/issues/4543 1回ビルドしないとダメぽい
pnpm build実行 → まだ落ちる
Since Jest will attempt to run tests in parallel, if your machine has many cores this may cause flaky test failures with multiple Playwright instances running at the same time. You can force the tests to run in series with
pnpm run test-serve -- --runInBand.
- らしいので、
pnpm run test-serve -- --runInBandを試したところパスした
とりあえずディレクトリを眺めてテストを通すところまでやった。
最初に全ファイル出力したときは「おおぅ...」となったが、playground系のコードを省いたらわりとなんとかなる気がしてきた。
次→ルートにある各種ファイルを見てみる
サイボウズのフロントエンドエキスパートチームに入った
今月からサイボウズに入社し、フロントエンドエキスパートチームのメンバーになった。
有休消化期間&GWでたっぷり眠る生活を続けていたので社会復帰できるか心配だったが、なんとかなってる。
よくある感じで数ヶ月は試用期間扱いで、それが終わってからブログに書こうかなと思っていた。ただ、試用期間中所属を隠すとなると、チームの活動内容のひとつに「発信」があるにも関わらず数ヶ月間なにも発信しない気かお前という話になってしまうし、そもそもそんな気持ちで仕事するの良くないよなという考えに至り、書いちゃうことにした。
なぜ
チームの存在は以前から知っていて、特にYouTubeでやってるフロントエンドマンスリーはよく観ていた。
最初は「ほ〜ん、すごい人たちがおるもんやな〜」ぐらいに思ってたのだが、フロントエンドを探究・研鑽しつつレガシーを改善するというテーマにも立ち向かっているのを知り、個人的にも本を出したくらいにはフロントエンドの脱レガシーには思い入れはあったので、なんとなく通ずるものは感じていた。
同時に自分自身は「このままだと良くないなぁ」という漠然とした不安みたいなものがあり、今思えばコンフォートゾーンってものへの危機感だったのかもしれない。
ぼんやりと「一緒に仕事できたら何か少しは役に立てる部分があるかもしれない」「つよいチームなので自分も成長できそう」「とはいえ自分で通用するのかな」とかを考えていたころ、偶然にも元同僚の身内に社員の方がいることを教えてもらい、その方に繋いでもらってカジュアル面談を受けることができた。本当に超絶感謝しています。
カジュアル面談にチームのほぼ全員が現れたのにはビビったが、採用に対してチームで真摯に向き合ってる証拠だと思えたし、実際に話せたことで想像していた以上に魅力に感じた。
そのあとは採用に応募し、なんやかんやあって今にいたる。
なお個人的に5年以上地域RubyコミュニティであるToyama.rbを運営しており、Rubyコミュニティ主催者がRuby書かなくてええんかという話だが、冷静に考えると自分はここ1年ぐらいのもくもく会ではRustとJSしかやってないし、そもそも内容を縛っているコミュニティではない。RubyもRailsも今でも好きだから問題ない。問題ない!!!
入ってみてどうか
社風
まずサイボウズという会社の風土にいい意味で驚くことが多い。自由度が高く、現場の裁量がとても大きい。これだけの規模の会社でこれを実現できているのは衝撃を受ける。ここまで辿り着くまでいったいどれだけの血と汗と涙が流れたのだろう..。
ただ、自由であることは同時に各々が考えと責任を持って行動する必要があるため、ぼんやりと決められた仕事をするのを期待していると死ぬことになりそう。
チーム
フロントエンドエキスパートチームについては、予想通り「すごい人たちの集まりやでぇ..!!」となってる。各々が何かしらで特出した領域を持っている印象。そしてみんな良い人。
チームの活動範囲は広く、組織横断的なチームなので関わるプロダクトも多い。そして社風もそうだが、チーム特性上さらに自由度が高く、何らかの目的意識を見つけて自主的に動いていく必要がある。
今のところはまだ特に何も色を出せてない焦りを早くも感じるが、自分は地に足をつけて泥臭い改善作業を時間かけて倒すみたいなところが力を出せる部分だと理解しているので、焦るよりも少し長いスパンで考えて成果に繋げることができたらいいなぁと思う。ひとまずは今までやってきたことを信じて頑張っていきたい。
今後
この機会なので今までやれてなかったことも取り組みたい。OSS活動と英語かなと思う。特に英語は信じられないぐらい貧弱なのでやばい。社内制度使ってレアジョブを始めることにしたので、ちゃんとやるぞ。
ちなみに
すぐにサイボウズに応募しようと決めたわけではなく、数ヶ月間で多くの企業のカジュアル面談を受けた。ご対応頂いた企業様・担当者様ありがとうございました。
あらためて、リモートワーク可能な会社がめっちゃ増えたんだなぁと実感した。
4年半前に転職活動をしたときは、富山からフルリモート可能な会社を探すのは非常に困難だった覚えがある。コロナによる影響だとは思うが、地方在住勢にとっては選択肢が増えるのは人生の幅が広がるのでとてもありがたい。4年半前に同じ転職が可能だったか?と言われると自信はない。
自分への戒めとして、地方に住んでいることを言い訳にすることがないように今後も頑張っていきたい。
おわり。
雑談用Shownoteの作成・共有サービスをつくった
チュートリアルじゃなくて、ちゃんと何か作って試しておかないとな〜という技術がたくさん自分の中で累積していたので、次のようなサービスを作って公開した。
概要
雑談・1on1・リモート飲み会・勉強会などで、話したいネタを事前に作成・共有しておき、会話時には再生する形で1つずつネタを提示してくれる。
いまのところShownote自体の作成はGoogleログインを必要としているが、作ったShownoteの閲覧や雑談トピックの追加みたいなところはURLさえ知ってれば誰でもオッケーにしてる。
ログインしたらボタンを押せばShownoteの雛形が作れる。

Shownote自体には自由に雑談テーマを追加できる。これはURLさえ知ってれば誰でもできるので、一緒に話したい人に事前共有しておける。

実際に話すときは、追加順 or ランダム再生で、1個ずつトピックを表示してくれる。

一応一定間隔でPollingしてるので、同じ画面を見ている人では表示が同期されるようになってる。5秒ほど開けてるので、同時に見ると少しラグとかズレがあるのは勘弁してほしい。websocketとか使えば良かったのだが、あんまりそのへんで頑張りたくなかったので雑にApolloClientでPollingとした。

話してる時間をそれっぽく計測しているので、最後に一番盛り上がったトークテーマがどれだったかとかが表示される。

なお一応公開しているが、使う人がいるかはわからなかったので現状以下の制約がある。
- PostgreSQLがHerokuのフリープランなので10000レコードでサービスが死ぬ
- アプリ自体がVercelにデプロイしてるので、DB接続で若干ラグがある
もし使う人がいるようであれば追々解消させる予定だが、誰もいなければ何もしない。
使った技術要素とそれらへの所感
次のようなものを使ってる
- Next.js
- Prisma (https://www.prisma.io/)
- Nexus (https://nexusjs.org/)
- Apollo Client
- GraphQL Code Generator
- Redux / Redux Toolkit
- Firebase Authentication
3画面しかないのに完全にオーバーキル構成である。 実現機能を考えればどう考えてもFirestoreとか使った方が10000000%良いのは最初からわかっていたが、これは仕事ではないので、そんなことより自分が勉強目的に使ってみたいモチベーションで使いたいものを全部入れた。問題はない。
Next.js
かなり昔のバージョンでチュートリアル的なことはやったことがあったが、改めてちゃんと何かを作ったのは始めてかもしれない。
普段Vueに慣れ親しんでいた身としては、ReactというだけでTS親和性が高くてとても良かった。また、next/image での画像最適化など、痒いところに手が届く感もあり、Vercelを使ったのでデプロイの簡単さも抜群だった。
一応部分的にSSRも採用したが、これは実際にプロダクション導入する場合は慎重に検討すべきところだな〜と改めて感じた。 Next.js側自体の推奨としても、認証が必要なページでのSSRとかはいらないよね??みたいな話もあったりするし、どうしても複雑化するので、サービスとして何を重視するのかとちゃんと向き合うのが最初にすべきことかもしれない
とはいえ、トータルの開発体験としてはとても素晴らしいものだった。新規で何か作るなら積極的に採用するよなぁというのも頷ける。
Prisma
今回このサービスを作った発端のモチベーションは「Prisma使って何か作ってみたい!!!」だったので、強引にPrismaを採用した。簡単に説明すると、Node.jsで動くORMです。
最近まで仕事でRailsを使っていて、今もRailsは好きなのだが、何が好きかというとActiveRecordの操作感に尽きるな〜と思ってる。(色々細かいことやりだすと大変なんだけども)
PrismaでのDB操作はかなりそれに近い感覚があり、prisma.user.findFirst(...) のような形でデータを取得できる。そして何よりも、それらを全てTypeScriptで型安全に操作できるのがとても良い。
また、現在はPrismaにはMigrationの仕組みが備わっていて、全体の主軸となる *.prisma に書かれたスキーマ定義をもとに、変更時には差分をMigrationとして抽出することができる。今回のような趣味レベルで使った範囲では特に困ることもなく動作したし、Migrationファイルの内容もただのALTER文が書かれているだけなので、SQL読める人なら内容も簡単に理解できて、編集しようと思えば編集できる。
全体的にかなり使いやすい印象。今後大きく流行っても全然不思議じゃない。
Nexus
APIはGraphQLにしたが、バックエンドにはNexusを使ってみた。
GraphQLでAPIを構築する際には Schema First か Code First の選択肢があるが、Nexusは Code Firstのフレームワークに該当する。
Schema First と Code First のどちらを良しとするかはサービスの規模感やチームの方針によって変わるため一概には判断できないと思うが、個人的には Code First のほうが全体の記述量が減ることが多いので好み。
ただ、Nexusにしたのは好みだけが理由ではなく、Prismaとの相性が良いのもある。 nexus-plugin-prismaというNexus用のプラグインが存在しており、たとえば「Bookモデルの中身を返すクエリ」を作るときに、Prismaが生成しているモデル情報をもとにレスポンスを定義することができる。
これの何が嬉しいかというと、DBが変わるとそれに伴ってNexusが生成するGraphQLスキーマも変化するので、そこからGraphQL Code Generatorを通せばクライアント側での型不整合なども全部チェックできるようになる。
なお、DBモデルをそのままクエリとして露呈するとか危ないでしょって最初は思ったが、公開するフィールドは明示的に指定したもののみに限定されるため、意図せず公開されるみたいなことは無く、そのあたりも問題なかった。
DB⇄APIサーバー⇄クライアントの全てが型定義で一貫してチェックされるのはとても快適で、DB変更時にクライアント側の変更が漏れたりするようなケースは開発中に一度も無かった。
Redux (Redux Toolkit)
Reduxをちゃんと使ったのは5年くらい前で、とてもツラかったような記憶があったのだが、Hooks対応なども経てとても良くなったと聞いたので試してみた。
結果としてはかなり使いやすくなってた。Redux Toolkitがとても良かった。昔Reduxでストレスに感じてたのは、やりたいことに対して手数があまりにも多すぎる点だったが、そのあたりが綺麗さっぱり解消されてる感覚があった。
これだけ使いやすいなら、下手にオレオレ状態管理するよりかは、何も考えずにとりあえずRedux採用したほうがトータルのコストは下がりそう。
まとめ
チュートリアルだけ過去にやった技術も多かったが、実際に小さいアプリケーションを作ってみることで得られる知見はとても多かった。モノを作って学ぶのは大事だな〜と改めて感じた。
ちなみに、今回のサービスのアイデアとしては「Clubhouseでのアジェンダ共有ができるといいかな〜」というのがあったが、先にClubhouseが瀕死になってしまった。
とはいえ中途半端で終わるのは悲しいので気合いで作り切った。完走するのは大事。
サービスとしては公開しておくので興味があれば使ってみてください。 https://shownotes.vercel.app/
不具合とか機能要望とかあれば @mugi_uno まで。
おわり