注册静态资源
简介
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(),接受要注册的静态资源数组:
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 文件。为此,他们需要用 @source 指令把你的 vendor 目录加入自定义主题的 CSS 文件。在其自定义主题 CSS 文件(例如 resources/css/filament/admin/theme.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 元素和整页都适用:
<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 Lazy Load Assets 包,用 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 的文件:
// 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:
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 属性来自 Async 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\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 文件添加为入口:
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 编译静态资源:
npm run build最后,使用 Vite::asset() 解析带版本的 URL,并注册编译后的静态资源:
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:assets。php artisan filament:install 命令会把 @php artisan filament:upgrade 加入应用 composer.json 的 post-autoload-dump 脚本,每次 Composer dump autoloader 时都会为你运行 filament:assets 命令。