Upgrade to Pro — share decks privately, control downloads, hide ads and more …

やさしくはじめるRemixとWeb

 やさしくはじめるRemixとWeb

この資料は、2023年4月23日に開催された「春のJavaScript祭り Online 2023」にて発表した資料になります。

https://javascript-fes.doorkeeper.jp/events/154047

Remixとは、2021年にバージョン1.0が登場したフルスタックWebフレームワークです。RemixはReact Routerを開発していたチームが主導で開発を行っています。RemixはSPAの次のアーキテクチャであるPESPAに沿って開発を行うことができる素晴らしいフレームワークです。
今回の発表ではRemixに備わっている機能の紹介、そして誕生することになった時代的背景などを含めて簡単に紹介します。そして、Remix CLIに搭載されているRemix Stacksという機能を使って、実際にプロジェクトを作成しデプロイするまでの過程を含めて、時間の許す範囲で紹介させていただければと思います。

たかぎとねこ

April 23, 2023
Tweet

More Decks by たかぎとねこ

Other Decks in Technology

Transcript

  1. SPA

  2. History of Remix 2020年にパンデミックが発生 Ryan FlorenceとMichael Jacksonが経営していたReact Trainingの 状況が悪化し、会社にとって最悪の状況を迎える React

    RouterをベースにしたWebフレームワークの開発を始める 2020年10月ベータ版の開発者プレビューをライセンス形式で公開す る(当時はライセンスの購入が必須)
  3. Kent C. Dodds' participation in Remix Kent C. Dodds 出典:

    https://remix.run/authors/profile-kent-c-dodds.png フィードバックを積極的に行う 自分のWebサイトをRemixで作 り直す Remixで人々がWebサイトを作 ることを手助けするために、フ ルタイムの仕事を辞めてRemix に参加することを決断
  4. 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
  5. 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!
  6. 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)
  7. export default function NotesPage() { return ( <div className="flex h-full

    min-h-screen flex-col"> ... <Outlet /> ... </div> ); 子ルートは親ルートの <Outlet /> を通してレンダリングされる <App /> <NotesPage /> <NoteDetailsPage />
  8. notes.tsx & notes.$noteId.tsx notes.tsxに存在する <Outlet /> は、 /notes 配下のルートをレンダ リングする

    notes.$noteId.tsxは、notes.tsxの <Outlet /> によってレンダリング される そのため、notes.tsxはnotes.$noteId.tsxにとっての親ルートとなる そして、notes.$noteId.tsxは子ルートとなる
  9. loader and action Route Moduleには、 default export されるコンポーネントの他に loader() 関数と

    action() 関数がある loader() 関数と action() 関数は、サーバーサイドでのみ実行され る コンポーネントに対してデータを提供する
  10. 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レスポンスを作成するためのユーティリティ関数
  11. useLoaderData hook loader() で返された値は useLoaderData() を使って取得できる export default function NoteDetailsPage()

    { const data = useLoaderData<typeof loader>(); return ( <div> <h3 className="text-2xl font-bold">{data.note.title}</h3> <p className="py-6">{data.note.body}</p> ... ); }
  12. 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"); }
  13. Features of action action() は GET 以外の POST 、 PUT

    、 PATCH 、 DELETE リクエストの 時に呼び出される その場合 loader() より先に呼び出される loader() へ GET リクエストが到達した時、ルート階層にマッチする すべての loader() が呼び出される しかし、 action() の場合は1つのみ
  14. 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() 理由はそれがもっとも深いルートだから
  15. 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 にリクエストを送る
  16. Remix Form export default function NoteDetailsPage() { const data =

    useLoaderData<typeof loader>(); return ( <div> <h3 className="text-2xl font-bold">{data.note.title}</h3> <p className="py-6">{data.note.body}</p> <hr className="my-4" /> <Form method="post"> <button type="submit" className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 focus:bg-blue-400" > Delete </button> </Form> </div> ); }
  17. Remix Form 1 / 3 25年以上も前からWebではFormを使ってデータに対する変更を行って きた 変更には作成、更新、削除が含まれる <Form />

    と action() によるデータの変更は、クライアント上で JavaScriptが動作していなくても実行される なぜならそれがWebの基本だから
  18. Remix Form 2 / 3 <Form /> によって値が action() に送信される時、送信されるデータ

    はシリアライズされる(通常のブラウザの挙動) action() が実行された後、ページ上のすべての loader() が再度呼び 出される データに変更がある場合、UIに即座に反映される
  19. Remix Form 3 / 3 action 属性を指定することで、現在のルート以外の action にリクエ ストを送信することができる

    method 属性には、 post 以外のメソッドタイプを指定することができ る <Form action="/projects/new" method="post" /> 。
  20. Sign up with flyctl flyctl のインストールが完了したら、ログインもしくはサインアップ を行う fly auth signup

    ログインしている場合は、期待通りのアカウントかどうかを確認する fly auth whoami
  21. Create app on Fly.io flyctl を使ってFly上にアプリを作成する % fly apps create

    javascript-fes ? Select Organization: *** (personal) New app created: javascript-fes アプリ名は必ず fly.toml の app フィールドに設定されているものと一 致させる 一致しない場合はデプロイできない
  22. 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 デプロイが完了しました