背景
ルビータブの Monaco Editor は、実行時に外部 CDN から本体を取りに行っている。
packages/scratch-gui/src/lib/monaco-i18n-helper.js:
loader.config({
paths: {
vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs',
},
});
つまり cdn.jsdelivr.net に到達できない環境ではルビータブが動かない。
なぜ直したいか
1. 学校ネットワークで CDN が塞がれると使えない(利用者側の実害)
小中学校のネットワークは URL フィルタが強く、汎用 CDN が丸ごと遮断されていることがある。
その環境ではエディタは開くのにルビータブだけ動かないという分かりにくい壊れ方をする。
Smalruby の主要ユーザー層を考えると、これは無視できない。
2. 開発コンテナの egress allowlist を広げざるを得ない
devcontainer は「非公開の認証情報を持つので宛先を限定する」方針(.claude/rules/devpod-workflow.md)。
Monaco のために 汎用 CDN である cdn.jsdelivr.net を許可する必要があり、allowlist としては
広い部類になる(jsdelivr は任意の npm パッケージを配れるため)。自前ホストにすればこの許可を
外せる。
3. オフライン(PWA)で動かない
本アプリは PWA としてビルドしている(workbox GenerateSW)が、Monaco 本体は毎回ネットワークから
取るのでオフラインではルビータブが使えない。
4. 依存としては既に入っているのに CDN から取っている(ねじれ)
packages/scratch-gui/package.json は既に monaco-editor: ^0.55.1 を依存に持っている
(日本語 NLS を monaco-editor/esm/nls.messages.ja.js から import しているため)。
同じものを npm でも持ち、実行時に CDN からも落としている状態。CDN の URL にバージョンを
直書きしているので、package.json を上げたときに片方だけ古くなる危険もある
(コード中の「to match our local NLS messages」というコメントは、まさにこの手動同期のこと)。
方針案
案 A: min/vs を静的配信物としてコピーする(推奨)
node_modules/monaco-editor/min/vs をビルド時に static/monaco/vs へコピーし、
loader.config({paths: {vs: <publicPath>/monaco/vs}}) を指す。
- webpack は既に
CopyWebpackPlugin で blockly/media や @mediapipe/face_detection を
同様にコピーしているので、既存の仕組みに素直に乗る
@monaco-editor/react の AMD ローダー経路は変えないので挙動の変化が最小
- バージョンは
node_modules 由来になるので、URL 直書きの手動同期が不要になる
注意点(実装時に必ず対応すること):
| 論点 |
内容 |
| publicPath |
dist ビルドは publicPath: 'auto'(scratch-desktop / android が相対解決を要求)。CI は smalruby.app / GitHub Pages / ブランチプレビューの 3 通りの base パスでビルドするので、/monaco/vs の絶対パス直書きは不可。__webpack_public_path__ などから組み立てる |
| PWA の precache 肥大 |
min/vs は 16MB。workbox の GenerateSW は maximumFileSizeToCacheInBytes: 64MB なので、何もしないと全部 precache されて初回インストールが重くなる。exclude に monaco を足し、runtime caching(StaleWhileRevalidate 等)に回すのが妥当 |
| 配信サイズ |
全言語を含めて 16MB。Ruby と基本機能だけにサブセット化する余地はあるが、まずは丸ごとコピーで良い(GitHub Pages の容量的には問題ない) |
案 B: webpack で ESM をバンドルする
import * as monaco from 'monaco-editor' して loader.config({monaco}) を渡す。
Web Worker の扱いに monaco-editor-webpack-plugin 相当の設定が要り、ビルド構成の変更が大きい。
tree-shaking で最終サイズは小さくできるが、リスクとリターンが釣り合わないと判断。
案 C: 現状維持(CDN)
上記 4 つの問題が残る。採らない。
スコープ外(別で扱う)
accounts.google.com / apis.google.com(Google ログイン)は機能上どうしても外部通信が要るので、
自前ホスト化の対象ではない。
- devcontainer の allowlist から
cdn.jsdelivr.net を外すのは、本 Issue が完了してから。
DoD
参考
- 現状の設定:
packages/scratch-gui/src/lib/monaco-i18n-helper.js
- コピーの前例:
packages/scratch-gui/webpack.config.js の CopyWebpackPlugin
- PWA: 同ファイルの
WorkboxPlugin.GenerateSW(exclude / maximumFileSizeToCacheInBytes)
autopilot-base: develop
autopilot-after: #1169
背景
ルビータブの Monaco Editor は、実行時に外部 CDN から本体を取りに行っている。
packages/scratch-gui/src/lib/monaco-i18n-helper.js:つまり
cdn.jsdelivr.netに到達できない環境ではルビータブが動かない。なぜ直したいか
1. 学校ネットワークで CDN が塞がれると使えない(利用者側の実害)
小中学校のネットワークは URL フィルタが強く、汎用 CDN が丸ごと遮断されていることがある。
その環境ではエディタは開くのにルビータブだけ動かないという分かりにくい壊れ方をする。
Smalruby の主要ユーザー層を考えると、これは無視できない。
2. 開発コンテナの egress allowlist を広げざるを得ない
devcontainer は「非公開の認証情報を持つので宛先を限定する」方針(
.claude/rules/devpod-workflow.md)。Monaco のために 汎用 CDN である
cdn.jsdelivr.netを許可する必要があり、allowlist としては広い部類になる(jsdelivr は任意の npm パッケージを配れるため)。自前ホストにすればこの許可を
外せる。
3. オフライン(PWA)で動かない
本アプリは PWA としてビルドしている(workbox
GenerateSW)が、Monaco 本体は毎回ネットワークから取るのでオフラインではルビータブが使えない。
4. 依存としては既に入っているのに CDN から取っている(ねじれ)
packages/scratch-gui/package.jsonは既にmonaco-editor: ^0.55.1を依存に持っている(日本語 NLS を
monaco-editor/esm/nls.messages.ja.jsから import しているため)。同じものを npm でも持ち、実行時に CDN からも落としている状態。CDN の URL にバージョンを
直書きしているので、
package.jsonを上げたときに片方だけ古くなる危険もある(コード中の「to match our local NLS messages」というコメントは、まさにこの手動同期のこと)。
方針案
案 A:
min/vsを静的配信物としてコピーする(推奨)node_modules/monaco-editor/min/vsをビルド時にstatic/monaco/vsへコピーし、loader.config({paths: {vs: <publicPath>/monaco/vs}})を指す。CopyWebpackPluginでblockly/mediaや@mediapipe/face_detectionを同様にコピーしているので、既存の仕組みに素直に乗る
@monaco-editor/reactの AMD ローダー経路は変えないので挙動の変化が最小node_modules由来になるので、URL 直書きの手動同期が不要になる注意点(実装時に必ず対応すること):
distビルドはpublicPath: 'auto'(scratch-desktop / android が相対解決を要求)。CI は smalruby.app / GitHub Pages / ブランチプレビューの 3 通りの base パスでビルドするので、/monaco/vsの絶対パス直書きは不可。__webpack_public_path__などから組み立てるmin/vsは 16MB。workbox のGenerateSWはmaximumFileSizeToCacheInBytes: 64MBなので、何もしないと全部 precache されて初回インストールが重くなる。excludeに monaco を足し、runtime caching(StaleWhileRevalidate等)に回すのが妥当案 B: webpack で ESM をバンドルする
import * as monaco from 'monaco-editor'してloader.config({monaco})を渡す。Web Worker の扱いに
monaco-editor-webpack-plugin相当の設定が要り、ビルド構成の変更が大きい。tree-shaking で最終サイズは小さくできるが、リスクとリターンが釣り合わないと判断。
案 C: 現状維持(CDN)
上記 4 つの問題が残る。採らない。
スコープ外(別で扱う)
accounts.google.com/apis.google.com(Google ログイン)は機能上どうしても外部通信が要るので、自前ホスト化の対象ではない。
cdn.jsdelivr.netを外すのは、本 Issue が完了してから。DoD
cdn.jsdelivr.netに一切アクセスせずに動作する(DevTools の Network で確認)正しい URL から読み込まれる
ja/ja-Hira)の NLS が従来どおり効くpackage.jsonのmonaco-editorを上げるだけでバージョンが揃う(URL 直書きが消えている).devcontainer/init-firewall.shのEXTRA_HOSTSからcdn.jsdelivr.netを外してもルビータブが動く(外す作業自体はこの Issue でやってよい)
docs/の該当機能ドキュメントを更新(Monaco の配信方法が変わるため)参考
packages/scratch-gui/src/lib/monaco-i18n-helper.jspackages/scratch-gui/webpack.config.jsのCopyWebpackPluginWorkboxPlugin.GenerateSW(exclude/maximumFileSizeToCacheInBytes)autopilot-base: develop
autopilot-after: #1169