资源打包

简介

Vite 是一个现代前端构建工具,提供极快的开发环境,并为生产环境打包你的代码。使用 Laravel 构建应用时,通常会用 Vite 把应用的 CSS 和 JavaScript 文件打包成生产可用的资源。

Laravel 通过官方插件和 Blade 指令与 Vite 无缝集成,帮你为开发和生产环境加载资源。

安装与配置

信息

以下文档介绍如何手动安装和配置 Laravel Vite 插件。不过,Laravel 的起步套件已经包含了所有这些脚手架,是上手 Laravel 和 Vite 最快的方式。

安装 Node

运行 Vite 和 Laravel 插件之前,确保已安装 Node.js(16+)和 NPM:

node -v
npm -v

可以从 Node 官网下载图形化安装程序安装最新版 Node 和 NPM。如果使用 Laravel Sail,可以通过 Sail 调用 Node 和 NPM:

./vendor/bin/sail node -v
./vendor/bin/sail npm -v

安装 Vite 和 Laravel 插件

全新安装的 Laravel 项目根目录下有一个 package.json 文件,默认已经包含了使用 Vite 和 Laravel 插件所需的一切。通过 NPM 安装前端依赖:

npm install

配置 Vite

Vite 通过项目根目录的 vite.config.js 文件配置。你可以根据需要自定义这个文件,也可以安装应用所需的其他插件,比如 @vitejs/plugin-react、@sveltejs/vite-plugin-svelte 或 @vitejs/plugin-vue。

Laravel Vite 插件要求你指定应用的入口文件,可以是 JavaScript 或 CSS 文件,也包括 TypeScript、JSX、TSX、Sass 等预处理语言:

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

export default defineConfig({
    plugins: [
        laravel([
            'resources/css/app.css',
            'resources/js/app.js',
        ]),
    ],
});

如果你在构建 SPA(包括用 Inertia 构建的应用),Vite 在没有 CSS 入口文件时效果最好:

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

export default defineConfig({
    plugins: [
        laravel([
            'resources/css/app.css', // [tl! remove]
            'resources/js/app.js',
        ]),
    ],
});

你应该通过 JavaScript 导入 CSS,通常在 resources/js/app.js 文件中:

import './bootstrap';
import '../css/app.css'; // [tl! add]

Laravel 插件还支持多个入口文件和高级配置选项,比如 SSR 入口文件。

使用 HTTPS 开发服务器

如果本地开发服务器通过 HTTPS 提供应用,连接 Vite 开发服务器时可能会遇到问题。

如果你使用 Laravel Herd 并已为站点启用 HTTPS,或使用 Laravel Valet 并对应用执行了 secure 命令,Laravel Vite 插件会自动检测并使用生成的 TLS 证书。

如果你使用的主机名与应用目录名不匹配,可以在 vite.config.js 中手动指定主机:

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

export default defineConfig({
    plugins: [
        laravel({
            // ...
            detectTls: 'my-app.test', // [tl! add]
        }),
    ],
});

使用其他 Web 服务器时,你需要生成受信任的证书并手动配置 Vite 使用该证书:

// ...
import fs from 'fs'; // [tl! add]

const host = 'my-app.test'; // [tl! add]

export default defineConfig({
    // ...
    server: { // [tl! add]
        host, // [tl! add]
        hmr: { host }, // [tl! add]
        https: { // [tl! add]
            key: fs.readFileSync(`/path/to/${host}.key`), // [tl! add]
            cert: fs.readFileSync(`/path/to/${host}.crt`), // [tl! add]
        }, // [tl! add]
    }, // [tl! add]
});

如果无法为系统生成受信任的证书,可以安装和配置 @vitejs/plugin-basic-ssl 插件。使用不受信任的证书时,运行 npm run dev 后需要在浏览器中通过控制台的 "Local" 链接接受 Vite 开发服务器的证书警告。

在 WSL2 上通过 Sail 运行开发服务器

在 Windows Subsystem for Linux 2(WSL2)上通过 Laravel Sail 运行 Vite 开发服务器时,需要在 vite.config.js 中添加以下配置,确保浏览器能与开发服务器通信:

// ...

export default defineConfig({
    // ...
    server: { // [tl! add:start]
        hmr: {
            host: 'localhost',
        },
    }, // [tl! add:end]
});

如果开发服务器运行时文件更改没有反映到浏览器中,你可能还需要配置 Vite 的 server.watch.usePolling 选项。

加载脚本和样式

配置好 Vite 入口文件后,在应用根模板的 <head> 中添加 @vite() Blade 指令来引用它们:

<!DOCTYPE html>
<head>
    {{-- ... --}}

    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>

如果你通过 JavaScript 导入 CSS,只需包含 JavaScript 入口文件:

<!DOCTYPE html>
<head>
    {{-- ... --}}

    @vite('resources/js/app.js')
</head>

@vite 指令会自动检测 Vite 开发服务器并注入 Vite 客户端以启用热模块替换(HMR)。在构建模式下,指令会加载编译并版本化后的资源,包括所有导入的 CSS。

需要时,你还可以在调用 @vite 指令时指定编译资源的构建路径:

<!doctype html>
<head>
    {{-- 给定的构建路径相对于 public 路径。 --}}

    @vite('resources/js/app.js', 'vendor/courier/build')
</head>

内联资源

有时可能需要直接输出资源的原始内容,而不是链接到版本化后的 URL。比如把 HTML 内容传给 PDF 生成器时,可能需要直接把资源内容嵌入页面。用 Vite 门面的 content() 方法输出 Vite 资源的内容:

@use('Illuminate\Support\Facades\Vite')

<!doctype html>
<head>
    {{-- ... --}}

    <style>
        {!! Vite::content('resources/css/app.css') !!}
    </style>
    <script>
        {!! Vite::content('resources/js/app.js') !!}
    </script>
</head>

运行 Vite

运行 Vite 有两种方式。通过 dev 命令运行开发服务器,适合本地开发。开发服务器会自动检测文件变化并即时反映到所有打开的浏览器窗口中。

或者,运行 build 命令会对应用资源进行版本化和打包,准备好部署到生产环境:

# 运行 Vite 开发服务器...
npm run dev

# 构建并版本化生产资源...
npm run build

如果你在 WSL2 上通过 Sail 运行开发服务器,可能需要一些额外配置。

使用 JavaScript

别名

默认情况下,Laravel 插件提供了一个常用别名,方便你快速导入应用资源:

{
    '@' => '/resources/js'
}

你可以在 vite.config.js 中覆盖 '@' 别名:

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

export default defineConfig({
    plugins: [
        laravel(['resources/ts/app.tsx']),
    ],
    resolve: {
        alias: {
            '@': '/resources/ts',
        },
    },
});

Vue

如果你想用 Vue 框架构建前端,需要安装 @vitejs/plugin-vue 插件:

npm install --save-dev @vitejs/plugin-vue

然后在 vite.config.js 中引入该插件。在 Laravel 中使用 Vue 插件时还需要一些额外配置:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
    plugins: [
        laravel(['resources/js/app.js']),
        vue({
            template: {
                transformAssetUrls: {
                    // Vue 插件会重写单文件组件中引用的资源 URL,
                    // 指向 Laravel Web 服务器。设为 null 可让 Laravel
                    // 插件改为重写资源 URL 指向 Vite 服务器。
                    base: null,

                    // Vue 插件会解析绝对 URL 并视为磁盘文件的绝对路径。
                    // 设为 false 会保留绝对 URL 不变,使其能按预期引用
                    // public 目录中的资源。
                    includeAbsolute: false,
                },
            },
        }),
    ],
});

信息

Laravel 的起步套件已包含正确的 Laravel、Vue 和 Vite 配置,是上手 Laravel + Vue + Vite 最快的方式。

React

如果你想用 React 框架构建前端,需要安装 @vitejs/plugin-react 插件:

npm install --save-dev @vitejs/plugin-react

然后在 vite.config.js 中引入该插件:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import react from '@vitejs/plugin-react';

export default defineConfig({
    plugins: [
        laravel(['resources/js/app.jsx']),
        react(),
    ],
});

确保包含 JSX 的文件使用 .jsx 或 .tsx 扩展名,必要时更新入口文件,如上面所示。

你还需要在现有的 @vite 指令旁加上 @viteReactRefresh Blade 指令:

@viteReactRefresh
@vite('resources/js/app.jsx')

@viteReactRefresh 指令必须在 @vite 指令之前调用。

信息

Laravel 的起步套件已包含正确的 Laravel、React 和 Vite 配置,是上手 Laravel + React + Vite 最快的方式。

Svelte

如果你想用 Svelte 框架构建前端,需要安装 @sveltejs/vite-plugin-svelte 插件:

npm install --save-dev @sveltejs/vite-plugin-svelte

然后在 vite.config.js 中引入该插件:

import { svelte } from '@sveltejs/vite-plugin-svelte';
import laravel from 'laravel-vite-plugin';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    laravel({
      input: ['resources/js/app.ts'],
      ssr: 'resources/js/ssr.ts',
      refresh: true,
    }),
    svelte(),
  ],
});

信息

Laravel 的起步套件已包含正确的 Laravel、Svelte 和 Vite 配置,是上手 Laravel + Svelte + Vite 最快的方式。

Inertia

Laravel Vite 插件提供了便捷的 resolvePageComponent 函数,帮你解析 Inertia 页面组件。下面是 Vue 3 的使用示例,也可以在 React 或 Svelte 等其他框架中使用:

import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';

createInertiaApp({
  resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el)
  },
});

如果你在 Inertia 中使用 Vite 的代码分割功能,建议配置资源预加载。

信息

Laravel 的起步套件已包含正确的 Laravel、Inertia 和 Vite 配置,是上手 Laravel + Inertia + Vite 最快的方式。

URL 处理

使用 Vite 时,在应用的 HTML、CSS 或 JS 中引用资源有几个注意事项。首先,如果你用绝对路径引用资源,Vite 不会将其纳入构建,因此要确保该资源在 public 目录中可用。使用专用 CSS 入口文件时应避免使用绝对路径,因为在开发期间浏览器会尝试从 Vite 开发服务器(CSS 所在位置)加载这些路径,而不是从 public 目录。

引用相对路径时,路径是相对于引用它的文件。通过相对路径引用的资源会被 Vite 重写、版本化和打包。

看下面的项目结构:

public/
  taylor.png
resources/
  js/
    Pages/
      Welcome.vue
  images/
    abigail.png

下面的例子展示了 Vite 如何处理相对和绝对 URL:

<!-- 这个资源不被 Vite 处理,不会包含在构建中 -->
<img src="/taylor.png">

<!-- 这个资源会被 Vite 重写、版本化和打包 -->
<img src="../../images/abigail.png">

使用样式表

信息

Laravel 的起步套件已包含正确的 Tailwind 和 Vite 配置。如果你想在不用起步套件的情况下使用 Tailwind 和 Laravel,请查看 Tailwind 的 Laravel 安装指南。

所有 Laravel 应用已经包含 Tailwind 和配置好的 vite.config.js 文件。所以你只需启动 Vite 开发服务器或运行 dev Composer 命令,它会同时启动 Laravel 和 Vite 开发服务器:

composer run dev

应用的 CSS 放在 resources/css/app.css 文件中。

使用字体

Laravel Vite 插件可以为你的应用提供优化后的自托管字体。配置字体后,插件会解析请求的字体文件、将它们作为 Vite 资源输出、生成字体 CSS,并写入可供 Blade @fonts 指令使用的字体清单。

要配置字体,从 laravel-vite-plugin/fonts 导入一个或多个提供商辅助函数,并将它们添加到 Laravel 插件的 fonts 选项中:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import { google } from 'laravel-vite-plugin/fonts';

export default defineConfig({
    plugins: [
        laravel({
            input: 'resources/js/app.js',
            fonts: [
                google('Inter', {
                    alias: 'sans',
                    weights: [400, 500, 600, 700],
                    styles: ['normal', 'italic'],
                    subsets: ['latin'],
                    display: 'swap',
                    preload: [
                        { weight: 400 },
                        { weight: 700 },
                    ],
                    fallbacks: ['system-ui', 'sans-serif'],
                }),
            ],
        }),
    ],
});

这个例子中,Inter 字体通过 sans 别名可用。插件会生成 --font-sans CSS 变量和 .font-sans 工具类来应用生成的字体栈。

字体提供商

Laravel Vite 插件内置了 Google Fonts、Bunny Fonts、Fontsource 和本地字体的提供商辅助函数:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import { bunny, fontsource, google, local } from 'laravel-vite-plugin/fonts';

export default defineConfig({
    plugins: [
        laravel({
            input: 'resources/js/app.js',
            fonts: [
                google('Inter', { alias: 'sans' }),
                bunny('Figtree', { alias: 'body' }),
                fontsource('JetBrains Mono', { alias: 'mono' }),
                local('Brand Sans', {
                    alias: 'brand',
                    src: 'resources/fonts/brand-sans',
                }),
            ],
        }),
    ],
});

fontsource 提供商从已安装的 Fontsource 包中读取字体。默认情况下,包名由字体族名派生,如 @fontsource/jetbrains-mono。如果你的应用使用不同的包名,可以通过 package 选项指定。

本地字体

使用本地字体时,src 选项可以指向单个字体文件、目录或 glob 模式。插件会发现支持的字体文件并从文件名推断其粗细和样式:

local('Brand Sans', {
    alias: 'brand',
    src: 'resources/fonts/brand-sans/*.woff2',
})

如果你需要完全控制可用变体,可以用 variants 选项显式定义:

local('Brand Sans', {
    alias: 'brand',
    variants: [
        { src: 'resources/fonts/BrandSans-Regular.woff2', weight: 400 },
        { src: 'resources/fonts/BrandSans-Italic.woff2', weight: 400, style: 'italic' },
        { src: ['resources/fonts/BrandSans-Bold.woff2', 'resources/fonts/BrandSans-Bold.ttf'], weight: 700 },
    ],
})

字体选项

根据提供商不同,字体定义可以接受多个选项来自定义生成的字体 CSS:

  • alias 定义 Blade @fonts 指令使用的名称,默认为字体族名的 slug。
  • variable 定义生成的 CSS 变量,默认为 --font-{alias}。
  • weights 定义要解析的远程或 Fontsource 字体粗细,默认为 [400]。
  • styles 定义要解析的远程或 Fontsource 字体样式,默认为 ['normal']。
  • subsets 定义要解析的远程或 Fontsource 字体子集,默认为 ['latin']。
  • display 定义 font-display 值,默认为 swap。
  • preload 控制哪些 WOFF2 字体变体应预加载。可以是 true、false 或 { weight, style } 选择器数组。
  • fallbacks 定义应追加到生成字体栈中的额外回退字体。
  • optimizedFallbacks 尝试使用可选的 fontaine 包生成度量调整的回退字体,默认为 true。

优化回退需要 fontaine 包,默认未安装。如果你想生成度量调整的回退字体,需要将 fontaine 安装为开发依赖:

npm install --save-dev fontaine

如果未安装 fontaine 或无法读取字体文件,Laravel 会跳过该字体的优化回退,继续使用通过 fallbacks 选项配置的字体。

本地字体通过上述 src 或 variants 选项解析,不使用 weights、styles 和 subsets。

与 Blade 和路由配合

用 Vite 处理静态资源

在 JavaScript 或 CSS 中引用资源时,Vite 会自动处理和版本化它们。此外,在构建基于 Blade 的应用时,Vite 也能处理和版本化你只在 Blade 模板中引用的静态资源。

为此,你需要通过插件的 assets 选项让 Vite 知道这些资源。这个选项用于你想直接用 Vite::asset 引用的静态文件。如果你想让 Laravel 生成字体 CSS 和预加载链接,请使用 fonts 选项。

比如要处理和版本化 resources/images 中的所有图片和 resources/fonts 中的所有字体,在 Vite 配置中添加:

laravel({
    input: 'resources/js/app.js',
    assets: ['resources/images/**', 'resources/fonts/**'],
})

运行 npm run build 时这些资源会被 Vite 处理。然后在 Blade 模板中用 Vite::asset 方法引用,返回给定资源的版本化 URL:

<img src="{{ Vite::asset('resources/images/logo.png') }}">

信息

在 Laravel Vite 插件 3 版本之前,静态资源必须通过 import.meta.glob 在应用入口文件中导入。assets 选项是因为 Vite 8 的变化而引入的。

保存时自动刷新

当应用使用传统的 Blade 服务端渲染时,Vite 可以通过在你修改视图文件时自动刷新浏览器来改善开发体验。只需将 refresh 选项设为 true:

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

export default defineConfig({
    plugins: [
        laravel({
            // ...
            refresh: true,
        }),
    ],
});

refresh 选项为 true 时,运行 npm run dev 期间保存以下目录中的文件会触发浏览器整页刷新:

  • app/Livewire/**
  • app/View/Components/**
  • lang/**
  • resources/lang/**
  • resources/views/**
  • routes/**

如果你使用 Ziggy 在前端生成路由链接,监听 routes/** 目录很有用。

如果这些默认路径不适合你,可以指定自己的监听路径列表:

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

export default defineConfig({
    plugins: [
        laravel({
            // ...
            refresh: ['resources/views/**'],
        }),
    ],
});

底层 Laravel Vite 插件使用 vite-plugin-full-reload 包,提供了一些高级配置选项来微调此功能。如果需要这种级别的自定义,可以提供 config 定义:

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

export default defineConfig({
    plugins: [
        laravel({
            // ...
            refresh: [{
                paths: ['path/to/watch/**'],
                config: { delay: 300 }
            }],
        }),
    ],
});

Blade 别名

在 JavaScript 应用中创建别名来引用常用目录很常见。你也可以通过 Illuminate\Support\Facades\Vite 类的 macro() 方法为 Blade 创建别名。通常"宏"应在服务提供者的 boot() 方法中定义:

/**
 * 启动应用服务。
 */
public function boot(): void
{
    Vite::macro('image', fn (string $asset) => $this->asset("resources/images/{$asset}"));
}

定义好宏后,就可以在模板中调用。比如用上面定义的 image 宏引用 resources/images/logo.png:

<img src="{{ Vite::image('logo.png') }}" alt="Laravel Logo">

资源预加载

使用 Vite 的代码分割功能构建 SPA 时,所需资源会在每次页面导航时获取。这个行为可能导致 UI 渲染延迟。如果你的前端框架受此困扰,Laravel 提供了在初始页面加载时预先获取应用 JavaScript 和 CSS 资源的能力。

在服务提供者的 boot() 方法中调用 Vite::prefetch() 方法来指示 Laravel 预加载资源:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Vite;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 注册应用服务。
     */
    public function register(): void
    {
        // ...
    }

    /**
     * 启动应用服务。
     */
    public function boot(): void
    {
        Vite::prefetch(concurrency: 3);
    }
}

上面的例子中,每次页面加载时会以最多 3 个并发下载来预加载资源。你可以根据应用需要调整并发数,或不指定并发限制让应用一次下载所有资源:

/**
 * 启动应用服务。
 */
public function boot(): void
{
    Vite::prefetch();
}

默认情况下,预加载在页面 load 事件触发时开始。如果你想自定义预加载开始的时机,可以指定 Vite 监听的事件:

/**
 * 启动应用服务。
 */
public function boot(): void
{
    Vite::prefetch(event: 'vite:prefetch');
}

上面的代码中,预加载会在你手动在 window 对象上触发 vite:prefetch 事件时开始。比如让预加载在页面加载 3 秒后开始:

<script>
    addEventListener('load', () => setTimeout(() => {
        dispatchEvent(new Event('vite:prefetch'))
    }, 3000))
</script>

自定义 Base URL

如果你的 Vite 编译资源部署在与应用不同的域名(如通过 CDN),必须在 .env 文件中指定 ASSET_URL 环境变量:

ASSET_URL=https://cdn.example.com

配置好资源 URL 后,所有重写后的资源 URL 都会以配置的值为前缀:

https://cdn.example.com/build/assets/app.9dce8d17.js

注意,绝对 URL 不会被 Vite 重写,所以不会被加前缀。

环境变量

在 .env 文件中以 VITE_ 前缀的环境变量可以注入到 JavaScript 中:

VITE_SENTRY_DSN_PUBLIC=http://example.com

通过 import.meta.env 对象访问注入的环境变量:

import.meta.env.VITE_SENTRY_DSN_PUBLIC

在测试中禁用 Vite

Laravel 的 Vite 集成在运行测试时会尝试解析资源,这要求你运行 Vite 开发服务器或构建资源。

如果你想在测试中模拟 Vite,可以调用 withoutVite() 方法,该方法适用于所有继承 Laravel TestCase 类的测试:

test('without vite example', function () {
    $this->withoutVite();

    // ...
});
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_without_vite_example(): void
    {
        $this->withoutVite();

        // ...
    }
}

如果你想为所有测试禁用 Vite,可以在基础 TestCase 类的 setUp() 方法中调用 withoutVite():

<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    protected function setUp(): void// [tl! add:start]
    {
        parent::setUp();

        $this->withoutVite();
    }// [tl! add:end]
}

服务端渲染(SSR)

Laravel Vite 插件让使用 Vite 设置服务端渲染变得轻松。在 resources/js/ssr.js 创建 SSR 入口文件,并通过配置选项传给 Laravel 插件:

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

export default defineConfig({
    plugins: [
        laravel({
            input: 'resources/js/app.js',
            ssr: 'resources/js/ssr.js',
        }),
    ],
});

为确保不忘记重新构建 SSR 入口文件,建议修改 package.json 中的 "build" 脚本来创建 SSR 构建:

"scripts": {
     "dev": "vite",
     "build": "vite build" // [tl! remove]
     "build": "vite build && vite build --ssr" // [tl! add]
}

然后构建并启动 SSR 服务器:

npm run build
node bootstrap/ssr/ssr.js

如果你使用 Inertia 的 SSR,可以用 inertia:start-ssr Artisan 命令启动 SSR 服务器:

php artisan inertia:start-ssr

信息

Laravel 的起步套件已包含正确的 Laravel、Inertia SSR 和 Vite 配置,是上手 Laravel + Inertia SSR + Vite 最快的方式。

Script 和 Style 标签属性

内容安全策略(CSP)Nonce

如果你想在 script 和 style 标签上包含 nonce 属性作为内容安全策略的一部分,可以在自定义中间件中用 useCspNonce() 方法生成或指定 nonce:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Vite;
use Symfony\Component\HttpFoundation\Response;

class AddContentSecurityPolicyHeaders
{
    /**
     * 处理传入请求。
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        Vite::useCspNonce();

        return $next($request)->withHeaders([
            'Content-Security-Policy' => "script-src 'nonce-".Vite::cspNonce()."'",
        ]);
    }
}

调用 useCspNonce() 后,Laravel 会自动在所有生成的 script 和 style 标签上包含 nonce 属性。

如果你需要在其他地方指定 nonce(包括 Laravel 起步套件中包含的 Ziggy @route 指令),可以用 cspNonce() 方法获取:

@routes(nonce: Vite::cspNonce())

如果你已有 nonce 想让 Laravel 使用,可以传给 useCspNonce() 方法:

Vite::useCspNonce($nonce);

子资源完整性(SRI)

如果你的 Vite 清单包含资源的 integrity 哈希,Laravel 会自动在生成的 script 和 style 标签上添加 integrity 属性以强制执行子资源完整性。默认情况下 Vite 不在清单中包含 integrity 哈希,但你可以通过安装 vite-plugin-manifest-sri NPM 插件来启用:

npm install --save-dev vite-plugin-manifest-sri

然后在 vite.config.js 中启用该插件:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import manifestSRI from 'vite-plugin-manifest-sri';// [tl! add]

export default defineConfig({
    plugins: [
        laravel({
            // ...
        }),
        manifestSRI(),// [tl! add]
    ],
});

如果需要,你还可以自定义完整性哈希所在的清单键:

use Illuminate\Support\Facades\Vite;

Vite::useIntegrityKey('custom-integrity-key');

如果想完全禁用此自动检测,可以给 useIntegrityKey() 传 false:

Vite::useIntegrityKey(false);

任意属性

如果你需要在 script 和 style 标签上包含额外属性(如 data-turbo-track 属性),可以通过 useScriptTagAttributes() 和 useStyleTagAttributes() 方法指定。通常应从服务提供者中调用:

use Illuminate\Support\Facades\Vite;

Vite::useScriptTagAttributes([
    'data-turbo-track' => 'reload', // 指定属性值...
    'async' => true, // 指定无值属性...
    'integrity' => false, // 排除本应包含的属性...
]);

Vite::useStyleTagAttributes([
    'data-turbo-track' => 'reload',
]);

如果需要条件性添加属性,可以传一个回调,接收资源源路径、URL、清单分块和整个清单:

use Illuminate\Support\Facades\Vite;

Vite::useScriptTagAttributes(fn (string $src, string $url, array|null $chunk, array|null $manifest) => [
    'data-turbo-track' => $src === 'resources/js/app.js' ? 'reload' : false,
]);

Vite::useStyleTagAttributes(fn (string $src, string $url, array|null $chunk, array|null $manifest) => [
    'data-turbo-track' => $chunk && $chunk['isEntry'] ? 'reload' : false,
]);

注意

Vite 开发服务器运行期间,$chunk 和 $manifest 参数将为 null。

高级自定义

开箱即用的 Laravel Vite 插件使用合理的约定,适用于大多数应用。但有时你可能需要自定义 Vite 的行为。为了启用更多自定义选项,我们提供了以下方法和选项,可用来替代 @vite Blade 指令:

<!doctype html>
<head>
    {{-- ... --}}

    {{
        Vite::useHotFile(storage_path('vite.hot')) // 自定义 "hot" 文件...
            ->useBuildDirectory('bundle') // 自定义构建目录...
            ->useManifestFilename('assets.json') // 自定义清单文件名...
            ->withEntryPoints(['resources/js/app.js']) // 指定入口文件...
            ->createAssetPathsUsing(function (string $path, ?bool $secure) { // 自定义构建资源的后端路径生成...
                return "https://cdn.example.com/{$path}";
            })
    }}
</head>

然后在 vite.config.js 中指定相同的配置:

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

export default defineConfig({
    plugins: [
        laravel({
            hotFile: 'storage/vite.hot', // 自定义 "hot" 文件...
            buildDirectory: 'bundle', // 自定义构建目录...
            input: ['resources/js/app.js'], // 指定入口文件...
        }),
    ],
    build: {
      manifest: 'assets.json', // 自定义清单文件名...
    },
});

开发服务器跨域资源共享(CORS)

如果从 Vite 开发服务器获取资源时浏览器遇到跨域资源共享(CORS)问题,你可能需要为自定义源授予开发服务器访问权限。Vite 结合 Laravel 插件允许以下来源,无需额外配置:

  • ::1
  • 127.0.0.1
  • localhost
  • *.test
  • *.localhost
  • 项目 .env 中的 APP_URL

为项目允许自定义源最简单的方式是确保应用的 APP_URL 环境变量与浏览器中访问的源匹配。比如你访问 https://my-app.laravel,应更新 .env:

APP_URL=https://my-app.laravel

如果你需要对源进行更精细的控制(如支持多个源),应使用 Vite 内置的全面灵活的 CORS 服务器配置。比如在 vite.config.js 的 server.cors.origin 配置选项中指定多个源:

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

export default defineConfig({
    plugins: [
        laravel({
            input: 'resources/js/app.js',
            refresh: true,
        }),
    ],
    server: {  // [tl! add]
        cors: {  // [tl! add]
            origin: [  // [tl! add]
                'https://backend.laravel',  // [tl! add]
                'http://admin.laravel:8566',  // [tl! add]
            ],  // [tl! add]
        },  // [tl! add]
    },  // [tl! add]
});

你还可以包含正则模式,这对于允许给定顶级域名的所有源很有用,比如 *.laravel:

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

export default defineConfig({
    plugins: [
        laravel({
            input: 'resources/js/app.js',
            refresh: true,
        }),
    ],
    server: {  // [tl! add]
        cors: {  // [tl! add]
            origin: [ // [tl! add]
                // 支持: SCHEME://DOMAIN.laravel[:PORT] [tl! add]
                /^https?:\/\/.*\.laravel(:\d+)?$/, //[tl! add]
            ], // [tl! add]
        }, // [tl! add]
    }, // [tl! add]
});

修正开发服务器 URL

Vite 生态中有些插件假设以斜杠开头的 URL 总是指向 Vite 开发服务器。但由于 Laravel 集成的特性,情况并非如此。

比如 vite-imagetools 插件在 Vite 提供资源时输出如下 URL:

<img src="/@imagetools/f0b2f404b13f052c604e632f2fb60381bf61a520">

vite-imagetools 插件期望输出 URL 被 Vite 拦截,然后插件处理所有以 /@imagetools 开头的 URL。如果你使用有这种行为的插件,需要手动修正 URL。在 vite.config.js 中使用 transformOnServe 选项:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import { imagetools } from 'vite-imagetools';

export default defineConfig({
    plugins: [
        laravel({
            // ...
            transformOnServe: (code, devServerUrl) => code.replaceAll('/@imagetools', devServerUrl+'/@imagetools'),
        }),
        imagetools(),
    ],
});

现在 Vite 提供资源时,输出的 URL 会指向 Vite 开发服务器:

- <img src="/@imagetools/f0b2f404b13f052c604e632f2fb60381bf61a520"><!-- [tl! remove] -->
+ <img src="http://[::1]:5173/@imagetools/f0b2f404b13f052c604e632f2fb60381bf61a520"><!-- [tl! add] -->