Skip to content

Commit

Permalink
[TSX] infer props and params from getStaticPaths (#873)
Browse files Browse the repository at this point in the history
* feat(tsx): automatically infer props and params from getStaticPaths

* chore: add changeset

* fix: prefix type utils with `ASTRO__`
  • Loading branch information
natemoo-re authored Oct 4, 2023
1 parent 7579d7c commit 09abfe4
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 41 deletions.
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.

0 comments on commit 09abfe4

Please sign in to comment.