@dudousxd/nestjs-inertia (英语)
Inertia.js v3 雀巢JS的适配器 — Express + fastify,多应用程序通过
forFeature,4个模板引擎,CSRF保护,完全Inertia v3协议等同.
安装
pnpm add @dudousxd/nestjs-inertia
# Pick your HTTP platform
pnpm add express @types/express # Express adapter (default)
# OR
pnpm add fastify @nestjs/platform-fastify @fastify/cookie # Fastify adapter
快开始
// app.module.ts
import { Module } from '@nestjs/common';
import { InertiaModule } from '@dudousxd/nestjs-inertia';
@Module({
imports: [
InertiaModule.forRoot({
version: () => process.env.ASSET_VERSION ?? 'dev',
rootView: 'inertia/root.html',
share: async (req) => ({ auth: req.user ?? null }),
}),
],
})
export class AppModule {}
inertia/root.html数字 :
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>My App</title>
@inertiaHead
@vite('app/client.tsx')
@viteRefresh
</head>
<body>
@inertia
</body>
</html>
主计长
两种等同模式共存:
import { Controller, Get, Req } from '@nestjs/common';
import { Inertia } from '@dudousxd/nestjs-inertia';
import type { Request } from 'express';
@Controller()
export class HomeController {
// Decorator pattern (idiomatic for new code)
@Get('/')
@Inertia('Home')
show() {
return { hello: 'world' };
}
// Imperative pattern (use when you need fine control)
@Get('/crew')
async list(@Req() req: Request) {
await req.inertia
.share({ flash: req.session?.flash ?? {} })
.render('Crew', { crew: await this.svc.list() });
}
}
Async 配置
InertiaModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (cfg: ConfigService) => ({
version: cfg.get('ASSET_VERSION'),
rootView: 'inertia/root.html',
}),
});
三种途径: useFactory + inject (最常见的), useClass, (中文(简体) ), useExisting。 。 。 .
多应用程序 forFeature
在同一 NestJS 进程中主机两个或两个以上Inertia apps——每个都有自己的Vite条目, shell,版本,共享,SSR捆绑. 用于管理面板,多租户白标签,或前端堆栈之间的迁移.
@Module({
imports: [
InertiaModule.forRoot({ // main app
vite: { entry: 'app/client.tsx' },
rootView: 'inertia/root.html',
share: req => ({ auth: req.user }),
}),
InertiaModule.forFeature({ // admin app
scope: 'admin',
vite: { entry: 'admin/client.tsx' },
rootView: 'inertia/admin-root.html',
share: req => ({ admin: req.adminContext }),
}),
],
})
export class AppModule {}
每个控制器/方法选择范围 @UseInertia('scope')数字 :
@Controller('admin')
@UseInertia('admin')
export class AdminDashboardController {
@Get('/')
@Inertia('AdminDashboard')
show() { return { stats: ... }; }
}
forFeatureAsync 工作与 forRootAsync (使用Factory/useClass/useExting). 保留范围: 'default' 属于 forRoot()。 。 。 .
模板引擎
rootView 接受 .html (拥有解析器)加 .hbs 编号 : .ejs 编号 : .pug 编号 : .liquid 如果安装了引擎包:
pnpm add handlebars # or: ejs, pug, liquidjs
InertiaModule.forRoot({
rootView: 'inertia/root.hbs', // auto-detects Handlebars
});
每个引擎看到本地 { page, inertia, inertiaHead, vite, viteRefresh, asset }. 使用引擎本身的逃生规则(例如, {{{inertia}}} 手帕的三重出击, <%- inertia %> 在EJS中, != inertia (原始内容存档于2013-10-12). In Pug. 这个 @inertia编号 :@inertiaHead编号 :@vite编号 :@viteRefresh编号 :@asset 指令也会在引擎的输出,根视图和部分/包含相同的情况下被扩展出,这样可以混合并匹配. JSON页、SSR标记和任何打印自 page 因此,页面数据从不被视为指令.
中国证监会
import { CsrfCookieInterceptor, CsrfGuard } from '@dudousxd/nestjs-inertia';
// Global cookie writer
app.useGlobalInterceptors(new CsrfCookieInterceptor({ secret: process.env.CSRF_SECRET }));
// Per-route validation
@UseGuards(new CsrfGuard({ secret: process.env.CSRF_SECRET }))
@Post('/profile')
async update() { ... }
Cookie 名称 XSRF-TOKEN,页眉名称 X-XSRF-TOKEN 两者都符合Inertia客户协议. 通过HMAC-SHA256签署. 要求数 cookie-parser (执行)或 @fastify/cookie (快活地)作为同级Dep.
快捷化
import { FastifyAdapter } from '@nestjs/platform-fastify';
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());
与"快递"(Express)的功能完全等同:中件,装饰器,所有截取器,卫所,滤取器,外壳指令,SSR,FlashStore. request.inertia 被电线通过 decorateRequest + 键 onRequest 当检测到快取Adapter时自动钩入.
道具标记
import { Inertia } from '@dudousxd/nestjs-inertia';
return {
user: Inertia.always(() => currentUser()),
stats: Inertia.optional(() => heavyStatsCalculation()),
activity: Inertia.defer(() => activityFeed(), 'secondary'),
rows: Inertia.merge(() => paginated(p), { matchOn: 'id', deep: false }),
csrfToken: Inertia.once(() => generateToken()),
};
always——解决每道曲折(包括部分重载)optional- 只有在列入清单时才能解决X-Inertia-Partial-Datadefer——列于page.deferredProps; 客户端 v2 发送后续请求merge——解决+标记作为合并目标(客户附件/替换)once——决定首次访问,缓存至X-Inertia-Reset-Once列出密钥
自动包含的基础设施
forRoot() 安装 :
InertiaMiddleware(执行)或FastifyInertiaPlugin[快活地] -req.inertia随处可见MethodSpoofMiddleware(POST + 多部分 +_method=PUT/PATCH/DELETE(中文(简体) )RedirectInterceptor根据PUT/PATCH/DELETE Inertia的请求升级(302 – 303)InertiaRenderInterceptor(手执@Inertia('Page')装饰)InertiaScopeSwitcherInterceptor(手执@UseInertia('scope')(中文(简体) )
通过旋钮禁用 :
InertiaModule.forRoot({
methodSpoofing: false,
autoUpgrade303: false,
});
专用公用事业
import { ErrorBagInterceptor } from '@dudousxd/nestjs-inertia';
// Error bag interceptor — opt-in per route
@UseInterceptors(ErrorBagInterceptor)
Auth- reirect 守护器和未找到的过滤器不是运出输出 —— 沿着 直接认证 和未找到的食谱.
FlashStore (误差)
NestJS没有自己的会话. 插入适配器 :
import type { FlashStore } from '@dudousxd/nestjs-inertia';
class ExpressSessionFlashStore implements FlashStore {
read(req) {
return (req as Request).session?.flash?.errors ?? {};
}
}
InertiaModule.forRoot({
flashStore: new ExpressSessionFlashStore(),
});
安全部门改革
InertiaModule.forRoot({
ssr: {
enabled: process.env.NODE_ENV === 'production',
bundlePath: 'dist/inertia/ssr/ssr.mjs',
throwOnError: false,
},
});
Bundle 必须导出 default { render(page) } 或名称 render(page) 返回时 { head: string[], body: string }。 。 。 .
在服务器捆绑中生成的文件
只有您的服务器将导入文件捆绑到生成的文件中,才能复制到 nest-cli.json数字 :
{
"compilerOptions": {
"assets": [".nestjs-inertia/**/*"],
"watchAssets": true
}
}
协议对等
完整Inertia协议:X-Inertia头,版本不匹配(409 + X-Inertia-Location,仅取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取.
配套包(计划)
@dudousxd/nestjs-inertia-vite- Vite dev/建设助手(B计划)@dudousxd/nestjs-inertia-testing—— 说expectInertia(res)火柴(B计划)@dudousxd/nestjs-inertia-codegen——打出页数(计划C)@dudousxd/nestjs-inertia-client——图约式打入的REST + TanStack 查询(计划 D)- 实例+文件网站+CI工作流程(计划E)
见 docs/design.md 完全设计.
许可证
麻省理工学院