frappe-ops-frontend-build について
Frappe v14およびv15以降のフロントエンドアセット構築を開発者向けに解説します。v14のbuild.json方式とv15以降のesbuildパイプラインを扱い、バンドル設定、アセット読み込み、開発時の監視、本番ビルドを説明します。
主な機能
- 01v14とv15以降のビルド方式解説
- 02バンドルのエントリーポイント自動検出
- 03DeskとPortalのアセット設定
- 04監視モードとライブリロード
- 05本番ビルドとアセット最適化
活用シーン
- 01Frappeアプリのフロントエンドバンドル設定
- 02v14のビルド構成をesbuildへ移行
- 03開発中のアセット変更を監視
- 04圧縮済みの本番アセットを準備
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ impertio-studio/ frappe_claude_skill_package --skill frappe-ops-frontend-buildインストール後、Claude Code で/frappe-ops-frontend-buildを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/.gitkeepanti-patterns.mdexamples.md詳細
フロントエンドビルドシステム
ビルド設定から本番環境向けの最適化まで、Frappe のフロントエンドアセットバンドルパイプラインを網羅したリファレンスです。
バージョン:v14(build.json)/v15 以降(esbuild)
クイックリファレンス:ビルドコマンド
| タスク | コマンド |
|---|---|
| すべてのアプリをビルド | bench build |
| 特定のアプリをビルド | bench build --app myapp |
| 複数のアプリをビルド | bench build --apps frappe,erpnext |
| 本番ビルド(圧縮) | bench build --production |
| 強制的に再ビルド | bench build --force |
| ウォッチモード(自動再ビルド) | bench watch |
| アセットをハードリンク | bench build --hard-link |
判断ツリー:ビルドシステムの選択
Which build system?
├── Frappe v14?
│ └── build.json — Concatenation-based bundling
├── Frappe v15+?
│ └── esbuild — ES module bundling with *.bundle.* convention
└── Migrating v14 → v15?
└── Replace build.json with *.bundle.* files in public/
ビルドパイプラインの概要
v15 以降(esbuild):現行システム
v15 以降のビルドシステムでは、ES モジュールを高速にバンドルするために esbuild を使用します。public/ ディレクトリをスキャンし、*.bundle.{js|ts|css|scss|sass|less|styl} に一致するファイルを探して、バンドルのエントリーポイントを自動的に検出します。
仕組み:
bench buildが各アプリのpublic/ディレクトリを再帰的にスキャンします*.bundle.*に一致するファイルをエントリーポイントとして扱います- esbuild が各エントリーポイントをコンパイル、バンドルし、必要に応じて圧縮します
- 出力先は
assets/dist/[app]/js/またはassets/dist/[app]/css/です - キャッシュを更新できるよう、ファイル名にコンテンツハッシュが含まれます:
main.bundle.HASH.js
対応するファイル形式:
.js— import/export を使用した ES6 モジュール.ts— TypeScript.vue— Vue 単一ファイルコンポーネント.css— 標準 CSS.scss/.sass— SASS/SCSS スタイルシート.less— Less スタイルシート.styl— Stylus スタイルシート
v14(build.json):レガシーシステム
v14 のシステムでは、アプリのルートにある build.json を使用して、ファイルの連結ルールを定義します。
{
"js/myapp.min.js": [
"public/js/file1.js",
"public/js/file2.js"
],
"css/myapp.min.css": [
"public/css/style1.css",
"public/css/style2.css"
]
}
v15 以降では、esbuild のパイプラインによって無視されるため、build.json は絶対に使用しないでください。
バンドルのエントリーポイント[v15 以降]
バンドルの作成
アプリの public/ ディレクトリに、.bundle. という命名規則に従ったファイルを配置します。
myapp/
└── public/
├── js/
│ └── myapp.bundle.js # → dist/myapp/js/myapp.bundle.HASH.js
├── css/
│ └── myapp.bundle.scss # → dist/myapp/css/myapp.bundle.HASH.css
└── components/
└── widget.bundle.js # → dist/myapp/js/widget.bundle.HASH.js
バンドルファイルの内容
// myapp/public/js/myapp.bundle.js
import { createApp } from "vue";
import MyComponent from "./components/MyComponent.vue";
// ES6 imports are resolved by esbuild
import "../css/myapp.bundle.scss";
// npm packages (installed via yarn) can be imported directly
import dayjs from "dayjs";
createApp(MyComponent).mount("#myapp-root");
出力のマッピング
| 入力 | 出力 |
|---|---|
public/js/main.bundle.js | assets/dist/[app]/js/main.bundle.[hash].js |
public/css/style.bundle.scss | assets/dist/[app]/css/style.bundle.[hash].css |
public/deep/nested/file.bundle.ts | assets/dist/[app]/js/file.bundle.[hash].js |
hooks.py でのアセットの指定
Desk アセット(バックエンドインターフェース)
# hooks.py — loads in /app (Desk)
app_include_js = "myapp.bundle.js"
app_include_css = "myapp.bundle.css"
# Multiple files
app_include_js = ["myapp.bundle.js", "extra.bundle.js"]
app_include_css = ["myapp.bundle.css", "extra.bundle.css"]
ポータルアセット(公開ウェブサイト)
# hooks.py — loads on web pages (portal)
web_include_js = "myapp-web.bundle.js"
web_include_css = "myapp-web.bundle.css"
ページ固有のアセット
# hooks.py — loads on specific Desk pages
page_js = {"page_name": "public/js/custom_page.js"}
ウェブフォームアセット(標準ウェブフォームのみ)
# hooks.py — loads on specific Web Forms
webform_include_js = {"ToDo": "public/js/custom_todo.js"}
webform_include_css = {"ToDo": "public/css/custom_todo.css"}
重要なルール
- v15 以降では、hooks.py に完全なパスではなくバンドルのファイル名を必ず使用してください
- hooks.py にハッシュを絶対に含めないでください。ハッシュ付きのファイル名は Frappe が自動的に解決します
- hooks.py を変更した後は、必ず再ビルドしてください:
bench build --app myapp - 複数のアプリで同じフックを定義できます。アセットはインストール済みのすべてのアプリから累積されます
テンプレートへのアセットの読み込み
Jinja ヘルパー
{{ include_script("myapp.bundle.js") }}
{{ include_style("myapp.bundle.css") }}
<script src="{{ bundled_asset('myapp.bundle.js') }}"></script>
Desk での遅延読み込み
// Load asset on demand (returns Promise)
frappe.require("myapp.bundle.js", () => {
// Asset loaded, initialize component
myapp.init();
});
// Multiple assets
frappe.require(["widget.bundle.js", "widget.bundle.css"], () => {
// Both loaded
});
SCSS/CSS のコンパイル
SCSS バンドルの例
// myapp/public/css/myapp.bundle.scss
// Import Frappe variables (available in all apps)
@import "frappe/public/scss/variables";
// Import partials (NOT bundles — no .bundle. in name)
@import "./components/header";
@import "./components/sidebar";
.myapp-container {
padding: var(--padding-lg);
background: var(--bg-color);
}
パーシャルファイル
パーシャル(_ で始まるファイル、またはファイル名に .bundle. を含まないファイル)は、エントリーポイントとしてコンパイルされません。@import を通じてのみ読み込まれます。
public/css/
├── myapp.bundle.scss # Entry point — compiled
├── _variables.scss # Partial — imported only
└── components/
├── _header.scss # Partial — imported only
└── _sidebar.scss # Partial — imported only
開発ワークフロー
ウォッチモード[v15 以降]
# Auto-rebuild on file changes
bench watch
- すべてのアプリの
public/ディレクトリを監視し、変更を検出します - 影響を受けるバンドルのみを再ビルドします(インクリメンタル)
live_reloadが有効な場合、アセットの変更時に Desk が自動で再読み込みされます
ライブリロードを有効にする
# Via config
bench set-config -g live_reload true
# Via environment variable
export LIVE_RELOAD=1
開発ビルドと本番ビルド
| 機能 | 開発(bench build) | 本番(bench build --production) |
|---|---|---|
| 圧縮 | いいえ | はい |
| ソースマップ | はい | いいえ |
| バンドルサイズ | 大きい | 最適化済み |
| ビルド速度 | 高速 | 低速 |
Frappe UI(Vue.js)のカスタムページ[v15 以降]
Vue ページのセットアップ
// myapp/public/js/mypage.bundle.js
import { createApp } from "vue";
import { FrappeUI } from "frappe-ui";
import App from "./App.vue";
const app = createApp(App);
app.use(FrappeUI);
app.mount("#myapp-page");
ページの登録
# Create a Page DocType or use www/ for web pages
# The bundle loads via hooks.py or include_script()
npm の依存関係
# Install from app directory
cd apps/myapp
yarn add vue frappe-ui dayjs
ビルド時に、esbuild が node_modules/ から依存関係を解決します。
よくあるビルドエラーと解決方法
エラー:「Could not resolve module」
ERROR: Could not resolve "some-package"
解決方法:不足している npm パッケージをインストールします。
cd apps/myapp && yarn add some-package
エラー:「No bundle entry points found」
解決方法:ファイルが *.bundle.* の命名規則に従い、public/ ディレクトリに配置されていることを確認します。
エラー:デプロイ後にアセットが古いままになる
解決方法:キャッシュをクリアして強制的に再ビルドします。
bench build --force
bench clear-cache
エラー:CSS が更新されない
解決方法:SCSS ファイルが正しくインポートされていること、およびエントリーポイントの名前に .bundle. が含まれていることを確認します。
bench build --app myapp --force
エラー:v15 で「build.json」が無視される
解決方法:*.bundle.* エントリーポイントに移行します。build.json は v14 専用の機能です。
本番環境向けのアセット最適化
デプロイ前のチェックリスト
- 本番フラグを指定してビルドする:
bench build --production - バンドルサイズを確認する:
assets/dist/で予想外に大きなファイルがないか確認します - 遅延読み込みを使用する:あまり使用されない機能を個別のバンドルに分割し、
frappe.require()で読み込みます - フックのインクルードを最小限にする:
app_include_js/cssには必須のアセットのみを含めます - CSS 変数を使用する:スタイルの重複を避け、Frappe に組み込まれている CSS カスタムプロパティを活用します
バンドル分割の方針
public/
├── js/
│ ├── myapp.bundle.js # Core — loaded on every page via hooks
│ ├── report-widget.bundle.js # Lazy — loaded only on report pages
│ └── chart-tools.bundle.js # Lazy — loaded only when charts needed
└── css/
├── myapp.bundle.scss # Core — loaded on every page via hooks
└── print.bundle.scss # Lazy — loaded only for print views
バージョンごとの差異
| 機能 | v14 | v15+ |
|---|---|---|
| ビルドシステム | build.json | esbuild |
| エントリーポイントの規則 | JSON で定義 | *.bundle.* を自動検出 |
| TypeScript のサポート | なし | あり |
| Vue SFC のサポート | なし | あり |
| SCSS のコンパイル | ビルドパイプライン経由 | esbuild 経由 |
| ウォッチモード | bench watch | bench watch(高速) |
| ライブリロード | 手動 | 自動(設定可能) |
| ソースマップ | 限定的 | 完全サポート |
| ツリーシェイキング | なし | あり |
| npm のインポート | 手動でのバンドルが必要 | ES6 のインポートを直接使用 |
参考ファイル
| ファイル | 内容 |
|---|---|
| examples.md | ビルド設定の完全な例 |
| anti-patterns.md | よくあるビルド上の誤りと解決方法 |