Closed12

Remix+CloudflareでWebサイトを作る 28(CORSエラー・Twitter埋め込み・Markdownのスクロール同期・ページネーション・Single Fetch・Docusaurus)

saneatsusaneatsu

【2024−06-08】Twitterの埋め込み対応しようとしたらクロスドメイン設定によるFaild to fetch発生

背景

https://bou7254.com/posts/zenn-markdown-html-twitter-link-embed-not-displayed

ここを参考にTwitterの埋め込みを行っている。
ZennのようにTwitterのリンクを張ったらプレビューできるようにしたい。

が、Chromeでfetch()を実行するとFaild to fetch のエラーが発生してしまう。
久々に見たな。

調べる

開発環境で避ける方法あったはず。
3年くらい前までは https://app.cacher.io/ というアプリを使ってSnippetを保存していたんだけど役に立ってくれた。

結論

Cacherに書いてあった中身をまんまコピペ。
よくやった過去の自分。

Chrome

開発環境では、クロスドメイン制約のためにローカルのファイルにアクセスできなくなる。

一旦Google Chromeを終了し以下のコマンドで立ち上げる場合は以下。

$ open /Applications/Google\ Chrome.app/ --args --disable-web-security --user-data-dir

一度Chromeを終了するのがめんどうな時は以下。

$ open -n -a /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security

Safari

開発タブを表示してクロスオリジンを無効化する

その他

イーロン・マスク、Xとは言わないぞ。
Twitter(新X)にすぎない。

saneatsusaneatsu
$ open -n -a /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security

GoogleAuthログイン実装してたんだけど、上記のコマンドで立ち上げたChromeだとGoogleAuthログインの画面に遷移しない罠あった。

saneatsusaneatsu

起こったこと

open コマンドで立ち上げるのは開発環境でうまくいっていたから気にしてなかったけど、本番環境で動いていなかった。

TypeError: Failed to fetch というエラーがConsoleで出ていた。

原因・解決方法

Twitterの埋め込みするのにAPIキーとか必要ないよな〜とか思ってたけど関係なかった。

結論、クライアント側でfetchしていたからエラーが出ていてサーバー側(loader)で同じコードを実行するとエラーは出なかった。

  1. Markdownでコンテンツを編集するコードを追加(クライアント側でパース処理)
  2. Markdownを表示するだけのコードを追加(1の流れでなんとなくクライアント側でパース)

ということをしていた。

2 に関してはサーバー側でどうにかして解決。

saneatsusaneatsu

【2024−06-09】<Link>を使って内部リンクで遷移する際にTwitterの埋め込みが機能しない

背景

リロードするとTwitterの埋め込みが正常に行われるが、<Link> を使って遷移した場合はTwitterの埋め込みが機能しない。

理由

ページ全体がリロードされず埋め込みスクリプトが再実行されないから?

調べる

https://zenn.dev/catnose99/articles/329d7d61968efb
https://zenn.dev/catnose99/articles/28cfc8de6dd15c59353c

twttr.widgets.load() で直った。

たまに表示されない問題

と思いきや、どうもFlakyな挙動をしていてたまに埋め込みが表示されない。

原因・解決方法(?)

https://bou7254.com/posts/zenn-markdown-html-twitter-link-embed-not-displayed

const twitterOrXLinkRegex =
  /<a\s+[^>]*href="https:\/\/twitter\.com\/[^"']*\/status\/(\d+)"[^>]*>https:\/\/twitter\.com\/[^<]+<\/a>\s*<a\s+[^>]*href="https:\/\/twitter\.com\/[^"']*\/status\/(\d+)"[^>]*>https:\/\/twitter\.com\/[^<]+<\/a>|<a\s+[^>]*href="https:\/\/x\.com\/[^"']*\/status\/(\d+)"[^>]*>https:\/\/x\.com\/[^<]+<\/a>\s*<a\s+[^>]*href="https:\/\/x\.com\/[^"']*\/status\/(\d+)"[^>]*>https:\/\/x\.com\/[^<]+<\/a>/g;
const urlRegex = /href="([^"]*)"/g;

export const convertXLinkToEmbedHtml = async (html: string) => {
  const xLinks = html.match(twitterOrXLinkRegex);
  if (!xinks) {
    return html;
  }

  for (const xLink of xinks) {
    const xUrlObj = urlRegex.exec(xLink); // ここがたま〜〜になぜかnullになるのが問題だった
    if (!xUrlObj) {
      continue;
    }

    const xUrl = xUrlObj[1];
    const res = await fetch(
      `https://publish.twitter.com/oembed?url=${xUrl}&omit_script=true`
    );

    const json = await res.json();
    if (!json) {
      continue;
    }

    html = html.replace(xLink, json.html);
  }
  return html;
};

上記サイトのconvertTwitterLinkToEmbedHtml()を変数名などいろいろ変えて上のような実装にしていた。
デバッグしていくとxUrlObj にちゃんと値が入っている場合もあればなぜかnullになる場合もあるっぽかった。

TypescriptのPlaygroundで試してみるが、ここだと常に値が入ってくる。

理由がわからないが試しに直書きしてみる。

- const xUrlObj = urlRegex.exec(xLink);
+ const xUrlObj = /href="([^"]*)"/g.exec(xLink);

すると常に埋め込みがうまくいくようになってしまった。

なぜなのか...?

saneatsusaneatsu

【2024−06️-10】QiitaみたいにMarkdownのプレビューとスクロールを同期させたい

調べる

https://qiita.com/ohakutsu/items/2246dd476bf76e2a034e

QiitaみたいにMarkdownのプレビュー位置を画像があってもTwitterの埋め込みがあってもいい感じにしたいな〜と思っていたけど調べてみたらQiita公式が記事を投稿していた。

<p data-sourcepos="1:1-1:169">この記事は、 <a href="https://qiita.com/advent-calendar/2022/qiita-inc">Qiita株式会社のカレンダー | Advent Calendar 2022 - Qiita</a> の 17 日目の記事です。</p>
<h2 data-sourcepos="3:1-3:15">はじめに</h2>
<p data-sourcepos="5:1-5:192">今年(2022年)の6月末にエディタのアップデートがベータ版としてリリースされました。(その後、7月末に正式版としてリリースされました)</p>

パーサーにマークダウンでの行数を出力させて、行ごとの高さ(px)計算してる感じかな?

React製のものが...!

https://zenn.dev/steelydylan/articles/react-split-mde

どうやらもともとZennの開発をしていた方。
Qiitaっぽく画面を分割してプレビューできないことがネックとのことで自分で開発したっぽい。

Twitter飛んでわかったけどこの人mosya開発してる人か!!

https://github.com/steelydylan/react-split-mde

優先度高くないから今はしないけど今度コード読もう。

saneatsusaneatsu

【2024−06-14】ページネーションを実装したい

背景

テーブルのページネーションはshadcnのExampleコードのままに@tanstack/react-table を使って実装した。
テーブルではないところでページネーションの実装をしたい。

React Query?

https://qiita.com/taisei-13046/items/05cac3a2b4daeced64aa

改めて学ぶか〜。

Remixの思想

と思ったけどRemixってページネーションのことどう考えてるんだろうか。

https://remix.run/docs/en/main/discussion/state-management#understanding-state-management-in-react

In certain scenarios, using these libraries may be warranted. However, with Remix's unique server-focused approach, their utility becomes less prevalent. In fact, most Remix applications forgo them entirely.

特定のシナリオでは、これらのライブラリを使用することが正当化されるかもしれません。しかし、Remix独自のサーバーに特化したアプローチでは、これらのライブラリの有用性は低くなります。実際、ほとんどのRemixアプリケーションでは、これらのライブラリは使用されていません。

うわ、違いそう...。

結局...

最適解がわからないままとりあえずuseState とPrismaのQueryを使って適当に実装してしまった。
数増えたときにキャッシュとかいい感じにやりたいけどどうするのが良いんだろうか。

saneatsusaneatsu

【2024−06-15】Remix v2.9で導入されたSingle Fetch

https://azukiazusa.dev/blog/single-fetch-in-remix/

Remix に対してドキュメントリクエストが行われると、Remix はリクエストパスにマッチしたすべての loader 関数を呼び出し、それらの結果を組み合わせてページを構築します。対して、ユーザーがクライアントサイドでのページ遷移を行った場合、Remix はそれぞれの loader 関数ごとに個別のリクエストをサーバーに対して行います。

この「ドキュメントリクエスト」と、「ユーザーがクライアントサイドでのページ遷移を行った場合」という言葉の違いがピンと来てない。
ドキュメントリクエストという言葉に接してこない人生だった。

後者が画面での遷移を指しているし「ドキュメントリクエスト」はサーバー側へリクエストが飛んでHTMLを的な意味でいいんだよな?

一応ChatGPTに聞いてみる。

あってそう。

また将来以下の機能を実装するための準備としての役割も担っています。

  • Middleware

!!!

そうなんだ。Middlewareがないか調べてるときにこのDiscussionチラ見したけど順調なのかな。

以下のようにすると使える。

// vite.config.ts
export default defineConfig({
  plugins: [
+   remix({
+     future: {
+       unstable_singleFetch: true,
+     },
+   }),
    tsconfigPaths(),
  ],
});

このことは loader 関数から Promise を返すために、もはや defer 関数を使用する必要がないことを意味します。defer 関数を使用している箇所は単純なオブジェクトを返すように変更できます。

turbo-stream よくわかってないけど便利そう。

Single Fetch では action 関数がステータスコード 4xx/5xx を設定して返した場合にデフォルトで再検証が行われなくなります。action 関数が 4xx/5xx エラーを返す多くの場合では、データのミューテーションを行っていないので、データを再読込する必要がないと考えられるためこのような変更が行われました。

なるほど。逆にしたければ export const shouldRevalidate = () => true; を書くと。

Single Fetch では headers 関数を export していても、その値はもはや使用されません。

色々あるなぁ。今のところheaderは書き換えてないけど。

Single Fetch ではステータスコードを設定するために、新たに Response オブジェクトを生成して返す必要がなくなりました。

これも変更になるのか。

clientLoader を使用している場合には Single Fetch の挙動が少々変わります。ルートファイルで clientLoader を export している場合、Single Fetch がオプトアウトされそのルートのみ単独でデータ取得が実行され、その他の clientLoader を export していないルートのみでリクエストがまとめられます。

ふむ。
clientLoader 、なんかあるな〜くらいに思ってる存在で未だに使い所わかってない。
こいつを使いたくなるときは来るんだろうか。

この人のブログ度々ヒットするしすごく詳細だな〜と思ってGitHubみたらHonoのコントリビューターだった。

saneatsusaneatsu

【2024−06-16】Dependabotが動かない

エラー内容

Dependabotを導入してみたが動かない。

エラーログを見てみる。

updater | 2024/06/14 08:48:38 INFO <job_841930192> Updating @conform-to/zod from 1.1.3 to 1.1.4
  proxy | 2024/06/14 08:48:40 [045] GET https://registry.npmjs.org:443/pnpm
  proxy | 2024/06/14 08:48:40 [045] 200 https://registry.npmjs.org:443/pnpm
  proxy | 2024/06/14 08:48:40 [045] WARN: Cannot write TLS response body from mitm'd client: write tcp 192.168.1.1:1080->192.168.1.2:47916: write: connection reset by peer
updater | 2024/06/14 08:48:40 ERROR <job_841930192> Error processing @conform-to/zod (Dependabot::SharedHelpers::HelperSubprocessFailed)
updater | 2024/06/14 08:48:40 ERROR <job_841930192>  EACCES  EACCES: permission denied, scandir '/root'

(中略)

updater | Dependabot encountered '20' error(s) during execution, please check the logs for more details.
updater | +--------------------------------------------------+
updater | |          Dependencies failed to update           |
updater | +----------------------------------+---------------+
updater | | @conform-to/react                | unknown_error |
updater | | @conform-to/zod                  | unknown_error |
updater | | @prisma/adapter-d1               | unknown_error |
updater | | @prisma/client                   | unknown_error |
updater | | isbot                            | unknown_error |
updater | | lucide-react                     | unknown_error |
updater | | markdown-it                      | unknown_error |
updater | | miniflare                        | unknown_error |
updater | | remix-auth                       | unknown_error |
updater | | @cloudflare/workers-types        | unknown_error |
updater | | @types/react                     | unknown_error |
updater | | @typescript-eslint/eslint-plugin | unknown_error |
updater | | @typescript-eslint/parser        | unknown_error |
updater | | eslint                           | unknown_error |
updater | | eslint-plugin-react              | unknown_error |
updater | | prisma                           | unknown_error |
updater | | sass                             | unknown_error |
updater | | tailwindcss                      | unknown_error |
updater | | vite                             | unknown_error |
updater | | wrangler                         | unknown_error |
updater | +----------------------------------+---------------+

EACCES  EACCES: permission denied という権限エラーが20個のパッケージで発生している。

ググるとシンプルにnpm installでこのエラーが発生しているケースは出てくるがDependabotのパターンが出てこない。

「Settings > Code security and analysis」では諸々Enableにしている。

dependabot.yml

dependabot.yml の中身はシンプル。

dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"    
    groups:
      dependencies:
        patterns:
          - "*"
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "monthly"

ignoreしてみる

エラーが発生している20個のパッケージを全部ignoreの対象にしてみたらうまく動くのだろうか?

saneatsusaneatsu

今「Insights > Dependency graph > Dependabot」を見たら数日前からずっと「Running now」というステータスになっているものが2つ。
Actionsタブから見たらキューに入っているっぽい。とりあえずコイツらをキャンセルしてワークフローを削除。

再度「Insights > Dependency graph > Dependabot」に戻っても「Running now」になっているものがまだ残っている。
つまり、Queueに入っているジョブを消しても画面上に残り続けてしまうので、Dependabotの設定を更新しても新たなジョブは実行されないということになる。

もうデバッグのしようがない。

saneatsusaneatsu

原因にまるで心当たりがない。
もはやリポジトリ作り直してそっちに向かって再接続するほうが早いんじゃね??

と思ってやってみたら治ってしまった。

敗北。

IssueとかLabel移し替えるのだるいな。

saneatsusaneatsu

【2024−06-15】Hello, Docusaurus! 🦖

https://github.com/zenn-dev/zenn-docs-for-developers/tree/main

Zennの開発者向けドキュメントとかはこれ使っているっぽい。
GitHub Pagesで公開してるけどPrivateにするの有料だよな〜。

https://developers.cloudflare.com/pages/framework-guides/deploy-a-docusaurus-site/

と思ってCloudflareとDocusaurusについて調べたら公式ドキュメントに書いてるじゃないか。
こっちにしよう。

やってみる

書いてある通り以下のコマンドを打つと、Cloudflare Pagesのデプロイまでやってくれた...!
便利すぎ!👏🏻

npm create cloudflare@latest my-docusaurus-app -- --framework=docusaurus

(Docusaurusのセットアップは省略)

(Cloudflareへデプロイ)
╭ Deploy with Cloudflare Step 3 of 3
│
├ Do you want to deploy your application?
│ yes deploy via `npm run deploy`
│
├ Logging into Cloudflare checking authentication status
│ logged in
│
├ Selecting Cloudflare account retrieving accounts
│ account more than one account available
│
├ Which account do you want to use?
│ account W.saneatsu@gmail.com's Account
│
├ Creating Pages project
│ created via `npx wrangler pages project create jader-docs --production-branch main`
│
├ Verifying Pages project
│ verified project is ready for deployment
│
├ Deploying your application
│ deployed via `npm run deploy`
│
├  SUCCESS  View your deployed application at https://my-docusaurus-app.pages.dev

コマンド一発打つだけでデプロイされて画面もできてる嬉しさ。

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