Inertia
Inertia 是一种与框架无关的方式,可让你在无需现代 SPA 大部分复杂性的情况下创建单页应用。
它是传统的服务端渲染应用(使用模板引擎)与现代 SPA(带有客户端路由和状态管理)之间的一个很好的中间方案。
使用 Inertia 将允许你使用最喜欢的前端框架(Vue.js、React、Svelte 或 Solid.js)创建 SPA,而无需创建一个单独的 API。
import type { HttpContext } from '@adonisjs/core/http'
export default class UsersController {
async index({ inertia }: HttpContext) {
const users = await User.all()
return inertia.render('users/index', { users })
}
}
<script setup lang="ts">
import { Link, Head } from '@inertiajs/vue3'
defineProps<{
users: SerializedUser[]
}>()
</script>
<template>
<Head title="Users" />
<div v-for="user in users" :key="user.id">
<Link :href="`/users/${user.id}`">
{{ user.name }}
</Link>
<div>{{ user.email }}</div>
</div>
</template>
安装
你正在启动一个新项目并想使用 Inertia 吗?请查看 Inertia 启动套件。
从 npm 仓库运行以下命令安装该包:
npm i @adonisjs/inertia
完成后,运行以下命令配置该包。
node ace configure @adonisjs/inertia
-
在
adonisrc.ts文件中注册以下服务提供者(service provider)和命令。{providers: [// ...other providers() => import('@adonisjs/inertia/inertia_provider')]} -
在
start/kernel.ts文件中注册以下中间件router.use([() => import('@adonisjs/inertia/inertia_middleware')]) -
创建
config/inertia.ts文件。 -
将一些桩(stub)文件复制到你的应用中以帮助你快速开始。每个复制的文件都会根据你之前选择的前端框架进行调整。
-
创建一个
./resources/views/inertia_layout.edge文件,用于渲染用于启动 Inertia 的 HTML 页面。 -
创建一个
./inertia/css/app.css文件,其中包含为inertia_layout.edge视图设置样式所需的内容。 -
创建一个
./inertia/tsconfig.json文件,以区分服务端和客户端侧的 TypeScript 配置。 -
创建一个
./inertia/app/app.ts文件用于启动 Inertia 和你选择的前端框架。 -
创建一个
./inertia/pages/home.{tsx|vue|svelte}文件来渲染应用的 home 页面。 -
创建
./inertia/pages/server_error.{tsx|vue|svelte}和./inertia/pages/not_found.{tsx|vue|svelte}文件来渲染错误页面。 -
在
vite.config.ts文件中添加正确的 vite 插件来编译你选择的前端框架。 -
在
start/routes.ts文件中添加一个位于/的哑路由,作为使用 Inertia 渲染 home 页面的示例。 -
根据所选的前端框架安装包。
完成后,你应该可以在 AdonisJS 应用中使用 Inertia 了。启动你的开发服务器,访问 localhost:3333 即可看到使用你选择的前端框架通过 Inertia 渲染的 home 页面。
阅读 Inertia 官方文档。
Inertia 是一个与后端无关的库。我们只是创建了一个适配器(adapter)让它能与 AdonisJS 一起工作。在阅读本文档之前,你应该熟悉 Inertia 的概念。
本文档中我们只会涵盖 AdonisJS 特有的部分。
客户端入口
如果你使用了 configure 或 add 命令,该包会已经在 inertia/app/app.ts 创建了一个入口文件,因此你可以跳过这一步。
基本上,这个文件将是你前端应用的主入口,用于启动 Inertia 和你选择的前端框架。这个文件应该是被你根 Edge 模板通过 @vite 标签加载的入口。
import { createApp, h } from 'vue'
import type { DefineComponent } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
import { resolvePageComponent } from '@adonisjs/inertia/helpers'
const appName = import.meta.env.VITE_APP_NAME || 'AdonisJS'
createInertiaApp({
title: (title) => {{ `${title} - ${appName}` }},
resolve: (name) => {
return resolvePageComponent(
`../pages/${name}.vue`,
import.meta.glob<DefineComponent>('../pages/**/*.vue'),
)
},
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el)
},
})
import { createRoot } from 'react-dom/client';
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from '@adonisjs/inertia/helpers'
const appName = import.meta.env.VITE_APP_NAME || 'AdonisJS'
createInertiaApp({
progress: { color: '#5468FF' },
title: (title) => `${title} - ${appName}`,
resolve: (name) => {
return resolvePageComponent(
`./pages/${name}.tsx`,
import.meta.glob('./pages/**/*.tsx'),
)
},
setup({ el, App, props }) {
const root = createRoot(el);
root.render(<App {...props} />);
},
});
import { createInertiaApp } from '@inertiajs/svelte'
import { resolvePageComponent } from '@adonisjs/inertia/helpers'
const appName = import.meta.env.VITE_APP_NAME || 'AdonisJS'
createInertiaApp({
progress: { color: '#5468FF' },
title: (title) => `${title} - ${appName}`,
resolve: (name) => {
return resolvePageComponent(
`./pages/${name}.svelte`,
import.meta.glob('./pages/**/*.svelte'),
)
},
setup({ el, App, props }) {
new App({ target: el, props })
},
})
import { render } from 'solid-js/web'
import { createInertiaApp } from 'inertia-adapter-solid'
import { resolvePageComponent } from '@adonisjs/inertia/helpers'
const appName = import.meta.env.VITE_APP_NAME || 'AdonisJS'
createInertiaApp({
progress: { color: '#5468FF' },
title: (title) => `${title} - ${appName}`,
resolve: (name) => {
return resolvePageComponent(
`./pages/${name}.tsx`,
import.meta.glob('./pages/**/*.tsx'),
)
},
setup({ el, App, props }) {
render(() => <App {...props} />, el)
},
})
这个文件的作用是创建一个 Inertia 应用并解析页面组件(page component)。你在使用 inertia.render 时编写的页面组件会被向下传递给 resolve 函数,该函数的作用就是返回需要渲染的组件。
渲染页面
在配置你的包时,一个 inertia_middleware 已被注册在 start/kernel.ts 文件中。该中间件负责在 HttpContext 上设置 inertia 对象。
要使用 Inertia 渲染视图,请使用 inertia.render 方法。该方法接受视图名称以及要作为 props 传递给组件的数据。
export default class HomeController {
async index({ inertia }: HttpContext) {
return inertia.render('home', { user: { name: 'julien' } })
}
}
你看到传给 inertia.render 方法的 home 了吗?它应该是相对于 inertia/pages 目录的组件文件路径。我们在这里渲染的是 inertia/pages/home.(vue,tsx) 文件。
你的前端组件将作为 prop 接收 user 对象:
<script setup lang="ts">
defineProps<{
user: { name: string }
}>()
</script>
<template>
<p>Hello {{ user.name }}</p>
</template>
export default function Home(props: { user: { name: string } }) {
return <p>Hello {props.user.name}</p>
}
<script lang="ts">
export let user: { name: string }
</script>
<Layout>
<p>Hello {user.name}</p>
</Layout>
export default function Home(props: { user: { name: string } }) {
return <p>Hello {props.user.name}</p>
}
就这么简单。
在将数据传递给前端时,所有内容都会被序列化为 JSON。不要指望能够传递模型实例、日期或其他复杂对象。
根 Edge 模板
根模板(Root template)是一个常规的 Edge 模板,会在你应用的首次页面访问时加载。你应该在这里包含你的 CSS 和 JavaScript 文件,以及 @inertia 标签。一个典型的根模板如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title inertia>AdonisJS x Inertia</title>
@inertiaHead()
@vite(['inertia/app/app.ts', `inertia/pages/${page.component}.vue`])
</head>
<body>
@inertia()
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title inertia>AdonisJS x Inertia</title>
@inertiaHead()
@viteReactRefresh()
@vite(['inertia/app/app.tsx', `inertia/pages/${page.component}.tsx`])
</head>
<body>
@inertia()
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title inertia>AdonisJS x Inertia</title>
@inertiaHead()
@vite(['inertia/app/app.ts', `inertia/pages/${page.component}.svelte`])
</head>
<body>
@inertia()
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title inertia>AdonisJS x Inertia</title>
@inertiaHead()
@vite(['inertia/app/app.tsx', `inertia/pages/${page.component}.tsx`])
</head>
<body>
@inertia()
</body>
</html>
你可以在 config/inertia.ts 文件中配置根模板路径。默认情况下,它假设你的模板位于 resources/views/inertia_layout.edge。
import { defineConfig } from '@adonisjs/inertia'
export default defineConfig({
// The path to the root template relative
// to the `resources/views` directory
rootView: 'app_root',
})
如果需要,你可以将函数传递给 rootView 属性,以动态决定应该使用哪个根模板。
import { defineConfig } from '@adonisjs/inertia'
import type { HttpContext } from '@adonisjs/core/http'
export default defineConfig({
rootView: ({ request }: HttpContext) => {
if (request.url().startsWith('/admin')) {
return 'admin_root'
}
return 'app_root'
}
})
根模板数据
你可能想要与你的根 Edge 模板共享数据。例如,用于添加 meta 标题或 open graph 标签。你可以通过使用 inertia.render 方法的第三个参数来做到这一点:
export default class PostsController {
async index({ inertia }: HttpContext) {
return inertia.render('posts/details', post, {
title: post.title,
description: post.description
})
}
}
title 和 description 现在将对根 Edge 模板可用:
<html>
<title>{{ title }}</title>
<meta name="description" content="{{ description }}">
<body>
@inertia()
</body>
</html
重定向
在 AdonisJS 中你应该这样做:
export default class UsersController {
async store({ response }: HttpContext) {
await User.create(request.body())
// 👇 You can use standard AdonisJS redirections
return response.redirect().toRoute('users.index')
}
async externalRedirect({ inertia }: HttpContext) {
// 👇 Or use the inertia.location for external redirects
return inertia.location('https://adonisjs.com')
}
}
更多信息请参阅 官方文档。
与所有视图共享数据
有时,你可能需要跨多个视图共享相同的数据。例如,我们正在与所有视图共享当前用户信息。对每个控制器都这样做会变得很繁琐。幸运的是,我们有两个解决方案来解决这个问题。
sharedData
在 config/inertia.ts 文件中,你可以定义一个 sharedData 对象。该对象允许你定义要与所有视图共享的数据。
import { defineConfig } from '@adonisjs/inertia'
export default defineConfig({
sharedData: {
appName: 'My App', // 👈 This will be available in all views
user: (ctx) => ctx.auth?.user, // 👈 Scoped to the current request
},
})
从中间件共享
有时,从中间件而不是 config/inertia.ts 文件共享数据可能更方便。你可以使用 inertia.share 方法来做到这一点:
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class MyMiddleware {
async handle({ inertia, auth }: HttpContext, next: NextFn) {
inertia.share({
appName: 'My App',
user: (ctx) => ctx.auth?.user
})
}
}
部分重载与惰性数据求值
首先阅读 官方文档 以了解什么是部分重载以及它们如何工作。
关于惰性数据求值,它在 AdonisJS 中的工作方式如下:
export default class UsersController {
async index({ inertia }: HttpContext) {
return inertia.render('users/index', {
// ALWAYS included on first visit.
// OPTIONALLY included on partial reloads.
// ALWAYS evaluated
users: await User.all(),
// ALWAYS included on first visit.
// OPTIONALLY included on partial reloads.
// ONLY evaluated when needed
users: () => User.all(),
// NEVER included on first visit.
// OPTIONALLY included on partial reloads.
// ONLY evaluated when needed
users: inertia.optional(() => User.all())
}),
}
}
类型共享
通常,你会想要共享传递给前端页面组件的数据类型。一个简单的做法就是使用 InferPageProps 类型。
export class UsersController {
index() {
return inertia.render('users/index', {
users: [
{ id: 1, name: 'julien' },
{ id: 2, name: 'virk' },
{ id: 3, name: 'romain' },
]
})
}
}
import { InferPageProps } from '@adonisjs/inertia/types'
import type { UsersController } from '../../controllers/users_controller.ts'
export function UsersPage(
// 👇 It will be correctly typed based
// on what you passed to inertia.render
// in your controller
props: InferPageProps<UsersController, 'index'>
) {
return (
// ...
)
}
如果你使用的是 Vue,你将不得不手动在 defineProps 中定义每个属性。这是 Vue 的一个令人讨厌的限制,请参阅 此 issue 了解更多信息。
<script setup lang="ts">
import { InferPageProps } from '@adonisjs/inertia/types'
defineProps<{
// 👇 You will have to manually define each prop
users: InferPageProps<UsersController, 'index'>['users'],
posts: InferPageProps<PostsController, 'index'>['posts'],
}>()
</script>
引用指令(Reference Directives)
由于你的 Inertia 应用是一个独立的 TypeScript 项目(带有自己的 tsconfig.json),你将需要帮助 TypeScript 理解某些类型。我们的许多官方包使用 模块扩充(module augmentation) 来向你的 AdonisJS 项目添加某些类型。
例如,HttpContext 上的 auth 属性及其类型,只有当你将 @adonisjs/auth/initialize_auth_middleware 导入到你的项目中时才可用。现在的问题是,我们在 Inertia 项目中并没有导入这个模块,所以如果你尝试从使用了 auth 的控制器推断页面 props,那么你很可能会收到 TypeScript 错误或无效的类型。
为了解决这个问题,你可以使用 引用指令(reference directives) 来帮助 TypeScript 理解某些类型。为此,你需要在 inertia/app/app.ts 文件中添加以下行:
/// <reference path="../../adonisrc.ts" />
根据你使用的类型,你可能需要添加其他引用指令,例如对同样使用模块扩充的某些配置文件的引用。
/// <reference path="../../adonisrc.ts" />
/// <reference path="../../config/ally.ts" />
/// <reference path="../../config/auth.ts" />
类型级别序列化
关于 InferPageProps 需要知道的一件重要事情是,它会在类型级别"序列化"你传递的数据。例如,如果你将一个 Date 对象传递给 inertia.render,那么 InferPageProps 得到的类型将是 string:
export default class UsersController {
async index({ inertia }: HttpContext) {
const users = [
{ id: 1, name: 'John Doe', createdAt: new Date() }
]
return inertia.render('users/index', { users })
}
}
import type { InferPageProps } from '@adonisjs/inertia/types'
export function UsersPage(
props: InferPageProps<UsersController, 'index'>
) {
props.users
// ^? { id: number, name: string, createdAt: string }[]
}
这完全说得通,因为当日期通过 JSON 在网络上传递时会被序列化为字符串。
模型序列化
记住上一点,另一件需要知道的重要事情是,如果你将一个 AdonisJS 模型传递给 inertia.render,那么 InferPageProps 得到的类型将是一个 ModelObject:一个几乎不包含任何信息的类型。这可能会是个问题。要解决这个问题,你有几个选项:
- 在将模型传递给
inertia.render之前,将其转换为简单的对象: - 使用 DTO(Data Transfer Object,数据传输对象)系统,在将模型传递给
inertia.render之前将其转换为简单对象。
class UsersController {
async edit({ inertia, params }: HttpContext) {
const user = users.serialize() as {
id: number
name: string
}
return inertia.render('user/edit', { user })
}
}
class UserDto {
constructor(private user: User) {}
toJson() {
return {
id: this.user.id,
name: this.user.name
}
}
}
class UsersController {
async edit({ inertia, params }: HttpContext) {
const user = await User.findOrFail(params.id)
return inertia.render('user/edit', { user: new UserDto(user).toJson() })
}
}
你将在前端组件中获得准确的类型。
共享 Props
要在你的组件中获得共享数据的类型,请确保你已在 config/inertia.ts 文件中执行了如下的模块扩充:
// file: config/inertia.ts
const inertiaConfig = defineConfig({
sharedData: {
appName: 'My App',
},
});
export default inertiaConfig;
declare module '@adonisjs/inertia/types' {
export interface SharedProps extends InferSharedProps<typeof inertiaConfig> {
// If necessary, you can also manually add some shared props,
// such as those shared from a middleware for example
propsSharedFromAMiddleware: number;
}
}
另外,请确保在你的 inertia/app/app.ts 文件中添加此引用指令:
/// <reference path="../../config/inertia.ts" />
完成后,你就可以通过 InferPageProps 在组件中访问你的共享 props 了。InferPageProps 将包含你的共享 props 的类型,以及由 inertia.render 传递的 props 类型:
// file: inertia/pages/users/index.tsx
import type { InferPageProps } from '@adonisjs/inertia/types'
export function UsersPage(
props: InferPageProps<UsersController, 'index'>
) {
props.appName
// ^? string
props.propsSharedFromAMiddleware
// ^? number
}
如果需要,你可以通过 SharedProps 类型只访问共享 props 的类型:
import type { SharedProps } from '@adonisjs/inertia/types'
const page = usePage<SharedProps>()
CSRF
如果你为应用启用了 CSRF 保护,请在 config/shield.ts 文件中启用 enableXsrfCookie 选项。
启用此选项将确保 XSRF-TOKEN cookie 在客户端设置,并随每个请求发送回服务器。
要使 Inertia 与 CSRF 保护一起工作,无需额外配置。
资源版本控制
在重新部署应用时,你的用户应该始终获得客户端资源的最新版本。这是 Inertia 协议和 AdonisJS 开箱即支持的功能。
默认情况下,@adonisjs/inertia 包会为 public/assets/manifest.json 文件计算一个哈希,并将其用作你的资源版本。
如果你想调整此行为,可以编辑 config/inertia.ts 文件。assetsVersion 属性定义了你的资源版本,可以是一个字符串或一个函数。
import { defineConfig } from '@adonisjs/inertia'
export default defineConfig({
assetsVersion: 'v1'
})
更多信息请阅读 官方文档。
SSR
启用 SSR
Inertia 启动套件 开箱即带有服务端渲染(SSR)支持。所以如果你想为应用启用 SSR,请确保使用它。
如果你在启动时没有启用 SSR,你随时可以按照以下步骤在以后启用它:
添加服务端入口
我们需要添加一个与服务端入口非常相似的服务端入口。这个入口将在服务端而不是浏览器上渲染首次页面访问。
你必须创建一个 inertia/app/ssr.ts,其默认导出一个如下的函数:
import { createInertiaApp } from '@inertiajs/vue3'
import { renderToString } from '@vue/server-renderer'
import { createSSRApp, h, type DefineComponent } from 'vue'
export default function render(page) {
return createInertiaApp({
page,
render: renderToString,
resolve: (name) => {
const pages = import.meta.glob<DefineComponent>('../pages/**/*.vue')
return pages[`../pages/${name}.vue`]()
},
setup({ App, props, plugin }) {
return createSSRApp({ render: () => h(App, props) }).use(plugin)
},
})
}
import ReactDOMServer from 'react-dom/server'
import { createInertiaApp } from '@inertiajs/react'
export default function render(page) {
return createInertiaApp({
page,
render: ReactDOMServer.renderToString,
resolve: (name) => {
const pages = import.meta.glob('./pages/**/*.tsx', { eager: true })
return pages[`./pages/${name}.tsx`]
},
setup: ({ App, props }) => <App {...props} />,
})
}
import { createInertiaApp } from '@inertiajs/svelte'
import createServer from '@inertiajs/svelte/server'
export default function render(page) {
return createInertiaApp({
page,
resolve: name => {
const pages = import.meta.glob('./pages/**/*.svelte', { eager: true })
return pages[`./pages/${name}.svelte`]
},
})
}
import { hydrate } from 'solid-js/web'
import { createInertiaApp } from 'inertia-adapter-solid'
export default function render(page: any) {
return createInertiaApp({
page,
resolve: (name) => {
const pages = import.meta.glob('./pages/**/*.tsx', { eager: true })
return pages[`./pages/${name}.tsx`]
},
setup({ el, App, props }) {
hydrate(() => <App {...props} />, el)
},
})
}
更新配置文件
前往 config/inertia.ts 文件并更新 ssr 属性以启用它。此外,如果你使用了不同的路径,请指向你的服务端入口。
import { defineConfig } from '@adonisjs/inertia'
export default defineConfig({
// ...
ssr: {
enabled: true,
entrypoint: 'inertia/app/ssr.ts'
}
})
更新 Vite 配置
首先,确保你已经注册了 inertia vite 插件。完成后,如果你使用了不同的路径,你应该在 vite.config.ts 文件中更新服务端入口的路径。
import { defineConfig } from 'vite'
import inertia from '@adonisjs/inertia/client'
export default defineConfig({
plugins: [
inertia({
ssr: {
enabled: true,
entrypoint: 'inertia/app/ssr.ts'
}
})
]
})
你现在应该能够在服务端渲染首次页面访问,然后继续客户端渲染。
SSR 允许列表
使用 SSR 时,你可能不想服务端渲染所有的组件。例如,你正在构建受身份认证保护的管理后台,因此这些路由没有理由在服务端渲染。但在同一个应用中,你可能有一个落地页,可以从 SSR 中受益以改善 SEO。
所以,你可以在 config/inertia.ts 文件中添加应该在服务端渲染的页面。
import { defineConfig } from '@adonisjs/inertia'
export default defineConfig({
ssr: {
enabled: true,
pages: ['home']
}
})
你也可以将函数传递给 pages 属性,以动态决定哪些页面应该在服务端渲染。
import { defineConfig } from '@adonisjs/inertia'
export default defineConfig({
ssr: {
enabled: true,
pages: (ctx, page) => !page.startsWith('admin')
}
})
测试
有几种方法可以测试你的前端代码:
- 端到端测试。你可以使用 Browser Client,它是 Japa 和 Playwright 之间无缝的集成。
- 单元测试。我们建议使用适配前端生态的测试工具,特别是 Vitest。
最后,你也可以测试你的 Inertia 端点,以确保它们返回正确的数据。为此,我们在 Japa 中有一些测试辅助函数可用。
首先,如果你还没有在 test/bootsrap.ts 文件中配置 inertiaApiClient 和 apiClient 插件,请确保配置它们:
import { assert } from '@japa/assert'
import app from '@adonisjs/core/services/app'
import { pluginAdonisJS } from '@japa/plugin-adonisjs'
import { apiClient } from '@japa/api-client'
import { inertiaApiClient } from '@adonisjs/inertia/plugins/api_client'
export const plugins: Config['plugins'] = [
assert(),
pluginAdonisJS(app),
apiClient(),
inertiaApiClient(app)
]
接下来,我们可以使用 withInertia() 请求我们的 Inertia 端点,以确保数据以 JSON 格式正确返回。
test('returns correct data', async ({ client }) => {
const response = await client.get('/home').withInertia()
response.assertStatus(200)
response.assertInertiaComponent('home/main')
response.assertInertiaProps({ user: { name: 'julien' } })
})
让我们来看看可用于测试端点的各种断言:
withInertia()
为请求添加 X-Inertia 头。它确保数据以 JSON 格式正确返回。
assertInertiaComponent()
检查服务器返回的组件是否为预期的组件。
test('returns correct data', async ({ client }) => {
const response = await client.get('/home').withInertia()
response.assertInertiaComponent('home/main')
})
assertInertiaProps()
检查服务器返回的 props 是否正是作为参数传递的那些。
test('returns correct data', async ({ client }) => {
const response = await client.get('/home').withInertia()
response.assertInertiaProps({ user: { name: 'julien' } })
})
assertInertiaPropsContains()
检查服务器返回的 props 是否包含作为参数传递的某些 props。它在底层使用了 containsSubset。
test('returns correct data', async ({ client }) => {
const response = await client.get('/home').withInertia()
response.assertInertiaPropsContains({ user: { name: 'julien' } })
})
附加属性
除此之外,你还可以在 ApiResponse 上访问以下属性:
test('returns correct data', async ({ client }) => {
const response = await client.get('/home').withInertia()
// 👇 The component returned by the server
console.log(response.inertiaComponent)
// 👇 The props returned by the server
console.log(response.inertiaProps)
})
FAQ
为什么我在更新前端代码时服务器一直在重新加载?
假设你正在使用 React。每次更新前端代码,服务器都会重新加载,浏览器也会刷新。你没有享受到热模块替换(HMR)特性。
你需要从根 tsconfig.json 文件中排除 inertia/**/* 才能使其工作。
{
"compilerOptions": {
// ...
},
"exclude": ["inertia/**/*"]
}
因为,负责重启服务器的 AdonisJS 进程正在监视包含在 tsconfig.json 文件中的文件。
为什么我的生产构建不工作?
如果你遇到如下错误:
X [ERROR] Failed to load url inertia/app/ssr.ts (resolved id: inertia/app/ssr.ts). Does the file exist?
一个常见的问题是你在运行生产构建时忘记设置 NODE_ENV=production。
NODE_ENV=production node build/server.js
Top-level await is not available...
如果你遇到如下错误:
X [ERROR] Top-level await is not available in the configured target environment ("chrome87", "edge88", "es2020", "firefox78", "safari14" + 2 overrides)
node_modules/@adonisjs/core/build/services/hash.js:15:0:
15 │ await app.booted(async () => {
╵ ~~~~~
那么极有可能是你正在将后端代码导入到前端。仔细查看由 Vite 生成的错误,我们看到它正在尝试编译来自 node_modules/@adonisjs/core 的代码。所以,这意味着我们的后端代码最终会出现在前端包中。那可能不是你想要的。
通常,当你尝试与前端共享类型时会发生此错误。如果这是你试图实现的目标,请确保始终只通过 import type 而不是 import 导入该类型:
// ✅ Correct
import type { User } from '#models/user'
// ❌ Incorrect
import { User } from '#models/user'