Skip to content

Commit 746a95d

Browse files
committed
feat: add new @inertia tag parameters
1 parent d4bcbd9 commit 746a95d

File tree

7 files changed

+223
-55
lines changed

7 files changed

+223
-55
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"c8": "^8.0.1",
5656
"copyfiles": "^2.4.1",
5757
"del-cli": "^5.1.0",
58+
"edge-parser": "^9.0.0",
5859
"edge.js": "^6.0.0",
5960
"eslint": "^8.53.0",
6061
"get-port": "^7.0.0",
@@ -69,6 +70,7 @@
6970
"dependencies": {
7071
"@poppinss/utils": "^6.5.1",
7172
"crc-32": "^1.2.2",
73+
"edge-error": "^4.0.0",
7274
"html-entities": "^2.4.0",
7375
"qs": "^6.11.2"
7476
},

providers/inertia_provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default class InertiaProvider {
2626
protected async registerEdgePlugin() {
2727
try {
2828
const edgeExports = await import('edge.js')
29-
const { edgePluginInertia } = await import('../src/plugins/edge.js')
29+
const { edgePluginInertia } = await import('../src/plugins/edge/plugin.js')
3030

3131
edgeExports.default.use(edgePluginInertia())
3232
} catch {}

src/plugins/edge.ts

Lines changed: 0 additions & 50 deletions
This file was deleted.

src/plugins/edge/plugin.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* @adonisjs/inertia
3+
*
4+
* (c) AdonisJS
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { encode } from 'html-entities'
11+
import type { PluginFn } from 'edge.js/types'
12+
13+
import debug from '../../debug.js'
14+
import { inertiaHeadTag, inertiaTag } from './tags.js'
15+
16+
/**
17+
* Register the Inertia tags and globals within Edge
18+
*/
19+
export const edgePluginInertia: () => PluginFn<undefined> = () => {
20+
return (edge) => {
21+
debug('sharing globals and inertia tags with edge')
22+
23+
/**
24+
* Register the `inertia` global used by the `@inertia` tag
25+
*/
26+
edge.global(
27+
'inertia',
28+
(page: Record<string, unknown> = {}, attributes: Record<string, any> = {}) => {
29+
if (page.ssrBody) return page.ssrBody
30+
31+
const className = attributes?.class ? ` class="${attributes.class}"` : ''
32+
const id = attributes?.id ? ` id="${attributes.id}"` : ' id="app"'
33+
const tag = attributes?.as || 'div'
34+
const dataPage = encode(JSON.stringify(page))
35+
36+
return `<${tag}${id}${className} data-page="${dataPage}"></${tag}>`
37+
}
38+
)
39+
40+
/**
41+
* Register the `inertiaHead` global used by the `@inertiaHead` tag
42+
*/
43+
edge.global('inertiaHead', (page: Record<string, unknown>) => {
44+
const { ssrHead = [] }: { ssrHead?: string[] } = page || {}
45+
return ssrHead.join('\n')
46+
})
47+
48+
/**
49+
* Register tags
50+
*/
51+
edge.registerTag(inertiaHeadTag)
52+
edge.registerTag(inertiaTag)
53+
}
54+
}

src/plugins/edge/tags.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* @adonisjs/inertia
3+
*
4+
* (c) AdonisJS
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { EdgeError } from 'edge-error'
11+
import { TagContract } from 'edge.js/types'
12+
13+
import { isSubsetOf } from './utils.js'
14+
15+
/**
16+
* `@inertia` tag is used to generate the root element with
17+
* encoded page data.
18+
*
19+
* We can pass an object with `as`, `class` and `id` properties
20+
* - `as` is the tag name for the root element. Defaults to `div`
21+
* - `class` is the class name for the root element.
22+
* - `id` is the id for the root element. Defaults to `app`
23+
*/
24+
export const inertiaTag: TagContract = {
25+
block: false,
26+
tagName: 'inertia',
27+
seekable: true,
28+
compile(parser, buffer, { filename, loc, properties }) {
29+
/**
30+
* Handle case where no arguments are passed to the tag
31+
*/
32+
if (properties.jsArg.trim() === '') {
33+
buffer.writeExpression(`out += state.inertia(state.page)`, filename, loc.start.line)
34+
return
35+
}
36+
37+
/**
38+
* Get AST for the arguments and ensure it is a valid object expression
39+
*/
40+
properties.jsArg = `(${properties.jsArg})`
41+
const parsed = parser.utils.transformAst(
42+
parser.utils.generateAST(properties.jsArg, loc, filename),
43+
filename,
44+
parser
45+
)
46+
47+
isSubsetOf(parsed, ['ObjectExpression'], () => {
48+
const { line, col } = parser.utils.getExpressionLoc(parsed)
49+
50+
throw new EdgeError(
51+
`"${properties.jsArg}" is not a valid argument for @inertia`,
52+
'E_UNALLOWED_EXPRESSION',
53+
{ line, col, filename }
54+
)
55+
})
56+
57+
/**
58+
* Stringify the object expression and pass it to the `inertia` helper
59+
*/
60+
const attributes = parser.utils.stringify(parsed)
61+
buffer.writeExpression(
62+
`out += state.inertia(state.page, ${attributes})`,
63+
filename,
64+
loc.start.line
65+
)
66+
},
67+
}
68+
69+
/**
70+
* `@inertiaHead` tag
71+
*/
72+
export const inertiaHeadTag: TagContract = {
73+
block: false,
74+
tagName: 'inertiaHead',
75+
seekable: false,
76+
compile(_, buffer, { filename, loc }) {
77+
buffer.writeExpression(`out += state.inertiaHead(state.page)`, filename, loc.start.line)
78+
},
79+
}

src/plugins/edge/utils.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { expressions as expressionsList } from 'edge-parser'
2+
3+
type ExpressionList = readonly (keyof typeof expressionsList | 'ObjectPattern' | 'ArrayPattern')[]
4+
5+
/**
6+
* Validates the expression type to be part of the allowed
7+
* expressions only.
8+
*
9+
* The filename is required to report errors.
10+
*
11+
* ```js
12+
* isNotSubsetOf(expression, ['Literal', 'Identifier'], () => {})
13+
* ```
14+
*/
15+
export function isSubsetOf(
16+
expression: any,
17+
expressions: ExpressionList,
18+
errorCallback: () => void
19+
) {
20+
if (!expressions.includes(expression.type)) {
21+
errorCallback()
22+
}
23+
}

tests/plugins/edge.plugin.spec.ts

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@
1010
import { Edge } from 'edge.js'
1111
import { test } from '@japa/runner'
1212

13-
import { edgePluginInertia } from '../../src/plugins/edge.js'
13+
import { edgePluginInertia } from '../../src/plugins/edge/plugin.js'
1414

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

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

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

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

27-
const html = await edge.renderRaw(`@inertia`, {
27+
const html = await edge.renderRaw(`@inertia()`, {
2828
page: { foo: 'bar' },
2929
})
3030

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

36+
test('throws if passing invalid argument', async () => {
37+
const edge = Edge.create().use(edgePluginInertia())
38+
39+
await edge.renderRaw(`@inertia('foo')`, { page: {} })
40+
}).throws(`"('foo')" is not a valid argument for @inertia`)
41+
42+
test('pass class to @inertia', async ({ assert }) => {
43+
const edge = Edge.create().use(edgePluginInertia())
44+
45+
const html = await edge.renderRaw(`@inertia({ class: 'foo' })`, {
46+
page: {},
47+
})
48+
49+
assert.deepEqual(html.split('\n'), ['<div id="app" class="foo" data-page="{}"></div>'])
50+
})
51+
52+
test('pass id to @inertia', async ({ assert }) => {
53+
const edge = Edge.create().use(edgePluginInertia())
54+
55+
const html = await edge.renderRaw(`@inertia({ id: 'foo' })`, {
56+
page: {},
57+
})
58+
59+
assert.deepEqual(html.split('\n'), ['<div id="foo" data-page="{}"></div>'])
60+
})
61+
62+
test('works with variable reference', async ({ assert }) => {
63+
const edge = Edge.create().use(edgePluginInertia())
64+
65+
const html = await edge.renderRaw(`@inertia({ class: mainClass })`, {
66+
mainClass: 'foo bar',
67+
page: {},
68+
})
69+
70+
assert.deepEqual(html.split('\n'), ['<div id="app" class="foo bar" data-page="{}"></div>'])
71+
})
72+
73+
test('works with function call', async ({ assert }) => {
74+
const edge = Edge.create().use(edgePluginInertia())
75+
76+
const html = await edge.renderRaw(`@inertia({ class: mainClass() })`, {
77+
mainClass() {
78+
return 'foo bar'
79+
},
80+
page: {},
81+
})
82+
83+
assert.deepEqual(html.split('\n'), ['<div id="app" class="foo bar" data-page="{}"></div>'])
84+
})
85+
86+
test('render root div as another tag', async ({ assert }) => {
87+
const edge = Edge.create().use(edgePluginInertia())
88+
89+
const html = await edge.renderRaw(`@inertia({ as: 'main' })`, {
90+
page: {},
91+
})
92+
93+
assert.deepEqual(html.split('\n'), ['<main id="app" data-page="{}"></main>'])
94+
})
95+
3696
test('@inertia just insert the ssrBody if present', async ({ assert }) => {
3797
const edge = Edge.create().use(edgePluginInertia())
3898

39-
const html = await edge.renderRaw(`@inertia`, {
99+
const html = await edge.renderRaw(`@inertia()`, {
40100
page: { ssrBody: '<div>foo</div>' },
41101
})
42102

0 commit comments

Comments
 (0)