From 76bf07749499d322fe849ffd7fba2d5f350b17db Mon Sep 17 00:00:00 2001 From: Jeremiasz Major Date: Fri, 11 Oct 2024 11:17:06 +0200 Subject: [PATCH] dev ssr --- packages/core/build.js | 1 + packages/core/package.json | 4 + packages/core/src/server.ts | 14 +- packages/core/src/serverUtils.ts | 9 + packages/core/src/vite.ts | 28 ++++ packages/svelte/src/server.ts | 1 + .../app/Providers/AppServiceProvider.php | 155 +----------------- playgrounds/svelte5/resources/js/ssr.ts | 3 +- playgrounds/svelte5/resources/js/viteSsr.ts | 8 + playgrounds/svelte5/vite.config.js | 4 + 10 files changed, 61 insertions(+), 166 deletions(-) create mode 100644 packages/core/src/serverUtils.ts create mode 100644 packages/core/src/vite.ts create mode 100644 playgrounds/svelte5/resources/js/viteSsr.ts 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/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/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/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(), ], })