Languages

Version

Theme

高级

注册静态资源

介绍

Filament 生态系统中的所有包都共享同一个资源管理系统。它允许官方插件及第三方插件注册用于 Blade 视图的 CSS 和 JavaScript 文件。

FilamentAsset Facade

FilamentAsset Facade 用于将文件注册到资源系统。这些文件可以来自于文件系统的任何地方,之后当运行 php artisan filament:assets 命令时,它们将被复制到应用的 public 目录中。通过将它们复制到 public 目录,我们可以在 Blade 视图中可预测地加载它们,同时确保第三方包也能够加载它们的资源,而不必操心资源所在的位置。

资源始终有一个由你确定的唯一 ID,当资源被复制到 /public 目录时,该 ID 将被用作文件名。此 ID 还用于在 Blade 视图中引用资源。虽然 ID 是唯一的,但如果你正在为插件注册资源,则不必担心 ID 会与其他插件冲突,因为资源将被复制到以你的插件命名的目录中。

FilamentAsset Facade 应该在服务提供者的 boot() 方法中使用。它可用于应用服务提供者(如 AppServiceProvider)之中,也可用于插件的服务提供者内。

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

use Filament\Support\Facades\FilamentAsset;

public function boot(): void
{
    // ...
    
    FilamentAsset::register([
        // ...
    ]);
    
    // ...
}

为插件注册静态资源

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

use Filament\Support\Facades\FilamentAsset;

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

现在,该插件的所有资源都会被复制到 public 下他们自己的目录之内,以避免与其他插件出现同名冲突。

注册 CSS 文件

要在资源系统中注册 CSS 文件,请在服务提供者的 boot 方法中使用 FilamentAsset::register() 方法。然后传递一个 Css,数组中的每个值对应于要注册到系统的 CSS 文件。

每个 Css 文件都有唯一的 ID 以及指向 CSS 文件的路径:

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 文件。为此,他们可能只需要将你的 vendor 目录添加到他们的 tailwind.config.js 文件的 content 数组中:

export default {
    content: [
        './resources/**/*.blade.php',
        './vendor/filament/**/*.blade.php',
        './vendor/danharrin/filament-blog/resources/views/**/*.blade.php', // Your plugin's vendor directory
    ],
    // ...
}

也就是说当他们编译他们自己的 Tailwind CSS 文件时,它将包含所有你插件视图中用到的实用(utility)类,以及他自己应用中和 Filament 核心中用到的实用类。

不过,使用这一技术,对于使用你插件的[面板构建器]用户来说,可能会有额外的复杂性。如果他们有自己的自定义主题,则没什么影响,因为他们无论如何都在使用 Tailwind CSS 构建自己的 CSS 文件。但是,如果他们使用的是面板生成器附带的默认样式表,你可能需要小心留意在插件视图中使用的实用类。比如,如果你使用了默认样式表中未曾使用过的实用类,而用户又没有自己编译,那么这些类就没有包含在最终的 CSS 文件中。也就是说你的插件视图外观可能不如预期那般展示。这是推荐在插件中编译和注册 Tailwind css 编译过的样式表的少数几种情况之一。

懒加载 CSS

默认情况下,使用资源系统注册的所有 CSS 文件都会在每一个 Filament 页面的 <head> 中加载。这是加载 CSS 文件的最简单方式,不过有时候这种方式可能偏于繁重,而且不是每个页面都需要它。这种情况下,你可以利用 Filament 中捆绑的 Alpine.js 懒加载资源包。它允许你使用 Alpine.js 按需加载 CSS 文件。引导很简单,你可以在元素上使用 x-load-css 指令,当该元素加载到页面上时,指定的 css 文件将加载到页面的 <head> 中。这对于需要 CSS 文件的小 UI 元素和整个页面来说都是完美的:

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

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

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() 方法:

<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 目录的样式表尤其有用。

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() 方法:

use Filament\Support\Facades\FilamentAsset;

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

现在,你可以在 CSS 文件中访问这些变量:

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

注册 JavaScript 文件

想要使用资源系统注册 JavaScript 文件,你可以在服务提供者的 boot() 方法中使用 FilamentAsset::register() 方法。然后传入一个 JS 对象数组,数组中的每个对象对应于你要在资源系统中注册的 JavaScript 文件。

每个 Js 文件都有唯一的 ID 以及指向 JavaScript 文件的路径:

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 懒加载资源包。它允许你使用 Alpine.js 按需加载 JavaScript 文件。引导很简单,你可以在元素上使用 x-load-js 指令,当该元素加载到页面上时,指定的 JavaScript 文件将加载到页面底部。这对于需要 JavaScript 文件的小 UI 元素和整个页面来说都是完美的:

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

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

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() 方法:

<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 文件:

npm install esbuild --save-dev

然后,你必须创建一个脚本来编译 JavaScript 和 Alpine 组件。你可以把它放在任何地方,例如 bin/build.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,如下:

// 从 NPM 导入外部 JavaScript 库

export default function testComponent({
    state,
}) {
    return {
        state,
        
        // 你可以在此处定义其他 Alpine.js 属性

        init() {
            // 根据需要在此处初始化 Alpine 组件
        },
        
        // 你可以在此处定义其他 Alpine.js 函数
    }
}

然后,运行如下命令,将该文件编译到 resources/js/dist/components/test-component.js

node bin/build.js

如果你想监控文件的变化,而非马上进行编译,可以使用如下命令:

node bin/build.js --dev

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

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 组件:

<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 属性来自于异步 Alpine包,该包的所有特性都可以在此处使用。

注册脚本数据

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

use Filament\Support\Facades\FilamentAsset;

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

现在,你可以使用 window.filamentData 对象,在运行时从 JavaScript 文件中访问这些数据:

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

使用 URL 注册 JavaScript 文件

如果你想注册一个基于 URL 的 JavaScript 文件,也是可行的。这些资源会像平常一样在每页中加载,而不会在运行 php artisan filament:assets 时复制到 /public 目录。这对于注册来自于 CDN 的外部脚本,或者已经直接编译到 /public 目录的脚本尤其有用:

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')),
]);
Edit on GitHub

Still need help? Join our Discord community or open a GitHub discussion

Previous
渲染钩子