Vite

本指南介绍在 AdonisJS 中使用 Vite 进行前端资源打包。你将学习如何:

  • 安装并配置 Vite 集成
  • 定义入口点并在 Edge 模板中引用资源
  • 处理图片和字体等静态资源
  • 为前端代码配置 TypeScript
  • 启用 React 的热模块替换(HMR)
  • 将打包后的资源部署到 CDN

概述

Vite 是一个现代化的前端构建工具,提供快速的开发服务器启动和即时的热模块替换。AdonisJS 将 Vite 直接嵌入到开发服务器中,而不是作为一个独立进程运行。这种嵌入式方法意味着你在开发期间只需管理一个服务器,并且 AdonisJS 可以直接访问 Vite 的运行时 API 来实现服务端渲染等特性。

该集成处理了将 Vite 与后端框架连接起来的复杂性。在开发期间,AdonisJS 通过中间件将资源请求代理给 Vite。在生产环境中,AdonisJS 读取 Vite 生成的 manifest 文件来解析打包后资源的正确路径。

官方的 @adonisjs/vite 包提供了用于生成资源 URL 的 Edge 辅助函数和标签、一个简化配置的专用 Vite 插件,以及用于服务端渲染的 Vite Runtime API 访问能力。

另请参阅: Vite 文档

安装

运行以下命令安装并配置该包。这会同时安装 @adonisjs/vitevite,然后创建必要的配置文件。

node ace add @adonisjs/vite
查看配置命令执行的步骤
  1. adonisrc.ts 文件中注册以下服务提供者。
{
  providers: [
    // ...其他 providers
    () => import('@adonisjs/vite/vite_provider')
  ]
}
  1. 创建 vite.config.tsconfig/vite.ts 配置文件。
  2. resources/js/app.js 创建前端入口点文件。

安装后,将以下内容添加到你的 adonisrc.ts 文件中,以将 Vite 与构建过程集成。

adonisrc.ts
import { defineConfig } from '@adonisjs/core/build/standalone'

export default defineConfig({
  hooks: {
    buildStarting: [() => import('@adonisjs/vite/build_hook')],
  },
})

assetsBundler 属性会禁用 AdonisJS Assembler 中默认的资源打包器管理。而 hooks 属性注册 Vite 构建钩子,以便在你运行 node ace build 时执行 Vite 构建过程。

另请参阅: Assembler 钩子

配置

设置过程会创建两个配置文件。vite.config.ts 文件配置 Vite 打包器本身,而 config/vite.ts 则配置 AdonisJS 在后端如何与 Vite 交互。

Vite 配置

vite.config.ts 文件是一个标准的 Vite 配置文件。你可以根据项目需求在这里安装并注册额外的 Vite 插件。

AdonisJS 插件接受以下选项。

vite.config.ts
import { defineConfig } from 'vite'
import adonisjs from '@adonisjs/vite/client'

export default defineConfig({
  plugins: [
    adonisjs({
      /**
       * 前端代码的入口点文件。每个入口点
       * 会产生一个单独的输出 bundle。你可以为应用的
       * 不同部分定义多个入口点。
       */
      entrypoints: ['resources/js/app.js'],

      /**
       * 文件更改时触发浏览器重新加载的 glob 模式。
       * 适用于 Vite 不追踪的模板文件。
       */
      reload: ['resources/views/**/*.edge'],
    }),
  ]
})
OptionDescriptionDefault
entrypoints前端代码的入口点文件数组。每个入口点产生一个单独的 bundle。必填
buildDirectory输出目录的相对路径。作为 build.outDir 传给 Vite。public/assets
reload更改时触发浏览器重新加载的文件 glob 模式数组。[]
assetsUrl生产环境中资源链接的 URL 前缀。将资源部署到 CDN 时,将其设置为你的 CDN URL。/assets
Tip

如果你更改了 buildDirectory,你必须更新 config/vite.ts 中的相同值,以使两个配置保持同步。

AdonisJS 配置

config/vite.ts 文件告诉 AdonisJS 在哪里可以找到 Vite 的构建输出,以及如何生成资源 URL。

config/vite.ts
import { defineConfig } from '@adonisjs/vite'

export default defineConfig({
  /**
   * Vite 构建输出目录的路径。必须与
   * vite.config.ts 中的 buildDirectory 选项匹配。
   */
  buildDirectory: 'public/assets',

  /**
   * 资源链接的 URL 前缀。将资源部署到 CDN 时,
   * 在生产环境中将其设置为你的 CDN URL。
   */
  assetsUrl: '/assets',
})
OptionDescription
buildDirectoryVite 构建输出目录的路径。必须与 vite.config.ts 中的值匹配。
assetsUrl生产环境中资源链接的 URL 前缀。将资源部署到 CDN 时,将其设置为你的 CDN URL。
scriptAttributes键值对,用于添加到 @vite 标签生成的 script 标签上的属性。
styleAttributes键值对,用于添加到 @vite 标签生成的 link 标签上的属性。

你可以为生成的 script 和 link 标签添加自定义属性。

config/vite.ts
import { defineConfig } from '@adonisjs/vite'

export default defineConfig({
  buildDirectory: 'public/assets',
  assetsUrl: '/assets',
  scriptAttributes: {
    defer: true,
  },
})

对于基于所加载资源的、有条件的属性,请改为传入一个函数。

config/vite.ts
import { defineConfig } from '@adonisjs/vite'

export default defineConfig({
  buildDirectory: 'public/assets',
  assetsUrl: '/assets',
  styleAttributes: ({ src, url }) => {
    if (src === 'resources/css/admin.css') {
      return {
        'data-turbo-track': 'reload'
      }
    }
  }
})

文件夹结构

AdonisJS 不强制前端资源采用特定的文件夹结构。但是,我们建议将它们存放在 resources 目录中,并按资源类型划分子目录。

resources
├── css
│   └── app.css
├── js
│   └── app.js
├── fonts
└── images

Vite 默认将打包后的文件输出到 public/assets/assets 子目录将 Vite 的输出与 public 文件夹中可能不想让 Vite 处理的其他静态文件分隔开。

启动开发服务器

使用 --hmr 标志启动你的应用以启用热模块替换(Hot Module Replacement)。AdonisJS 会自动将资源请求代理给嵌入的 Vite 服务器。

node ace serve --hmr

热模块替换(HMR) 允许 Vite 在不重新加载整个页面的情况下更新浏览器中的模块。当你编辑 CSS 文件或 JavaScript 模块时,更改会立即出现,同时保留应用状态。

在模板中包含入口点

使用 @vite Edge 标签为你的入口点渲染 script 和 link 标签。该标签接受一个入口点路径数组,并生成相应的 HTML 标签。

resources/views/layouts/main.edge
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    @vite(['resources/js/app.js'])
</head>
<body>
    @!section('content')
</body>
</html>

我们建议将 CSS 文件导入到你的 JavaScript 入口点中,而不是将它们注册为单独的入口点。这种方式让 Vite 自动处理 CSS 处理和热替换。

resources/js/app.js
/**
 * 在你的 JavaScript 入口点中导入 CSS。Vite 会处理
 * CSS 并自动处理热替换。
 */
import '../css/app.css'

在模板中引用资源

Vite 会为你的入口点导入的文件构建依赖图,并在打包输出中更新它们的路径。但是,Vite 无法检测到仅在 Edge 模板中引用的资源,因为它不解析模板文件。

使用 asset 辅助函数为 Vite 处理的文件创建 URL。在开发期间,该辅助函数返回指向 Vite 开发服务器的 URL。在生产环境中,它返回带有文件名内容哈希的打包文件路径。

resources/views/pages/home.edge
<img src="{{ asset('resources/images/logo.png') }}" alt="Logo">
<!-- 开发环境中的输出 -->
<img src="http://localhost:5173/resources/images/logo.png" alt="Logo">
<!-- 生产环境中的输出 -->
<img src="/assets/logo-3bc29777.png" alt="Logo">

处理静态资源

Vite 会忽略未被前端代码导入的静态资源。仅在 Edge 模板中引用的图片、字体和图标就属于这一类。

要将这些资源包含到构建中,请在你的入口点文件中使用 Vite 的 glob 导入 API。这告诉 Vite 处理被匹配的文件,即使它们没有被直接导入。

resources/js/app.js
import '../css/app.css'

/**
 * 告诉 Vite 处理 resources/images 目录中的所有图片。
 * 如果没有这个,仅在模板中引用的图片会从
 * 生产构建中缺失。
 */
import.meta.glob(['../images/**'])

添加 glob 导入后,你可以在模板中使用 asset 辅助函数引用这些图片。

resources/views/pages/home.edge
<img src="{{ asset('resources/images/hero.jpg') }}" alt="Hero image">

TypeScript 配置

如果你将 TypeScript 用于前端代码,请在 resources 目录中创建一个单独的 tsconfig.json。Vite 和你的代码编辑器会将该配置用于 resources 目录中的 TypeScript 文件。

resources/tsconfig.json
{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "baseUrl": ".",
    "lib": ["DOM"],
    "paths": {
      "@/*": ["./js/*"]
    }
  }
}

如果你使用 React,请添加 jsx 选项。

resources/tsconfig.json
{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "baseUrl": ".",
    "lib": ["DOM"],
    "jsx": "preserve",
    "paths": {
      "@/*": ["./js/*"]
    }
  }
}

React 与热模块替换

要在开发期间启用 React Fast Refresh,请在布局中的 @vite 标签之前添加 @viteReactRefresh Edge 标签。

resources/views/layouts/main.edge
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    @viteReactRefresh()
    @vite(['resources/js/app.js'])
</head>
<body>
    <div id="root"></div>
</body>
</html>

然后在你的 Vite 配置中配置 React 插件。

vite.config.ts
import { defineConfig } from 'vite'
import adonisjs from '@adonisjs/vite/client'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    adonisjs({
      entrypoints: ['resources/js/app.js'],
    }),
    react(),
  ],
})

将资源部署到 CDN

要在生产环境中从 CDN 提供打包后的资源,请在两个配置文件中配置 assetsUrl 选项。这确保 manifest 文件中的 URL 和懒加载的 chunk 都指向你的 CDN 服务器。

vite.config.ts
import { defineConfig } from 'vite'
import adonisjs from '@adonisjs/vite/client'

export default defineConfig({
  plugins: [
    adonisjs({
      entrypoints: ['resources/js/app.js'],
      reload: ['resources/views/**/*.edge'],
      assetsUrl: 'https://cdn.example.com/',
    }),
  ]
})
config/vite.ts
import { defineConfig } from '@adonisjs/vite'

export default defineConfig({
  buildDirectory: 'public/assets',
  assetsUrl: 'https://cdn.example.com/',
})

使用 node ace build 构建应用后,将 public/assets 的内容上传到你的 CDN。

常见问题

资源在生产环境中未加载

如果资源在生产环境中加载失败,请验证 Vite 是否已在 public/assets/.vite/manifest.json 生成 manifest 文件。该 manifest 将源文件映射到它们的打包输出路径。

如果 manifest 缺失,请确保构建钩子已在 adonisrc.ts 中注册,并再次运行 node ace build

静态图片从构建中缺失

仅在模板中引用的图片和其他静态资源不会自动包含在构建中。Vite 只处理被你的 JavaScript 代码导入的文件。

向你的入口点添加一个 glob 导入以包含静态资源。

resources/js/app.js
import '../css/app.css'

// 将所有图片包含到构建中
import.meta.glob(['../images/**', '../fonts/**'])

HMR 不工作

热模块替换在启动开发服务器时需要 --hmr 标志。

node ace serve --hmr

如果 HMR 仍然不工作,请检查你的浏览器控制台是否有 WebSocket 连接错误。防火墙或代理配置可能会阻止 HMR 的 WebSocket 连接。

中间件模式

在 3.x 版本中,Vite 运行在中间件模式(middleware mode)下。AdonisJS 不是将 Vite 作为一个带自己服务器的独立进程启动,而是嵌入 Vite 并通过中间件代理匹配的请求。

中间件模式的优势包括:可以直接访问 Vite Runtime API 以进行服务端渲染,以及只需管理一个开发服务器。所有资源都通过 AdonisJS 提供,而不是通过独立的 Vite 进程。

另请参阅: Vite SSR 文档

Manifest 文件

当你为生产环境构建时,Vite 会在打包资源旁边生成一个 manifest 文件。该 manifest 是一个 JSON 文件,将源文件路径映射到它们的打包输出路径,包含内容哈希。

AdonisJS 读取此 manifest 来解析资源 URL。当你在生产环境中调用 asset 辅助函数或使用 @vite 标签时,AdonisJS 会在 manifest 中查找该文件并返回正确的打包路径。

manifest 文件默认位于 public/assets/.vite/manifest.json

另请参阅: Vite 后端集成