From d2ab1de82ead36f84021bab7347da954e2eed8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Tue, 19 Dec 2023 22:21:55 +0900 Subject: [PATCH 1/2] docs: 2023-12-19-mac202319 (#61) --- ...X__Blog__2023-12-19-mac202319__Figure1.vue | 58 +++++ content/blog/2023-12-19-mac202319.md | 238 ++++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 components/content/X__Blog__2023-12-19-mac202319__Figure1.vue create mode 100644 content/blog/2023-12-19-mac202319.md diff --git a/components/content/X__Blog__2023-12-19-mac202319__Figure1.vue b/components/content/X__Blog__2023-12-19-mac202319__Figure1.vue new file mode 100644 index 00000000..08ed1edc --- /dev/null +++ b/components/content/X__Blog__2023-12-19-mac202319__Figure1.vue @@ -0,0 +1,58 @@ + + + diff --git a/content/blog/2023-12-19-mac202319.md b/content/blog/2023-12-19-mac202319.md new file mode 100644 index 00000000..c978ea68 --- /dev/null +++ b/content/blog/2023-12-19-mac202319.md @@ -0,0 +1,238 @@ +--- +date: 2023-12-19 +--- + +# Deep dive on the linkage between Misskey and Vue.js + +:::tip +これは [Misskey Advent Calendar 2023](https://adventar.org/calendars/8742) 19 日目の記事です. +::: + +こんにちは, コアチームメンバーの [acid-chicken](https://github.com/acid-chicken) です. Misskey の開発には nighthike v4 あたりから参加しており, 現在は本業の傍ら, 余暇にリファクタリングやコードレビューなどをやっていることが多いです. + +Misskey では [2018 年からフロントエンドの UI フレームワークに Vue.js を採用し](https://github.com/misskey-dev/misskey/pull/1116)ており, メジャーアップデートのマイグレーションなどを経て, 現在も継続して使用しています. 今回は, Misskey のフロントエンド構造について, Vue.js の機能との接点を中心に深掘りしていきます. + +:::tip +大まかな解説は既に syuilo 連載[「Misskey & Webテクノロジー最前線」9月](/blog/2023-09-11-gihyo)などで触れられています. 一方で, 本記事では連載で触れないような, 細かい部分に焦点を絞った話題を扱うため, もしかすると読んでいてつまらない内容になっているかもしれません. 予めご了承ください. +::: + +## Misskey のフロントエンド構造 + +現在 (nasubi 開始時点) の Misskey は, 以下のようなレイヤー構造の構成によってフロントエンドを描画しています. + +
+ +::X__Blog__2023-12-19-mac202319__Figure1 +:: + +
+Misskey のフロントエンド構造 +
+
+ +コードベースでは, 図における上部のレイヤーと下部のレイヤーが分かれており, (少なくとも便宜上は) 前者をフロントエンド, 後者をバックエンドと呼んでいます. ビルド時に, フロントエンドは Vite によってバンドルされ, その成果物はバックエンドのアセットとして配置されます. バックエンドは, ユーザーエージェント (多くの場合, Web ブラウザ) からのリクエストに対して適切な HTML を構築し, それにアセットを参照させることで, フロントエンドを描画します. + +フロントエンドにおいては, 参照するサードパーティライブラリを必要最低限に抑えることで, コードベースをより統一的な管理下に置き, Misskey の開発指針やデザインテーマが実効性を伴いやすくなっています. 結果, フロントエンドは Vue.js ランタイム, 数百からなるコンポーネントと, ルーター (nirax) やストア (pizzax) といったアプリケーションを管理するためのシステム, そしていくつかの内製 (browser-image-resizer, buraha, etc.) および外製 (Chart.js, PhotoSwipe, etc.) サードパーティライブラリの組み合わせで構成されています. + +Vite が生成する Misskey のフロントエンドアセットは, 全体を合計すると, Blotli 圧縮後のサイズでおよそ 1.4 MB にのぼります. このサイズが小さくなるよう努めることは, アプリケーションを提供するうえで重要な要素です. + +- JavaScript や CSS の成果物サイズが小さくなると, ユーザーエージェントがそれらを解析し, 実行する際のコストが削減されます. + - 特に, JavaScript は多くの場合, Web ブラウザのメインスレッドで解析および実行されるため, 同程度のバイナリサイズで構成される画像ファイルなどと比較して処理にかかる負荷が非常に高く, その負荷を削減することは重要です. + - また, JavaScript や CSS の成果物サイズが小さいということは, 多くの場合, それがシンプルであることを意味します. シンプルなコードは, 多くの場合, 軽快でパフォーマンスが高いといえます. つまり, コードサイズの削減は, パフォーマンスの観点からみても理にかなっています. +- フロントエンドアセットのサイズが小さくなると, 当然ながら, ユーザーエージェントにそれらを配信する際の通信量が削減されます. + - 高速通信技術が発展した現代においても, ユーザーが常にその恩恵を享受できる環境にあるとは限りません. 人と人のコミュニケーションを確立するアプリケーションとして, 不安定な通信環境においても, 快適性を可能な限り向上させるよう努めることは重要です. + - アセットのサイズが小さくなると, より多くのアセットを CDN のキャッシュに蓄積させることができます. その結果, アセットのキャッシュヒット率が向上するので, ユーザーエージェントがアセットの取得に要する時間は, 削減されたアセットのバイナリサイズ分以上に短縮されることが期待できます. + - 例えば, 多くのサーバーが利用している Cloudflare では, 同一ドメイン上でドライブファイルなどを配信すると, エッジキャッシュのバジェットがそれらと取り合いになります. これによってキャッシュヒット率の低下を招くと, 逆に Misskey の通信コストが非線形に増加する可能性を見積もれます. + +先にも述べたように, フロントエンドのコードベースはその多くを数多の Vue.js コンポーネントで占めているわけですから, Vue.js を効率的に活用することは, フロントエンドのアセットサイズ削減に直結し, ひいてはユーザー体験の向上につながるといえます. + +## Misskey における Vue.js の使用方法 + +Vue.js は, 世界で最も人気のある UI フレームワークの一つです. 人気とは, 一朝一夕に獲得できるものではありません. Vue.js にはモダンフレームワークなりの歴史があり, そして, 多種多様なフロントエンドの需要に応えるために, 様々な機能を提供して成長してきました. もっとも, ここまで読み進めている方の多くは, そんなことは百も承知かもしれませんが, とにもかくにも, Vue.js の使い方は様々な形態があり, ユースケースに合わせて適切な使い方を選択することが重要です. とはいえ, その内の SFC を使用するか否か (使用しています) や, TypeScript を使用するか否か (使用しています), および Composition API を使用するか否か (使用しています) については, 先述の syuilo 連載[「Misskey & Webテクノロジー最前線」9月](/blog/2023-09-11-gihyo)以上に掘り下げることが多くないので, ここでは割愛します. + +代わりに, コンポーネントのスタイル連繫について見ていきましょう. Rich Web UI を謳う Misskey は, 個々のコンポーネントに細かくスタイルをつけています. 先述の通り, Misskey には数百のコンポーネントがありますから, スタイルデータはそれなりの量があります. そのため, スタイルがどのように管理され, 配信されるかは, 配信戦略において重要な要素の一つになります. + +さて, HTML で Web ブラウザにスタイルを提供する方法は, 大まかに分けて 3 つあります. + +
+ +```html +
Hello, world!
+``` + +
+スタイル属性 +
+
+ +
+ +```html + +
Hello, world!
+``` + +
+スタイル要素 +
+
+ +
+ +```html + +
Hello, world!
+``` + +```css +.red { + color: red; +} +``` + +
+スタイルシート +
+
+ +このうち, 最後のスタイルシートによるスタイル連繫は, コンポーネントのロジック部分とスタイル部分が分離されることで, それぞれのライフタイムの長寿化を期待することができるため, プロダクションにおいては望ましい形式といえます. スタイルシートのスタイルルールは, セレクタを記述して, 条件に合致する要素にスタイルを適用するよう Web ブラウザに指示します. セレクタは大局的なものから局所的なものまで多種多様な指定が可能ですが, コンポーネントのパーツに細かくスタイルをつけていくという状況においては, そのほとんどは局所的かつ単純なものになります. なお, 再利用性を担保してなるべくシンプルにセレクタを記述する方法は, 単一のクラス名を指定するのが, もっともパフォーマンスが高いとされています. この理由をきちんと説明するには, Web ブラウザの実装の話などが大きく絡むので, ここでは割愛します. + +Misskey と Vue.js に話を戻すと, SFC にはスタイルシートを直接記述できる機能が備わっています. この機能を使用して SFC にスタイルを直接記述すると, vue/compiler-sfc によってスタイルシートが抽出され, @vitejs/plugin-vue によって仮想モジュールとして Vite に参照されるようになり, 最終的に Vite がそれらをバンドルします. このおかげで, 成果物として適切な様態で CSS が配信されることを保証しながら, 一方で開発体験としてはコンポーネントごとに関心を寄せてスタイルを記述できるようになります. + +さて, 個々のコンポーネントが自由にスタイルを記述し, それを統合した場合, 実際にはそれらのルールが意図せず他のコンポーネントに影響を及ぼしたりする問題が予想されます. SFC の機能には, この問題を避けるため, スタイルをコンポーネントのスコープに閉じ込めるよう指示できるものがあります. スコープ付き CSS は, ビルド時にコンポーネント毎に一意の識別子を生成し, コンポーネント内の要素にそれを属性として割り当て, スタイルシートのセレクタにも書き足すことで, ユーザーのコード変更なしにスタイルをスコープに分離することができます. SFC のタグに属性を足すだけでドロップインに使用できる手軽さから, 多くの Vue.js ユーザーに使用され, Misskey もかつて主方針として使用していました. しかしその実, [スコープは完全ではなく](https://github.com/vuejs/vue-loader/issues/957), また, セレクタが肥大化してしまう問題も孕んでいました. + +より踏み入った代替策として, SFC では CSS モジュールを使用することができます. これは, ビルド時にセレクタのクラス名を機械的に再構成し, そのバインドを JavaScript で参照できるようにするものです. コンポーネントにおけるテンプレート内のクラス名は直接指定ではなくバインドされるフィールドへの識別子に置き換える必要があるので, コンポーネントのリファクタリングが必要ですが, スタイル連繋における課題点は概ね払拭されます. 現在の Misskey では, ほとんどのコンポーネントが CSS モジュールを使用しています. + +## CSS モジュール注入の最適化 + +Misskey が CSS モジュールを使うようになった後のある日, syuilo は言いました. + +:::fukidashi{chara="syuilo" charaName="しゅいろ"} +えー、CSS Modulesってminifyしてくれにゃいんだ + +![](https://s3.arkjp.net/misskey/1b008643-4932-40d8-980b-fe3da75db856.png) +::: + +— https://misskey.io/notes/9fd9w06qah + +このノートには, CSS モジュールのクラス名バインド用マップが成果物に丸々含まれていることを憂う気持ちが込められています. 例えば, 次のような SFC があったとします. + +
+ +```vue + + + +``` + +
+赤色で挨拶文を表示するコンポーネント +
+
+ +このコンポーネントは次のように変換されて欲しいです. + +
+ +```jsx +export const HelloWorld = defineComponent({ + setup() { + return () => jsx( // 実際にはより具象的なコードになる +
Hello, world!
+ ); + }, +}); +``` + +```css +.r3a9t { + color: red; +} +``` + +
+理想的な変換後のイメージ +
+
+ +しかし, 実際には, 次のように変換されてしまいます. + +
+ +```jsx +export const HelloWorld = defineComponent({ + setup() { + return (_ctx) => jsx( // 実際にはより具象的なコードになる +
Hello, world!
+ ); + }, +}); + +HelloWorld.__cssModules = { + $style: { + redColoredText: "r3a9t", + }, +}; +``` + +```css +.r3a9t { + color: red; +} +``` + +
+実際の変換後のイメージ +
+
+ +このようなことになってしまうのは, バインドの参照を常に静的に置換できるとは限らないためです. 例えば, $style.redColoredText のような参照は静的に置換できても, $style\[color + "ColoredText"\] のような参照はビルド時に color の値が定まるとは保証できないので, 静的に置換できません. また, Vue.js の Composition API では, useCssModule() を呼び出すことで, バインド用のマップ全体を取得することを許容しています. このような経緯で, 成果物にマップがそのまま含まれているのです. 逆に, それらの機能を一切使わないのであれば, 完全にそれらは無駄になっているといえます. 完全に無駄なものは安全に除去できるはずです. そこで, Misskey では, $style 配下を識別子のメンバーアクセスによる参照のみを認めるルールで運用することを前提に, 静的置換を行う Rollup プラグインを開発および使用することで, 成果物からマップを除去するようにしました. これにより, バンドルサイズの 3% 程度の削減につながりました. + +:::tip +詳細は [#10923](https://github.com/misskey-dev/misskey/issues/10923) を参照してください. +::: + +## 今後の展望 + +現在まだ取り組まれていない最適化として, ルーティングの静的化を検討しています. 記事の最初の方に提示した図を見ると Router がレイヤーの中でも上部にあることがわかります. そのため, Page Components の読み込みはページが読み込まれてしばらくしてから始まります. しかし, どのルートがどのページを表示するかはビルド時にほぼ決定できると言って差し支えありません. この情報を静的に管理してバックエンドに連繫することで, バックエンドはより早いタイミングでユーザーエージェントに必要なアセットを知らせることができるので, ユーザー体験の向上を見積もることができます. + +ここで, SFC の機能を利用して, + +```vue + + + + + +name: note +path: /notes/:noteId + +``` + +といったようにページコンポーネントに直接ルーティング情報を記述できれば, ビルドの際ルーティング情報を抽出して静的に集約でき, ついでに path props も同一ファイル内で管理でき, 保守性の向上にもつながります. + +あくまでも構想かつ一例にすぎませんが, このようにコンパイラの機能を使用するなどして, Misskey の開発では今後も表層的な枠組みに囚われず, 野心的に様々なものを活用し, より良いユーザー体験に貢献できるよう努めていきたいと思っています. From fd8db6e1c7f88435a6d4c013ac177625826a9b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 20 Dec 2023 13:58:12 +0900 Subject: [PATCH 2/2] New Crowdin updates (#60) * New translations ja-jp.yml (French) * New translations 1.troubleshooting.md (Chinese Traditional) --- .../4.resources/1.troubleshooting.md | 4 +- locales/fr-FR.yml | 48 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/content/tw/docs/2.for-users/4.resources/1.troubleshooting.md b/content/tw/docs/2.for-users/4.resources/1.troubleshooting.md index 46260def..d3ef1585 100644 --- a/content/tw/docs/2.for-users/4.resources/1.troubleshooting.md +++ b/content/tw/docs/2.for-users/4.resources/1.troubleshooting.md @@ -20,7 +20,7 @@ description: 如果您遇到問題,請先檢查此處。 ## 頁面無法載入 -如果出現用戶端啟動但無法載入頁面的錯誤,請檢查網路是否有問題。 也要檢查伺服器是否當機。也要檢查伺服器是否當機。 +如果出現用戶端啟動但無法載入頁面的錯誤,請檢查網路是否有問題。也要檢查伺服器是否當機。 儘管這種情況很少見,但這可能是由快取引起的。請清除瀏覽器快取並重試。 @@ -43,7 +43,7 @@ description: 如果您遇到問題,請先檢查此處。 - 動作の「Pagesのスクリプトを無効にする」を無効にする - アピアランスの「動きのあるMFMを無効にする」を有効にする -2.網路瀏覽器設定 +2.網頁瀏覽器設定 - 在網頁瀏覽器設定中啟用硬體加速 - 檢查您的網頁瀏覽器外掛程式和擴充功能設定或將其關閉 diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index a4582a65..0347c475 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -3,16 +3,16 @@ learnMore: "En savoir plus" loading: "Chargement en cours…" clickToExpand: "Cliquer pour développer" copy: "Copier" -share: "共有する" -note: "ノート" -other: "その他" +share: "Partager" +note: "Note" +other: "Autres" _error: - notFound: "ページが見つかりませんでした" - notFoundDesc: "お探しのページは見つかりませんでした。URLをお確かめください。" - generalError: "エラーが発生しました" - generalErrorDesc: "これが意図した操作でない場合は、以下から不具合報告をお願いします。" - reportProblem: "問題を報告する" - goToTop: "トップページにもどる" + notFound: "La page demandée est introuvable !" + notFoundDesc: "La page que vous cherchiez n'a pas pu être trouvée. Veuillez vérifier l'URL." + generalError: "Une erreur s'est produite" + generalErrorDesc: "Si ce n'est pas ce que vous avez voulu faire, veuillez signaler le problème ci-dessous." + reportProblem: "Signaler un problème" + goToTop: "Retourner à la page d'accueil" _seo: siteName: "Misskey Hub" siteNameLong: "Misskey Hub – Site web officiel du projet Misskey" @@ -98,12 +98,12 @@ _servers: _system: fetchError: "Le chargement de données a échoué. Veuillez réessayer plus tard." _statistics: - title: "サーバー統計" - description: "Misskeyサーバーの統計データをグラフでご紹介。" - viewFullStats: "詳しい統計を見る" - lang: "プライマリ言語" - registerAcceptance: "新規登録受付方式" - version: "バージョン" + title: "Statistiques de l'instance" + description: "Présentation graphique des données statistiques des instances Misskey." + viewFullStats: "Voir des statistiques détaillées" + lang: "Langue principale" + registerAcceptance: "Politique d'inscription" + version: "Version" notes: "Nombre de notes" users: "Nombre d’utilisateurs" servers: "Nombre d'instances" @@ -115,7 +115,7 @@ _servers: orderBy: "Trier par" recomendded: "Par défaut" notesCount: "Nombre de notes" - notesPer15Days: "1日あたりのノート数(15日平均)" + notesPer15Days: "Nombre de notes par jour (moyenne des 15 derniers jours)" usersCount: "Nombre d’utilisateurs" _registerAcceptance: title: "Politique d'Inscription" @@ -159,7 +159,7 @@ _content: danger: "Avertissement" preview: "Aperçu" _other: - title: "もっと!" + title: "Plus !" description: "Misskeyをもっと楽しめるその他のリソースをご紹介しています。" _brandAssets: title: "アセット集" @@ -182,24 +182,24 @@ _links: title: "Serveur Discord" description: "Misskeyに関する質問や会話などをお楽しみいただけます。" _aiChan: - title: "AI-Chan" - description: "Misskeyの看板娘、藍のウェブサイトです。" + title: "Ai-Chan" + description: "Le site web de la mascotte de Misskey, Ai-Chan." _tools: title: "ツール集" index: "ツール集 ホーム画面" description: "Misskey向けの便利ツールを公開中!" menuToggle: "Menu" _forUsers: - title: "Misskeyユーザー向け" + title: "Pour les utilisateurs de Misskey" _mfmPlayground: - title: "MFMお試しコーナー" + title: "Terrain de jeu de MFM" description: "MFMを自由に練習できます!Misskeyの投稿画面・ノートの画面を再現!" - preview: "プレビュー" + preview: "Prévisualisation" disclaimer: "ここに表示される通りに描画されるとは限りません。コードのシンタックスハイライトには対応していません。" mfm: "MFM" - character: "{0} 文字" + character: "{0} caractères" domain: "表示を再現するサーバー" - noteIt: "ノート" + noteIt: "Noter" clearEmojiCacheDescription: "絵文字が表示されないとき" clearEmojiCache: "絵文字のキャッシュを削除" _aidConverter: