> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 包的公开资源与更新

> 解读 Laravel 13 的发布处理，从长期维护的角度讲解 JavaScript 和 CSS 的分发、标签的选择范围、覆盖选项，以及 Composer 更新时的重新发布。

即使更新了包的 JavaScript 或 CSS，已经复制到应用程序 `public` 中的文件也不会自动改变。为了避免只有 PHP 代码变成新版本、而浏览器仍在使用旧版本资源的情况，需要确定发布目标的所有者以及更新步骤。

本页以[Laravel 包开发](/zh-CN/advanced/package-development)为前提，从 Laravel 13 的发布处理出发，梳理分发与维护的设计。实现的确认基于 `laravel/framework` 的 `v13.35.0`。

## 发布既不是构建也不是同步

`ServiceProvider::publishes()` 用于注册复制源和复制目标。实际复制文件的是 `vendor:publish`。它不会进行 JavaScript 转译、CSS 构建，也不会添加到应用程序的 Vite 入口中。

如果由包分发已构建好的文件，可以采用如下结构。

```text theme={null}
courier/
├── public/
│   ├── courier.css
│   └── courier.js
└── src/
    └── CourierServiceProvider.php
```

```php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__.'/../public' => public_path('vendor/courier'),
            ], 'courier-assets');
        }
    }
}
```

首次发布时，请显式指定 Provider 和标签。

```bash theme={null}
php artisan vendor:publish --provider="Acme\Courier\CourierServiceProvider" --tag=courier-assets
```

在这个示例中，会生成 `public/vendor/courier/courier.css` 和 `courier.js`。如果设计为以普通 CSS 和 JavaScript 的形式分发，可以在 Blade 中按如下方式引用。

```blade theme={null}
<link rel="stylesheet" href="{{ asset('vendor/courier/courier.css') }}">
<script src="{{ asset('vendor/courier/courier.js') }}" defer></script>
```

如果以 ES modules 等形式分发，请根据分发格式调整加载方式。`asset()` 是生成 URL 的辅助函数，它不负责构建、发布，也不会根据内容生成文件名。

<Warning>
  复制源中只应放置可以公开的构建产物。本示例的发布目标是可从 Web 访问的 `public`。请不要把配置文件或内部数据放入同一个发布组中。
</Warning>

## 标签不是 Provider 专属的命名空间

`ServiceProvider` 会把发布路径分别注册到按 Provider 类划分的数组和按标签划分的数组中。按标签划分的数组由多个 Provider 共享，因此如果使用 `public` 这样的通用标签，其他包也可能成为对象。

| 指定方式 | 选中的发布路径 |
| - | - |
| `--tag=courier-assets` | 注册了该标签的所有 Provider 的路径 |
| `--provider="Acme\Courier\CourierServiceProvider"` | 该 Provider 注册的所有路径 |
| 同时指定 Provider 和标签 | 该 Provider 的路径与该标签的路径的交集 |
| `--all` | 所有 Provider 的发布路径 |

同时指定两者时，`pathsForProviderAndGroup()` 会**以复制源路径为键**使用 `array_intersect_key()`。这并不是根据标签切换到不同复制目标的机制。请避免多次注册同一个复制源、为其设置不同用途的复制目标的设计。

`--tag` 可以重复指定，此时会依次发布各个标签。`--all` 会在选择处理的开头直接返回，因此即使同时加上 `--provider` 或 `--tag`，也不会用于缩小范围。

<Tip>
  为了避免连用户的配置和视图也被覆盖，更新步骤中请使用包专属的资源标签。如果只指定 Provider 并加上 `--force`，同一 Provider 的配置和视图也可能成为对象。
</Tip>

## 区分使用重新发布选项

`VendorPublishCommand` 的文件发布和目录发布会根据复制目标文件是否存在以及选项来判断是否复制。下表是针对复制源中存在的普通资源文件的行为。

| 选项 | 复制目标中不存在的文件 | 复制目标中已存在的文件 |
| - | - | - |
| 无 | 添加 | 保留 |
| `--force` | 添加 | 覆盖 |
| `--existing` | 不添加 | 覆盖 |
| `--existing --force` | 不添加 | 覆盖 |

`--existing` 并不是保护编辑内容的选项。它会覆盖已有文件，但不会发布新版本中新增的文件。如果更新后的 JavaScript 需要新增的文件，仅使用 `--existing` 可能无法让构建产物齐全。

如果约定由包管理发布目标、用户不直接编辑，则在更新后执行以下命令。

```bash theme={null}
php artisan vendor:publish --provider="Acme\Courier\CourierServiceProvider" --tag=courier-assets --force
```

<Warning>
  `--force` 不会合并差异，也会覆盖用户的编辑。对于用户自定义的 CSS，请将其作为单独的文件加载等，与包管理的构建产物分离。其更新方针应与配置和视图的自定义分开。
</Warning>

### 已删除的文件会残留在发布目标中

目录发布的 `moveManagedFiles()` 会遍历复制源中的文件并写入。它没有查找并删除仅存在于发布目标中的文件的处理。`--force` 也不会实现目录的完全同步。

例如，即使在新版本中删除了 `legacy.js`，如果已经发布过旧版本，`public/vendor/courier/legacy.js` 仍会残留。重命名时旧名称的文件同样会残留，因此请在发布说明中记录已删除、已重命名的文件以及引用位置的变更。

如果提供清除旧文件的步骤，请具体指明包所拥有的文件。不要设计成整体删除可能放有用户自有文件的目录的步骤。

## 加入 laravel-assets 意味着接受覆盖约定

Laravel 13 的官方应用程序骨架在 `composer.json` 的 `post-update-cmd` 中包含如下脚本。

```json theme={null}
{
    "scripts": {
        "post-update-cmd": [
            "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
        ]
    }
}
```

这是应用程序一侧的脚本。并不是包的自动发现本身在更新已发布的文件。在现有应用程序中，该脚本可能已被修改或删除，因此请确认使用方的配置。

如果要加入这一更新路径，请把前面 `publishes()` 的第 2 个参数改为数组，将同一资源注册到两个标签中。

```php theme={null}
$this->publishes([
    __DIR__.'/../public' => public_path('vendor/courier'),
], ['courier-assets', 'laravel-assets']);
```

`laravel-assets` 并不是带有特殊复制处理的标签。由于骨架中的脚本会以带 `--force` 的方式发布该标签，加入其中的文件会在 Composer 更新时成为覆盖对象。请不要注册用户会编辑的配置或视图。

<Info>
  自动更新的前提是：应用程序一侧存在该脚本、该事件会被执行，并且 Provider 已注册发布路径。为了在不满足这些前提的部署中也能完成更新，请提供使用包专属标签的重新发布命令。
</Info>

## 在部署中保持 PHP 与资源的版本一致

```mermaid theme={null}
flowchart TD
    A["更新包"] --> B["注册发布路径"]
    B --> C["使用专属标签执行 vendor:publish --force<br>或 laravel-assets 的更新脚本"]
    C --> D["添加新文件<br>覆盖已有文件"]
    D --> E["清理指定的旧文件<br>应用浏览器与 CDN 的缓存策略"]
    E --> F["确认 PHP 与资源以同一版本运行"]
```

`config:cache` 和 `view:cache` 不会改写已发布的 JavaScript 和 CSS。如果设计为发布后仍以相同 URL 提供，浏览器或 CDN 的缓存可能导致使用旧内容。请把反映分发物版本的 URL、缓存失效等应用程序的分发策略也纳入更新步骤。

每次发布版本时，请确认以下组合。

* 在尚未发布的应用程序中，所有必要的构建产物都会被发布。
* 在已发布旧版本的状态下，`--force` 会更新已有文件并添加新文件。
* 资源更新不会覆盖用户的配置、视图和自有 CSS。
* 已明确说明已删除、已重命名文件的处理方式，且不再残留对旧版本的引用。
* 通过实际的分发 URL 能获取到新版本的内容，PHP 与浏览器端的处理能够协同工作。

## 相关页面

<Columns cols={2}>
  <Card title="包自动发现" icon="magnifying-glass" href="/zh-CN/advanced/package-discovery">
    确认 Composer 更新、Provider 发现与文件发布之间的区别。
  </Card>

  <Card title="包视图的覆盖与更新" icon="eye" href="/zh-CN/advanced/package-views">
    确认用户自定义模板的维护方针。
  </Card>

  <Card title="包缓存与 optimize 的集成" icon="gears" href="/zh-CN/advanced/package-optimization">
    讲解与文件发布分开管理的包缓存。
  </Card>

  <Card title="版本兼容性管理" icon="code-branch" href="/zh-CN/advanced/package-versioning">
    将发布目标和分发格式的变更作为兼容性约定来处理。
  </Card>
</Columns>

## 参考的一手资料

* [Laravel 官方文档：Public assets](https://github.com/laravel/docs/blob/13.x/packages.md#public-assets)
* [Laravel 官方文档：Publishing file groups](https://github.com/laravel/docs/blob/13.x/packages.md#publishing-file-groups)
* [Laravel Framework v13.35.0：ServiceProvider](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Support/ServiceProvider.php) — `publishes()`、`addPublishGroup()`、`pathsToPublish()`、`pathsForProviderAndGroup()`。
* [Laravel Framework v13.35.0：VendorPublishCommand](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Foundation/Console/VendorPublishCommand.php) — 选择范围、覆盖条件、目录内的复制处理。
* [Laravel 13 官方应用程序骨架：composer.json](https://github.com/laravel/laravel/blob/13.x/composer.json) — 通过 `post-update-cmd` 重新发布 `laravel-assets`。


## Related topics

- [Laravel 包开发](/zh-CN/advanced/package-development.md)
- [进阶主题](/zh-CN/advanced/index.md)
- [包的静态分析（PHPStan / Larastan）](/zh-CN/advanced/package-static-analysis.md)
- [包视图的覆盖与更新](/zh-CN/advanced/package-views.md)
- [包迁移的发布与更新](/zh-CN/advanced/package-migrations.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.