-
Notifications
You must be signed in to change notification settings - Fork 546
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
Breaking up templates in Vue3 #96
Comments
What ideas you have in mind? Is the goal of this to split template into multiple parts within the same file? If so, I think it is an interesting idea! Would something like "named template" work? <template>
<use-template name="header" />
<use-template name="main" />
<use-template name="footer" />
</template>
<template name="header">
<header>
...
</header>
</template>
<template name="main">
<header>
...
</header>
</template>
<template name="footer">
<header>
...
</header>
</template> Just throwing ideas. This is sort of replicating how you can assign virtual dom to variables in a render function; which could be quite useful if we figure a nice way to do it. |
I always wished for something like this 👍 The official answer for this has usually been to use additional components, but sometimes you want something lighter directly within the same file. |
Building off of @ycmjason's proposal, what if each named This would be nice for situations where you'll never reuse these one-off components and you don't want the overhead of putting them in separate <template>
<Header :user="user" />
<Main />
<Footer />
</template>
<template name="Header" functional>
<header>
<h1>Hello, {{ props.user }}!</h1>
</header>
</template>
<template name="Main">
<main>
...
</main>
</template>
<template name="Footer">
<footer>
...
</footer>
</template> |
So a functional template would be reusable in other files? The syntax idea sounds great to me. |
I really like the idea of using functional component. However, Vue 3 is going to remove support for I think these named templates should be treated as "partials" that form the bigger part of The following code example will make you 🤢
<template>
<div v-for="i in 5"><use-template name="water" /></div>
</template>
<template name="water">
<div>{{ i }}</div>
</template> I am definitely more inclined to having named templates to have their own scoped and things passed in as "props". But we just need to find the right syntax to do this? |
You can have implicit (not declared) props in Vue 3, so you could totally pass props to template-only components. |
@Akryum but would this mean that, if vue team decided to go down this route, that each named template would create a new instance of vue component? |
But I think Vue 3 component will be very cheap to create if they are template only? Combining with @Akryum's suggestion on using implicit props, we might be able to do: <template>
<Header :user="user" />
<Main />
<Footer />
</template>
<template name="Header">
<header>
<h1>Hello, {{ $props.user }}!</h1>
</header>
</template>
<template name="Main">
<main>
...
</main>
</template>
<template name="Footer">
<footer>
...
</footer>
</template> |
See #25 |
+1 to having to pass props to these private components. Probably best if they aren't magically passed props from the parent scope. They could explicitly access |
I also wanted to add that since the whole point of this feature is better grouping stuff together in these huge components: Not sure if it can get to something like this: <template name="header">
<header>
...
</header>
</template>
<script>
function myHeaderStuffForCompositionAPI () {
// ...
}
</script> |
What's the advantage of this over having separate components that are just a I don't understand the cognitive costs referenced in the description as I generally find smaller, focused files easier to mentally consume and understand... and this approach seems more like file bloat than separate components would since you're ending up with one large file. When you're in your main Explicitly passing props also makes this sound like separate components... or rather, it sounds like you want a way to define multiple, separate components in a single file? |
Sometimes the smaller parts of a large component does not make sense on its own. So it would be nice if those smaller parts could be local to the larger component. Regarding cognitive costs, it is down to personal preferences and ways of working. I don't think we are claiming superiority of named template over components in its own file. I agree that |
Understood this approach would be optional, I'm just struggling to understand the use case -- to understand what problem it's solving. The idea of simply separating one's layout pieces is not a very comprehensive use case... and if this is something where that's the only use case -- something most projects basically do once and rarely revisit -- then this is creating alot of work for something that's very narrowly focused. Maybe better use cases would better illustrate the concern? I can think of two times where I've personally considered something somewhat related to this idea: Complex Components Complex Loops But that said... |
Angular |
@IlCallo is this sort of a way to separate functionality and presentation? I'm not familiar with Angular but that's the gist I'm getting from the guide's If so, would the composition API be another way to approach that? I see myself building pieces of functionality that components would |
Not really, presentation and functionality are often mixed in web components. I can paste an usage example if you want some real world code where it came in handy. |
Relevant tweet:
|
But your template code is actually 136 lines long. That's not a lot for such a component. The largest part of your SFC is CSS, which should be imported from a separate file in this case. The same thing applies to script declaration, where it's mostly about As for the template code the suggestion of braking it up into multiple components does solve your issue. Not sure what you mean by cognitive cost when working with component composition. It's a standard paradigm in component frameworks that it's indeed created to decrease cognitive load and split your work into manageable parts. Merging multiple components into a single one defeats that purpose. Moreover, introducing reusable templates will actually increase mental overhead for a developer. Imagine you have a scoped slot and within that slot you want to reuse a scoped slot variable. When you're working with that external template there's no easy way to tell where that variable came from, because it's not declared in component options, but rather is hidden somewhere in the markup. <!-- container -->
<div v-slot="{ foo }">
<template src="./bar.template.html" />
</div> <!-- bar.template.html -->
<div>
{{ foo }}
</div> Also imagine a situation where your reusable template declares a slot. How you'd be able to tell what slots does your component have without peeking at every single reusable template? <template>
<template name="foo" />
</template>
<template name="foo">
<slot />
</template> To recap:
I strongly vote against this feature since we already have a mechanism to reuse templates, which is a component composition. This feature would introduce more problems than solve. |
Isn't the whole angle with the composition API about "code organization"? With that perspective you could say the same thing: "why create the composition API when you could just break things into separate components?" Also, nothing introduces more friction for me in my projects than hopping between files and folders. Maybe I'm the only one... However! I do agree with you that if there are substantial technical tradeoffs and "footguns" with this, then it might not be worth it. I just felt it warranted a discussion. |
(You're not the only one, @arpowers.) When we're talking about plain functions, this is a widely-accepted best practice: when one function is starting to do too much or becomes hard to follow, it might be time to break some of that functionality into another function and call it from the first. And in doing so, there's no best practice that says we need to automatically move that function to another file. If it's only going to be referenced from this file, it's probably best to leave it here until the moment comes where it's needed elsewhere. To others who don't have this pain or see the value in this proposal: it doesn't have to be useful to you to have value for others. As stated above, I have been in many situations where I wanted to break out a chunk of template for readability's sake even though I have no intention of using it anywhere other than in this component. |
The composition API solves a need that doesn't have a great solution currently.. there are half measures like mixins and component composition, but they have major drawbacks. I wouldn't argue that this is the same thing. I also don't think it can be an argument of someone not having to use this if they don't want to -- everything takes time to build so time spent on one feature is time away from another... plus the cost of maintenance after it's built. The core of Vue should be hyper-focused to features that everyone needs. Niche features can be implemented in 3rd party libs. To address the use case in the original post (cause I'm just noticing it now).. I kind of have to disagree that this should all be in one file, regardless of whether or not it could have multiple I'd break up the data into separate files: benefits, features, and compares. Data should always be separate from code, and it doesn't need to be in a database -- json files work great for this.. or even a js file that returns an object. Then you can use it from anywhere and non-tech team members can add new items or fix spelling mistakes without wading through a sea of code. The css is definitely way heavier than the markup.. sometimes it's worthwhile to break something into a component simply because of the css.. especially once you're doing animations and transitions and making things responsive. You already basically have it split into components: The fiction site is beautiful and you clearly know how to organize and structure code.. I have been trying to understand the motivation behind this idea but it feels like the issue is perhaps with your code editor making it hard to switch between files moreso than anything else..? 🤔 Saying that creating files or hopping between files is painful or time-consuming is a tooling issue, not a framework issue. To the other people that agree with this proposal, please list some actual use cases. Even I tried to list some with my experiences with complex components and |
The problem working across files happens when you go from say 5 components to 25 components as you try and break things out discretely as possible. It gets harder to remember where things are and what things do without following the dependency chain each time. Feel free to close this issue if it's too controversial to deal with right now. Let's note that it would be "purely additive" hence shouldnt be an issue to add in 3.X release. |
I think that's a great point, and I think most people struggle with that at some point... I've definitely been at both extremes during my career -- either ending up with massive components that are everything in a page or tiny components that are barely more than a few lines of markup. My goal is no longer to make components as discrete as possible, or even as reusable as possible. There are 2 criteria I usually use: repetition complexity/size If we look at something like the composition api, part of it's goal is to get the props, computeds, methods, etc that are all related to the same feature into the same block of code together so that the code looks like |
I don't know <template>
<div>
<FolderTree :data="{files}" />
</div>
</template>
<fragment name="FolderTree" :data="{files}">
<ul class="pl-4">
<li class v-for="(file, index) of files" :key="index">
<div>
<input type="checkbox" name :id="file._id" />
<span class="ml-2">{{file.name}}</span>
</div>
<div v-if="file.isOpen">
<FolderTree />
</div>
</li>
</ul>
</fragment> |
I genuinely think that this would just increase confusion, without adding anything beneficial to the framework. |
I'm all for @ycmjason's idea (#96 (comment)). That seems easy to support, easy to optimize, and very similar to how you would approach the issue if you were writing JSX. I was curious if something similar could be hacked together currently. I ended up with something like this: <template>
<div class="hello">
<Define name="Link" v-slot="{ url, name }">
<li>
<a :href="url">
<strong>{{ prefix }}:</strong>
{{ name }}
</a>
</li>
</Define>
<Define name="Header" v-slot="{ title }">
<h3>{{ title }}</h3>
</Define>
<Define name="LinkGroup" v-slot="{ title, links }">
<Header title="Big Tech Companies"/>
<ul>
<Link v-for="{ url, name } in links" :key="name" v-bind="{ url, name }"/>
</ul>
</Define>
<h1>{{ msg }}</h1>
<Header title="Big Tech Companies"/>
<ul>
<Link url="http://google.com" name="Google"/>
<Link url="http://apple.com" name="Apple"/>
</ul>
<LinkGroup title="Frameworks" :links="links"/>
</div>
</template>
<script>
import Define from "./Define";
export default {
name: "HelloWorld",
components: { Define },
props: {
msg: String
},
data() {
return {
prefix: "Go to",
links: [
{ name: "Vue", url: "https://vuejs.org" },
{ name: "React", url: "https://reactjs.org/" },
{ name: "Angular", url: "https://angular.io/" }
]
};
}
};
</script> see Define.js in the sandbox for the functional renderless component that enables this: https://codesandbox.io/s/vue-template-47veo It relies on the parent mounting after the children so the children can register functional components on the parent before render. So yeah, it's a hack. No idea on how easily it would break, but clearly no ones should do this. This is just for kicks. But in terms of the ergonomics of this type of pattern, I guess i could see it being useful for groups of tightly coupled components that have repetitive patterns and specific characteristics (not very generic or useful elsewhere). I can't say I've ever really run into cases where I would find this useful, though. Ordinarily companion components grow and benefit from separate files in my experience (promoting private components to separate files would be super easy in @ycmjason's example, which I like). This type of pattern could probably be natively supported (i.e. compiler could optimize for it, like hoisting the defined component if not referencing anything from parent scope). The only advantage of this approach, though, is access to the parent scope. Otherwise, it's more clunky and and less accessible to newer devs compared to the simplicity of @ycmjason's example. Update: I was also able to get it to work like this, which might look a little cleaner <template>
<div class="hello">
<Components>
<template #Link="{ url, name }">
<li>
<a :href="url">
<strong>{{ prefix }}:</strong>
{{ name }}
</a>
</li>
</template>
<template #Header="{ title }">
<h3>{{ title }}</h3>
</template>
<template #LinkGroup="{ title, links }">
<Header title="Big Tech Companies"/>
<ul>
<Link v-for="{ url, name } in links" :key="name" v-bind="{ url, name }"/>
</ul>
</template>
</Components>
<h1>{{ msg }}</h1>
<Header title="Big Tech Companies"/>
<ul>
<Link url="http://google.com" name="Google"/>
<Link url="http://apple.com" name="Apple"/>
</ul>
<LinkGroup title="Frameworks" :links="links"/>
</div>
</template> However, I'm not sure this hack would work at all in the Vue 3 template compiler. Based on the output render function from the compiler, it looks like it tries to resolve the components all at once, before the renderless component would have a chance to register the nested components. So, Vue 3 would have to have a special built-in component like <template>
<components>
<template #Link="{ url, name }">
<li>
<a :href="url">
<strong>{{ prefix }}:</strong>
{{ name }}
</a>
</li>
</template>
<template #Header="{ title }">
<h3>{{ title }}</h3>
</template>
<template #LinkGroup="{ title, links }">
<Header title="Big Tech Companies"/>
<ul>
<Link v-for="{ url, name } in links" :key="name" v-bind="{ url, name }"/>
</ul>
</template>
</components>
<h1>{{ msg }}</h1>
<Header title="Big Tech Companies"/>
<ul>
<Link url="http://google.com" name="Google"/>
<Link url="http://apple.com" name="Apple"/>
</ul>
<LinkGroup title="Frameworks" :links="links"/>
</template>
<script>
import { reactive, ref } from "vue";
// rather than creating a new component with state, just compose more state here
function usePrefix() {
return { prefix: ref("Go to") }
}
function useLinks() {
const links = reactive([
{ name: "Vue", url: "https://vuejs.org" },
{ name: "React", url: "https://reactjs.org/" },
{ name: "Angular", url: "https://angular.io/" }
]);
return { links };
}
export default {
name: "HelloWorld",
props: {
msg: String
},
setup() {
return {
...usePrefix(),
...useLinks()
};
}
};
</script> I want to say that I would like something like this. There is no need for more templates in different parts of the file. You aren't shoving multiple components in the file. It's still a single file component. But, it allows you to easily create reusable templates, and if they require new pieces of state or logic, it's really easy to drop that in using composition. No need to create a new component with a single data attribute or computed. If needed, it's easy to copy the template and associate composition functions to a new file. And if unrestricted access to parent scope is a concern, you can maybe add a However, this is a change to how the template compiler works. So even though slots are already idiomatic to the framework, this would not be an insignificant change. And it's not clear to me if there would be any side effects from adding this ability. The other option of just including fragment template that use |
I've hit a similar issue a couple of times, and breaking out the repeated markup didn't really make sense as it was tightly coupled to the parent component and couldn't be used elsewhere. I found JSX to be really useful - you can easily make small components (with logic too if you need) in the same file. Not sure that the template syntax would add much given that JSX exists |
@SamWoolerton interesting thought, would it be possible to get a link to somewhere this is done in JSX + Vue? It would def be a good study. |
@arpowers Here's a basic overview JSX is a bit different with event handlers etc but not that bad, especially if you're mainly doing this for repeated markup |
@samwoolertonLW thanks! It's probably worthwhile to learn a few things about the advantages of JSX and why it exists anyway. |
Currently this is true. But, with Vue 3, the template syntax provides additional optimizations. It would still be good to have a way to do this sort of thing using the template syntax. That way, folks don't have to forgo those optimizations, introduce an additional syntax to the project, or split small components into separate files when it feels unnecessary. |
In addition, the Vue spec was based on the Web Components spec, which uses templates. I believe Vue is more oriented towards "extending" official standards rather than inventing its own. |
What what be even more amazing besides being able to have multiple reference-able template tags would be to also be able to reference those template tags in the That way you could have a more dynamic component that need more power than simple template tags can provide, but while keeping the ability to use the elegant I mention this in a ticket in vue-loader in the last paragraph: vuejs/vue-loader#1686 |
Just ran into this myself.
As far as I am concerned, that suggestion is the tool. If the html can be broken out with its relative logic, and can be reusable or has specific purpose (reason for change), then it should be its own component. It isn't file bloat, it's SoC and SRP. Scott |
I honestly feel that it's necessary to bring this discussion up again, but the problem I have with single file components is that sometimes I want to reuse parts of a template in different places within a component, but creating a separate file for it seems to be quite the hassle. Personally I feel that only being able to break up markup templates is too limiting, we should be able to define styling and logic for it. Below is an example for "fragments", and while this could be simplified to without using "fragments", I can at least say that the need for reuse does come up from time to time. <template>
<div v-for='...'>
<message-divider />
</div>
</template>
<fragment name='message-divider'>
<template>
<div class='divider'></div>
</template>
<style scoped>
.divider {
border-top: 1px solid red;
}
</style>
</fragment> The other thing I feel is necessary, is that in addition to reusing "fragments" in multiple places within a single component, is that we should be able to colocate several small components into one file and have them exportable. Though of course if we're defining a template that's not within a "fragment" then it should be a default export, and we could have a <fragment name='manga-list'>
<script setup>
// ...
</script>
<template>
...
</template>
<style scoped>
/* ... */
</style>
</fragment>
<fragment name='manga-entry'>
<script setup>
// ...
</script>
<template>
...
</template>
<style scoped>
/* ... */
</style>
</fragment> import { MangaList, MangaEntry } from './Manga.vue';
// ... |
Another compelling reason for breaking it up is for the new Suspense feature. Unlike Svelte where there is a logic block that handles promises directly, we rely on Suspense which is different as it delegates showing placeholders to its parent component (this delegation is super nifty for composition) So there could be a pattern where you'd define both the placeholder and the actual component in the same file, while also adding a default component that wraps them both for you (if needed be) <script setup>
// ...
</script>
<template>
<suspense>
<template #default>
<manga />
</template>
<template #fallback>
<manga-fallback />
</template>
</suspense>
</template>
<fragment name='manga'>
...
</fragment>
<fragment name='manga-fallback'>
...
</fragment> |
After coming from Flutter, I would echo this request, as you often split up larger/more content heavy components into smaller methods, so that they don't clutter the base component. It offers more readable code, as you can explain with your own names what parts make up this component. A long page with a lot of static markup doesn't always make sense to move to its own file, like when splitting up a complex function into private helper methods. I'm a big fan of SRP, and I think this feature would only encourage more clean code, and make it easier to split into separate components when the need for reuse arise. |
another voice here. use case
would be really nice to be able to template those rows. right now i have lots of copy paste. using SFC on this (vue 3.2) the cognitive costs argument should be voided tho right? isnt that up to the developer to do? the whole concept of using something like vue and components is to not repeat yourself. |
In this case I would use |
so i guess if the "data parts" are from different sources then you would have to create a new array with them in to be able to use it.. so what if you want to have some other content between it then you sorta sol. not sure how it would work tho. right now just gave up and creating a file for each part but its REALLY messy (3 lines of template, 4 lines of wouldnt it just be easier to just be able to create a component in another? vue prides itself in putting the power to the developer. so from "cogative" sense this is a moot point. but if there are technical reasons.. then so be it i guess. edit: just realized my comment has "tone" in it. sorry. not ment to have. just had a frustrating day. your suggestion is somewhat valid. i could built up a content tree in an object / array. but then i might as well just copy and paste the lines. |
This is a follow up to a discussion in @yyx990803's workshop yesterday.
Specifically I wanted to register a vote for an ability to break up and organize template "chunks" along with reorganized functions/options in Vue 3's composition API.
Use case.. here is the code for the landing page you see at fiction.com
As you can see, the code is nearly 1000 lines long. The composition API will definitely help with this. However, the template alone clocks in at 150 lines. In this there are discrete areas of functionality like figures vs grids, etc..
The initial suggestion was to break things out into multiple components, however, this comes at a cognitive cost in jumping around to different files. As well as what you might call "file bloat"...
Im not sure of suggestions for the direct implementation details, however please consider its feasibility if you haven't already.
The text was updated successfully, but these errors were encountered: