Frappe Website Script 详解:全站自定义 JavaScript 注入与第三方追踪分析集成
【免费下载链接】frappeLow code web framework for real world applications, in Python and Javascript项目地址: https://gitcode.com/GitHub_Trending/fr/frappe
Frappe 框架的 Website Script(网站脚本)是一个全局性的单例 DocType,用于向站点所有网页的末尾追加自定义 JavaScript,其典型用途是集成第三方统计与流量分析工具(如 Google Analytics)。本文以 frappe/website/doctype/website_script/README.md 为纲领,结合 website_script.json、渲染入口 frappe/www/website_script.py 与模板 frappe/www/website_script.js 等源码,完整讲解其字段模型、渲染注入管线、缓存策略与内置的流量追踪能力,帮助你理解并正确使用这一机制。
一、Website Script 的定位与数据模型
按照 README 的原始描述,Website Script 的作用是:
Custom javascript to be appended at the end of the page. Used to include 3rd party tracking / analytics tools.
即:在页面末尾追加自定义 JavaScript,用于引入第三方追踪 / 分析工具。这是一个全局站点级的能力——所有网页都会加载它,因此适合放置全站统计代码、热力图脚本、转化追踪像素等。
1.1 字段模型
从 website_script.json 可以看到其完整定义:
issingle: 1:这是单例(Single)DocType,站点上只有一条记录,在 Desk 中通过「Website Script」表单直接编辑,无需新建列表项;module: "Website":归属于 Website 模块;- 唯一字段
javascript:fieldname: "javascript"fieldtype: "Code":代码编辑器字段;options: "Javascript":指定语法高亮为 JavaScript;label: "Javascript";
track_changes: 1:开启字段变更追踪,可审计脚本内容的每次修改;- 图标为
fa fa-code,直观表示这是一个代码承载类型。
1.2 权限模型
权限表中只有Website Manager角色拥有完整权限(create/read/write/email/print/share均为 1)。这意味着只有具备网站管理员角色的用户才能查看和修改这段全局脚本,防止普通用户向全站注入任意代码——这是一个重要的安全边界。
1.3 服务端模型与缓存失效
website_script.py 中定义控制器类WebsiteScript(Document),其javascript字段类型标注为DF.Code | None。关键逻辑在on_update钩子中:
def on_update(self): """clear cache""" frappe.clear_cache(user="Guest") from frappe.website.utils import clear_cache clear_cache()由于脚本内容会被缓存用于快速响应,一旦内容更新,必须立即清理缓存,否则线上页面会继续使用旧脚本。这里同时清除了 Guest 用户缓存与整站缓存,确保改动即时生效。
二、渲染与注入管线:脚本如何出现在每个页面末尾
Website Script 的注入依赖 Frappe 的web_include_js钩子机制,整个链路分为两层:注册与渲染。
2.1 注册到每个网页
在 frappe/hooks.py 中:
web_include_js = ["website_script.js"]web_include_js列表中的每个条目代表一个需要追加到所有网页的 JavaScript 资源。网页上下文构建时(见 frappe/website/doctype/website_settings/website_settings.py):
context.web_include_js = hooks.web_include_js or []这个上下文最终被网页模板消费,将website_script.js以<script>标签形式插入页面(通常位于</body>之前的末尾位置),这正是 README 所说 "appended at the end of the page" 的实现依据。
2.2 动态渲染入口
website_script.js并不是一个静态文件,而是一个按请求动态渲染的网页路由。入口位于 frappe/www/website_script.py:
base_template_path = "www/website_script.js":指定渲染模板;no_cache = True:见下一节,这是一个有意的"误导性"设置。
get_context(context)按顺序完成脚本内容组装:
def get_context(context): should_cache = not_modified_recently(frappe.get_website_settings("modified")) website_script = frappe.get_cached_doc("Website Script") context.javascript = website_script.javascript or "" should_cache &= not_modified_recently(website_script.modified) if theme := get_active_theme(): js = strip(theme.js or "") if js: context.javascript += "\n" + js should_cache &= not_modified_recently(theme.modified) if not frappe.conf.developer_mode: context["google_analytics_id"] = get_setting("google_analytics_id") context["google_analytics_anonymize_ip"] = get_setting("google_analytics_anonymize_ip")要点归纳:
- 脚本来源拼接:最终输出 =
Website Script.javascript+ 活动主题(Website Theme)中配置的自定义 JS(若有),两者用换行连接; - Google Analytics 注入:非
developer_mode时,会从 Website Settings 或站点配置文件(frappe.conf)读取google_analytics_id与google_analytics_anonymize_ip注入上下文;get_setting的取值优先级是website_settings优先于conf; - 缓存判断:
not_modified_recently检查文档修改时间是否早于 10 分钟前,用于决定是否启用启发式缓存。
2.3 模板输出
模板 frappe/www/website_script.js 是一个 Jinja 模板,其核心输出逻辑:
{% if javascript -%}{{ javascript }}{%- endif %}即:若上下文中有脚本内容,则原样输出。该文件同时内建了两类可选输出(详见第四节)。
三、缓存策略:no_cache的"误导性"与 SWR 缓存头
website_script.js会被每一个网站页面加载,是最热门的资源之一,因此其缓存策略需要特殊设计。源码中的注释直接解释了这一点:
# NOTE: This is misleading. # We want to avoid Redis cache and instead use proxy cache as website_script.js gets loaded on # every website page and never really changes. no_cache = True # 5 minutes public cache, SWR after that to avoid hard "misses". cache_headers = {"Cache-Control": "public,max-age=300,stale-while-revalidate=10800"}这里有两层含义:
no_cache = True不是让页面完全不缓存,而是绕过 Redis 应用层缓存(避免每次都穿透到应用进程),改用反向代理/浏览器层的 HTTP 缓存,因为该资源几乎从不变化;- 通过
Cache-Control响应头实现SWR(Stale-While-Revalidate)模式:public,max-age=300:CDN/浏览器可公开缓存 5 分钟;stale-while-revalidate=10800:过期后 3 小时内允许继续使用过期副本,同时后台异步刷新,避免硬性缓存未命中(hard miss)。
3.1 启发式缓存开关
是否发送上述缓存头由should_cache决定,其判断逻辑not_modified_recently为:
def not_modified_recently(timestamp): ten_minutes_ago = add_to_date(minutes=-10, as_datetime=True, as_string=False) return ten_minutes_ago > get_datetime(timestamp)即:只有当Website Settings 的修改时间、Website Script 的修改时间、活动主题的修改时间都在 10 分钟之前(三者都满足)时,才认为内容稳定、可以启用缓存头。这样设计是为了避免"正在编辑/刚编辑过"时用户看到陈旧脚本,同时又不至于在内容稳定后失去缓存收益。该启发式依据的是 HTTP 规范中的启发式缓存原则(见源码内注释)。
四、内置的追踪与分析集成
除了用户自定义脚本,frappe/www/website_script.js 模板中还内置了两套开箱即用的追踪能力。
4.1 Google Analytics(经典版)
当上下文存在google_analytics_id时,模板输出完整的 GA 经典版(analytics.js)异步加载代码:
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;...})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); ga('create', '{{ google_analytics_id }}', 'auto'); {% if google_analytics_anonymize_ip %} ga('set', 'anonymizeIp', true); {% endif %} ga('send', 'pageview');ga('create', ...)使用站点配置的跟踪 ID 初始化;- 若
google_analytics_anonymize_ip为真,则启用 IP 匿名化(anonymizeIp),满足 GDPR 等隐私合规要求; - 最后发送
pageview事件。
这两个值可在Website Settings中配置,也可通过站点配置文件(frappe.conf)中的同名键提供,读取优先级见 get_setting 实现。
4.2 页面浏览量跟踪(enable_view_tracking)
模板尾部还有一个enable_view_tracking代码块:当该开关开启且浏览器未设置navigator.doNotTrack == 1、页面非 404 时,脚本会在frappe.ready后:
- 动态加载
/assets/frappe/js/lib/fingerprintjs.js生成访问者唯一 ID(fingerprint); - 调用
frappe.website.doctype.web_page_view.web_page_view.make_view_log上报:referrer、浏览器名与版本、时区,以及source/medium/campaign/content(读取 URL 中的同名参数或utm_*参数); - 配合 visitor_id 形成访问者级别的浏览日志,为 Web Page View 统计提供数据。
这套逻辑体现了 Website Script 作为"全站分析承载点"的完整闭环:自定义脚本(第三方工具)+ 内置 GA + 内置访问日志三者在同一资源中输出。
五、实战:如何配置与验证
- 写入全局脚本:以 Website Manager 角色登录 Desk,打开「Website Script」表单,在
Javascript字段(Code 编辑器)粘贴自定义代码,保存。保存时on_update会自动清理缓存,无需手动操作; - 验证注入:
on_update会清理缓存,随后用浏览器开发者工具或curl请求任意网页,确认页面末尾的<script src="/website_script.js">(资源 URL 由web_include_js机制生成)存在; - 直接检查输出:直接请求
/website_script.js,响应体即当前生效的完整脚本(含主题 JS、GA 代码、浏览跟踪代码的拼接结果); - 配置 GA:在 Website Settings 中填入
google_analytics_id;若需 IP 匿名化,同时开启google_analytics_anonymize_ip; - 调试注意:
developer_mode下不会注入 GA 代码,因此本地开发默认不会产生统计上报; - 安全提示:该字段仅 Website Manager 可写,全站所有页面都会执行其中的代码,务必只信任受控来源的脚本。
六、源码路径速查
- 文档说明:frappe/website/doctype/website_script/README.md
- DocType 定义(字段 / 权限 / 单例声明):frappe/website/doctype/website_script/website_script.json
- 控制器与缓存清理:frappe/website/doctype/website_script/website_script.py
- 渲染入口(缓存头 / GA 注入 / 主题 JS 拼接):frappe/www/website_script.py
- 输出模板(自定义脚本 + GA + 浏览跟踪):frappe/www/website_script.js
- 全局注册点
web_include_js:frappe/hooks.py - 上下文注入实现:frappe/website/doctype/website_settings/website_settings.py
【免费下载链接】frappeLow code web framework for real world applications, in Python and Javascript项目地址: https://gitcode.com/GitHub_Trending/fr/frappe
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考