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

[TSX] infer props and params from getStaticPaths #873

Merged
merged 3 commits into from
Oct 4, 2023
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: 5 additions & 0 deletions .changeset/grumpy-spoons-rescue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/compiler': minor
---

Adds ability for TSX output to automatically infer `Astro.props` and `Astro.params` when `getStaticPaths` is used
15 changes: 15 additions & 0 deletions internal/js_scanner/js_scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,21 @@ func HoistImports(source []byte) HoistedScripts {
return HoistedScripts{Hoisted: imports, HoistedLocs: importLocs, Body: body, BodyLocs: bodyLocs}
}

func HasGetStaticPaths(source []byte) bool {
ident := []byte("getStaticPaths")
if !bytes.Contains(source, ident) {
return false
}

exports := HoistExports(source)
for _, statement := range exports.Hoisted {
if bytes.Contains(statement, ident) {
return true
}
}
return false
}

type Props struct {
Ident string
Statement string
Expand Down
29 changes: 25 additions & 4 deletions internal/printer/print-to-tsx.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ func getTextType(n *astro.Node) TextType {
func renderTsx(p *printer, n *Node) {
// Root of the document, print all children
if n.Type == DocumentNode {
props := js_scanner.GetPropsType([]byte(p.sourcetext))
source := []byte(p.sourcetext)
props := js_scanner.GetPropsType(source)
hasGetStaticPaths := js_scanner.HasGetStaticPaths(source)
hasChildren := false
for c := n.FirstChild; c != nil; c = c.NextSibling {
// This checks for the first node that comes *after* the frontmatter
Expand Down Expand Up @@ -134,15 +136,34 @@ func renderTsx(p *printer, n *Node) {
p.print("</Fragment>\n")
}
componentName := getTSXComponentName(p.opts.Filename)
propsIdent := props.Ident
paramsIdent := ""
if hasGetStaticPaths {
paramsIdent = "ASTRO__Get<ASTRO__InferredGetStaticPath, 'params'>"
if propsIdent == "Record<string, any>" {
propsIdent = "ASTRO__Get<ASTRO__InferredGetStaticPath, 'props'>"
}
}

p.print(fmt.Sprintf("export default function %s%s(_props: %s%s): any {}\n", componentName, props.Statement, props.Ident, props.Generics))
if props.Ident != "Record<string, any>" {
p.print(fmt.Sprintf("export default function %s%s(_props: %s%s): any {}\n", componentName, props.Statement, propsIdent, props.Generics))
if hasGetStaticPaths {
p.printf(`type ASTRO__ArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
type ASTRO__Flattened<T> = T extends Array<infer U> ? ASTRO__Flattened<U> : T;
type ASTRO__InferredGetStaticPath = ASTRO__Flattened<ASTRO__ArrayElement<Awaited<ReturnType<typeof getStaticPaths>>>>;
type ASTRO__Get<T, K> = T extends undefined ? undefined : K extends keyof T ? T[K] : never;%s`, "\n")
}

if propsIdent != "Record<string, any>" {
p.printf(`/**
* Astro global available in all contexts in .astro files
*
* [Astro documentation](https://docs.astro.build/reference/api-reference/#astro-global)
*/
declare const Astro: Readonly<import('astro').AstroGlobal<%s, typeof %s>>`, props.Ident, componentName)
declare const Astro: Readonly<import('astro').AstroGlobal<%s, typeof %s`, propsIdent, componentName)
if paramsIdent != "" {
p.printf(", %s", paramsIdent)
}
p.print(">>")
}
return
}
Expand Down
79 changes: 79 additions & 0 deletions packages/compiler/test/tsx/props-and-getStaticPaths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { convertToTSX } from '@astrojs/compiler';
import { test } from 'uvu';
import * as assert from 'uvu/assert';

function getPrefix({
props = `ASTRO__Get<ASTRO__InferredGetStaticPath, 'props'>`,
component = '__AstroComponent_',
params = `ASTRO__Get<ASTRO__InferredGetStaticPath, 'params'>`,
}: {
props?: string;
component?: string;
params?: string;
} = {}) {
return `/**
* Astro global available in all contexts in .astro files
*
* [Astro documentation](https://docs.astro.build/reference/api-reference/#astro-global)
*/
declare const Astro: Readonly<import('astro').AstroGlobal<${props}, typeof ${component}${params ? `, ${params}` : ''}>>`;
}

function getSuffix() {
return `type ASTRO__ArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
type ASTRO__Flattened<T> = T extends Array<infer U> ? ASTRO__Flattened<U> : T;
type ASTRO__InferredGetStaticPath = ASTRO__Flattened<ASTRO__ArrayElement<Awaited<ReturnType<typeof getStaticPaths>>>>;
type ASTRO__Get<T, K> = T extends undefined ? undefined : K extends keyof T ? T[K] : never;`;
}

test('explicit props definition', async () => {
const input = `---
interface Props {};
export function getStaticPaths() {
return {};
}
---

<div></div>`;
const output =
'\n' +
`interface Props {};
export function getStaticPaths() {
return {};
}

"";<Fragment>
<div></div>
</Fragment>
export default function __AstroComponent_(_props: Props): any {}
${getSuffix()}
${getPrefix({ props: 'Props' })}`;
const { code } = await convertToTSX(input, { sourcemap: 'external' });
assert.snapshot(code, output, `expected code to match snapshot`);
});

test('inferred props', async () => {
const input = `---
export function getStaticPaths() {
return {};
}
---

<div></div>`;
const output =
'\n' +
`export function getStaticPaths() {
return {};
}

"";<Fragment>
<div></div>
</Fragment>
export default function __AstroComponent_(_props: ASTRO__Get<ASTRO__InferredGetStaticPath, 'props'>): any {}
${getSuffix()}
${getPrefix()}`;
const { code } = await convertToTSX(input, { sourcemap: 'external' });
assert.snapshot(code, output, `expected code to match snapshot`);
});

test.run();
37 changes: 0 additions & 37 deletions packages/compiler/test/tsx/props-and-staticPaths.ts

This file was deleted.

Loading