Comments (18)
This is all just IMO—I'm not pushing for any particular solution here.
Another nice thing about Astro.props
is that destructuring is how folks are used to providing default values. It also seems nice that you can keep your props in a single TypeScript interface and use the name?: string
syntax rather than let name: string|undefined
.
Side-by-side comparison. A is more verbose but self-explanatory, B is more terse but has some cognitive overhead.
https://gohugo.io/templates/shortcode-templates/
TODAY: Svelte approach
---
export let greeting: string;
export let name: string|undefined = 'anonymous';
const { attrs } = Astro;
---
<div {...attrs}>{greeting} {name}!</div>
A. The Nate approach
// JavaScript
const { greeting, name = 'anonymous', ...props } = Astro.props;
---
// TypeScript
export interface Props {
greeting: string;
name?: string;
[attr?: string]: any;
}
const { greeting, name = 'anonymous', ...props } = Astro.props as Props;
---
<div {...props}>{greeting} {name}!</div>
C. Vue Approach
Taken from #139 (comment)
---
export interface Props {
greeting: string;
name?: string;
}
const { greeting, name = 'anonymous', ...props } = Astro.defineProps<Props>();
---
D. Compiled Label Approach 2 (sorta-Decorator)
---
let greeting: string = "Hello!";
---
E. Compiled Label Approach (sorta-Svelte)
---
prop: let greeting = 'Hello!'; // as string
prop: let name: string;
---
from astro.
I'm not opposed to the Svelte syntax, but another option could be to expose them through the Astro
global. e.g:
---
const { name = 'anonymous' } = Astro.props;
---
<h1>Hello {name}!</h1>
I suspect that could complicate static analysis though.
from astro.
A quick overview of what other tools do:
React and Preact
Components are functions. Not really an option for us.
import type { FunctionalComponent } from 'preact';
interface Props {
foo?: boolean
}
const Component: FunctionalComponent<Props> = (props) => { }
Svelte
Components are similar to Astro. export
keyword differentiates external props from internal variables.
<script>
export let foo: boolean = false;
</script>
Vue
Components are objects with a "props" value. TypeScript support requires users to explicitly cast their prop.
<script lang="ts">
import { PropType } from 'vue';
interface MyComplexProp { }
export default {
props: {
foo: {
type: Boolean
},
bar: {
type: Object as PropType<MyComplexProp>
required: true
}
}
};
</script>
from astro.
An alternative that occurred to me:
Type guards
type TypeGuard<T> = (value: unknown) => value is T;
interface PropOptions<T> {
default?: T;
optional?: boolean;
// ...
}
type PropFactory<T> = (options?: PropOptions<T>) => TypeGuard<T>;
type Prop<T> = PropFactory<T> | TypeGuard<T>;
interface Astro {
prop: {
string: Prop<string>;
// ...
};
}
// Example usage:
import { prop } from 'astro';
export const foo = Prop.string({ default: 'bar' }),
export const quux = Prop.string;
A prop
call exported from an Astro template declares that the template accepts a prop of [variable name]: T
. This is much closer to the current design, but much more explicit.
A convenience Astro.props
could accept a dictionary:
type PropType<T extends Prop<any>> = T extends Prop<infer U> ? U : never;
type Props<T extends Record<string, Prop<any>> = {
[K in keyof T]: PropType<T[K]>;
}
interface Astro {
props<T extends Record<string, Prop<any>>(props: T): Props<T>;
}
// Example usage:
import { prop, props } from 'astro';
export const { foo, quux } = props({
foo: prop.string({ default: 'bar' }),
quux: prop.string,
});
And Astro could either provide a set of common prop factories, or defer to existing libraries like io-ts or zod (with whatever their own semantics). These prop types could optionally be used for runtime/build time prop validation, and would provide static type checking by default.
Edit: sorry if this is a mess, hard to write even pseudo-code on mobile.
from astro.
Why not just have the same api, but choose an acceptable "keyword" for props like prop default hello = "hello world"
instead of export default hello = "hello world"
and exports can be used to expose data from components?
from astro.
Following down that trail, a label could avoid introducing a new keyword. A bit like what Svelte does to identify reactive expressions with $:
prop: let showTitle = false;
from astro.
RFC Call Consensus: move forward with the A. The Nate approach
as outlined above, with smaller details (ex: Astro.props
vs. Astro.defineProps
) to be discussed and resolved separately.
from astro.
another option could be to expose them through the
Astro
global
@mxmul I like that option quite a bit—it avoids the pitfalls of more "magical" approaches.
Two big questions would be
- How to validate prop types—since all frontmatter is TypeScript, is it just
export interface Props {}
? - How to support TypeScript—just cast
Astro.props as Props
?
---
export interface Props {
name?: string;
}
const { name = 'anonymous' } = Astro.props as Props;
---
<h1>Hello {name}!</h1>
from astro.
another option could be to expose them through the
Astro
global
I think another problem with using the Astro
global is that now it becomes access/invocation context-dependent, which is I believe one thing people tend to avoid with normal variables and similarly the reason people explicitly designed import.meta
to host context-specific metadata.
I am actually okay with export
, even if it might look inverted a little bit (in that sense we can explain to the user that we are exporting the "shape" of the component so it could be manipulated from the outside). They are built with static analysis in mind and is probably having minimal friction with JS itself as we are not going to design some new magical syntax.
from astro.
I quite like the Svelte way. Deconstructing from Astro.props
feels like a bit too much syntax for something as common as declaring a prop.
from astro.
One thing that deconstructing from Astro.props
gives us is a straight-forward API for supporting props that aren't explicitly defined.
---
const { name = 'anonymous', ...props } = Astro.props;
---
<div {...props}>Hello {name}!</div>
The alternative approaches:
- Svelte exposes
$$props
and$$restProps
as globals. - Vue has a similar approach with
$attrs
(essentially the same as$$restProps
) but automatically applies them if you have a single root node. There are options to disable automatic inheritance.
IMO it is better to be explicit like Svelte, but I do like the concept/name of Vue's attrs
. In our case, a mixed approach could look something like:
---
export let name = 'anonymous';
const { attrs } = Astro;
---
<div {...attrs}>Hello {name}!</div>
from astro.
I like the general direction of option B, as it doesn't let you repeat prop names, instead you just name and type them once. In the case of export let name: string|undefined
, I'm pretty sure you could just do export let name: string = ''
, so the default value would just be an empty string. Maybe I'm missing something? Curious to hear your thoughts.
from astro.
So since I prompted this issue, I’ve been mulling it over when I’ve had some free brain cycles over the last week. I’ve already mentioned in a message to @natemoo-re that I think a more holistic Astro format RFC could be beneficial, as the format’s evolution feels somewhat piecemeal. But I’ll try to stay focused on the topic of props (narrator voice: they didn't stay focused on props).
One of the things that immediately felt like a breath of fresh air about Astro on first approach is the sentiment of embracing HTML. I think there’s an opportunity here to embrace it further: Web Components.
astro-prop
An astro-prop
element is semantically similar to a meta
tag, but the metadata it communicates is its external interface.
- An Astro component could declare its props with custom
astro-prop
elements, e.g.<astro-prop name="foo">
. - Optional default values would be expressed as attributes or expressions on a
default
attribute. - Optional static types could be expressed either as a
type
attribute or a correspondingexport type
referencing any valid-in-scope type.
Alternatives
The biggest drawback to the custom element proposal IMO is that HTML syntax can be verbose. I think it’s philosophically the best fit, but I want to acknowledge that and provide similarly philosophically aligned ideas I thought of but like less.
- Props could be expressed as some form of Astro-namespaced attribute on the containing element. Downsides: it’s harder to add additional metadata (eg static types), requires a containing element.
- Props could be expressed by exporting some declaration on a well-known variable name, static type name or class instance. Downsides: naming is hard, need to choose a declaration format suitable for everyone, namespace collisions, still relies on implicit conventions. In the static type name case, imposes static types where they might not be adopted otherwise (even if I wish they would).
slot
🎶 Whoa, we’re halfway there 🎶
- Children already use the relevant standard:
slot
! - Children static types could similarly be expressed on a
type
attribute. - Default children could be expressed as children of the
slot
. (It’s not clear from a cursory search whether this is already supported, but if not it may be expected.)
Or...
I know I said I wanted to focus on the props issue and not overload this with holistic format feedback. This is a less fully formed proposal but it’s the backdrop for everything above. Should the holistic approach be open to discussion, there’s another alternative which might be significantly more attractive: go all in on HTML. Instead of mixing features of several formats (Markdown-style frontmatter fences, ESM-style imperative code, Svelte-style prop exports and CSS isolation, JSX naming conventions and expression syntax):
- Astro components are a strict, limited superset of HTML.
- They’re fragments that can be imported, referenced and mounted as Web Components, within component-local
script
tags, with an appropriatetype
. (Alternately anastro-script
.) - Similar facilities for scoped styles.
- The superset adds a limited, well-defined, expression syntax for attributes with similar limits to the current syntax.
- Addresses the control flow elephant in the room by adopting compiler-friendly components similar to those provided by Marko and Solid.
from astro.
I’m a little nervous proposing something so big on this still new project. But I want to also give credit where it’s due and point out that one important reason TypeScript succeeded is because the team and language got really serious about coalescing with (and participating in) the standards process. There’s some cruft where they tried some novel concepts early on that were too widespread to remove (and one of them, decorators, so widespread it’s probably delayed the standards process!). To everyone’s benefit they re-oriented their process to favor standards first and help shape the runtime rather than invent new and incompatible things. I think a project like Astro has a special opportunity to benefit from that lesson given where it sits between so many other projects and idioms.
from astro.
:2cents:
I love option A from #139 (comment)
it doesn't do the weird svelte reversal of export and it frees up the real exports for #309 too 🎉
from astro.
Vue's new experimental <script setup>
syntax uses a function defineProps
for declaring props. Maybe something like that would be nicer in TypeScript compared to casting Astro.props as Props
.
Vue example:
<script setup lang="ts">
import { defineProps } from 'vue';
const props = defineProps<{
foo: string;
bar?: number;
}>();
</script>
from astro.
I just realized that if Astro.props
is given the type any
, I think you can write
const props: Props = Astro.props
instead of
const props = Astro.props as Props
. It’s a small difference, but it feels better to me than using a type cast.
With that in mind, I think Astro.props
is better than my Astro.defineProps
suggestion, even in TypeScript. (Vue also has other reasons for using defineProps
that don’t apply to Astro.)
from astro.
yup, agreed that both forms should be supported.
from astro.
Related Issues (20)
- Passed a `encoded url` to endpont as params, get a error HOT 1
- resolve unprefixed routes when `prefixDefaultLocale: true` HOT 1
- SSL errors with hybird/prerendered 404 pages HOT 5
- Astro DB build fails with Cloudflare adapter HOT 32
- Elements don't render as expected. HOT 14
- Dynamic tags do not pass along style `define:vars` HOT 5
- <script tag import> in {curly brace syntax} is not processed like a simple <script tag import> in Astro template file HOT 2
- Hard to debug collection render() error HOT 9
- Astro cannot load Sharp HOT 3
- Prod build collections cache can be modified
- import.meta.glob with path aliases HOT 5
- Image does not appear on production mode HOT 7
- Show console output even set `--silent`. HOT 6
- Trailing Slash Should Be Added Automatically HOT 4
- solid-js onCleanup is called on the server and it breaks the app HOT 1
- Cookie values getting URL encoded HOT 6
- Cannot seed Astro DB when seed file is under `srcDir`
- SVG-animations don't work after navigating HOT 2
- The errorOverlay's themeSwitch does not work as expected. HOT 1
- Empty paragraphs around React element, when using `dangerouslySetInnerHtml` HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from astro.