Skip to content

fix(gui): Monaco Editor を自前ホストして CDN 依存を無くす #1171

Description

@smalruby3-editor-bot

背景

ルビータブの 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 は既に CopyWebpackPluginblockly/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/vs16MB。workbox の GenerateSWmaximumFileSizeToCacheInBytes: 64MB なので、何もしないと全部 precache されて初回インストールが重くなる。exclude に monaco を足し、runtime cachingStaleWhileRevalidate 等)に回すのが妥当
配信サイズ 全言語を含めて 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

  • ルビータブが cdn.jsdelivr.net に一切アクセスせずに動作する(DevTools の Network で確認)
  • 3 通りのビルド(smalruby.app / GitHub Pages / ブランチプレビュー)すべてで Monaco が
    正しい URL から読み込まれる
  • 日本語 UI(ja / ja-Hira)の NLS が従来どおり効く
  • PWA の precache が肥大していない(monaco は precache から除外し、runtime caching で扱う)
  • package.jsonmonaco-editor を上げるだけでバージョンが揃う(URL 直書きが消えている)
  • .devcontainer/init-firewall.shEXTRA_HOSTS から cdn.jsdelivr.net を外しても
    ルビータブが動く(外す作業自体はこの Issue でやってよい)
  • docs/ の該当機能ドキュメントを更新(Monaco の配信方法が変わるため)

参考

  • 現状の設定: packages/scratch-gui/src/lib/monaco-i18n-helper.js
  • コピーの前例: packages/scratch-gui/webpack.config.jsCopyWebpackPlugin
  • PWA: 同ファイルの WorkboxPlugin.GenerateSWexclude / maximumFileSizeToCacheInBytes

autopilot-base: develop
autopilot-after: #1169

Metadata

Metadata

Assignees

Labels

🙋 HITL人間の対応待ち(Project HITL=Yes の投影)🤖 autopilotautopilot が処理対象にした Issue/PR(daemon が付与)

Type

No type

Projects

  • Status
    Review

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions