Closed5

Remix+CloudflareでWebサイトを作る 17(Markdownに画像をペーストてR2にアップ・wrangler dev実行エラー・Googleログイン・Viteの環境変数を使用する)

saneatsusaneatsu

【2024-03-20】Markdownエディターで画像をアップロードできるようにしたいけどカーソル位置に取得したURLを挿入するには

やりたいこと

ZennやGitHubにもあるが、エディターにファイルをドラッグ&ドロップしたらR2に画像をアップロードし、URLを取得してエディターに表示されるようなものを作りたい。

画像

  • 画像は1〜10枚まで
  • jpeg、png、gifに対応
  • サイズは一旦3MBとする

今回やること

Textareaにペーストする
.jpg .jpeg .png になっているかチェック
→ 大丈夫そうならR2にアップロードしてURLを取得
→ 取得したURLをペーストしたカーソル位置に挿入する

ということをしたい。

実装

https://zenn.dev/fugithora812/articles/c24305098f2774

function insertNewValue(
  textareaEl: HTMLTextAreaElement,
  markdown: string,
  newValue: string
) {
  // カーソル位置を取得
  const selectionStart =
    textareaEl.selectionStart !== null ? textareaEl.selectionStart : 0;
  const selectionEnd =
    textareaEl.selectionEnd !== null ? textareaEl.selectionEnd : 0;

  // カーソル位置の前のMarkdown
  const beforeCursor = markdown.substring(0, selectionStart);
  // カーソル位置の後のMarkdown
  const afterCursor = markdown.substring(selectionEnd);

  // カーソル位置に newValue を足して新たなMarkdownを作成して返す
  return beforeCursor + newValue + afterCursor;
}

// ここでR2に画像をアップロードしてアップロード先のURLを取得するが一旦仮の値
const url = "https://storage.googleapis.com/zenn-user-upload/42c8f570411d-20240320.png";
const markdownUrl = `![](${url})`;

// 元の`markdown` の現在のカーソル位置に `url` を追加して新たなMarkdownを取得する
const textarea = event.target as HTMLTextAreaElement;
const newMarkdown = insertNewValue(textarea, markdown || "", markdownUrl); 

まだできていないこと

元記事では以下の形式でペーストしたところにカーソルを移動させていたが、このように書いても、カーソル位置が一番最後に来てしまう。

「Markdownを更新する前に移動させている」みたいなタイミングの問題な気がしつつもなかなかうまくいかない。

textarea.setSelectionRange(newCursorPosition, newCursorPosition);

その他

JoyUI + markdown-itで作るMarkdownエディターのコード長くなりそうだから全部実装終わったらまとめて記事書こう。

saneatsusaneatsu

【2024-03-21】wrangler dev 実行時にHandler does not export a fetch() function. が発生

やりたいこと

Cloudflare R2のドメインをローカルでも使用できるようにしたい。
具体的には、 http://localhost:8787/<r2_key> のような形式でR2にアップロードした画像にローカルでアクセスできるようにしたい。

現在、context.cloudflare.env.BUCKET でR2へのアクセスはできる(画像のアップロード・取得)
ローカルでR2へアップロードすると、アプリがあるディレクトリの直下の.wranglerディレクトリにファイルがアップロードされる。
例: .wrangler/state/v3/r2/<YOUR_BUCKET_NAME>/blobs/5a521b3211a4729fbddd57c67f0b2ae...

やってみる

.dev.vars
+ R2_DOMAIN=http://localhost:8787
worker-configuration.d.ts
interface Env {
+  R2_DOMAIN: string;

+  BUCKET: R2Bucket;
}
wrangler.toml
[[r2_buckets]]
+ binding = "BUCKET"
+ bucket_name = "<YOUR_BUCKET_NAME>"

以下のコマンドで実行

$ npx wrangler dev

[wrangler:inf] Ready on http://localhost:8787

Handler does not export a fetch() function.というエラー発生

調査

Deploy a Remix site · Cloudflare Pages docs

公式通りに以下でRemixアプリを作った後にnpx wrangler devを実行しても同じエラーが出てくる。

$ npm create cloudflare@latest my-remix-app -- --framework=remix

wrangler.tomlmain = "app/root.tsx"にしているからか。
けど以下のようにしたところでまた、以下のScrapのエラーが発生するのか。。

Remix+CloudflareでWebサイトを作る 11 > 【2024-03-01】ローカルでR2にアップロードした画像を取得する方法

$ npx wrangler dev 'functions/[[path]].ts'
saneatsusaneatsu

【2024-03-22】remix-google-authでGoogleAuthログインにする

背景

元々remix-auth を使ってEmail/Passwordでログインしてたが、パスワードを忘れた際の再設定導線とか作るのめんどうだしGoogle認証を使ってログインする形式に変更する。

実装

Googleの認証情報の取得

https://zenn.dev/hayato94087/articles/91179fbbe1cad4#認証情報の取得

認証情報の取得 の章を参考に認証に必要な設定をしてClient ID、Client Secretを取得

Remixの実装

https://sendo-blog.com/article/remix-auth-google

https://zenn.dev/sc30gsw/articles/f908adb5579795#おわりに

EmailとPasswordの認証でremix-auth自体は使ってたということもあるけどさくっとできた。

saneatsusaneatsu

【2024-03-23】Viteの環境変数のサジェストを効かせる

背景

1つ前の投稿 内の参考サイトでは process.env を使用しているがRemix v2.7からViteを使えるようになった。
Viteではimport.meta.env で環境変数を取得できるので修正する

実装

環境変数の型定義ファイルの削除・新規作成

ファイルごと削除

env.d.ts
- /// <reference types="@remix-run/cloudflare" />
- /// <reference types="vite/client" />

- /// <reference types="@remix-run/dev" />
- /// <reference types="@cloudflare/workers-types" />

- declare const process: {
-  env: {
-     NODE_ENV: "development" | "production";
-     CLIENT_URL: string;
-     SESSION_SECRET: string;
-     GOOGLE_CLIENT_ID: string;
-     GOOGLE_CLIENT_SECRET: string;
-   };
- };

ファイルを新規作成。
環境変数とモード | ヴィーテ

vite-env.d.ts
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_CLIENT_URL: string;
  readonly VITE_SESSION_SECRET: string;
  readonly VITE_GOOGLE_CLIENT_ID: string;
  readonly VITE_GOOGLE_CLIENT_SECRET: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

これでimport.meta.envまで書いた時にサジェストが効くようになる。

既存のコードを process.env → import.meta.env に修正

import.meta.envにはデフォルトでMODE, BASE_URL, PROD, DEV, SSR があるので、それを使うようにコードを修正

export const sessionStorage = createCookieSessionStorage({
  cookie: {
    name: "__session",
    sameSite: "lax",
    path: "/",
    httpOnly: true,
    secrets: [sessionSecret],
-   secure: process.env.NODE_ENV === "production",
+   secure: import.meta.env.PROD,
    maxAge: 60 * 60 * 24,
  },
});

.env.development の新規作成

環境変数が誤ってクライアントに漏れてしまうことを防ぐために、VITE_ から始まる変数のみが Vite で処理されたコードに公開されます。

ref: 環境変数とモード | Vite

VITE_CLIENT_URL=http://localhost:5174
VITE_SESSION_SECRET=xxx
VITE_GOOGLE_CLIENT_ID=xxx
VITE_GOOGLE_CLIENT_SECRET=xxx

package.jsonの更新

場合によっては、vite build を別のモードで実行して、別のタイトルをレンダリングしたいかもしれません。オプションフラグの --mode を渡すことで、コマンドで使用されるデフォルトのモードを上書きすることができます。

ref: 環境変数とモード | Vite

--mode development と書くことで .env.development を参照させる。

package.json
- "dev": "concurrently \"npm run sass:watch\" \"remix vite:dev\"",
+ "dev": "concurrently \"npm run sass:watch\" \"remix vite:dev --mode development\"",

参考

saneatsusaneatsu

【2024-03-23】Viteのビルトイン変数が空になる

問題

Viteにはデフォルトでいくつかの環境変数が用意されているが、それらを各環境で出力させてみたら意図した結果ではなかったものがある。

まずはそれぞれの結果を見てみる。

Dev環境(build時に--mode develomentと指定)

import.meta.env.MODE = development
import.meta.env.BASE_URL = /
import.meta.env.PROD =
import.meta.env.DEV =

Staging環境(build時に--mode stagingと指定)

import.meta.env.MODE = staging
import.meta.env.BASE_URL = /
import.meta.env.PROD =
import.meta.env.DEV =

PRODDEVが空になっている。

調べる

環境変数とモード | Vite

ビルド時にNODE_ENV=developmentとか指定すれば良さそう。

結果

以下のように書いてみたけど結局空だった。

package.json
"dev": "concurrently \"npm run sass:watch\" \"NODE_ENV=development remix vite:dev --mode development\"",

.env.developmentに以下のように書いてもうまくいかず。

NODE_ENV=development

実はうまくいってた

と思っていたが、これは<p>import.meta.env.PROD = {import.meta.env.PROD}</p>という感じでフロント側で出していたからでサーバー側で確かめたらちゃんと出力されていた。

以下はDev環境

export async function loader({ context }: LoaderFunctionArgs) {
  console.log(import.meta.env.PROD); // false
  console.log(import.meta.env.DEV); // true
  return null 
}

それか<p>import.meta.env.PROD = {String(import.meta.env.PROD)}</p>とするか

このスクラップは1ヶ月前にクローズされました