资源打包(Vite)


介绍

Vite 是一个现代化的前端构建工具,它提供了一个极快的开发环境,并将您的代码打包为生产准备的资产。在使用 Laravel 构建应用程序时,您通常会使用 Vite 将应用程序的 CSS 和 JavaScript 文件打包成生产就绪的资源。

Laravel 通过提供官方插件和 Blade 指令,与 Vite 无缝集成,以便在开发和生产中加载您的资产。

您正在运行 Laravel Mix 吗?Vite 已经在新的 Laravel 安装中替换了 Laravel Mix。有关 Mix 的文档,请访问 Laravel Mix 网站。如果您想切换到 Vite,请参阅我们的迁移指南

在 Vite 和 Laravel Mix 之间选择

在使用 Vite 前,新的 Laravel 应用程序在打包资产时使用了 Mix,它由 webpack 驱动。Vite 的重点是提供一个更快、更高效的开发体验,用于构建富 JavaScript 应用程序。如果你正在开发一个单页面应用程序(包括使用 Inertia 等工具开发的应用程序),那么 Vite 将是最佳选择。

Vite 也适用于传统的服务器端渲染应用程序,包括使用 Livewire 的 JavaScript "sprinkles" 应用程序。然而,它缺少一些 Laravel Mix 支持的功能,比如复制任意资产到构建中,即使这些资产没有直接在你的 JavaScript 应用程序中引用。

回归 Mix

如果你使用我们的 Vite 脚手架开始了一个新的 Laravel 应用程序,但需要回归到 Laravel Mix 和 webpack,那么没有问题。请参考我们的官方指南,了解从 Vite 迁移到 Mix 的方法。

安装与设置

以下文档讨论如何手动安装和配置 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 文件。默认的 package.json 文件已经包含了您需要开始使用 Vite 和 Laravel 插件的所有内容。您可以通过 NPM 安装应用程序的前端依赖项:

npm install

配置 Vite

Vite 的配置文件位于项目根目录下的 vite.config.js。您可以根据自己的需要自定义此文件,并安装其他插件,例如 @vitejs/plugin-vue@vitejs/plugin-react

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 构建的应用程序,最好不要使用 CSS 入口点来使用 Vite:

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

相反,您应该通过 JavaScript 导入 CSS。通常,这应该在应用程序的 resources/js/app.js 文件中完成:

import './bootstrap';
import '../css/app.css'; 

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

使用安全的开发服务器进行开发

如果您的本地开发 Web 服务器通过 HTTPS 服务您的应用程序,则可能会遇到连接 Vite 开发服务器的问题。

如果您在使用 Laravel Valet 进行本地开发,并已针对应用程序运行了 secure 命令,则可以配置 Vite 开发服务器自动使用 Valet 生成的 TLS 证书:

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

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

当使用另一个 Web 服务器时,您应该生成受信任的证书并手动配置 Vite 使用生成的证书:

image-20230223001757295

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

加载脚本和样式

有了 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 客户端以启用热模块替换。在构建模式下,该指令将加载您编译和版本化的资产,包括任何导入的 CSS。

如果需要,您还可以在调用 @vite 指令时指定编译后的资产构建路径:

<!doctype html>
<head>
    {{-- 给定的构建路径相对于 public 路径 --}}
 
    @vite('resources/js/app.js', 'vendor/courier/build')
</head>

运行 Vite

有两种运行 Vite 的方式。你可以通过 dev 命令运行开发服务器,这在本地开发时非常有用。开发服务器将自动检测文件的更改,并立即在任何打开的浏览器窗口中反映这些更改。

或者,运行 build 命令将版本化并捆绑你应用的资源并准备好部署到生产环境中:

# Run the Vite development server...
npm run dev
 
# Build and version the assets for production...
npm run build

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 配置文件中包含插件。使用 Vue 插件与 Laravel 时,需要一些其他选项:

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: {
                    // The Vue plugin will re-write asset URLs, when referenced
                    // in Single File Components, to point to the Laravel web
                    // server. Setting this to `null` allows the Laravel plugin
                    // to instead re-write asset URLs to point to the Vite
                    // server instead.
                    base: null,
 
                    // The Vue plugin will parse absolute URLs and treat them
                    // as absolute paths to files on disk. Setting this to
                    // `false` will leave absolute URLs un-touched so they can
                    // reference assets in the public directory as expected.
                    includeAbsolute: false,
                },
            },
        }),
    ],
});

Laravel 的入门套件已经包含了正确的 Laravel、Vue 和 Vite 配置。快速开始使用 Laravel、Vue 和 Vite,请查看 Laravel Breeze

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 Breeze 以获得使用 Laravel、React 和 Vite 的最快捷方式。

Inertia

Laravel Vite 插件提供了一个方便的 resolvePageComponent 函数,以帮助您解决 Inertia 页面组件。以下是使用 Vue 3 的助手示例,但您也可以在其他框架中使用该函数,例如 React:

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 }) {
    return createApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el)
  },
});

Laravel 的入门套件已经包含了适当的 Laravel、React 和 Vite 配置。快速查看 Laravel Breeze 以获得使用 Laravel、React 和 Vite 的最快捷方式。

URL 处理

在使用 Vite 引用应用程序的 HTML、CSS 或 JS 的资源时,需要注意一些细节。首先,如果您使用绝对路径引用资源,Vite 将不会将资源包含在构建中;因此,您应确保资源在公共目录中可用。

当引用相对资源路径时,应记住这些路径是相对于引用它们的文件的。任何通过相对路径引用的资源将由 Vite 重新编写、打包并进行版本控制。

考虑以下项目结构:

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

以下示例演示了 Vite 如何处理相对和绝对 URL:

<!-- This asset is not handled by Vite and will not be included in the build -->
<img src="/taylor.png">
 
<!-- This asset will be re-written, versioned, and bundled by Vite -->
<img src="../../images/abigail.png">

样式表处理

您可以在 Vite 文档中了解有关 Vite CSS 支持的更多信息。如果您正在使用 PostCSS 插件,例如 Tailwind,可以在项目根目录下创建一个 postcss.config.js 文件,Vite 将自动应用它:

module.exports = {
    plugins: {
        tailwindcss: {},
        autoprefixer: {},
    },
};

Laravel 的入门套件包已经包含了正确的 Tailwind、PostCSS 和 Vite 配置。或者,如果您想使用 Tailwind 和 Laravel 而不使用我们的启动工具包,请查看 Laravel 的 Tailwind 安装指南

使用 Blade 和路由

使用 Vite 处理静态资源

在 JavaScript 或 CSS 中引用资源时,Vite 会自动处理和版本化这些资源。此外,在构建基于 Blade 的应用程序时,Vite 还可以处理和版本化您仅在 Blade 模板中引用的静态资源。

但是,为了实现这一点,您需要通过将静态资源导入应用程序入口点,使 Vite 意识到这些资源。例如,如果要处理并对存储在 resources/images 中的所有图像和存储在 resources/fonts 中的所有字体进行版本化,应该在应用程序的 resources/js/app.js 入口点中添加以下内容:

import.meta.glob([
  '../images/**',
  '../fonts/**',
]);

这些资产现在将在运行 npm run build 时由 Vite 处理。然后,您可以使用 Vite::asset 方法在 Blade 模板中引用这些资产,该方法将返回给定资产的版本化 URL:

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

保存时刷新

当您使用传统的服务器端渲染 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/View/Components/**
  • lang/**
  • resources/lang/**
  • resources/views/**
  • routes/**

观看 routes/** 目录对于在应用程序前端中利用 Ziggy 生成路由链接非常有用。

如果这些默认路径不适合您的需求,您可以指定自己的路径列表:

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 }
            }],
        }),
    ],
});

别名

在 JavaScript 应用程序中常常会创建别名用于引用常用的目录。您也可以使用 Illuminate\Support\Facades\Vite 类上的宏方法创建 Blade 中使用的别名。通常,"宏" 应该在服务提供程序boot 方法中定义:

/**
 * Bootstrap any application services.
 */
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">

自定义基本 URL

如果 Vite 编译的资产部署在与应用程序不同的域中,例如通过 CDN,必须在应用程序的 .env 文件中指定 ASSET_URL 环境变量:

ASSET_URL=https://cdn.example.com

配置资产 URL 后,所有重写的 URL 将以配置的值作为前缀:

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

请记住,Vite 不会重写绝对 URL,因此它们不会被前缀。

环境变量

您可以通过在应用程序的 .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 类的测试:

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
{
    use CreatesApplication;
 
    protected function setUp(): void
    {
        parent::setUp();
 
        $this->withoutVite();
    }
}

服务器端渲染 (SSR)

Laravel Vite 插件使得设置与 Vite 相关的服务器端渲染变得轻而易举。要开始,可以创建一个 SSR 入口点 resources/js/ssr.js,然后通过在 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 版本:

image-20230223003838101

然后,为了构建和启动 SSR 服务器,可以运行以下命令:

npm run build
node bootstrap/ssr/ssr.mjs

Laravel 的入门套件已经包含了适当的 Laravel、Inertia SSR 和 Vite 配置。如果想快速开始使用 Laravel、Inertia SSR 和 Vite,请查看 Laravel Breeze

脚本和样式标签属性

内容安全策略(CSP)Nonce

如果您希望在脚本和样式标记中包含一个 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
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next, string $role): Response
    {
        Vite::useCspNonce();
 
        return $next($request)->withHeaders([
            'Content-Security-Policy' => "script-src 'nonce-".Vite::cspNonce()."'",
        ]);
    }
}

调用 useCspNonce 方法后,Laravel 将自动在所有生成的脚本和样式标记上包含 nonce 属性。

如果您需要在其他地方指定 nonce,包括 Laravel 入门套件中包含的 Ziggy @route 指令,可以使用 cspNonce 方法检索它:

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

如果您已经有一个 nonce,您可以通过将 nonce 传递给 useCspNonce 方法来指示 Laravel 使用它:

Vite::useCspNonce($nonce);

子资源完整性 (SRI)

如果您的 Vite 清单包括用于资产的完整性哈希,则 Laravel 将自动在其生成的任何脚本和样式标记上添加 integrity 属性,以强制执行子资源完整性。默认情况下,Vite 在其清单中不包含完整性哈希,但是您可以通过安装 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';
 
export default defineConfig({
    plugins: [
        laravel({
            // ...
        }),
        manifestSRI(),
    ],
});

如果需要,您还可以自定义清单密钥以查找完整性哈希:

use Illuminate\Support\Facades\Vite;
 
Vite::useIntegrityKey('custom-integrity-key');

如果您想完全禁用此自动检测,则可以将 false 传递给 useIntegrityKey 方法:

Vite::useIntegrityKey(false);

任意属性

如果您需要在 scriptstyle 标签上包含额外的属性,比如 data-turbo-track 属性,您可以通过 useScriptTagAttributesuseStyleTagAttributes 方法指定它们。通常,这些方法应该在服务提供者中被调用:

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']) // 指定入口点...
    }}
</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', // 自定义清单文件名...
    },
});

更正开发服务器的 URL

Vite 生态系统中的某些插件假定以斜杠开头的 URL 总是指向 Vite 开发服务器。然而,由于 Laravel 集成的性质,这并不是事实。

例如,当 Vite 提供您的资产时,vite-imagetools 插件会输出以下 URL:

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

vite-imagetools 插件期望输出 URL 将被 Vite 拦截,并且插件随后可以处理以 /@imagetools 开头的所有 URL。如果您使用的是预期此行为的插件,则需要手动更正 URL。您可以在 vite.config.js 文件中使用 transformOnServe 选项来执行此操作。

在此示例中,我们将在生成的代码中的所有 /@imagetools 出现处附加开发服务器 URL:

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 提供资产时,它将输出指向 Vite 开发服务器的 URL:

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

点赞 取消点赞 收藏 取消收藏

<< 上一篇: Blade 模板

>> 下一篇: URL 生成