Remix+CloudflareでWebサイトを作る 17(Markdownに画像をペーストてR2にアップ・wrangler dev実行エラー・Googleログイン・Viteの環境変数を使用する)
【2024-03-20】Markdownエディターで画像をアップロードできるようにしたいけどカーソル位置に取得したURLを挿入するには
やりたいこと
ZennやGitHubにもあるが、エディターにファイルをドラッグ&ドロップしたらR2に画像をアップロードし、URLを取得してエディターに表示されるようなものを作りたい。
画像
- 画像は1〜10枚まで
- jpeg、png、gifに対応
- サイズは一旦3MBとする
今回やること
Textareaにペーストする
→.jpg
.jpeg
.png
になっているかチェック
→ 大丈夫そうならR2にアップロードしてURLを取得
→ 取得したURLをペーストしたカーソル位置に挿入する
ということをしたい。
実装
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エディターのコード長くなりそうだから全部実装終わったらまとめて記事書こう。
wrangler dev
実行時にHandler does not export a fetch() function.
が発生
【2024-03-21】やりたいこと
Cloudflare R2のドメインをローカルでも使用できるようにしたい。
具体的には、 http://localhost:8787/<r2_key> のような形式でR2にアップロードした画像にローカルでアクセスできるようにしたい。
現在、context.cloudflare.env.BUCKET
でR2へのアクセスはできる(画像のアップロード・取得)
ローカルでR2へアップロードすると、アプリがあるディレクトリの直下の.wrangler
ディレクトリにファイルがアップロードされる。
例: .wrangler/state/v3/r2/<YOUR_BUCKET_NAME>/blobs/5a521b3211a4729fbddd57c67f0b2ae...
やってみる
+ R2_DOMAIN=http://localhost:8787
interface Env {
+ R2_DOMAIN: string;
+ BUCKET: R2Bucket;
}
[[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.toml
でmain = "app/root.tsx"
にしているからか。
けど以下のようにしたところでまた、以下のScrapのエラーが発生するのか。。
Remix+CloudflareでWebサイトを作る 11 > 【2024-03-01】ローカルでR2にアップロードした画像を取得する方法
$ npx wrangler dev 'functions/[[path]].ts'
【2024-03-22】remix-google-authでGoogleAuthログインにする
背景
元々remix-auth を使ってEmail/Passwordでログインしてたが、パスワードを忘れた際の再設定導線とか作るのめんどうだしGoogle認証を使ってログインする形式に変更する。
実装
Googleの認証情報の取得
認証情報の取得 の章を参考に認証に必要な設定をしてClient ID、Client Secretを取得
Remixの実装
EmailとPasswordの認証でremix-auth自体は使ってたということもあるけどさくっとできた。
【2024-03-23】Viteの環境変数のサジェストを効かせる
背景
1つ前の投稿 内の参考サイトでは process.env
を使用しているがRemix v2.7からViteを使えるようになった。
Viteではimport.meta.env
で環境変数を取得できるので修正する
実装
環境変数の型定義ファイルの削除・新規作成
ファイルごと削除
- /// <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;
- };
- };
ファイルを新規作成。
環境変数とモード | ヴィーテ
/// <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
を参照させる。
- "dev": "concurrently \"npm run sass:watch\" \"remix vite:dev\"",
+ "dev": "concurrently \"npm run sass:watch\" \"remix vite:dev --mode development\"",
参考
【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 =
PROD
とDEV
が空になっている。
調べる
ビルド時にNODE_ENV=development
とか指定すれば良さそう。
結果
以下のように書いてみたけど結局空だった。
"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>
とするか