Skip to main content
对于允许用户自定义页面或邮件模板的包,不仅需要发布视图,还需要一套在保留已发布文件的同时进行更新的约定。即使修改了包中的 Blade 文件,使用方应用程序也不一定在渲染该文件。 本页以包开发基础为前提,将视图的选择、文件的发布和缓存分开讨论。官方文档参考的是 Laravel 13,框架实现参考的是最新发布版本 v13.34.0。

注册与发布是不同的处理

loadViewsFrom() 会为命名空间注册查找路径。publishes() 注册复制源和复制目标,实际的复制由 vendor:publish 完成。在下面的示例中,即使不发布也可以使用 courier::deliveries.show。
src/CourierServiceProvider.php
包中的文件放在 resources/views/deliveries/show.blade.php。视图名中的点号在查找时会被转换为目录分隔符。
resources/views/deliveries/show.blade.php
视图的命名空间与 Composer 的包名和 PHP 的命名空间是不同的。在这里,loadViewsFrom() 第二个参数指定的 courier 构成了视图引用和覆盖目录的约定。

按文件查找覆盖位置

ServiceProvider::loadViewsFrom() 会在 view 被解析时,依次检查配置中的 view.paths。如果各路径下存在 vendor/courier 目录,就将该目录添加到命名空间中,最后再添加包的路径。 FileViewFinder 会依次查找该命名空间的路径,并返回第一个找到的文件。在使用标准 resources/views 的配置下,顺序如下。 这并不是整个目录的切换。即使用户只覆盖了 deliveries/show.blade.php,其他未覆盖的视图仍会从包中加载。
在存在多个 view.paths 的配置中,覆盖位置也可能有多个。resource_path('views/vendor/courier') 只是本示例中的发布目标,并不是将查找范围固定在该位置的处理。请使用包专属的命名空间,避免多个提供者向同一名称添加路径的设计。

只自定义需要的视图

用户可以通过以下命令复制模板。指定提供者和标签,以免牵连其他资源。
在这种注册方式下,整个视图目录都会被发布。如果不需要全部覆盖,可以在确认内容后只保留要自定义的文件,或者只将需要的文件手动复制到相同的相对路径。这是因为未编辑的副本只要存在,也会被视为覆盖。
已发布的模板不会随包的更新自动同步。在旧副本优先的状态下只修改包中的文件,该视图的变更不会生效。显示问题的修复和表单变更等也需要与覆盖文件进行比较。

重新发布不会合并差异

VendorPublishCommand 通常在存在同名发布目标文件时跳过复制。--force 会覆盖现有文件。此外,--existing 也是”覆盖已发布文件”的选项,并不是保留用户编辑的模式。 无论哪种方式,都不是对旧版、新版和用户编辑进行比较后的合并。也不会自动从发布目标中删除已从包中移除的视图。请不要将更新步骤简化为”只需重新发布同一标签”。

将视图也作为公开 API 进行维护

不仅是视图名,接收的数据和引用的组件也会影响用户的自定义内容。例如,如果在新版中将 trackingCode 改为其他变量名,保留旧模板的用户将无法从新代码中获得所需的值。 发布前,请确认以下约定。
  • 不要随意更改命名空间和 deliveries.show 等视图名。
  • 记录传递的变量、类型以及必填与可选的区别。
  • 将 @include 和 @extends 的引用目标、Blade 组件的 props 也纳入变更范围。
  • 在发布说明中写明修改过的视图,以及需要应用到已发布旧版模板的变更。
面向用户,应提供比较旧版与新版包视图、并将所需变更手动合并到已自定义文件中的步骤。对于不再需要覆盖的文件,先通过备份或版本控制保存变更后再移除,即可恢复为包中的视图。

Blade 缓存不会更新覆盖文件

view:cache 会将 Blade 模板预编译为 PHP。ViewCacheCommand 首先执行 view:clear,然后收集普通视图路径和注册到命名空间的路径来查找编译对象。
该处理不会改写已发布的 Blade 文件,也不会改变视图的查找优先级。如果存在旧的覆盖文件,即使重建缓存,该文件仍会被继续选中。部署时,请在完成代码和覆盖文件的更新之后再进行编译。 在开发过程中,如果想删除已编译文件并重新渲染,请使用以下命令。
如果启用了常规的时间戳检查,Blade 编译器会比较源文件与已编译文件的修改时间。但由于也存在禁用时间戳检查的配置,请不要将部署时的重建完全交给自动判断。

与查找结果缓存区分开

FileViewFinder::find() 会将找到的路径保存到该 Finder 实例的 $views 数组中。此外,覆盖目录的存在性检查是在 loadViewsFrom() 的回调中进行的。即使在启动后添加了新目录,也不会自动加入已注册的查找路径。 view:clear 并不是一次性清除其他运行中进程所持有的 Finder 状态的命令。对于 Octane 等长时间运行的进程,请按照常规部署步骤重新加载。Finder 的 flush() 会清除查找结果,但不会注册新的覆盖目录。

发布前需要确认的事项

除了包的测试之外,还要在使用方应用程序中确认以下组合。只渲染最新模板的测试无法验证对已发布旧版的用户的兼容性。
  • 在未发布的状态下,渲染的是包中的视图。
  • 只覆盖一个文件时,仅该文件优先,其他文件回退到包中。
  • 在保留旧版已发布模板的状态下,也能使用新版传递的数据进行渲染。
  • 普通的重新发布会保留自定义内容,未发布文件的添加也符合预期。
  • 修改覆盖文件后 view:cache 成功,并且重新启动后显示的是修改后的内容。

相关页面

视图

了解视图的创建、数据传递和预编译的基础知识。

Blade 模板

了解布局、include 和组件的用法。

版本兼容性管理

将模板约定的变更与发布策略关联起来。

Octane

了解长时间运行的应用程序的生命周期和重新加载。

参考的一手资料

最后修改于 2026年10月4日