模板继承嵌套到第四层时,静态资源路径开始集体"叛逃"
上周给后台管理端做主题换肤功能,想着用模板继承把公共骨架抽出来,子页面只管填坑。ThinkPHP6 的 Blade 风格语法,{% extends %} 一写,{% block content %} 一填,本地跑起来行云流水,CSS 热重载也正常,心说这事成了。
打包发到测试机,F12 一开,满屏 404。`admin.css` 找成了 `//admin.css`,`logo.png` 跑到了根域名下,更绝的是深层嵌套的子页面,资源路径直接跨级上访,三层目录的页面去请求了一层目录的 `../js/app.js`,拼出来个不存在的路径。
问题出在静态资源的发布路径和模板继承的相对基准不是同一个坐标系。本地我用的是 `VITE_DEV_SERVER_URL`,绝对路径伺候,测试环境走 `php think view:publish`,TP 把模板预编译到 `runtime/view/` 下面,继承链里的 `__ASSETS__` 替换规则在每一层嵌套时的"当前目录"理解不一致。第一层继承算的是控制器对应的视图目录,第二层继承算的是被继承模板的目录,第三层开始基本就是玄学,取决于 `fetch()` 的时候传的是相对路径还是 `@` 符号的绝对路径。
我试过三种解法,各有各的坑:
方案一:全改成绝对路径 `/static/admin/...`
最简单,但换域名、套 CDN、做子目录部署的时候集体阵亡。客户那边要内网穿透加个二级目录,全站样式崩成毛坯房。
方案二:模板里用 `{:url()}` 或者 `{:asset()}` 助手函数
理论上动态计算,但模板继承的预编译时机比函数执行早,有些页面缓存后助手函数输出的是编译时的路径,不是请求时的路径,清缓存之前一切正常,缓存一命中就回到解放前。
方案三:配置 `view_replace_str`,按环境变量注入前缀
目前用的这个。`.env` 里定义 `ASSETS_PREFIX`,`config/view.php` 里做替换,模板里写 `__ASSETS__/css/admin.css`。但继承嵌套时有个细节:如果父模板和子模板不在同一个模块(比如父模板在 `view/common/`,子模板在 `view/user/`),替换字符串在父模板里被解析一次,子模板里又被解析一次,双下划线前缀如果设计得不好会互相污染。
我现在定了个死规矩:所有被继承的基础模板(layout、blank、error 那几套),资源路径必须用 `__ASSETS__` 且不带任何相对层级;子页面如果需要额外资源,用 `{% block css %}{% endblock %}` 插槽注入,不在子页面里直接写 ``。这样至少保证继承链上的路径基准统一在基础模板那一层,不会层层漂移。
还有个容易忽略的点:TP6 的 `view:clear` 和 `view:publish` 不会联动。你改了 `config/view.php` 里的替换规则,如果不清 runtime,老编译文件还在用旧前缀,表现就是"配置明明改了怎么没生效"。我现在部署脚本里强制先 `view:clear` 再 `publish`,省得自己怀疑自己。
这事折腾完,我把项目里所有 `../static/` 和 `./static/` 的路径全 grep 出来改了一遍,共 37 处。以前觉得模板继承是"省代码",现在发现它其实是"签契约"——每一层继承都在约定路径解析的上下文,上下文乱了,资源比业务逻辑先崩给你看。