Slide 1

Slide 1 text

やさしくはじめるRemixとWeb 〜ネットワークキャズムの間に橋を架けよう〜 たかぎとねこ

Slide 2

Slide 2 text

whoami たかぎとねこ(@takagimeow) フロントエンドエンジニア 最近の活動 PESPAの翻訳記事を投稿 ChatGPTのVSCode拡張を公開 rsstreeというWebアプリを公開 Androidアプリを2つ公開

Slide 3

Slide 3 text

What is Remix?

Slide 4

Slide 4 text

About Remix RemixはフルスタックWebフレームワーク 開発者がUIを中心に開発できるように設計されている Web標準に従って開発を進めることができる React Routerをベースに開発されている PESPAがデフォルトのアーキテクチャ

Slide 5

Slide 5 text

PEMPA

Slide 6

Slide 6 text

SPA

Slide 7

Slide 7 text

PESPA

Slide 8

Slide 8 text

Comparison with Rails and Laravel 従来のバックエンドフレームワークはModel、View、Controllerの3 つを提供する RemixはViewとControllerのみフレームワークの機能として提供す る RemixはModelを機能として提供しない

Slide 9

Slide 9 text

Reasons for not providing models JavaScriptというエコシステムには、既にすばらしいライブラリが 存在している 開発者はPrismaなどのライブラリをモデルとして採用することがで きる 採用するライブラリをフレームワークに強制されず、開発者が自由 に選択できる

Slide 10

Slide 10 text

Explanation from MVC aspects Remixでは、それぞれのView自身がそのコントローラーを担う設計 データとコンポーネントをひとつのモジュールにまとめることで、 完全なUIを構築することを目標としている(ルートモジュール)

Slide 11

Slide 11 text

Questions about state management Remixでは、グローバルな状態管理が必要ない Reduxを使う必要がない GraphQLのような巨大なモジュールをクライアントに送る必要がな い

Slide 12

Slide 12 text

Misconceptions about Remix RemixはWebフレームワークである RemixはUIフレームワークではない RemixにおいてReactはViewの機能としてのみ使われている ReactはViewにとって現在サポートされているUIフレームワークの ひとつにすぎない 将来的にはVueなどのUIフレームワークをサポートする予定

Slide 13

Slide 13 text

Origins of Remix

Slide 14

Slide 14 text

Authors Michael Jackson 出典: https://remix.run/authors/profile-michael-jackson.png Ryan Florence 出典: https://remix.run/authors/profile-ryan-florence.png

Slide 15

Slide 15 text

History of Remix 2020年にパンデミックが発生 Ryan FlorenceとMichael Jacksonが経営していたReact Trainingの 状況が悪化し、会社にとって最悪の状況を迎える React RouterをベースにしたWebフレームワークの開発を始める 2020年10月ベータ版の開発者プレビューをライセンス形式で公開す る(当時はライセンスの購入が必須)

Slide 16

Slide 16 text

Kent C. Dodds' participation in Remix Kent C. Dodds 出典: https://remix.run/authors/profile-kent-c-dodds.png フィードバックを積極的に行う 自分のWebサイトをRemixで作 り直す Remixで人々がWebサイトを作 ることを手助けするために、フ ルタイムの仕事を辞めてRemix に参加することを決断

Slide 17

Slide 17 text

Let's develop an app with Remix

Slide 18

Slide 18 text

Remix Indie Stack Remix Indie Stack 出典: https://github.com/remix-run/indie-stack Docker & CI/CD & Fly.io Deployment Setup SQLite & Prisma Cypress & Vitest & Testing Library MSW ESLint & Prettier etc. 引用: https://github.com/remix-run/indie-stack

Slide 19

Slide 19 text

Setting up the project % npx create-remix@latest --template remix-run/indie-stack ? Where would you like to create your app? (./my-remix-app) ? TypeScript or JavaScript? (Use arrow keys) ❯ TypeScript JavaScript ? Do you want me to run `npm install`? (Y/n) ... Start development with `npm run dev ` That's it!

Slide 20

Slide 20 text

Start a server for development cd ./javascript-fes npm run dev ... Mock server running Rebuilding... Done in 128ms. Loading environment variables from .env Remix App Server started at http://localhost:3000 (http://192.168.xx.x:3000)

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

Nested Routes

Slide 23

Slide 23 text

プロジェクトをVSCodeで開いた時の状態

Slide 24

Slide 24 text

次のようなURLがあった場合 http://example.com/notes/clg275yvp0003zzm9kc セグメントとレンダリングされるUIコンポーネントが紐づけられる URL ファイル UIコンポーネント http://example.com root.tsx App /notes notes.tsx NotesPage /clg275yvp0003zzm9kc notes.$noteId.tsx NoteDetailsPage

Slide 25

Slide 25 text

export default function NotesPage() { return (
... ...
); 子ルートは親ルートの を通してレンダリングされる

Slide 26

Slide 26 text

root.tsx root.tsxの は、すべてのルートをレンダリングする root.tsxはアプリケーション全体のレイアウトとして機能するた め、Root Routeと呼ばれる

Slide 27

Slide 27 text

notes.tsx & notes.$noteId.tsx notes.tsxに存在する は、 /notes 配下のルートをレンダ リングする notes.$noteId.tsxは、notes.tsxの によってレンダリング される そのため、notes.tsxはnotes.$noteId.tsxにとっての親ルートとなる そして、notes.$noteId.tsxは子ルートとなる

Slide 28

Slide 28 text

それぞれのコンポーネントが大きい箱と小さい箱のように機能する

Slide 29

Slide 29 text

Route Module これらのコンポーネントのことをRouteと呼ぶ Routeは loader() や action() と一緒に定義され、これらをまとめて エクスポートするモジュール(ファイル)のことをRoute Moduleと 呼ぶ

Slide 30

Slide 30 text

File Name Conventions notes.tsx を親ルートにしたい時、子ルートのファイルは必ず notes. から始まるファイル名にする notes._index.tsx 、 notes.$noteId.tsx 、そして notes.new.tsx は必 ず notes.tsx の子ルートとなる

Slide 31

Slide 31 text

What is notes._index.tsx? /notes にアクセスした時にレンダリングする子ルートが存在しない ため、デフォルトの子ルートとして扱われる これをIndex Routeと呼ぶ

Slide 32

Slide 32 text

Nested Routes up to V1 V1まではディレクトリを分けることでルートの階層化を行っていた routes ├── notes │ ├── index.tsx │ ├── $noteId.tsx │ └── new.tsx └── notes.tsx

Slide 33

Slide 33 text

Nested Routes from V2 V2ではファイル名を使ってルートの階層化を行う ルートの階層化を行うために、ディレクトリを作成する必要がなくな った(フラットになった) routes ├── notes._index.tsx ├── notes.$noteId.tsx ├── notes.new.tsx └── notes.tsx

Slide 34

Slide 34 text

loader and action Route Moduleには、 default export されるコンポーネントの他に loader() 関数と action() 関数がある loader() 関数と action() 関数は、サーバーサイドでのみ実行され る コンポーネントに対してデータを提供する

Slide 35

Slide 35 text

loader export async function loader({ request, params }: LoaderArgs) { const userId = await requireUserId(request); invariant(params.noteId, "noteId not found"); const note = await getNote({ userId, id: params.noteId }); if (!note) { throw new Response("Not Found", { status: 404 }); } return json({ note }); } json() はHTTPレスポンスを作成するためのユーティリティ関数

Slide 36

Slide 36 text

useLoaderData hook loader() で返された値は useLoaderData() を使って取得できる export default function NoteDetailsPage() { const data = useLoaderData(); return (

{data.note.title}

{data.note.body}

... ); }

Slide 37

Slide 37 text

Features of loader GET リクエストの時に呼び出される 最初にSSRされるときにHTMLドキュメントに対してデータを提供 する ブラウザ上でナビゲーションを行うたびに、セグメントとマッチす るRoute Moduleに存在する loader() 関数が fetch によって呼び出さ れる

Slide 38

Slide 38 text

Problems solved by loader loader() がサーバー上で動作することにより、SPAでは課題だった プライベートキーなどの問題が発生しない サードパーティのAPIやDBとの通信も行える 非同期の await を使うことができる つまり、クライアント上でのAPI呼び出しに関して悩む必要がなくな った

Slide 39

Slide 39 text

action export async function action({ request, params }: ActionArgs) { const userId = await requireUserId(request); invariant(params.noteId, "noteId not found"); await deleteNote({ userId, id: params.noteId }); return redirect("/notes"); }

Slide 40

Slide 40 text

Features of action action() は GET 以外の POST 、 PUT 、 PATCH 、 DELETE リクエストの 時に呼び出される その場合 loader() より先に呼び出される loader() へ GET リクエストが到達した時、ルート階層にマッチする すべての loader() が呼び出される しかし、 action() の場合は1つのみ

Slide 41

Slide 41 text

Conditions under which an action is determined to be one 1 / 2 どのルートの action() が呼び出されるかの条件は、最も深くマッチす るルートであること 例えば、 /notes/$noteId の場合を考える notes.tsx と notes. $noteId.tsx の両方に action() が定義されていた としても、呼び出されるのは notes. $noteId.tsx の action() 理由はそれがもっとも深いルートだから

Slide 42

Slide 42 text

Conditions under which an action is determined to be one 2 / 2 しかし、 /notes にリクエストが来た時の挙動は違う notes._index.tsx があったとしても、 notes.tsx の action() が呼び出 される 明確に notes._index.tsx の action() を呼び出したい場合は、 ?index を付与して /notes?index にリクエストを送る

Slide 43

Slide 43 text

How to call action クライアントが action() を呼び出すために axios() を使う必要はな い Web標準に従って を使えば良い

Slide 44

Slide 44 text

Remix Form export default function NoteDetailsPage() { const data = useLoaderData(); return (

{data.note.title}

{data.note.body}


Delete
); }

Slide 45

Slide 45 text

Remix Form 1 / 3 25年以上も前からWebではFormを使ってデータに対する変更を行って きた 変更には作成、更新、削除が含まれる と action() によるデータの変更は、クライアント上で JavaScriptが動作していなくても実行される なぜならそれがWebの基本だから

Slide 46

Slide 46 text

Remix Form 2 / 3 によって値が action() に送信される時、送信されるデータ はシリアライズされる(通常のブラウザの挙動) action() が実行された後、ページ上のすべての loader() が再度呼び 出される データに変更がある場合、UIに即座に反映される

Slide 47

Slide 47 text

Remix Form 3 / 3 action 属性を指定することで、現在のルート以外の action にリクエ ストを送信することができる method 属性には、 post 以外のメソッドタイプを指定することができ る 。

Slide 48

Slide 48 text

Let's deploy to Fly.io!

Slide 49

Slide 49 text

出典: https://fly.io/

Slide 50

Slide 50 text

https://fly.io/docs/hands-on/install-flyctl/

Slide 51

Slide 51 text

Sign up with flyctl flyctl のインストールが完了したら、ログインもしくはサインアップ を行う fly auth signup ログインしている場合は、期待通りのアカウントかどうかを確認する fly auth whoami

Slide 52

Slide 52 text

Create app on Fly.io flyctl を使ってFly上にアプリを作成する % fly apps create javascript-fes ? Select Organization: *** (personal) New app created: javascript-fes アプリ名は必ず fly.toml の app フィールドに設定されているものと一 致させる 一致しない場合はデプロイできない

Slide 53

Slide 53 text

Create a Secret on Fly.io プロジェクトの内部では SESSION_SECRET 環境変数が使われている これはアカウント管理のロジックで使われている secretsを設定することで環境変数として値を渡すことができる % fly secrets set SESSION_SECRET=$(openssl rand -hex 32) --app javascript-fes Secrets are staged for the first deployment%

Slide 54

Slide 54 text

Create a persistent volume 今回のプロジェクトではデータベースとしてSQLiteが採用されてる デプロイするたびにファイルシステムが再構築されるため、SQLiteの データベースファイルを永続的なボリュームに配置する必要がある % fly volumes create data --size 1 --app javascript-fes ? Select region: Dallas, Texas (US) (dfw) ID: vol_xxx ... App: javascript-fes ... Size GB: 1

Slide 55

Slide 55 text

Deploy to Fly.io それでは、Fly.ioにデプロイしよう fly launch を実行するだけです % fly launch ... 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 2 total, 1 passing, 1 critical] --> v0 deployed successfully デプロイが完了しました

Slide 56

Slide 56 text

Let's check it out! 実際にアプリが動作しているかを確認しましょう https://javascript-fes.fly.dev/

Slide 57

Slide 57 text

出典: https://javascript-fes.fly.dev/

Slide 58

Slide 58 text

Conclusion Remixで大事にされている4つの考え 1. コードとデータを分離させたサーバー・クライアントモデルを採用 する 2. Webの基本に忠実に従う。例えばブラウザやHTTP、HTMLといった 基本中の基本 3. UXを向上させるためにJavaScriptを使い、ブラウザの動作をエミュ レートする 4. 基本となる技術を抽象化させすぎない

Slide 59

Slide 59 text

Conclusion Webの基本的な技術は25年以上前に作られた この技術は今もなお使われている素晴らしい技術 Remixはその上にすべてが構築されている Remixを学ぶということはWeb標準を学ぶこと Web標準を学べば自然とRemixを使いこなせるようになる このシンプルなメンタルモデルこそがRemixの魅力である

Slide 60

Slide 60 text

Thank you!