Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,8 @@ jobs:
- name: Build @fastify/vite
run: pnpm --filter @fastify/vite build

- name: Build @fastify/tanstack
run: pnpm --filter @fastify/tanstack build

- name: Run integration tests
run: pnpm test:examples
29 changes: 29 additions & 0 deletions examples/tanstack-vanilla-ts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@fastify-vite/example-tanstack-vanilla-ts",
"private": true,
"type": "module",
"scripts": {
"dev": "tsx src/server.ts --dev",
"start": "NODE_ENV=production node build/server.js",
"build": "pnpm build:server && pnpm build:client",
"build:client": "vite build --app",
"build:server": "tsc",
"test": "tsx --test"
},
"dependencies": {
"@fastify/tanstack": "workspace:^",
"@fastify/vite": "workspace:^",
"@tanstack/react-router": "^1.0.0",
"fastify": "catalog:",
"react": "catalog:react",
"react-dom": "catalog:react"
},
"devDependencies": {
"@types/node": "^24.12.2",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"tsx": "catalog:",
"typescript": "catalog:",
"vite": "catalog:"
}
}
8 changes: 8 additions & 0 deletions examples/tanstack-vanilla-ts/src/client/create.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routes.ts'

export function createAppRouter() {
return createRouter({
routeTree,
})
}
12 changes: 12 additions & 0 deletions examples/tanstack-vanilla-ts/src/client/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- head -->
</head>
<body>
<div id="root"><!-- element --></div>
<script type="module" src="/$app/mount.ts"></script>
</body>
</html>
18 changes: 18 additions & 0 deletions examples/tanstack-vanilla-ts/src/client/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createElement, Fragment } from 'react'
import { createRootRoute, createRoute, Outlet, Scripts } from '@tanstack/react-router'

const rootRoute = createRootRoute({
component: function Root() {
return createElement(Fragment, null, createElement(Outlet), createElement(Scripts))
},
})

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: function Index() {
return createElement('h1', null, 'Hello from TanStack Router SSR!')
},
})

export const routeTree = rootRoute.addChildren([indexRoute])
16 changes: 16 additions & 0 deletions examples/tanstack-vanilla-ts/src/client/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"allowImportingTsExtensions": true,
"allowJs": false,
"jsx": "react-jsx",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"module": "ESNext",
"moduleResolution": "bundler",
"noEmit": true,
"target": "ES2023",
"types": ["vite/client"]
},
"include": ["**/*"],
"exclude": []
}
12 changes: 12 additions & 0 deletions examples/tanstack-vanilla-ts/src/server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import test from 'node:test'
import { resolve } from 'node:path'
import { makeBuildTest, makeIndexTest } from '../../test-factories.mjs'
import { main } from './server.ts'

const viteConfigLocation = resolve(import.meta.dirname, '..')

test('tanstack-vanilla-ts', async (t) => {
await t.test('build production bundle', makeBuildTest({ cwd: viteConfigLocation }))
await t.test('render index page in production', makeIndexTest({ main, dev: false }))
await t.test('render index page in development', makeIndexTest({ main, dev: true }))
})
22 changes: 22 additions & 0 deletions examples/tanstack-vanilla-ts/src/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { resolve } from 'node:path'
import Fastify from 'fastify'
import FastifyVite from '@fastify/vite'
import * as renderer from '@fastify/tanstack'

export async function main(dev?: boolean) {
const server = Fastify()

await server.register(FastifyVite, {
root: resolve(import.meta.dirname, '..'),
dev: dev || process.argv.includes('--dev'),
renderer,
})

await server.vite.ready()
return server
}

if (process.argv[1] === import.meta.filename) {
const server = await main()
await server.listen({ port: 3000 })
}
27 changes: 27 additions & 0 deletions examples/tanstack-vanilla-ts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"checkJs": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"isolatedModules": true,
"lib": ["ESNext"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "build",
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"removeComments": true,
"resolveJsonModule": true,
"rootDir": "src",
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"target": "ESNext",
"verbatimModuleSyntax": true
},
"include": ["src"],
"exclude": ["src/client/**/*", "src/**/*.test.ts"]
}
11 changes: 11 additions & 0 deletions examples/tanstack-vanilla-ts/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { resolve } from 'node:path'
import viteFastifyTanstack from '@fastify/tanstack/plugin'

export default {
root: resolve(import.meta.dirname, 'src', 'client'),
plugins: [viteFastifyTanstack()],
build: {
emptyOutDir: true,
outDir: resolve(import.meta.dirname, 'build'),
},
}
69 changes: 69 additions & 0 deletions packages/fastify-tanstack/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"name": "@fastify/tanstack",
"version": "0.0.1",
"description": "@fastify/vite renderer for TanStack Router SSR",
"keywords": [
"fastify",
"ssr",
"tanstack",
"tanstack-router",
"vite"
],
"homepage": "https://github.com/fastify/fastify-vite",
"bugs": {
"url": "https://github.com/fastify/fastify-vite/issues"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/fastify/fastify-vite.git"
},
"files": [
"dist/**/*",
"!dist/**/*.test.*",
"virtual/**/*"
],
"type": "module",
"main": "dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./plugin": {
"types": "./dist/plugin/index.d.ts",
"import": "./dist/plugin/index.js"
},
"./server": {
"types": "./dist/server.d.ts",
"import": "./dist/server.js"
}
},
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc",
"prepublishOnly": "pnpm build"
},
"dependencies": {
"@fastify/vite": "workspace:^",
"@tanstack/history": "^1.0.0",
"@tanstack/react-router": "^1.0.0",
"@tanstack/router-core": "^1.0.0",
"mlly": "^1.7.4",
"react": "catalog:react",
"react-dom": "catalog:react",
"youch": "^3.3.4"
},
"devDependencies": {
"@types/node": "^24.12.2",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"fastify": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
"vitest": "catalog:"
}
}
7 changes: 7 additions & 0 deletions packages/fastify-tanstack/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { prepareServer } from './server.ts'

export { prepareClient, createRouteHandler, createErrorHandler, createRoute } from './routing.ts'

export { createRenderFunction, createHtmlFunction } from './rendering.ts'

export const clientModule = '$app/index.ts'
93 changes: 93 additions & 0 deletions packages/fastify-tanstack/src/plugin/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import viteFastify from '@fastify/vite/plugin'
import type { Plugin, ResolvedConfig } from 'vite'
import {
prefix,
resolveId,
loadSource,
loadVirtualModule,
createPlaceholderExports,
} from './virtual.ts'
import { closeBundle as closeBundleImpl } from './preload.ts'

interface PluginContext {
root: string
resolvedConfig: ResolvedConfig | null
}

export default function viteFastifyTanstackPlugin(): Plugin[] {
let resolvedBundle: Record<string, unknown> | null = null

const context: PluginContext = {
root: '',
resolvedConfig: null,
}
return [
viteFastify({
clientModule: '$app/index.ts',
}),
{
name: 'vite-plugin-tanstack-fastify',
config: configHook,
configResolved: configResolved.bind(context),
resolveId: resolveId.bind(context),
async load(id) {
if (id.includes('?server') && !this.environment.config.build?.ssr) {
const source = loadSource(id)
return createPlaceholderExports(source)
}
if (id.includes('?client') && this.environment.config.build?.ssr) {
const source = loadSource(id)
return createPlaceholderExports(source)
}
if (prefix.test(id)) {
const [, virtual] = id.split(prefix)
if (virtual) {
return loadVirtualModule(virtual)
}
}
},
transformIndexHtml: {
order: 'post',
handler(_html, { bundle }) {
if (bundle) {
resolvedBundle = bundle
}
},
},
closeBundle() {
closeBundleImpl.call(this, resolvedBundle)
},
},
]
}

function configResolved(this: PluginContext, config: ResolvedConfig) {
this.resolvedConfig = config
this.root = config.root
}

function configHook(config: Record<string, any>, { command }: { command: string }) {
if (command === 'build') {
if (!config.build) {
config.build = {}
}
if (!config.build.rollupOptions) {
config.build.rollupOptions = {}
}
config.build.rollupOptions.onwarn = onwarn
}
}

function onwarn(
warning: { code?: string; message?: string },
rollupWarn: (warning: unknown) => void,
) {
if (
!(
warning.code == 'PLUGIN_WARNING' &&
warning.message?.includes?.('dynamic import will not move module into another chunk')
)
) {
rollupWarn(warning)
}
}
Loading