Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(define-page): support JSX #514

Merged
merged 10 commits into from
Mar 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/codegen/generateRouteRecords.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getLang } from '@vue-macros/common'
import type { TreeNode } from '../core/tree'
import { ImportsMap } from '../core/utils'
import { type ResolvedOptions } from '../options'
Expand Down Expand Up @@ -32,9 +33,11 @@ ${node
for (const [name, filePath] of node.value.components) {
const pageDataImport = `_definePage_${name}_${importsMap.size}`
definePageDataList.push(pageDataImport)
const lang = getLang(filePath)
importsMap.addDefault(
// TODO: apply the language used in the sfc
`${filePath}?definePage&vue&lang.tsx`,
`${filePath}?definePage&` +
(lang === 'vue' ? 'vue&lang.tsx' : `lang.${lang}`),
pageDataImport
)
}
Expand Down
7 changes: 7 additions & 0 deletions src/core/__snapshots__/definePage.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,10 @@ const b = 1
</template>
"
`;

exports[`definePage > works with jsx 1`] = `
"export default {
name: 'custom',
path: '/custom',
}"
`;
6 changes: 3 additions & 3 deletions src/core/definePage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ definePage({
})
})

it.todo('works with jsx', async () => {
it('works with jsx', async () => {
const code = `
const a = 1
definePage({
Expand All @@ -146,11 +146,11 @@ definePage({
`,
result = (await definePageTransform({
code,
id: 'src/pages/basic.vue?definePage&jsx',
id: 'src/pages/basic.jsx?definePage&lang.jsx',
})) as Exclude<TransformResult, string>
expect(result).toBeDefined()
expect(result).toHaveProperty('code')
expect(result?.code).toMatchInlineSnapshot()
expect(result?.code).toMatchSnapshot()
})

it('throws if definePage uses a variable from the setup', async () => {
Expand Down
81 changes: 45 additions & 36 deletions src/core/definePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import {
parseSFC,
MagicString,
checkInvalidScopeReference,
babelParse,
getLang,
} from '@vue-macros/common'
import type { Thenable, TransformResult } from 'unplugin'
import type {
CallExpression,
Node,
ObjectProperty,
Program,
Statement,
StringLiteral,
} from '@babel/types'
Expand All @@ -25,6 +28,39 @@ function isStringLiteral(node: Node | null | undefined): node is StringLiteral {
return node?.type === 'StringLiteral'
}

/**
* Generate the ast from a code string and an id. Works with SFC and non-SFC files.
*/
function getCodeAst(code: string, id: string) {
let offset = 0
let ast: Program | undefined
const lang = getLang(id.split(MACRO_DEFINE_PAGE_QUERY)[0]!)
if (lang === 'vue') {
const sfc = parseSFC(code, id)
if (sfc.scriptSetup) {
ast = sfc.getSetupAst()
offset = sfc.scriptSetup.loc.start.offset
} else if (sfc.script) {
ast = sfc.getScriptAst()
offset = sfc.script.loc.start.offset
}
} else if (/[jt]sx?$/.test(lang)) {
ast = babelParse(code, lang)
}

const definePageNodes: CallExpression[] = (ast?.body || [])
.map((node) => {
const definePageCallNode =
node.type === 'ExpressionStatement' ? node.expression : node
return isCallOf(definePageCallNode, MACRO_DEFINE_PAGE)
? definePageCallNode
: null
})
.filter((node) => !!node)

return { ast, offset, definePageNodes }
}

export function definePageTransform({
code,
id,
Expand All @@ -41,20 +77,8 @@ export function definePageTransform({
return isExtractingDefinePage ? 'export default {}' : undefined
}

// TODO: handle also non SFC

const sfc = parseSFC(code, id)
if (!sfc.scriptSetup) return

const { scriptSetup, getSetupAst } = sfc
const setupAst = getSetupAst()

const definePageNodes = (setupAst?.body || ([] as Node[]))
.map((node) => {
if (node.type === 'ExpressionStatement') node = node.expression
return isCallOf(node, MACRO_DEFINE_PAGE) ? node : null
})
.filter((node): node is CallExpression => !!node)
const { ast, offset, definePageNodes } = getCodeAst(code, id)
if (!ast) return

if (!definePageNodes.length) {
return isExtractingDefinePage
Expand All @@ -67,7 +91,6 @@ export function definePageTransform({
}

const definePageNode = definePageNodes[0]!
const setupOffset = scriptSetup.loc.start.offset

// we only want the page info
if (isExtractingDefinePage) {
Expand All @@ -82,13 +105,13 @@ export function definePageTransform({
)
}

const scriptBindings = setupAst?.body ? getIdentifiers(setupAst.body) : []
const scriptBindings = ast.body ? getIdentifiers(ast.body) : []

// this will throw if a property from the script setup is used in definePage
checkInvalidScopeReference(routeRecord, MACRO_DEFINE_PAGE, scriptBindings)

s.remove(setupOffset + routeRecord.end!, code.length)
s.remove(0, setupOffset + routeRecord.start!)
s.remove(offset + routeRecord.end!, code.length)
s.remove(0, offset + routeRecord.start!)
s.prepend(`export default `)

// find all static imports and filter out the ones that are not used
Expand Down Expand Up @@ -156,11 +179,8 @@ export function definePageTransform({

const s = new MagicString(code)

// s.removeNode(definePageNode, { offset: setupOffset })
s.remove(
setupOffset + definePageNode.start!,
setupOffset + definePageNode.end!
)
// s.removeNode(definePageNode, { offset })
s.remove(offset + definePageNode.start!, offset + definePageNode.end!)

return generateTransform(s, id)
}
Expand All @@ -172,19 +192,8 @@ export function extractDefinePageNameAndPath(
): { name?: string; path?: string } | null | undefined {
if (!sfcCode.includes(MACRO_DEFINE_PAGE)) return

const sfc = parseSFC(sfcCode, id)

if (!sfc.scriptSetup) return

const { getSetupAst } = sfc
const setupAst = getSetupAst()

const definePageNodes = (setupAst?.body ?? ([] as Node[]))
.map((node) => {
if (node.type === 'ExpressionStatement') node = node.expression
return isCallOf(node, MACRO_DEFINE_PAGE) ? node : null
})
.filter((node): node is CallExpression => !!node)
const { ast, definePageNodes } = getCodeAst(sfcCode, id)
if (!ast) return

if (!definePageNodes.length) {
return
Expand Down