Frappe_Claude_Skill_Package frappe-ops-frontend-build
install
source · Clone the upstream repo
git clone https://github.com/OpenAEC-Foundation/Frappe_Claude_Skill_Package
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/OpenAEC-Foundation/Frappe_Claude_Skill_Package "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/source/ops/frappe-ops-frontend-build" ~/.claude/skills/openaec-foundation-frappe-claude-skill-package-frappe-ops-frontend-build && rm -rf "$T"
manifest:
skills/source/ops/frappe-ops-frontend-build/SKILL.mdsource content
Frontend Build System
Complete reference for Frappe's frontend asset bundling pipeline, from build configuration to production optimization.
Versions: v14 (build.json) / v15+ (esbuild)
Quick Reference: Build Commands
| Task | Command |
|---|---|
| Build all apps | |
| Build specific app | |
| Build multiple apps | |
| Production build (minified) | |
| Force rebuild | |
| Watch mode (auto-rebuild) | |
| Hard link assets | |
Decision Tree: Build System Selection
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/
Build Pipeline Overview
v15+ (esbuild): Current System
The v15+ build system uses esbuild for fast ES module bundling. It automatically discovers bundle entry points by scanning the
public/ directory for files matching *.bundle.{js|ts|css|scss|sass|less|styl}.
How it works:
scans each app'sbench build
directory recursivelypublic/- Files matching
are treated as entry points*.bundle.* - esbuild compiles, bundles, and optionally minifies each entry point
- Output goes to
orassets/dist/[app]/js/assets/dist/[app]/css/ - Filenames include content hashes for cache-busting:
main.bundle.HASH.js
Supported file types:
— ES6 modules with import/export.js
— TypeScript.ts
— Vue single-file components.vue
— Standard CSS.css
/.scss
— SASS/SCSS stylesheets.sass
— Less stylesheets.less
— Stylus stylesheets.styl
v14 (build.json): Legacy System
The v14 system uses
build.json in the app root to define concatenation rules.
{ "js/myapp.min.js": [ "public/js/file1.js", "public/js/file2.js" ], "css/myapp.min.css": [ "public/css/style1.css", "public/css/style2.css" ] }
NEVER use
build.json in v15+ — it is ignored by the esbuild pipeline.
Bundle Entry Points [v15+]
Creating a Bundle
Place files in your app's
public/ directory with the .bundle. naming convention:
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
Bundle File Content
// 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");
Output Mapping
| Input | Output |
|---|---|
| |
| |
| |
hooks.py Asset Inclusion
Desk Assets (Backend Interface)
# 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"]
Portal Assets (Public Website)
# hooks.py — loads on web pages (portal) web_include_js = "myapp-web.bundle.js" web_include_css = "myapp-web.bundle.css"
Page-Specific Assets
# hooks.py — loads on specific Desk pages page_js = {"page_name": "public/js/custom_page.js"}
Web Form Assets (Standard Web Forms Only)
# 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"}
Critical Rules
- ALWAYS use the bundle filename (not the full path) in hooks.py for v15+
- NEVER include the hash in hooks.py — Frappe resolves the hashed filename automatically
- ALWAYS rebuild after changing hooks.py:
bench build --app myapp - Multiple apps can define the same hooks — assets accumulate across all installed apps
Including Assets in Templates
Jinja Helpers
<!-- Include script with correct hash --> {{ include_script("myapp.bundle.js") }} <!-- Include stylesheet with correct hash --> {{ include_style("myapp.bundle.css") }} <!-- Get path string only (no HTML tag) --> <script src="{{ bundled_asset('myapp.bundle.js') }}"></script>
Lazy Loading in 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 Compilation
SCSS Bundle Example
// 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); }
Partial Files
Partials (files starting with
_ or without .bundle. in the name) are NOT compiled as entry points. They are only included via @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
Development Workflow
Watch Mode [v15+]
# Auto-rebuild on file changes bench watch
- Watches all apps'
directories for changespublic/ - Rebuilds only affected bundles (incremental)
- Desk auto-reloads when assets change (if
is enabled)live_reload
Enabling Live Reload
# Via config bench set-config -g live_reload true # Via environment variable export LIVE_RELOAD=1
Development vs Production Build
| Feature | Development () | Production () |
|---|---|---|
| Minification | No | Yes |
| Source maps | Yes | No |
| Bundle size | Larger | Optimized |
| Build speed | Fast | Slower |
Frappe UI (Vue.js) Custom Pages [v15+]
Setting Up a Vue Page
// 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");
Registering the Page
# Create a Page DocType or use www/ for web pages # The bundle loads via hooks.py or include_script()
npm Dependencies
# Install from app directory cd apps/myapp yarn add vue frappe-ui dayjs
Dependencies are resolved by esbuild from
node_modules/ during build.
Common Build Errors and Fixes
Error: "Could not resolve module"
ERROR: Could not resolve "some-package"
Fix: Install the missing npm package:
cd apps/myapp && yarn add some-package
Error: "No bundle entry points found"
Fix: Ensure files use the
*.bundle.* naming convention and are in the public/ directory.
Error: Stale Assets After Deployment
Fix: Force rebuild with cache clear:
bench build --force bench clear-cache
Error: CSS Not Updating
Fix: Check that SCSS files import correctly and the entry point has
.bundle. in the name:
bench build --app myapp --force
Error: "build.json" Ignored in v15
Fix: Migrate to
*.bundle.* entry points. build.json is a v14-only feature.
Asset Optimization for Production
Pre-Deployment Checklist
- Build with production flag:
bench build --production - Verify bundle sizes: Check
for unexpectedly large filesassets/dist/ - Use lazy loading: Split rarely-used features into separate bundles loaded via
frappe.require() - Minimize hook includes: Only include essential assets in
app_include_js/css - Use CSS variables: Leverage Frappe's built-in CSS custom properties instead of duplicating styles
Bundle Splitting Strategy
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
Version Differences
| Feature | v14 | v15+ |
|---|---|---|
| Build system | build.json | esbuild |
| Entry point convention | Defined in JSON | auto-discovery |
| TypeScript support | No | Yes |
| Vue SFC support | No | Yes |
| SCSS compilation | Via build pipeline | Via esbuild |
| Watch mode | | (faster) |
| Live reload | Manual | Automatic (configurable) |
| Source maps | Limited | Full support |
| Tree shaking | No | Yes |
| npm imports | Requires manual bundling | Direct ES6 imports |
Reference Files
| File | Contents |
|---|---|
| examples.md | Complete build configuration examples |
| anti-patterns.md | Common build mistakes and fixes |