diff --git a/packages/core/build.js b/packages/core/build.js index 47a19fa32..c17eafac4 100755 --- a/packages/core/build.js +++ b/packages/core/build.js @@ -30,6 +30,7 @@ const builds = [ { entryPoints: ['src/index.ts'], format: 'cjs', outfile: 'dist/index.js', platform: 'browser' }, { entryPoints: ['src/server.ts'], format: 'esm', outfile: 'dist/server.esm.js', platform: 'node' }, { entryPoints: ['src/server.ts'], format: 'cjs', outfile: 'dist/server.js', platform: 'node' }, + { entryPoints: ['src/vite.ts'], format: 'esm', outfile: 'dist/vite.js', platform: 'node' }, ] builds.forEach(async (build) => { diff --git a/packages/core/package.json b/packages/core/package.json index 2d0227091..71261f185 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -34,6 +34,10 @@ "types": "./types/server.d.ts", "import": "./dist/server.esm.js", "require": "./dist/server.js" + }, + "./vite": { + "types": "./types/vite.d.ts", + "default": "./dist/vite.js" } }, "typesVersions": { @@ -54,6 +58,14 @@ "es-toolkit": "^1.34.1", "qs": "^6.9.0" }, + "peerDependencies": { + "vite": "^6.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + }, "devDependencies": { "@types/deepmerge": "^2.2.0", "@types/node": "^18.4", @@ -61,6 +73,7 @@ "@types/qs": "^6.9.0", "esbuild": "^0.25.0", "esbuild-node-externals": "^1.6.0", - "typescript": "^4.9.4" + "typescript": "^4.9.4", + "vite": "^5.4.8" } } diff --git a/packages/core/src/server.ts b/packages/core/src/server.ts index ca3bb99c2..c8b908e2d 100644 --- a/packages/core/src/server.ts +++ b/packages/core/src/server.ts @@ -2,9 +2,11 @@ import { createServer, IncomingMessage } from 'http' import cluster from 'node:cluster' import { availableParallelism } from 'node:os' import * as process from 'process' -import { InertiaAppResponse, Page } from './types' +import { readableToString } from './serverUtils' +import type { InertiaAppResponse, Page } from './types' + +export type AppCallback = (page: Page) => InertiaAppResponse -type AppCallback = (page: Page) => InertiaAppResponse type RouteHandler = (request: IncomingMessage) => Promise type ServerOptions = { port?: number @@ -12,14 +14,6 @@ type ServerOptions = { } type Port = number -const readableToString: (readable: IncomingMessage) => Promise = (readable) => - new Promise((resolve, reject) => { - let data = '' - readable.on('data', (chunk) => (data += chunk)) - readable.on('end', () => resolve(data)) - readable.on('error', (err) => reject(err)) - }) - export default (render: AppCallback, options?: Port | ServerOptions): void => { const _port = typeof options === 'number' ? options : (options?.port ?? 13714) const _useCluster = typeof options === 'object' && options?.cluster !== undefined ? options.cluster : false diff --git a/packages/core/src/serverUtils.ts b/packages/core/src/serverUtils.ts new file mode 100644 index 000000000..011403cc6 --- /dev/null +++ b/packages/core/src/serverUtils.ts @@ -0,0 +1,9 @@ +import type { IncomingMessage } from 'http' + +export const readableToString: (readable: IncomingMessage) => Promise = (readable) => + new Promise((resolve, reject) => { + let data = '' + readable.on('data', (chunk) => (data += chunk)) + readable.on('end', () => resolve(data)) + readable.on('error', (err) => reject(err)) + }) diff --git a/packages/core/src/vite.ts b/packages/core/src/vite.ts new file mode 100644 index 000000000..ba08f2dc3 --- /dev/null +++ b/packages/core/src/vite.ts @@ -0,0 +1,43 @@ +import type { Plugin } from 'vite' +import { readableToString } from './serverUtils' + +interface PluginConfig { + renderer: string +} + +export default function inertia(config: string | PluginConfig): Plugin { + const resolvedConfig = resolveConfig(config) + + return { + name: '@inertiajs/core/vite', + async configureServer(server) { + return () => + server.middlewares.use(async (req, res, next) => { + if (req.url !== '/render') { + next() + } + + const { default: render } = await server.ssrLoadModule(resolvedConfig.renderer) + const response = await render(JSON.parse(await readableToString(req))) + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify(response)) + }) + }, + } +} + +function resolveConfig(config: string | PluginConfig) { + if (typeof config === 'undefined') { + throw new Error('@inertiajs/core/vite: missing configuration.') + } + + if (typeof config === 'string') { + return { renderer: config } + } + + if (typeof config.renderer === 'undefined') { + throw new Error('@inertiajs/core/vite: missing configuration for "renderer".') + } + + return config +} diff --git a/packages/react/src/server.ts b/packages/react/src/server.ts index a3168b6e2..5984c8adb 100644 --- a/packages/react/src/server.ts +++ b/packages/react/src/server.ts @@ -1 +1,2 @@ +export * from '@inertiajs/core/server' export { default as default } from '@inertiajs/core/server' diff --git a/packages/svelte/src/server.ts b/packages/svelte/src/server.ts index a3168b6e2..5984c8adb 100644 --- a/packages/svelte/src/server.ts +++ b/packages/svelte/src/server.ts @@ -1 +1,2 @@ +export * from '@inertiajs/core/server' export { default as default } from '@inertiajs/core/server' diff --git a/packages/vue3/src/server.ts b/packages/vue3/src/server.ts index a3168b6e2..5984c8adb 100644 --- a/packages/vue3/src/server.ts +++ b/packages/vue3/src/server.ts @@ -1 +1,2 @@ +export * from '@inertiajs/core/server' export { default as default } from '@inertiajs/core/server' diff --git a/playgrounds/react/resources/js/viteSsr.tsx b/playgrounds/react/resources/js/viteSsr.tsx new file mode 100644 index 000000000..15a2dbeb2 --- /dev/null +++ b/playgrounds/react/resources/js/viteSsr.tsx @@ -0,0 +1,14 @@ +import { createInertiaApp } from '@inertiajs/react' +import type { AppCallback } from '@inertiajs/react/server' +import * as ReactDOMServer from 'react-dom/server' + +const render: AppCallback = (page) => + createInertiaApp({ + page, + render: ReactDOMServer.renderToString, + title: (title) => `${title} - React Playground`, + resolve: (name) => import(`./Pages/${name}.tsx`), + setup: ({ App, props }) => , + }) + +export default render diff --git a/playgrounds/react/vite.config.ts b/playgrounds/react/vite.config.ts index 98b8cf0c8..5e67920a8 100644 --- a/playgrounds/react/vite.config.ts +++ b/playgrounds/react/vite.config.ts @@ -1,3 +1,4 @@ +import inertia from '@inertiajs/core/vite' import tailwindcss from '@tailwindcss/vite' import react from '@vitejs/plugin-react' import laravel from 'laravel-vite-plugin' @@ -13,6 +14,7 @@ export default defineConfig({ ssr: 'resources/js/ssr.tsx', refresh: true, }), + inertia('resources/js/viteSsr.tsx'), react(), tailwindcss(), ], diff --git a/playgrounds/svelte4/resources/js/viteSsr.ts b/playgrounds/svelte4/resources/js/viteSsr.ts new file mode 100644 index 000000000..c77eed5b6 --- /dev/null +++ b/playgrounds/svelte4/resources/js/viteSsr.ts @@ -0,0 +1,10 @@ +import { createInertiaApp } from '@inertiajs/svelte' +import type { AppCallback } from '@inertiajs/svelte/server' + +const render: AppCallback = (page) => + createInertiaApp({ + page, + resolve: (name) => import(`./Pages/${name}.svelte`), + }) + +export default render diff --git a/playgrounds/svelte4/vite.config.js b/playgrounds/svelte4/vite.config.js index 9bda49256..7147585d3 100644 --- a/playgrounds/svelte4/vite.config.js +++ b/playgrounds/svelte4/vite.config.js @@ -1,3 +1,4 @@ +import inertia from '@inertiajs/core/vite' import { svelte } from '@sveltejs/vite-plugin-svelte' import tailwindcss from '@tailwindcss/vite' import laravel from 'laravel-vite-plugin' @@ -13,6 +14,7 @@ export default defineConfig({ ssr: 'resources/js/ssr.ts', refresh: true, }), + inertia('resources/js/viteSsr.ts'), svelte({ compilerOptions: { hydratable: true, diff --git a/playgrounds/svelte5/resources/js/viteSsr.ts b/playgrounds/svelte5/resources/js/viteSsr.ts new file mode 100644 index 000000000..c77eed5b6 --- /dev/null +++ b/playgrounds/svelte5/resources/js/viteSsr.ts @@ -0,0 +1,10 @@ +import { createInertiaApp } from '@inertiajs/svelte' +import type { AppCallback } from '@inertiajs/svelte/server' + +const render: AppCallback = (page) => + createInertiaApp({ + page, + resolve: (name) => import(`./Pages/${name}.svelte`), + }) + +export default render diff --git a/playgrounds/svelte5/vite.config.js b/playgrounds/svelte5/vite.config.js index 2e504d7b6..9cb3b2f68 100644 --- a/playgrounds/svelte5/vite.config.js +++ b/playgrounds/svelte5/vite.config.js @@ -1,3 +1,4 @@ +import inertia from '@inertiajs/core/vite' import { svelte } from '@sveltejs/vite-plugin-svelte' import tailwindcss from '@tailwindcss/vite' import laravel from 'laravel-vite-plugin' @@ -13,6 +14,7 @@ export default defineConfig({ ssr: 'resources/js/ssr.ts', refresh: true, }), + inertia('resources/js/viteSsr.ts'), svelte(), tailwindcss(), ], diff --git a/playgrounds/vue3/resources/js/viteSsr.ts b/playgrounds/vue3/resources/js/viteSsr.ts new file mode 100644 index 000000000..4099e1ea8 --- /dev/null +++ b/playgrounds/vue3/resources/js/viteSsr.ts @@ -0,0 +1,19 @@ +import { createInertiaApp } from '@inertiajs/vue3' +import type { AppCallback } from '@inertiajs/vue3/server' +import { renderToString } from '@vue/server-renderer' +import { createSSRApp, h } from 'vue' + +const render: AppCallback = (page) => + createInertiaApp({ + page, + render: renderToString, + title: (title) => `${title} - Vue 3 Playground`, + resolve: (name) => import(`./Pages/${name}.vue`), + setup({ App, props, plugin }) { + return createSSRApp({ + render: () => h(App, props), + }).use(plugin) + }, + }) + +export default render diff --git a/playgrounds/vue3/vite.config.ts b/playgrounds/vue3/vite.config.ts index 8a91359fe..db14582f7 100644 --- a/playgrounds/vue3/vite.config.ts +++ b/playgrounds/vue3/vite.config.ts @@ -1,3 +1,4 @@ +import inertia from '@inertiajs/core/vite' import tailwindcss from '@tailwindcss/vite' import vue from '@vitejs/plugin-vue' import laravel from 'laravel-vite-plugin' @@ -13,6 +14,7 @@ export default defineConfig({ ssr: 'resources/js/ssr.ts', refresh: true, }), + inertia('resources/js/viteSsr.ts'), vue({ template: { transformAssetUrls: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9a8043ec..f4f2cb97b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -61,6 +61,9 @@ importers: typescript: specifier: ^4.9.4 version: 4.9.5 + vite: + specifier: ^5.4.8 + version: 5.4.19(@types/node@18.19.111)(lightningcss@1.30.1)(terser@5.43.1) packages/react: dependencies: @@ -5559,6 +5562,17 @@ snapshots: picocolors: 1.1.1 picomatch: 2.3.1 + vite@5.4.19(@types/node@18.19.111)(lightningcss@1.30.1)(terser@5.43.1): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.45.1 + optionalDependencies: + '@types/node': 18.19.111 + fsevents: 2.3.3 + lightningcss: 1.30.1 + terser: 5.43.1 + vite@5.4.19(@types/node@24.1.0)(lightningcss@1.30.1)(terser@5.43.1): dependencies: esbuild: 0.21.5