关于 frappe-ops-frontend-build
为 Frappe v14 和 v15+ 前端资源构建提供参考,面向开发者,介绍 v14 的 build.json 与 v15+ 的 esbuild 流程。内容包括资源打包、页面引入、开发监视及生产构建。
主要功能
- 01v14 与 v15+ 构建方式说明
- 02自动发现 bundle 入口文件
- 03Desk 和 Portal 资源配置
- 04监视模式与实时重载
- 05生产构建和资源优化
适用场景
- 01为 Frappe 应用配置前端打包
- 02将 v14 构建配置迁移至 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+ 构建系统使用 esbuild 快速打包 ES 模块。它会扫描 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+ 切勿使用 build.json——esbuild 构建流程会忽略它。
打包入口点 [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"]
Portal 资源(公共网站)
# 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"}
Web 表单资源(仅限标准 Web 表单)
# 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/目录是否有更改 - 仅重新构建受影响的打包文件(增量构建)
- 资源更改时,Desk 会自动重新加载(如果启用了
live_reload)
启用实时重新加载
# Via config
bench set-config -g live_reload true
# Via environment variable
export LIVE_RELOAD=1
开发构建与生产构建
| 特性 | 开发环境(bench build) | 生产环境(bench build --production) |
|---|---|---|
| 压缩 | 否 | 是 |
| Source map | 是 | 否 |
| 打包文件大小 | 较大 | 已优化 |
| 构建速度 | 快 | 较慢 |
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(更快) |
| 热重载 | 手动 | 自动(可配置) |
| Source map | 有限支持 | 完全支持 |
| Tree shaking | 否 | 是 |
| npm 导入 | 需要手动打包 | 直接 ES6 导入 |
参考文件
| 文件 | 内容 |
|---|---|
| examples.md | 完整的构建配置示例 |
| anti-patterns.md | 常见构建错误及修复方法 |