Skip to content

Commit

Permalink
feat: add new @inertia tag parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
Julien-R44 committed Nov 21, 2023
1 parent d4bcbd9 commit 746a95d
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 55 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"c8": "^8.0.1",
"copyfiles": "^2.4.1",
"del-cli": "^5.1.0",
"edge-parser": "^9.0.0",
"edge.js": "^6.0.0",
"eslint": "^8.53.0",
"get-port": "^7.0.0",
Expand All @@ -69,6 +70,7 @@
"dependencies": {
"@poppinss/utils": "^6.5.1",
"crc-32": "^1.2.2",
"edge-error": "^4.0.0",
"html-entities": "^2.4.0",
"qs": "^6.11.2"
},
Expand Down
2 changes: 1 addition & 1 deletion providers/inertia_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default class InertiaProvider {
protected async registerEdgePlugin() {
try {
const edgeExports = await import('edge.js')
const { edgePluginInertia } = await import('../src/plugins/edge.js')
const { edgePluginInertia } = await import('../src/plugins/edge/plugin.js')

edgeExports.default.use(edgePluginInertia())
} catch {}
Expand Down
50 changes: 0 additions & 50 deletions src/plugins/edge.ts

This file was deleted.

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

import { encode } from 'html-entities'
import type { PluginFn } from 'edge.js/types'

import debug from '../../debug.js'
import { inertiaHeadTag, inertiaTag } from './tags.js'

/**
* Register the Inertia tags and globals within Edge
*/
export const edgePluginInertia: () => PluginFn<undefined> = () => {
return (edge) => {
debug('sharing globals and inertia tags with edge')

/**
* Register the `inertia` global used by the `@inertia` tag
*/
edge.global(
'inertia',
(page: Record<string, unknown> = {}, attributes: Record<string, any> = {}) => {
if (page.ssrBody) return page.ssrBody

const className = attributes?.class ? ` class="${attributes.class}"` : ''
const id = attributes?.id ? ` id="${attributes.id}"` : ' id="app"'
const tag = attributes?.as || 'div'
const dataPage = encode(JSON.stringify(page))

return `<${tag}${id}${className} data-page="${dataPage}"></${tag}>`
}
)

/**
* Register the `inertiaHead` global used by the `@inertiaHead` tag
*/
edge.global('inertiaHead', (page: Record<string, unknown>) => {
const { ssrHead = [] }: { ssrHead?: string[] } = page || {}
return ssrHead.join('\n')
})

/**
* Register tags
*/
edge.registerTag(inertiaHeadTag)
edge.registerTag(inertiaTag)
}
}
79 changes: 79 additions & 0 deletions src/plugins/edge/tags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* @adonisjs/inertia
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { EdgeError } from 'edge-error'
import { TagContract } from 'edge.js/types'

import { isSubsetOf } from './utils.js'

/**
* `@inertia` tag is used to generate the root element with
* encoded page data.
*
* We can pass an object with `as`, `class` and `id` properties
* - `as` is the tag name for the root element. Defaults to `div`
* - `class` is the class name for the root element.
* - `id` is the id for the root element. Defaults to `app`
*/
export const inertiaTag: TagContract = {
block: false,
tagName: 'inertia',
seekable: true,
compile(parser, buffer, { filename, loc, properties }) {
/**
* Handle case where no arguments are passed to the tag
*/
if (properties.jsArg.trim() === '') {
buffer.writeExpression(`out += state.inertia(state.page)`, filename, loc.start.line)
return
}

/**
* Get AST for the arguments and ensure it is a valid object expression
*/
properties.jsArg = `(${properties.jsArg})`
const parsed = parser.utils.transformAst(
parser.utils.generateAST(properties.jsArg, loc, filename),
filename,
parser
)

isSubsetOf(parsed, ['ObjectExpression'], () => {
const { line, col } = parser.utils.getExpressionLoc(parsed)

throw new EdgeError(
`"${properties.jsArg}" is not a valid argument for @inertia`,
'E_UNALLOWED_EXPRESSION',
{ line, col, filename }
)
})

/**
* Stringify the object expression and pass it to the `inertia` helper
*/
const attributes = parser.utils.stringify(parsed)
buffer.writeExpression(
`out += state.inertia(state.page, ${attributes})`,
filename,
loc.start.line
)
},
}

/**
* `@inertiaHead` tag
*/
export const inertiaHeadTag: TagContract = {
block: false,
tagName: 'inertiaHead',
seekable: false,
compile(_, buffer, { filename, loc }) {
buffer.writeExpression(`out += state.inertiaHead(state.page)`, filename, loc.start.line)
},
}
23 changes: 23 additions & 0 deletions src/plugins/edge/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expressions as expressionsList } from 'edge-parser'

type ExpressionList = readonly (keyof typeof expressionsList | 'ObjectPattern' | 'ArrayPattern')[]

/**
* Validates the expression type to be part of the allowed
* expressions only.
*
* The filename is required to report errors.
*
* ```js
* isNotSubsetOf(expression, ['Literal', 'Identifier'], () => {})
* ```
*/
export function isSubsetOf(
expression: any,
expressions: ExpressionList,
errorCallback: () => void
) {
if (!expressions.includes(expression.type)) {
errorCallback()
}
}
68 changes: 64 additions & 4 deletions tests/plugins/edge.plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@
import { Edge } from 'edge.js'
import { test } from '@japa/runner'

import { edgePluginInertia } from '../../src/plugins/edge.js'
import { edgePluginInertia } from '../../src/plugins/edge/plugin.js'

test.group('Edge plugin', () => {
test('@inertia generate a root div with data-page', async ({ assert }) => {
const edge = Edge.create().use(edgePluginInertia())

const html = await edge.renderRaw(`@inertia`, { page: {} })
const html = await edge.renderRaw(`@inertia()`, { page: {} })

assert.deepEqual(html.split('\n'), ['<div id="app" data-page="{}"></div>'])
})

test('@inertia generate a root dive with data-page filled and encoded', async ({ assert }) => {
const edge = Edge.create().use(edgePluginInertia())

const html = await edge.renderRaw(`@inertia`, {
const html = await edge.renderRaw(`@inertia()`, {
page: { foo: 'bar' },
})

Expand All @@ -33,10 +33,70 @@ test.group('Edge plugin', () => {
])
})

test('throws if passing invalid argument', async () => {
const edge = Edge.create().use(edgePluginInertia())

await edge.renderRaw(`@inertia('foo')`, { page: {} })
}).throws(`"('foo')" is not a valid argument for @inertia`)

test('pass class to @inertia', async ({ assert }) => {
const edge = Edge.create().use(edgePluginInertia())

const html = await edge.renderRaw(`@inertia({ class: 'foo' })`, {
page: {},
})

assert.deepEqual(html.split('\n'), ['<div id="app" class="foo" data-page="{}"></div>'])
})

test('pass id to @inertia', async ({ assert }) => {
const edge = Edge.create().use(edgePluginInertia())

const html = await edge.renderRaw(`@inertia({ id: 'foo' })`, {
page: {},
})

assert.deepEqual(html.split('\n'), ['<div id="foo" data-page="{}"></div>'])
})

test('works with variable reference', async ({ assert }) => {
const edge = Edge.create().use(edgePluginInertia())

const html = await edge.renderRaw(`@inertia({ class: mainClass })`, {
mainClass: 'foo bar',
page: {},
})

assert.deepEqual(html.split('\n'), ['<div id="app" class="foo bar" data-page="{}"></div>'])
})

test('works with function call', async ({ assert }) => {
const edge = Edge.create().use(edgePluginInertia())

const html = await edge.renderRaw(`@inertia({ class: mainClass() })`, {
mainClass() {
return 'foo bar'
},
page: {},
})

assert.deepEqual(html.split('\n'), ['<div id="app" class="foo bar" data-page="{}"></div>'])
})

test('render root div as another tag', async ({ assert }) => {
const edge = Edge.create().use(edgePluginInertia())

const html = await edge.renderRaw(`@inertia({ as: 'main' })`, {
page: {},
})

assert.deepEqual(html.split('\n'), ['<main id="app" data-page="{}"></main>'])
})

test('@inertia just insert the ssrBody if present', async ({ assert }) => {
const edge = Edge.create().use(edgePluginInertia())

const html = await edge.renderRaw(`@inertia`, {
const html = await edge.renderRaw(`@inertia()`, {
page: { ssrBody: '<div>foo</div>' },
})

Expand Down

0 comments on commit 746a95d

Please sign in to comment.