Git Product home page Git Product logo

Comments (18)

natemoo-re avatar natemoo-re commented on May 3, 2024 5

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.

mxmul avatar mxmul commented on May 3, 2024 2

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.

natemoo-re avatar natemoo-re commented on May 3, 2024 1

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.

eyelidlessness avatar eyelidlessness commented on May 3, 2024 1

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.

jasikpark avatar jasikpark commented on May 3, 2024 1

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.

romaricpascal avatar romaricpascal commented on May 3, 2024 1

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.

FredKSchott avatar FredKSchott commented on May 3, 2024 1

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.

natemoo-re avatar natemoo-re commented on May 3, 2024

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.

kevinkassimo avatar kevinkassimo commented on May 3, 2024

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.

didier avatar didier commented on May 3, 2024

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.

natemoo-re avatar natemoo-re commented on May 3, 2024

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.

didier avatar didier commented on May 3, 2024

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.

eyelidlessness avatar eyelidlessness commented on May 3, 2024

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 corresponding export 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 appropriate type. (Alternately an astro-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.

eyelidlessness avatar eyelidlessness commented on May 3, 2024

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.

JaneOri avatar JaneOri commented on May 3, 2024

: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.

AjaiKN avatar AjaiKN commented on May 3, 2024

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.

AjaiKN avatar AjaiKN commented on May 3, 2024

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.

FredKSchott avatar FredKSchott commented on May 3, 2024

yup, agreed that both forms should be supported.

from astro.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.