diff --git a/packages/core/build.js b/packages/core/build.js index 11e7e1d58..e7005707b 100755 --- a/packages/core/build.js +++ b/packages/core/build.js @@ -17,6 +17,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((build) => { diff --git a/packages/core/package.json b/packages/core/package.json index 73c187ae7..a5496b975 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": { diff --git a/packages/core/src/server.ts b/packages/core/src/server.ts index 0be5230c6..b6eab134a 100644 --- a/packages/core/src/server.ts +++ b/packages/core/src/server.ts @@ -1,17 +1,11 @@ import { createServer, IncomingMessage } from 'http' import * as process from 'process' -import { InertiaAppResponse, Page } from './types' +import { readableToString } from './serverUtils' +import type { InertiaAppResponse, Page } from './types' -type AppCallback = (page: Page) => InertiaAppResponse -type RouteHandler = (request: IncomingMessage) => Promise +export type AppCallback = (page: Page) => InertiaAppResponse -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)) - }) +type RouteHandler = (request: IncomingMessage) => Promise export default (render: AppCallback, port?: number): void => { const _port = port || 13714 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..c6c57d50c --- /dev/null +++ b/packages/core/src/vite.ts @@ -0,0 +1,28 @@ +import type { Plugin } from 'vite' +import { readableToString } from './serverUtils' + +interface PluginConfig { + renderer: string +} + +export default function inertia(config: PluginConfig): Plugin { + // todo: validate config + + return { + name: '@inertiajs/svelte/ssr', + async configureServer(server) { + return () => + server.middlewares.use(async (req, res, next) => { + if (req.url !== '/render') { + next() + } + + // todo: check if render is a function + const { render } = await server.ssrLoadModule(config.renderer) + const response = await render(JSON.parse(await readableToString(req))) + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify(response)) + }) + }, + } +} 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/playgrounds/react/composer.json b/playgrounds/react/composer.json index 3011c58c1..ccf7e7f7b 100644 --- a/playgrounds/react/composer.json +++ b/playgrounds/react/composer.json @@ -10,7 +10,7 @@ "require": { "php": "^8.0.2", "guzzlehttp/guzzle": "^7.2", - "inertiajs/inertia-laravel": "2.x-dev", + "inertiajs/inertia-laravel": "@dev", "laravel/framework": "^10.0", "laravel/sanctum": "^3.2", "laravel/tinker": "^2.7" @@ -65,5 +65,14 @@ } }, "minimum-stability": "dev", - "prefer-stable": true + "prefer-stable": true, + "repositories": [ + { + "type": "path", + "url": "./../../../inertia-laravel/", + "options": { + "symlink": true + } + } + ] } diff --git a/playgrounds/react/composer.lock b/playgrounds/react/composer.lock index a3c1160bc..04484fefe 100644 --- a/playgrounds/react/composer.lock +++ b/playgrounds/react/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "45a9e748df5cc8af0c2f5ab8453946d6", + "content-hash": "bdfcad99e41dfb5ea6825fc3ec074ed5", "packages": [ { "name": "brick/math", @@ -1057,16 +1057,10 @@ { "name": "inertiajs/inertia-laravel", "version": "2.x-dev", - "source": { - "type": "git", - "url": "https://github.com/inertiajs/inertia-laravel.git", - "reference": "141256b2ed1833158852c9d239d00abec8ff4942" - }, "dist": { - "type": "zip", - "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/141256b2ed1833158852c9d239d00abec8ff4942", - "reference": "141256b2ed1833158852c9d239d00abec8ff4942", - "shasum": "" + "type": "path", + "url": "./../../../inertia-laravel", + "reference": "6fa3696aad5e20495264a8b5a2ee6d4c0be92b40" }, "require": { "ext-json": "*", @@ -1096,14 +1090,18 @@ } }, "autoload": { + "psr-4": { + "Inertia\\": "src" + }, "files": [ "./helpers.php" - ], + ] + }, + "autoload-dev": { "psr-4": { - "Inertia\\": "src" + "Inertia\\Tests\\": "tests/" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1119,17 +1117,10 @@ "inertia", "laravel" ], - "support": { - "issues": "https://github.com/inertiajs/inertia-laravel/issues", - "source": "https://github.com/inertiajs/inertia-laravel/tree/2.x" - }, - "funding": [ - { - "url": "https://github.com/reinink", - "type": "github" - } - ], - "time": "2024-10-10T13:16:21+00:00" + "transport-options": { + "symlink": true, + "relative": true + } }, { "name": "laravel/framework", diff --git a/playgrounds/react/resources/js/viteSsr.tsx b/playgrounds/react/resources/js/viteSsr.tsx new file mode 100644 index 000000000..303ad1535 --- /dev/null +++ b/playgrounds/react/resources/js/viteSsr.tsx @@ -0,0 +1,12 @@ +import { createInertiaApp } from '@inertiajs/react' +import * as ReactDOMServer from 'react-dom/server' +import type { AppCallback } from '@inertiajs/svelte/server' + +export const render: AppCallback = (page) => + createInertiaApp({ + page, + render: ReactDOMServer.renderToString, + title: (title) => `${title} - React Playground`, + resolve: (name) => import(`./Pages/${name}.tsx`), + setup: ({ App, props }) => , + }) diff --git a/playgrounds/react/vite.config.ts b/playgrounds/react/vite.config.ts index 367e62448..6b66f15ae 100644 --- a/playgrounds/react/vite.config.ts +++ b/playgrounds/react/vite.config.ts @@ -1,3 +1,4 @@ +import inertia from '@inertiajs/core/vite' import react from '@vitejs/plugin-react' import laravel from 'laravel-vite-plugin' import { defineConfig } from 'vite' @@ -9,6 +10,9 @@ export default defineConfig({ ssr: 'resources/js/ssr.tsx', refresh: true, }), + inertia({ + renderer: 'resources/js/viteSsr.tsx', + }), react({}), ], }) diff --git a/playgrounds/svelte4/composer.lock b/playgrounds/svelte4/composer.lock index a658e38f8..8111693fe 100644 --- a/playgrounds/svelte4/composer.lock +++ b/playgrounds/svelte4/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "764d9aef4bf3cc9e043aed896c9ff744", + "content-hash": "bdfcad99e41dfb5ea6825fc3ec074ed5", "packages": [ { "name": "brick/math", @@ -1055,8 +1055,8 @@ "version": "2.x-dev", "dist": { "type": "path", - "url": "./../../../inertia-laravel-v2", - "reference": "f6ad746994d2e0b64693af56c4fdfbed7b53ea0f" + "url": "./../../../inertia-laravel", + "reference": "6fa3696aad5e20495264a8b5a2ee6d4c0be92b40" }, "require": { "ext-json": "*", diff --git a/playgrounds/svelte5/app/Providers/AppServiceProvider.php b/playgrounds/svelte5/app/Providers/AppServiceProvider.php index 334bd31ed..ee8ca5bcd 100644 --- a/playgrounds/svelte5/app/Providers/AppServiceProvider.php +++ b/playgrounds/svelte5/app/Providers/AppServiceProvider.php @@ -2,9 +2,6 @@ namespace App\Providers; -use Illuminate\Foundation\Vite; -use Illuminate\Support\HtmlString; -use Illuminate\Support\Js; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -16,157 +13,7 @@ class AppServiceProvider extends ServiceProvider */ public function register() { - $this->app->singleton(Vite::class, fn () => new class extends Vite - { - /** - * The prefetching strategy to use. - * - * @var 'waterfall'|'aggressive' - */ - protected $prefetchStrategy = 'waterfall'; - - /** - * When using the "waterfall" strategy, the count of assets to load at one time. - * - * @param int - */ - protected $prefetchChunks = 3; - - /** - * Set the prefetching strategy. - * - * @param 'waterfall'|'aggressive' $strategy - * @param ...mixed $config - */ - public function usePrefetchStrategy(string $strategy, mixed ...$config): static - { - $this->prefetchStrategy = $strategy; - - if ($strategy === 'waterfall') { - $this->prefetchChunks = $config[0] ?? 3; - } - - return $this; - } - - /** - * Generate Vite tags for an entrypoint. - * - * @param string|string[] $entrypoints - * @param string|null $buildDirectory - * @return \Illuminate\Support\HtmlString - */ - public function __invoke($entrypoints, $buildDirectory = null) - { - $manifest = $this->manifest($buildDirectory ??= $this->buildDirectory); - $base = parent::__invoke($entrypoints, $buildDirectory); - - if ($this->isRunningHot()) { - return $base; - } - - return collect($entrypoints) - ->flatMap(fn ($entrypoint) => collect($manifest[$entrypoint]['dynamicImports'] ?? []) - ->map(fn ($import) => $manifest[$import]) - ->filter(fn ($chunk) => str_ends_with($chunk['file'], '.js') || str_ends_with($chunk['file'], '.css')) - ->flatMap($resolveImportChunks = function ($chunk) use (&$resolveImportChunks, $manifest) { - return collect([...$chunk['imports'] ?? [], ...$chunk['dynamicImports'] ?? []]) - ->reduce( - fn ($chunks, $import) => $chunks->merge( - $resolveImportChunks($manifest[$import]) - ), - collect([$chunk]) - ) - ->merge(collect($chunk['css'] ?? [])->map( - fn ($css) => collect($manifest)->first(fn ($chunk) => $chunk['file'] === $css) ?? [ - 'file' => $css, - ], - )); - }) - ->map(function ($chunk) use ($buildDirectory, $manifest) { - return collect([ - ...$this->resolvePreloadTagAttributes( - $chunk['src'] ?? null, - $url = $this->assetPath("{$buildDirectory}/{$chunk['file']}"), - $chunk, - $manifest, - ), - 'rel' => 'prefetch', - 'href' => $url, - ])->reject( - fn ($value) => in_array($value, [null, false], true) - )->mapWithKeys(fn ($value, $key) => [ - $key = (is_int($key) ? $value : $key) => $value === true ? $key : $value, - ])->all(); - }) - ->reject(fn ($attributes) => isset($this->preloadedAssets[$attributes['href']]))) - ->unique('href') - ->values() - ->pipe(fn ($assets) => with(Js::from($assets), fn ($assets) => match ($this->prefetchStrategy) { - 'waterfall' => new HtmlString($base.<< - window.addEventListener('load', () => window.setTimeout(() => { - const linkTemplate = document.createElement('link') - linkTemplate.rel = 'prefetch' - - const makeLink = (asset) => { - const link = linkTemplate.cloneNode() - - Object.keys(asset).forEach((attribute) => { - link.setAttribute(attribute, asset[attribute]) - }) - - return link - } - - const loadNext = (assets, count) => window.setTimeout(() => { - const fragment = new DocumentFragment - - while (count > 0) { - const link = makeLink(assets.shift()) - fragment.append(link) - count-- - - if (assets.length) { - link.onload = () => loadNext(assets, 1) - link.error = () => loadNext(assets, 1) - } - } - - document.head.append(fragment) - }) - - loadNext({$assets}, {$this->prefetchChunks}) - })) - - HTML), - 'aggressive' => new HtmlString($base.<< - window.addEventListener('load', () => window.setTimeout(() => { - const linkTemplate = document.createElement('link') - linkTemplate.rel = 'prefetch' - - const makeLink = (asset) => { - const link = linkTemplate.cloneNode() - - Object.keys(asset).forEach((attribute) => { - link.setAttribute(attribute, asset[attribute]) - }) - - return link - } - - const fragment = new DocumentFragment - {$assets}.forEach((asset) => fragment.append(makeLink(asset))) - document.head.append(fragment) - })) - - HTML), - })); - } - }); + // } /** diff --git a/playgrounds/svelte5/composer.lock b/playgrounds/svelte5/composer.lock index a658e38f8..8111693fe 100644 --- a/playgrounds/svelte5/composer.lock +++ b/playgrounds/svelte5/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "764d9aef4bf3cc9e043aed896c9ff744", + "content-hash": "bdfcad99e41dfb5ea6825fc3ec074ed5", "packages": [ { "name": "brick/math", @@ -1055,8 +1055,8 @@ "version": "2.x-dev", "dist": { "type": "path", - "url": "./../../../inertia-laravel-v2", - "reference": "f6ad746994d2e0b64693af56c4fdfbed7b53ea0f" + "url": "./../../../inertia-laravel", + "reference": "6fa3696aad5e20495264a8b5a2ee6d4c0be92b40" }, "require": { "ext-json": "*", diff --git a/playgrounds/svelte5/resources/js/ssr.ts b/playgrounds/svelte5/resources/js/ssr.ts index ff4261952..97b41c924 100644 --- a/playgrounds/svelte5/resources/js/ssr.ts +++ b/playgrounds/svelte5/resources/js/ssr.ts @@ -1,6 +1,5 @@ -import { createInertiaApp } from '@inertiajs/svelte' +import { createInertiaApp, type ResolvedComponent } from '@inertiajs/svelte' import createServer from '@inertiajs/svelte/server' -import { ResolvedComponent } from '@inertiajs/svelte' createServer((page) => createInertiaApp({ diff --git a/playgrounds/svelte5/resources/js/viteSsr.ts b/playgrounds/svelte5/resources/js/viteSsr.ts new file mode 100644 index 000000000..c9e8912bd --- /dev/null +++ b/playgrounds/svelte5/resources/js/viteSsr.ts @@ -0,0 +1,8 @@ +import { createInertiaApp } from '@inertiajs/svelte' +import type { AppCallback } from '@inertiajs/svelte/server' + +export const render: AppCallback = (page) => + createInertiaApp({ + page, + resolve: (name) => import(`./Pages/${name}.svelte`), + }) diff --git a/playgrounds/svelte5/vite.config.js b/playgrounds/svelte5/vite.config.js index 99630ab59..69f013639 100644 --- a/playgrounds/svelte5/vite.config.js +++ b/playgrounds/svelte5/vite.config.js @@ -1,6 +1,7 @@ import { svelte } from '@sveltejs/vite-plugin-svelte' import laravel from 'laravel-vite-plugin' import { defineConfig } from 'vite' +import inertia from '@inertiajs/core/vite'; export default defineConfig({ plugins: [ @@ -9,6 +10,9 @@ export default defineConfig({ ssr: 'resources/js/ssr.ts', refresh: true, }), + inertia({ + renderer: 'resources/js/viteSsr.ts', + }), svelte(), ], })