Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
openaec-foundation avatar

Frappe Ops Frontend Build

  • 23 installs
  • 159 repo stars
  • Updated July 8, 2026
  • openaec-foundation/frappe_claude_skill_package

Helps with frontend development tasks.

About

frappe-ops-frontend-build is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted development.

  • frappe-ops-frontend-build
  • Frontend Development
  • AI-coding skill

Frappe Ops Frontend Build by the numbers

  • 23 all-time installs (skills.sh)
  • Ranked #1,515 of 2,245 Frontend Development skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/frappe_claude_skill_package --skill frappe-ops-frontend-build

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs23
repo stars159
Last updatedJuly 8, 2026
Repositoryopenaec-foundation/frappe_claude_skill_package

What it does

Helps with frontend development tasks.

Files

SKILL.mdMarkdownGitHub ↗

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

TaskCommand
Build all appsbench build
Build specific appbench build --app myapp
Build multiple appsbench build --apps frappe,erpnext
Production build (minified)bench build --production
Force rebuildbench build --force
Watch mode (auto-rebuild)bench watch
Hard link assetsbench build --hard-link

---

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:

1. bench build scans each app's public/ directory recursively 2. Files matching *.bundle.* are treated as entry points 3. esbuild compiles, bundles, and optionally minifies each entry point 4. Output goes to assets/dist/[app]/js/ or assets/dist/[app]/css/ 5. Filenames include content hashes for cache-busting: main.bundle.HASH.js

Supported file types:

  • .js — ES6 modules with import/export
  • .ts — TypeScript
  • .vue — Vue single-file components
  • .css — Standard CSS
  • .scss / .sass — SASS/SCSS stylesheets
  • .less — Less stylesheets
  • .styl — Stylus stylesheets

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

InputOutput
public/js/main.bundle.jsassets/dist/[app]/js/main.bundle.[hash].js
public/css/style.bundle.scssassets/dist/[app]/css/style.bundle.[hash].css
public/deep/nested/file.bundle.tsassets/dist/[app]/js/file.bundle.[hash].js

---

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' public/ directories for changes
  • Rebuilds only affected bundles (incremental)
  • Desk auto-reloads when assets change (if live_reload is enabled)

Enabling Live Reload

# Via config
bench set-config -g live_reload true

# Via environment variable
export LIVE_RELOAD=1

Development vs Production Build

FeatureDevelopment (bench build)Production (bench build --production)
MinificationNoYes
Source mapsYesNo
Bundle sizeLargerOptimized
Build speedFastSlower

---

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

1. Build with production flag: bench build --production 2. Verify bundle sizes: Check assets/dist/ for unexpectedly large files 3. Use lazy loading: Split rarely-used features into separate bundles loaded via frappe.require() 4. Minimize hook includes: Only include essential assets in app_include_js/css 5. 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

Featurev14v15+
Build systembuild.jsonesbuild
Entry point conventionDefined in JSON*.bundle.* auto-discovery
TypeScript supportNoYes
Vue SFC supportNoYes
SCSS compilationVia build pipelineVia esbuild
Watch modebench watchbench watch (faster)
Live reloadManualAutomatic (configurable)
Source mapsLimitedFull support
Tree shakingNoYes
npm importsRequires manual bundlingDirect ES6 imports

---

Reference Files

FileContents
examples.mdComplete build configuration examples
anti-patterns.mdCommon build mistakes and fixes

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.