Skip to content

Commit

Permalink
feat: ssr version
Browse files Browse the repository at this point in the history
  • Loading branch information
Julien-R44 committed Feb 26, 2024
1 parent bfd4aa9 commit 8106869
Show file tree
Hide file tree
Showing 13 changed files with 293 additions and 25 deletions.
11 changes: 9 additions & 2 deletions factories/inertia_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
* file that was distributed with this source code.
*/

import type { ViteRuntime } from 'vite/runtime'
import { HttpContext } from '@adonisjs/core/http'
import { AppFactory } from '@adonisjs/core/factories/app'
import { HttpContextFactory } from '@adonisjs/core/factories/http'
import { ApplicationService } from '@adonisjs/core/types'
import { HttpContextFactory } from '@adonisjs/core/factories/http'

import { defineConfig } from '../index.js'
import { Inertia } from '../src/inertia.js'
Expand All @@ -29,6 +30,7 @@ export class InertiaFactory {
#parameters: FactoryParameters = {
ctx: new HttpContextFactory().create(),
}
#viteRuntime?: ViteRuntime

#getApp() {
return new AppFactory().create(new URL('./', import.meta.url), () => {}) as ApplicationService
Expand All @@ -50,6 +52,11 @@ export class InertiaFactory {
return this
}

withViteRuntime(runtime: { executeEntrypoint: (path: string) => Promise<any> }) {
this.#viteRuntime = runtime as any
return this
}

withInertiaPartialData(data: string[]) {
this.#parameters.ctx.request.request.headers['x-inertia-partial-data'] = data.join(',')
return this
Expand All @@ -62,6 +69,6 @@ export class InertiaFactory {

async create() {
const config = await defineConfig(this.#parameters.config || {}).resolver(this.#getApp())
return new Inertia(this.#parameters.ctx, config)
return new Inertia(this.#parameters.ctx, config, this.#viteRuntime)
}
}
37 changes: 21 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"./inertia_middleware": "./build/src/inertia_middleware.js",
"./inertia_provider": "./build/providers/inertia_provider.js",
"./plugins/edge": "./build/src/plugins/edge/plugin.js",
"./plugins/api_client": "./build/src/plugins/api_client.js"
"./plugins/api_client": "./build/src/plugins/japa/api_client.js",
"./client": "./build/src/plugins/vite.js"
},
"scripts": {
"clean": "del-cli build",
Expand All @@ -36,39 +37,42 @@
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@adonisjs/assembler": "^7.0.0",
"@adonisjs/core": "6.2.0",
"@adonisjs/assembler": "^7.2.1",
"@adonisjs/core": "6.3.0",
"@adonisjs/eslint-config": "^1.2.1",
"@adonisjs/prettier-config": "^1.2.1",
"@adonisjs/session": "7.0.0",
"@adonisjs/session": "7.1.1",
"@adonisjs/tsconfig": "^1.2.1",
"@adonisjs/vite": "^3.0.0-0",
"@japa/api-client": "^2.0.2",
"@japa/assert": "2.1.0",
"@japa/expect-type": "^2.0.1",
"@japa/file-system": "^2.1.1",
"@japa/plugin-adonisjs": "^2.0.2",
"@japa/file-system": "^2.2.0",
"@japa/plugin-adonisjs": "^2.0.3",
"@japa/runner": "3.1.1",
"@swc/core": "^1.3.102",
"@types/node": "^20.10.8",
"@swc/core": "^1.4.2",
"@types/node": "^20.11.20",
"@types/qs": "^6.9.11",
"@types/supertest": "^6.0.2",
"c8": "^9.0.0",
"@vavite/multibuild": "^4.1.1",
"c8": "^9.1.0",
"copyfiles": "^2.4.1",
"del-cli": "^5.1.0",
"edge-parser": "^9.0.1",
"edge.js": "^6.0.1",
"eslint": "^8.56.0",
"eslint": "^8.57.0",
"get-port": "^7.0.0",
"np": "^9.2.0",
"prettier": "^3.1.1",
"supertest": "^6.3.3",
"tinybench": "^2.5.1",
"prettier": "^3.2.5",
"supertest": "^6.3.4",
"tinybench": "^2.6.0",
"ts-node": "^10.9.2",
"tsup": "^8.0.1",
"typescript": "~5.3.3"
"tsup": "^8.0.2",
"typescript": "~5.3.3",
"vite": "^5.1.4"
},
"dependencies": {
"@poppinss/utils": "^6.7.0",
"@poppinss/utils": "^6.7.2",
"crc-32": "^1.2.2",
"edge-error": "^4.0.1",
"html-entities": "^2.4.0",
Expand All @@ -77,6 +81,7 @@
"peerDependencies": {
"@adonisjs/core": "^6.2.0",
"@adonisjs/session": "^7.0.0",
"@adonisjs/vite": "^2.0.2",
"@japa/api-client": "^2.0.0",
"edge.js": "^6.0.0"
},
Expand Down
5 changes: 4 additions & 1 deletion providers/inertia_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* file that was distributed with this source code.
*/

/// <reference types="@adonisjs/vite/vite_provider" />

import { configProvider } from '@adonisjs/core'
import { RuntimeException } from '@poppinss/utils'
import type { ApplicationService } from '@adonisjs/core/types'
Expand Down Expand Up @@ -38,14 +40,15 @@ export default class InertiaProvider {
this.app.container.singleton(InertiaMiddleware, async () => {
const inertiaConfigProvider = this.app.config.get<InertiaConfig>('inertia')
const config = await configProvider.resolve<ResolvedConfig>(this.app, inertiaConfigProvider)
const vite = await this.app.container.make('vite')

if (!config) {
throw new RuntimeException(
'Invalid "config/inertia.ts" file. Make sure you are using the "defineConfig" method'
)
}

return new InertiaMiddleware(config)
return new InertiaMiddleware(config, vite)
})

await this.registerEdgePlugin()
Expand Down
6 changes: 6 additions & 0 deletions src/define_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export function defineConfig(config: InertiaConfig): ConfigProvider<ResolvedConf
rootView: config.rootView ?? 'root',
sharedData: config.sharedData || {},
versionCache,
ssr: {
enabled: config.ssr?.enabled ?? false,
pages: config.ssr?.pages,
entrypoint: config.ssr?.entrypoint ?? app.makePath('resources/ssr.ts'),
bundle: config.ssr?.bundle ?? app.makePath('ssr/ssr.js'),
},
}
})
}
43 changes: 42 additions & 1 deletion src/inertia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

/// <reference types="@adonisjs/core/providers/edge_provider" />

import type { Vite } from '@adonisjs/vite'
import type { HttpContext } from '@adonisjs/core/http'

import type { Data, MaybePromise, PageProps, ResolvedConfig, SharedData } from './types.js'

/**
Expand All @@ -25,7 +27,8 @@ export class Inertia {

constructor(
protected ctx: HttpContext,
protected config: ResolvedConfig
protected config: ResolvedConfig,
protected viteRuntime?: ReturnType<Vite['getRuntime']>
) {
this.#sharedData = config.sharedData
}
Expand Down Expand Up @@ -97,6 +100,41 @@ export class Inertia {
}
}

/**
* If the page should be rendered on the server
*/
#shouldRenderOnServer(component: string) {
const isSsrEnabled = this.config.ssr.enabled
const isSsrEnabledForPage = this.config.ssr.pages
? this.config.ssr.pages.includes(component)
: true

return isSsrEnabled && isSsrEnabledForPage
}

/**
* Render the page on the server
*
* On development, we use the Vite Runtime API
* On production, we just import and use the SSR bundle generated by Vite
*/
async #renderOnServer(pageObject: any, viewProps?: Record<string, any>) {
let render: { default: (page: any) => Promise<{ head: string; body: string }> }

if (this.viteRuntime) {
render = await this.viteRuntime.executeEntrypoint(this.config.ssr.entrypoint!)
} else {
render = await import(this.config.ssr.bundle!)
}

const result = await render.default(pageObject)

return this.ctx.view.render(this.config.rootView, {
...viewProps,
page: { ssrHead: result.head, ssrBody: result.body },
})
}

/**
* Share data for the current request.
* This data will override any shared data defined in the config.
Expand All @@ -116,6 +154,9 @@ export class Inertia {
const isInertiaRequest = !!this.ctx.request.header('x-inertia')

if (!isInertiaRequest) {
const shouldRenderOnServer = this.#shouldRenderOnServer(component)
if (shouldRenderOnServer) return this.#renderOnServer(pageObject, viewProps)

return this.ctx.view.render(this.config.rootView, { ...viewProps, page: pageObject })
}

Expand Down
12 changes: 10 additions & 2 deletions src/inertia_middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* file that was distributed with this source code.
*/

import type { Vite } from '@adonisjs/vite'
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'

Expand All @@ -27,12 +28,19 @@ declare module '@adonisjs/core/http' {
* set appropriate headers/status
*/
export default class InertiaMiddleware {
constructor(protected config: ResolvedConfig) {}
#runtime: ReturnType<Vite['getRuntime']> | undefined

constructor(
protected config: ResolvedConfig,
vite?: Vite
) {
this.#runtime = vite?.getRuntime()
}

async handle(ctx: HttpContext, next: NextFn) {
const { response, request } = ctx

ctx.inertia = new Inertia(ctx, this.config)
ctx.inertia = new Inertia(ctx, this.config, this.#runtime)

await next()

Expand Down
66 changes: 66 additions & 0 deletions src/plugins/vite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* @adonisjs/inertia
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/// <reference types="@vavite/multibuild" />

import type { PluginOption } from 'vite'

export type InertiaPluginOptions = {
ssr?:
| {
/**
* Whether or not to enable server-side rendering
*/
enabled: true

/**
* The entrypoint for the server-side rendering
*/
entrypoint: string

/**
* The output directory for the server-side rendering bundle
*/
output?: string
}
| { enabled: false }
}

/**
* Inertia plugin for Vite that is tailored for AdonisJS
*/
export default function inertia(options: InertiaPluginOptions): PluginOption {
return {
name: 'vite-plugin-inertia',
config: () => {
if (!options.ssr?.enabled) return {}

return {
buildSteps: [
{
name: 'build-client',
description: 'build inertia client bundle',
config: { build: { outDir: 'build/public/assets/' } },
},
{
name: 'build-ssr',
description: 'build inertia server bundle',
config: {
build: {
ssr: true,
outDir: options.ssr.output || 'build/ssr',
rollupOptions: { input: options.ssr.entrypoint },
},
},
},
],
}
},
}
}
31 changes: 31 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,31 @@ export interface InertiaConfig {
* Data that should be shared with all rendered pages
*/
sharedData?: SharedData

/**
* Options to configure SSR
*/
ssr?: {
/**
* Enable or disable SSR
*/
enabled: boolean

/**
* List of components that should be rendered on the server
*/
pages?: string[]

/**
* Path to the SSR entrypoint file
*/
entrypoint?: string

/**
* Path to the SSR bundled file that will be used in production
*/
bundle?: string
}
}

/**
Expand All @@ -55,4 +80,10 @@ export interface ResolvedConfig {
rootView: string
versionCache: VersionCache
sharedData: SharedData
ssr: {
enabled: boolean
entrypoint: string
pages?: string[]
bundle: string
}
}
1 change: 0 additions & 1 deletion tests/configure.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { BASE_URL } from '../tests_helpers/index.js'
import { FileSystem } from '@japa/file-system'

async function setupApp() {
console.log(BASE_URL)
const ignitor = new IgnitorFactory()
.withCoreProviders()
.withCoreConfig()
Expand Down
Loading

0 comments on commit 8106869

Please sign in to comment.