Skip to content
全部文档

注册静态资源

简介

Filament 生态中的所有包共享一套静态资源管理系统。官方插件与第三方插件都可以注册 CSS 和 JavaScript 文件,再由 Blade 视图使用。

FilamentAsset facade

FilamentAsset facade 用于将文件注册到静态资源系统。这些文件可以来自文件系统中的任意位置,但在运行 php artisan filament:assets 命令时会被复制到应用的 /public 目录。帮你复制到 /public 后,我们就能在 Blade 视图中可预期地加载它们,同时也能确保第三方包加载静态资源时不必操心文件位置。

静态资源始终有一个由你选择的唯一 ID,复制到 /public 目录时会用作文件名。该 ID 也用于在 Blade 视图中引用该资源。虽然 ID 必须唯一,但若是为插件注册静态资源,则不必担心与其他插件的 ID 冲突,因为资源会被复制到以插件命名的目录中。

应在服务提供者的 boot() 方法中使用 FilamentAsset facade。可以在 AppServiceProvider 等应用服务提供者中使用,也可以在插件服务提供者中使用。

FilamentAsset facade 有一个主要方法 register(),接受要注册的静态资源数组:

php
use Filament\Support\Facades\FilamentAsset;

public function boot(): void
{
    // ...

    FilamentAsset::register([
        // ...
    ]);

    // ...
}

为插件注册静态资源

为插件注册静态资源时,应将 Composer 包名作为 register() 方法的第二个参数传入:

php
use Filament\Support\Facades\FilamentAsset;

FilamentAsset::register([
    // ...
], package: 'danharrin/filament-blog');

现在,该插件的所有静态资源都会被复制到 /public 下属于自己的目录中,以避免与其他插件同名文件冲突。

注册 CSS 文件

要向静态资源系统注册 CSS 文件,请在服务提供者的 boot() 方法中使用 FilamentAsset::register()。必须传入 Css 对象数组,每个对象代表应注册到静态资源系统中的一个 CSS 文件。

每个 Css 对象都有唯一 ID 和 CSS 文件路径:

php
use Filament\Support\Assets\Css;
use Filament\Support\Facades\FilamentAsset;

FilamentAsset::register([
    Css::make('custom-stylesheet', __DIR__ . '/../../resources/css/custom.css'),
]);

本例中,我们使用 __DIR__ 生成从当前文件到静态资源的相对路径。例如,若把这段代码加到 /app/Providers/AppServiceProvider.php,则 CSS 文件应位于 /resources/css/custom.css

现在,运行 php artisan filament:assets 命令时,该 CSS 文件会被复制到 /public 目录。此外,它会被加载到所有使用 Filament 的 Blade 视图中。若只想在页面上的元素需要时才加载 CSS,请参阅 延迟加载 CSS 一节。

在插件中使用 Tailwind CSS

通常,注册 CSS 文件用于为应用注册自定义样式表。若要用 Tailwind CSS 处理这些文件,需要考虑其影响,尤其当你是插件开发者时。

Tailwind 的构建结果对每个应用都是独一无二的——它们只包含你实际在应用中使用的最小工具类集合。这意味着若你是插件开发者,通常不应把 Tailwind CSS 文件构建进插件。相反,应提供原始 CSS 文件,并指导用户自行构建 Tailwind CSS 文件。为此,他们需要用 @source 指令把你的 vendor 目录加入自定义主题的 CSS 文件。在其自定义主题 CSS 文件(例如 resources/css/filament/admin/theme.css)中,应添加:

css
@import "tailwindcss";

@source '../../../../app/Filament/**/*';
@source '../../../../resources/views/filament/**/*';
@source '../../../../vendor/danharrin/filament-blog/resources/views/**/*'; /* Your plugin's vendor directory */

这样,他们构建 Tailwind CSS 文件时,就会包含插件视图中使用的全部工具类,以及他们应用和 Filament 核心中使用的工具类。

不过,对将插件与 Panel Builder 一起使用的用户,这种做法可能带来额外麻烦。若他们有自定义主题,则没问题,因为他们反正会用 Tailwind CSS 构建自己的 CSS 文件。但若他们使用 Panel Builder 附带的默认样式表,你就需要小心插件视图中使用的工具类。例如,若使用了默认样式表中没有的工具类,用户又不会自行编译,它就不会出现在最终 CSS 中。这意味着插件视图可能看起来不符合预期。这是少数建议在插件中编译并注册 一份由 Tailwind CSS 编译的样式表的情况之一。

延迟加载 CSS

默认情况下,注册到静态资源系统的所有 CSS 文件都会加载到每个 Filament 页面的 <head> 中。这是加载 CSS 最简单的方式,但有时文件较重,且并非每页都需要。此时可以利用 Filament 内置的 Alpine.js Lazy Load Assets 包,用 Alpine.js 按需加载 CSS。原理很简单:在元素上使用 x-load-css 指令,当该元素加载到页面时,指定的 CSS 文件就会被加载到页面的 <head> 中。这对需要 CSS 文件的小型 UI 元素和整页都适用:

blade
<div
    x-data="{}"
    x-load-css="[@js(\Filament\Support\Facades\FilamentAsset::getStyleHref('custom-stylesheet'))]"
>
    <!-- ... -->
</div>

要阻止 CSS 文件自动加载,可以使用 loadedOnRequest() 方法:

php
use Filament\Support\Assets\Css;
use Filament\Support\Facades\FilamentAsset;

FilamentAsset::register([
    Css::make('custom-stylesheet', __DIR__ . '/../../resources/css/custom.css')->loadedOnRequest(),
]);

若 CSS 文件是为插件注册的,必须将其作为第二个参数传给 FilamentAsset::getStyleHref() 方法:

blade
<div
    x-data="{}"
    x-load-css="[@js(\Filament\Support\Facades\FilamentAsset::getStyleHref('custom-stylesheet', package: 'danharrin/filament-blog'))]"
>
    <!-- ... -->
</div>

从 URL 注册 CSS 文件

若要从 URL 注册 CSS 文件,也可以这样做。这些静态资源仍会像往常一样在每页加载,但运行 php artisan filament:assets 时不会复制到 /public 目录。这适用于注册来自 CDN 的外部样式表,或你已直接编译到 /public 目录的样式表:

php
use Filament\Support\Assets\Css;
use Filament\Support\Facades\FilamentAsset;

FilamentAsset::register([
    Css::make('example-external-stylesheet', 'https://example.com/external.css'),
    Css::make('example-local-stylesheet', asset('css/local.css')),
]);

注册 CSS 变量

有时,你可能希望在 CSS 文件中使用来自后端的动态数据。为此,可在服务提供者的 boot() 方法中使用 FilamentAsset::registerCssVariables()

php
use Filament\Support\Facades\FilamentAsset;

FilamentAsset::registerCssVariables([
    'background-image' => asset('images/background.jpg'),
]);

现在,可以从任意 CSS 文件访问这些变量:

css
background-image: var(--background-image);

注册 JavaScript 文件

要向静态资源系统注册 JavaScript 文件,请在服务提供者的 boot() 方法中使用 FilamentAsset::register()。必须传入 Js 对象数组,每个对象代表应注册到静态资源系统中的一个 JavaScript 文件。

每个 Js 对象都有唯一 ID 和 JavaScript 文件路径:

php
use Filament\Support\Assets\Js;

FilamentAsset::register([
    Js::make('custom-script', __DIR__ . '/../../resources/js/custom.js'),
]);

本例中,我们使用 __DIR__ 生成从当前文件到静态资源的相对路径。例如,若把这段代码加到 /app/Providers/AppServiceProvider.php,则 JavaScript 文件应位于 /resources/js/custom.js

现在,运行 php artisan filament:assets 命令时,该 JavaScript 文件会被复制到 /public 目录。此外,它会被加载到所有使用 Filament 的 Blade 视图中。若只想在页面上的元素需要时才加载 JavaScript,请参阅 延迟加载 JavaScript 一节。

延迟加载 JavaScript

默认情况下,注册到静态资源系统的所有 JavaScript 文件都会加载到每个 Filament 页面底部。这是加载 JavaScript 最简单的方式,但有时文件较重,且并非每页都需要。此时可以利用 Filament 内置的 Alpine.js Lazy Load Assets 包,用 Alpine.js 按需加载 JavaScript。原理很简单:在元素上使用 x-load-js 指令,当该元素加载到页面时,指定的 JavaScript 文件就会被加载到页面底部。这对需要 JavaScript 文件的小型 UI 元素和整页都适用:

blade
<div
    x-data="{}"
    x-load-js="[@js(\Filament\Support\Facades\FilamentAsset::getScriptSrc('custom-script'))]"
>
    <!-- ... -->
</div>

要阻止 JavaScript 文件自动加载,可以使用 loadedOnRequest() 方法:

php
use Filament\Support\Assets\Js;
use Filament\Support\Facades\FilamentAsset;

FilamentAsset::register([
    Js::make('custom-script', __DIR__ . '/../../resources/js/custom.js')->loadedOnRequest(),
]);

若 JavaScript 文件是为插件注册的,必须将其作为第二个参数传给 FilamentAsset::getScriptSrc() 方法:

blade
<div
    x-data="{}"
    x-load-js="[@js(\Filament\Support\Facades\FilamentAsset::getScriptSrc('custom-script', package: 'danharrin/filament-blog'))]"
>
    <!-- ... -->
</div>

异步 Alpine.js 组件

有时,你可能希望为基于 Alpine.js 的组件加载外部 JavaScript 库。最好的做法是把编译后的 JavaScript 和 Alpine 组件存到单独文件中,在组件渲染时由我们加载。

首先,应通过 NPM 安装 esbuild,我们将用它创建一个包含外部库和 Alpine 组件的单一 JavaScript 文件:

bash
npm install esbuild --save-dev

然后,必须创建一个脚本来编译 JavaScript 和 Alpine 组件。可以放在任意位置,例如 bin/build.js

js
import * as esbuild from 'esbuild'

const isDev = process.argv.includes('--dev')

async function compile(options) {
    const context = await esbuild.context(options)

    if (isDev) {
        await context.watch()
    } else {
        await context.rebuild()
        await context.dispose()
    }
}

const defaultOptions = {
    define: {
        'process.env.NODE_ENV': isDev ? `'development'` : `'production'`,
    },
    bundle: true,
    mainFields: ['module', 'main'],
    platform: 'neutral',
    sourcemap: isDev ? 'inline' : false,
    sourcesContent: isDev,
    treeShaking: true,
    target: ['es2020'],
    minify: !isDev,
    plugins: [{
        name: 'watchPlugin',
        setup(build) {
            build.onStart(() => {
                console.log(`Build started at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`)
            })

            build.onEnd((result) => {
                if (result.errors.length > 0) {
                    console.log(`Build failed at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`, result.errors)
                } else {
                    console.log(`Build finished at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`)
                }
            })
        }
    }],
}

compile({
    ...defaultOptions,
    entryPoints: ['./resources/js/components/test-component.js'],
    outfile: './resources/js/dist/components/test-component.js',
})

如脚本底部所示,我们把名为 resources/js/components/test-component.js 的文件编译到 resources/js/dist/components/test-component.js。你可以根据需要更改这些路径,也可以编译任意数量的组件。

现在,新建名为 resources/js/components/test-component.js 的文件:

js
// Import any external JavaScript libraries from NPM here.

export default function testComponent({
    state,
}) {
    return {
        state,

        // You can define any other Alpine.js properties here.

        init() {
            // Initialise the Alpine component here, if you need to.
        },

        // You can define any other Alpine.js functions here.
    }
}

现在,可以运行以下命令,将该文件编译到 resources/js/dist/components/test-component.js

bash
node bin/build.js

若想监视该文件的更改而不是只编译一次,可尝试以下命令:

bash
node bin/build.js --dev

现在需要告诉 Filament 将这份编译后的 JavaScript 文件发布到 Laravel 应用的 /public 目录,以便浏览器可以访问。为此,可在服务提供者的 boot() 方法中使用 FilamentAsset::register(),并传入 AlpineComponent 对象:

php
use Filament\Support\Assets\AlpineComponent;
use Filament\Support\Facades\FilamentAsset;

FilamentAsset::register([
    AlpineComponent::make('test-component', __DIR__ . '/../../resources/js/dist/components/test-component.js'),
]);

运行 php artisan filament:assets 时,编译后的文件会被复制到 /public 目录。

最后,可以在视图中使用 x-load 属性和 FilamentAsset::getAlpineComponentSrc() 方法加载这个异步 Alpine 组件:

blade
<div
    x-load
    x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('test-component') }}"
    x-data="testComponent({
        state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
    })"
>
    <input x-model="state" />
</div>

本例面向自定义表单字段。它将 state 作为参数传给 testComponent() 函数,并与 Livewire 组件属性纠缠(entangle)。你可以传入任意参数,并在 testComponent() 中访问它们。若不是自定义表单字段,可以忽略本例中的 state 参数。

x-load 属性来自 Async Alpine 包,该包的任何功能都可以在此使用。

注册脚本数据

有时,你可能希望让 JavaScript 文件能使用来自后端的数据。为此,可在服务提供者的 boot() 方法中使用 FilamentAsset::registerScriptData()

php
use Filament\Support\Facades\FilamentAsset;

FilamentAsset::registerScriptData([
    'user' => [
        'name' => auth()->user()?->name,
    ],
]);

现在,可以在运行时通过 window.filamentData 对象从任意 JavaScript 文件访问该数据:

js
window.filamentData.user.name // 'Dan Harrin'

从 URL 注册 JavaScript 文件

若要从 URL 注册 JavaScript 文件,也可以这样做。这些静态资源仍会像往常一样在每页加载,但运行 php artisan filament:assets 时不会复制到 /public 目录。这适用于注册来自 CDN 的外部脚本,或你已直接编译到 /public 目录的脚本:

php
use Filament\Support\Facades\FilamentAsset;
use Filament\Support\Assets\Js;

FilamentAsset::register([
    Js::make('example-external-script', 'https://example.com/external.js'),
    Js::make('example-local-script', asset('js/local.js')),
]);

使用 Vite 编译的 JavaScript 文件

php artisan filament:assets 命令会原样把文件复制到 /public 目录,不会打包或解析依赖。这意味着若 JavaScript 文件用 import 语句引入 npm 包,浏览器将无法解析它们。要使用需要打包的 JavaScript 文件,应先用 Vite 编译,再将编译输出注册为基于 URL 的静态资源。

首先,在 vite.config.js 中把 JavaScript 文件添加为入口:

js
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.js',
                'resources/js/timezone.js', // Your custom script
            ],
        }),
    ],
})

然后用 Vite 编译静态资源:

bash
npm run build

最后,使用 Vite::asset() 解析带版本的 URL,并注册编译后的静态资源:

php
use Filament\Support\Assets\Js;
use Filament\Support\Facades\FilamentAsset;
use Illuminate\Support\Facades\Vite;

FilamentAsset::register([
    Js::make('timezone', Vite::asset('resources/js/timezone.js')),
]);

这种方法也适用于 TypeScript 文件或任何需要构建步骤的 JavaScript。由于 Vite::asset() 返回的是 URL,php artisan filament:assets 不会复制该资源——它会直接从 Vite 的构建输出提供。

INFO

若需要为异步 Alpine.js 组件 打包 JavaScript,请考虑改用 esbuild,详见该节说明。

在版本控制中忽略已发布的静态资源

php artisan filament:assets 为 Filament 自身的包复制到 /public 目录的文件是生成的,因此不必提交到版本控制。运行 php artisan filament:install 时,若应用的 .gitignore 中尚未包含以下规则,Filament 会添加它们:

/public/css/filament
/public/fonts/filament
/public/js/filament

若已在 config/filament.php 中自定义 assets_path,规则会改用该路径,例如 /public/filament/css/filament

应用或第三方插件注册的静态资源可能会发布到这些 filament 目录之外。如有需要,应单独把它们的生成路径加入 .gitignore

/public/css/filament 规则也会忽略你直接编译到该目录、而非通过 Vite 编译的自定义主题。应在部署流程中编译这些主题,因为 filament:assets 命令不会构建它们。

由于 Filament 已发布的包静态资源不会被提交,部署应用时应运行 php artisan filament:assetsphp artisan filament:install 命令会把 @php artisan filament:upgrade 加入应用 composer.jsonpost-autoload-dump 脚本,每次 Composer dump autoloader 时都会为你运行 filament:assets 命令。