# Moving from `<tag>` to top-level `@attr` (or death to the `tag` tag) Currently the `<tag>` tag has some aspects we do not love. * It requires mixing both tag parameters and tag variable syntaxes which can look daunting * It is currently able to be nested anywhere in the template and gives the variable to the render scope, but we would like to restrict that so that these tags are within the static scope of the template. * Because of the above it is impossible to export these tag tags which can be useful for some scenarios. We are toying with the idea of re-using the attribute tag syntax for defining these tags/macros. Essentially, top level attribute tags could automatically expose JavaScript identifiers which you can work with. (Currently top level attribute tags are meaningless and a compile error). An attribute tag is essentially a way to define an object (which is normally passed to another tag). The special thing about attribute tags is that they can also have Marko renderBody content. As it stands there is no way at the module scope/static level to create or expose renderBody content (other than what is exported by default with the template). We are proposing making top level attribute tags create a module scope variable which would allow you to create and export renderBody content. This is useful for tools like storybook, where you may want to create a story for your component and pass in some Marko renderBody content. Currently doing this requires either creating another Marko wrapper component, or using inline (and hacky) renderBody functions. What if you could write your stories using Marko directly? (Note this all would work for concise or html mode) ## Storybook Examples <details> <summary>Concise mode</summary> ```marko import component from "<my-button>" export default { title: "My Button", component } export { Primary } @Primary @args -- Hello export { Secondary } @Secondary @args type="secondary" -- Hello ``` </details> <details open> <summary>HTML mode</summary> ```marko import component from "<my-button>" export default { title: "My Button", component } export { Primary } <@Primary> <@args> Hello </> </> export { Secondary } <@Secondary> <@args type="secondary"> Hello </> </> ``` </details> ## Macro Examples <details> <summary>Concise mode</summary> <!-- { name: string, children: [{ isDirectory: boo }] } --> ```marko @FileDisplay|input| if=input.files span -- 📂 ${input.name} div for|file| of=input.files FileDisplay ...file else span -- 📄 ${input.name} h1 -- File Tree div for|file| of=input.files FileDisplay ...file ``` </details> <details open> <summary>HTML mode</summary> ```marko <@FileDisplay|input|> <if=input.files> <span>📂 ${input.name}</span> <div> <for|file| of=input.files> <FileDisplay ...file/> </for> </div> </if> <else> <span>📄 ${input.name}</span> </else> </> <h1>File Tree</h1> <div> <for|file| of=input.files> <FileDisplay ...file/> </for> </div> ``` </details> ## Alternatives for `export` Potentially there could be an export shorthand for these top level attribute tags. <details> <summary>Concise mode</summary> _existing method:_ ```marko export { Primary } @Primary @args -- hello ``` _shorthand:_ ```marko export @Primary @args -- hello ``` </details> <details open> <summary>HTML mode</summary> _existing method:_ ```marko export { Primary } <@Primary> <@args> hello </> </> ``` _shorthand:_ ```marko export <@Primary> <@args> hello </> </> ``` </details> ## Rough Idea ```marko static const Primary = { args: { count: 0 } } // would be the same as <@Primary> <@args count=0/> </> ``` ## Other considerations #### Currently there is no way to cast the type for an attribute tag. To address that we are also proposing that passing type arguments to an attribute tag would cast it, eg ```marko <@MyAttributeTag<{ count: number }> count="Would Error"/> ``` This means in practice for the (typed) storybook examples you would have something like: ```marko import type { Story, Meta } from "@storybook/marko"; import component, { type Input } from "./index.marko"; export default { title: "My Component", component } as Meta<Input>; export { Example }; <@Example<Story<Input>> <@args> Hello! </> </> ``` #### Currently you cannot export default in a Marko template. We could add support for this. Although if your template had any render code (eg stuff that is not static/export or a top level attribute) then that'd be a compile error when mixed with an explict default export. ## Saulo's notes I took some time to think about it because something looked odd to me but I couldn’t put my finger on it. After reading what @angusmorton said, I got it. The name “attribute tag” suggests its defining an attribute, which demands something that has that attribute. That something in this case would be the exports object, but that isn’t made clear by the syntax. How about we do… https://markojs.com/docs/custom-tags/#from-variables ### Reasoning I think before we focus on a specific syntax, we should agree on what this change means. For me, the impact of this is greater than the syntax. Correct me if I'm wrong, but I think we are about to: 1. **Named exports**, while up until now we could only export an `Input` type/interface, and a Marko Template as the `default` export. 2. **Variable referencing templates**, indirectly via `Primary.args.renderBody` Right now the proposed syntax does not support directly setting the value of a variable to be a template, but, if we can do... ``` export { Primary } <@Primary> Hello </> ``` then there's a `Primary` variable, which has a renderBody (which is almost as good as a Template). I'm sure users will at least try to do things like... ``` <@DefaultBody> Click Me </> export const Primary: Story = { args: { renderBody: DefaultBody.renderBody, }, }; ``` I believe we should focus on finding the best syntax for those 2 features and only then decide what to do with `<macro>`, `<tag>`, and `<@att>`. ### Guidelines I followed - Remain as close to JS/TS as possible as `exports` are their land language-wise - The standard syntax should be obvious as everything in Marko has been, think of syntax sugars later - The `!` before the `exports` was intentional. I've been wanting to propose we add that to the tags that do not affect the template output for a while, like (`let`, `const`, etc...), but this discussion should happen in another tread. My reason for suggesting it is that in HTML tags that add metadata or control behavior without changing the visible DOM structure always start with `!`: `<!doctype>`, `<!-- -->`, and `<![CDATA[ ]]>`. Here's my proposal: - `<@Primary<Story<Input>>` gives the impression that `Story<Input>` is a type parameter So my first thought is: "where is the Primary type defined?" Instead, I suggest we follow TS standard and go for `<@Primary:Story<Input>>` (no space allowed after `:`, or `<@(Primary: Story<Input>)>` (space allowed). *Maybe we can allow space in the first option, but I'm not familiar enough with the parser to know for sure* ### Proposed Syntax In summary: - `<@attr>` remains as a setter of its parent's attribute - we can export any value as a named export - we define bodies using the Fragment syntax `<></>` ```marko export default { title: "My Component", component, } as Meta<Input>; const baseStory = { args: { variant: "primary" | "secondary" | "danger", size: "medium" }, parameters: { docs: { code: "<Button>Default Button</Button>", }, }, } <!exports> <@Secondary:Story<Input> ...baseStory> <@args ...baseStory.args variant="secondary"> Secondary Button </> </> <@(Danger: Story<Input>) ...baseStory> <@args ...baseStory.args variant="danger"> Secondary Button </> </> </> ``` For a single named export, I'm not so sure what to do. But after trying several syntaxes, I'm leaning toward the Fragment syntax from JSX. ```marko export const Primary: Story = { ...baseStory args: { ...baseStory.args, renderBody: <> <span class="confirm"></span> Primary Button </> }, }; ``` No matter what syntax is chosen, I think the key here is to allow the `renderBody` (maybe a Template too) to be assigned to variable right after it is created. That variable should be made available to the static context so it can be exported, and the same should happen to `tagVariables`. This allows for some interesting reusability patterns: ```marko type Icon = 'confirm' | 'cancel' | 'none'; type StoryName = 'Primary' | 'Secondary'; type ButtonBody = { icon: IconName, label: StoryName }; <tag<ButtonBody>|{ icon, name }|/buttonBody> <span class=icon></span> ${name} Button </tag> export const buttonBody; export const Primary: Story = { ...baseStory args: { ...baseStory.args, renderBody: <buttonBody icon="confirm" label="Primary" />, }, }; ``` Fragments would also enable us to return render functions seamlessly from inside functions... ```marko function bodyFor(story: StoryName, icon: IconName = 'none') { return <> <span class=icon></span> ${story} Button </> } export const Default: Story = { ...baseStory args: { ...baseStory.args, renderBody: bodyFor("Default"), }, }; ``` If we really want to use the `<@attr>` at the top level, I think a nameless attr tag should be allowed (or even required) when it has no parent... ```marko export const Secondary: Story<Input> = <@ ...baseStory> <@args ...baseStory.args variant="secondary"> <span class=icon></span> ${story} Button </> </>; ``` If we allow `<!exports>` or `exports {` to be used multiple times (like `static`) then the shorthand could be something like this... ```marko <!exports> <@Secondary ...baseStory> <@args ...baseStory.args variant="secondary"> <span class=icon></span> ${story} Button </> </> </> // or exports { <@Secondary ...baseStory> <@args ...baseStory.args variant="secondary"> <span class=icon></span> ${story} Button </> </>; } ``` Where both would mean: `module.exports.Secondary = { ...baseStory, ... }`