-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Multiple components in one file #2940
Comments
I like the idea about private components |
While this would reduce file count on some projects, it would make the syntax of a svelte component/file a bit further from appearing to be just ordinary HTML CSS and JavaScript. It would add another layer of learning complexity, another speed bump to new users. At the same time, "one file per component" is already substantially more dense and concise than is generally seen in some other SPA frameworks out there (at least if you follow their style guides). Perhaps one file per component is dense enough after all? |
as a newbie to JS & Svelte, I'd like to add a voice here discouraging extra syntax and confusion for component definition. the reason Svelte is working so well for me is that it's really easy to understand what's happening, and where it's defined. Anything that adds complexity will increase the barriers to adoption. Thanks for listening :) |
This isn't something we're interested in natively providing in the Svelte compiler. This sounds like something that could be implemented in, for example, a Rollup plugin without a tremendous amount of effort. |
Personally I think Svelte should support multiple components per file. It's very common to need micro components (buttons, etc) for a single use in a view. Svelte and Vue both have this single file component concept and personally I've found it quite impractical. What happens in practice is that instead of creating new files for these micro components these become part of the template and then the logic becomes a mess. The fact there is no official solution for this problem is one of the reasons we won't be adopting Svelte. If this was solved in any way, it should be either included in the official compiler or some sort of official plugin/loader/etc. A third party solution to such a central problem would only introduce fragility. |
All these comments are great, but it would be really great if Svelte could support some kind of sub-components or templates. For example in Angular (bear with me) they have a tag called Would the native html |
I have to disagree with @PierBover and @benwinding - it's trivial to create another small file for sub-components - I do this myself, using a parent folder for the main component, and keeping my smaller (sub) compoents folder local to it. I view the concept of multi-components in a file, and having some sort of extra magic tag as increasing complexity for no real reason. |
It's not about magic and certainly not "for no real reason". Being able to create multiple components in the same file answers to very pragmatic reasons. It's not even a matter of opinion, in some situations it is objectively faster to work with multiple components in the same file. Following your workflow of creating a folder. What if you created a component and later on you needed to add another component in it? Now you have to create a folder, move the component there, change your imports in other files, etc. Compare that to just adding another component to the same file which takes seconds. Not only that but every time you want to work on that view you have to open all those little files in your editor instead of opening a single file. That again takes more time than just opening a single file. |
we have over 2000 components in our react app. it's not possible to maintain 1500 different files. reducing the amount of files is a requirement for moving from react to selve. |
Ok, after working with svelte for a while I'm officially reversing my opinion on this (I commented above). Components are better off in a SINGLE file I come from the Angular 2+ world where every component must be added to a module before being used in the parent component. This makes components very heavy weight as you need to change at least 2 places in order to use a component. Svelte components on the other hand are extremely light-weight and only need to be imported by the component that's using it! So there's no real gain in having it in the same file! except for the fact that it's a private component... which is not the best reason in my opinion Single file components advantages:
I'm sticking with single file components! ✊ |
kudos for publicly changing your mind :) |
I guess it's very subjective but IMO less files and less folders makes projects much easier to navigate and reason about.
I've experienced quite the opposite in Vue. I tended to created fat components since it's quite tedious to create a new component. OTOH it's also true that Vue 2 components have a lot more boilerplate than Svelte 3 components. Another benefit of having multiple components in the same file is when you have a collection of related components together. It makes it super easy to implement changes in all of those and see how these are related together. I was very pro Vue single file components and anti JSX a couple of years ago, but after using different libraries and frameworks with JSX on a couple of projects I changed my mind. Anyway, I won't continue to beat a dead horse... Cheers! |
@clitetailor I know, this is not what you looking for, but maybe it would help a little bit: ./components/design/index.js export { default as Button } from './components/Button.svelte';
export { default as Input } from './components/Input.svelte'; ./components/design/components/Button.svelte <button class="button" {...$$props}>
<slot></slot>
</button>
<style>
.button {
background: blue;
}
</style> Usage: <Button>Hi</Button>
<script>
import { Button, Input } from './components/design';
</script> |
@PaulMaly, does this work OK with SSR in Sapper? |
@saintech I believe it should. |
awkwardly acceptable while trying to maintain a clean code structure. |
edit: i have to admit, a top level switch is a bad solution for sub-components the solution is here is the repl <div
class="svelte_self_recursion_demo {'comp_level_'+level}"
class:main_component={type == 'main_component'}
class:sub_component={type == 'sub_component'}
>
{#if type == 'main_component'}
<div>main_component<br/>lets include sub_component ....</div>
<svelte:self type="sub_component" level={level + 1} />
{:else if type == 'sub_component'}
<div>sub_component on level {level}</div>
{#if level < 4}
<svelte:self type="sub_component" level={level + 1} />
{/if}
{/if}
</div>
<script>
export let type = 'main_component';
export let level = 0;
</script>
<style>
.svelte_self_recursion_demo {
border: solid 1px black;
padding: 0.5em;
}
.main_component { }
.sub_component { }
.comp_level_0 { color: black; }
.comp_level_1 { color: blue; }
.comp_level_2 { color: green; }
.comp_level_3 { color: red; }
.comp_level_4 { color: yellow; }
</style> edit: why the downvote? : P i expect a compiler to optimize such dead code .... to optimize such code, we would need "set once" props, like export init set_me_only_once; proposing the new pseudo type edit: we could require a list of constant values, like in a switch statement export init set_me_only_once = ['preset_1', 'preset_2']; currently, this is not optimized by svelte, but by rollup <script>
let name = 'world';
if (false) name = 'dead code js';
</script>
<h1>Hello {name}!</h1>
{#if false}<div>dead code html</div>{/if} for my svelte-layout i need two large components that differ only in details svelte could optimize this with a space-time-tradeoff ("function unswitching") maybe this can be implemented as a preprocessor edit: this probably only makes sense |
Having worked with Vue on a medium sized project (hundreds of components), I can definitely say I have done exactly what @PierBover described - instead of tediously creating multiple components (so multiple files), I left them as part of the template of the main component, which ultimately led to extremely unmaintainable code. Now I can't imagine using Vue or Svelte for a bigger project and having thousands of files for every little component. This is where React most certainly has the upper hand. Now I know Svelte's components are more lightweight than Vue's, but it's still the same problem which leads to such anti-patterns as described above. |
assuming we want something like <!-- App.svelte -->
<script>
import Widget from './WidgetCollection.svelte';
let name;
</script>
<div>
<Widget.Input bind:value={name} />
<Widget.Output>{name}</Widget.Output>
<Widget.InputAndOutput />
</div> and <!-- WidgetCollection.svelte -->
<!-- proposal of a {#component} block -->
{#component Input}
<script>
export let value;
</script>
<input bind:value={value} />
{/component}
{#component Output}
<div class="output"><slot /></div>
{/component}
{#component InputAndOutput}
<script>
export let value;
</script>
<Input bind:value={value} />
<Output>{value}</Output>
{/component} so the the sub-components can be defined as static properties like // WidgetCollection.svelte.js
class WidgetCollection extends SvelteComponent { ... }
WidgetCollection.Input = class Input extends SvelteComponent { ... };
WidgetCollection.Output = class Output extends SvelteComponent { ... };
export default WidgetCollection; then subcomponent instances can be created like let input1 = new WidgetCollection.Input();
let input2 = new this.constructor.Input(); // inside a `WidgetCollection` instance this could be implemented as a bundler plugin
|
... five hours later, here (diff) is my working prototype. sample input: <!-- App.svelte -->
{#component SubComponent}
<script>
export let value;
</script>
<div>
subcomponent.value = <input bind:value={value} />
</div>
{/component}
<script>
let message = 'Hello World';
</script>
<main>
<div>
app.message = {message}
</div>
<SubComponent bind:value={message} />
</main> in some cases the svelte runtime says |
@milahu This looks like a good attempt, gotta try that out! like! |
What about implementing something like this:
Because Svelte is a compiler it can be transform into:
Also:
|
There could be some way to define sub-components and use them in the same file (into the parent component) only. Such sub-component would share the same scope of styling and scripting. They could be called internal templates or just templates. With that, we could reduce code further by pre-configuring. Example <!--Form.svelte-->
<script>
export let onAnyInput;
import PublicCustomLabel from './PublicCustomLabel.svelte'
import PublicCustomInput from './PublicCustomInput .svelte';
import { capitalize} from '../logic/strings.js'
import { generateRandomName } from '../logic/random.js'
import { logChangeFor } from '../logic/logging.js';
import { v4 as uuid } from 'uuid';
const secondPrivateFieldProps = {
id: "field-2",
name: "second field",
type: "number",
};
const thirdPrivateFieldProps = createPropsForThirdPrivateField();
function handleSubmit() {
/* Perform request */
}
function createPropsForThirdPrivateField() {
return {
id: `field-3-${uuid4()}`,
name: generateRandomName (),
type: 'datetime-local',
labelClass: 'special-label',
};
}
</script>
<form on:submit|preventDefault={handleSubmit} />
<PrivateField id="field-1" name="first field" type="text" inputClass="special-input">Some Text</PrivateField>
<PrivateField {...secondPrivateFieldProps}>Some Number</PrivateField>
<PrivateField {...thirdPrivateFieldProps}>capitalize(thirdPrivateFieldProps.name)</PrivateField>
<button type="submit">Ahoy!</button>
</form>
{#template PrivateField with props={id, labelClass: "common-label", inputClass: "common-input"}}
<PublicCustomLabel class={props.labelClass} for={props.id}>
<slot />
</PublicCustomLabel
<PrivateCustomeInput {{ ...props, class: props.inputClass }}>
{/template}
{#template PrivateCustomInput with props={id, name, type, class: "common-input"}}
<PublicCustomInput
{...props}
on:input={onAnyInput}
on:change={() => logChangeFor(props.id, props.name, props.type)}
>
{/template}
<style>
.common-input {
/* Some CSS */
}
.special-input {
/* Some CSS */
}
.common-label {
/* Some CSS */
}
.special-label {
/* Some CSS */
}
</style> Then, when I use it: <!--App.svelte-->
<script>
import Form from './components/Form.svelte';
function handleAnyInput() {
/* Code to handle any input... */
/* This event would be triggered by every PrivateCustomInput element into the Form */
}
</script>
<main>
<Form onAnyInput={handleAnyInput} />
</main> The <!--SomeComponent.js-->
{#template SomeSubComponent exported with props}
<!--Some markup here-->
{/template} And then: import { SomeSubComponent } from './components/SomeComponent.svelte';
import SomeComponent from './components/SomeComponent.svelte'; // but still, default export points to parent component And it would share the same script, style and props that the parent component would do. Although honestly, if I ever need a file to have several exported components, I think a JavaScript file with some /* ComponenCollection.js */
export {default as SomeComponent} from './components/SomeComponent.svelte';
export {default as OtherComponent} from './components/OtherComponent.svelte';
export {default as OneMoreComponent} from './components/OneMoreComponent.svelte'; |
I also suffered a lot in Vue with this problem. |
hey this is all i need but it doesnt really work with typescript (index.ts instead of index.js) |
I agree support of multiple components in one file, as well as private component. You need to write a function to do three things, B,C and D. Each thing takes about 20 lines of code.You have three choices of implementation.
For separate of concerns, the best choice is 2, but you can't pick it. Only 1. and 3. are left. Let's replace back the term component to function, write one file for one component. Benefit of private component is, some components are intended to be private, so they don't pollute the namespace, and telling other programmers it only serves locally. In some cases it can be reused as well. Private component has the benefits of option 1 and option 3, a good balance between both. |
+1. |
This comment was marked as off-topic.
This comment was marked as off-topic.
Saying private components is not needed is like saying Java should not have inner classes and C# should not have nested classes. I really hope we can take existing wisdoms, rather than re-experiencing all the frustrations of the lack of inner class/private components and the incomplete alternatives which don't solve all the problems before we finally realize again the feature is needed. Even only 5% of people have encountered valid use cases for inline components, it's already enough to make it a valid feature. Also, when persuading other developers and management to adopt a new framework like Svelte instead of React, sometimes, a single downside (apart from ecosystem being small) is enough for them to turn you down, despite having so many other advantages. And the lack of inline components is one of such downsides. This features may look small, but if you need it, then it is a critical feature. I want to use Svelte, but I don't know what argument I could make up for the lack for this feature. Asking people to go back to React is a lazy argument which makes the competitor happy. Don't you want to win, Svelte? |
Proof of conceptI've done a proof of concept of what Svelte might look like if the components were functions instead of files, just like most other frameworks do. You can see the result here, selecting the "Svelte 2" option above (credit to component-party, this is a fork). I've gotten as far as converting the examples up to the "Slot Fallback" section. As you can see, in most cases "Svelte 2" has even fewer characters than the official "Svelte" version, without sacrificing its elegant syntax. The reason why I did this is because I see that the initial proposal of this thread is based on using a wrapper tag for the whole component, and I see that it is something similar to the RFC that the Svelte team has been discussing here. Using a wrapper instead of using functions results in code that is less aesthetic, more verbose, and more detached from most frameworks. Also, I think it's easier to reason about component properties in terms of function parameters, rather than As an additional note, in the component-party fork I linked to above, I used Summary of arguments in favor of multiple components per fileThis thread already has 38 comments, and the similar RFC I already mentioned has 85 comments. Therefore, I would like to recap the arguments that I consider most relevant to the discussion, and perhaps add a new one. 1. In some situations it is objectively faster to work with multiple components in the same file.@PierBover 's comment is incredibly well worded. I don't think I can improve on what he said, so I'll quote him:
2. This will not prevent you from continuing to use one component per fileAs I and others mentioned above. Even if the previous point was false and having multiple components in the same file was only a matter of opinion and personal taste... that would not prevent you from using one component per file! I think the personal opinions in this thread about why some people don't like this practice just add noise to the discussion. "Why not allow everyone to do what they want?". EDIT: Just to be clear. I am not proposing that Svelte leave SFC. The two forms can coexist as it happens with Vuejs 3. Reduction to the absurd.The following argument I am almost sure I have heard from Dan Abramov and in my words, it goes something like this:
If you think about it, a component is actually nothing more than a function. If it was "bad practice to put components in the same file, it would also be bad practice to have JS functions in the same file. Of course, I don't think anyone thinks that. 4. Facilitates migration from other frameworks and enriches the ecosystem.For many, migrating a project to Svelte is an impediment due to SFCs. Allowing multiple components per file would allow many users to adopt Svelte and others to be happier using it. And even those who do not care about this feature, it would benefit them by increasing the users and therefore the ecosystem. |
Example simplified So ya'll telling me that I need to create a separate component for const Component = (props) => {
const iconMarkup = iconType === 'default' && props.icon && (
<Icon size='sm' icon={props.icon} />
);
const titleMarkup = title && (
<TitleTag>
{title}
</TitleTag>
);
const descriptionMarkup = description && (
<p>
{description}
</p>
);
const actionMarkup = primaryActionContent && (
<Button>
{primaryActionContent}
</Button>
);
return (
<div id={id}>
{iconMarkup}
<div>
<div>
{titleMarkup}
{descriptionMarkup}
</div>
{actionMarkup}
</div>
</div>
); |
Multiple components per file in some way needs to be implemented. Both parties are satisfied if multiple components per file is implemented. |
This comment was marked as off-topic.
This comment was marked as off-topic.
This post has inspired me to work on a project. I really love svelte, but I missed JSX and function components, including the ability to have multiple components in a single file. This project is a work in progress, but thought I would share for anyone who might be interested. |
Will be possible with new upcoming svelte 5? |
People complains about svelte having a lot of syntax already, but that is due to the design decisions made. Svelte thought that custom syntax for things the language can already solve was a great idea, and hence it ended with a lot of syntax that really looks like a niche metalanguage. My point is that, Svelte should accept that they require a lot of syntax for things that are trivial in other frameworks, and fulfill the needs of their community. |
Snippets enable this to some extent https://svelte-5-preview.vercel.app/docs/snippets At least the "private" component part |
Another valid use case for multiple components in single file would be for something like shadcn ui. More specifically shadcn-svelte, where something that should be an easy copy paste now necessitates multiple copy paste files and probably another folder for grouping https://www.shadcn-svelte.com/docs/installation |
While working with react, I get this freedom. Having multiple components in the same file is definitely a feature I miss working with svelte now. Sometimes I need to define a small component that doesn't need to be put into a separate file of its own. It would be awesome if svelte allows this feature to exist in svelte, Thanks in advance! |
While I still appreciate something like function components, I find that Svelte 5 Snippets sufficiently meet my needs. |
New Idea: Compile time variant flags/item flagging:Pros / ConsReasons
Downsides
Example Usage Idea:
<script>
import { Buttons(v1, a2, a3, a4) } from './Buttons.svelte';
</script>
<Buttons:v1 answer={"Button 1"}></Buttons:v1>
<Buttons:a2:a3 answer={"Button 2"}>Button2</Buttons:a2:a3>
<Buttons:a2:a4 answer={"Button 2"}>Button2</Buttons:a2:a4>
<script>
export let answer;
</script>
{def:v1}
<button class="button">{answer}</button>
<style>
.button {
background: blue;
}
</style>
{/def}
{def:a2}
<button class="button">{answer}</button>
<button class="button">{answer}</button>
{def:a3}
<style>
.button {
background: blue;
}
</style>
{/def}
{def:a4}
<style>
.button {
background: blue;
}
</style>
{/def}
{/def} ProposalAdd the Final ThingShould I make this a new issue? It's only partially related, as it has other uses. |
@WhyFenceCode make it a new issue for sure |
One very important detail about Svelte 5 Snippets is that you can't bind to snippet parameters.
I have a situation where I have to show basically the same form twice in different parts of the page. The first copy has to bind to a new empty object and the second copy has to bind to an existing object. I hoped I could use snippets here, but you can't.
Instead I created 2 extra files to put the field and form components in. It's not great because these extra files only make sense in this particular component. Also not great because I have to come up with globally unique names for these components. Instead of having clear, concise names like in the example above, now I have to name these Finally, having these multiple files hurts readability because now I'm separating related code. One short screenful of code just got broken up into 3 different tabs. It also got more complex because now I have to think about the import graph. Snippets don't fully address multiple components in one file. |
@tgf9 Didn't test, but would something like this work? I recall having done something simliar on my side at one point. <script>
let newPerson = $state({firstName: "", lastName: ""});
let existingPerson = $state({firstName: "John", lastName: "Smith"});
</script>
{#snippet field(label, anyPerson, fieldName)}
<div>
<label>{label}</label>
<input type="text" bind:value={anyPerson[fieldName]} />
</div>
{/snippet}
{#snippet form(p)}
{@render field("first name", p, "firstName")}
{@render field("last name", p, "lastName")}
<!-- many more fields... -->
{/snippet}
<div>
<p>New Person</p>
{@render form(newPerson)}
</div>
<div>
<p>Existing Person</p>
{@render form(existingPerson)}
</div> |
Hey, @nicksulkers! Yeah, I also just noticed that while you can't bind directly to snippet parameters, you can bind to a object parameter's properties! {#snippet field(label, anyPerson, fieldName)}
<div>
<label>{label}</label>
<input type="text" bind:value={anyPerson[fieldName]} />
</div>
{/snippet} A little awkward, but it works! Hopefully, this is the intended behavior and they don't change it for the official release! 😅 🙏 |
Can snippets have script inside? |
@sken130 they cannot, but they can access functions and variables in their parent component. <script>
let count = $state(0);
function increaseCount(){
++count;
}
</script>
{#snippet someSnippet}
<button onclick={increaseCount}>Count: {count}</button>
{/snippet}
{@render someSnippet()} If you need more than you can accomplish with svelte's templating syntax, you'll probably need a separate component. |
Although a hacky way, without the if condition, svelte throws an error NOTE: Test before using in production {#snippet myChild(snippetLoaded1, name1)}
<script>
let name2 = 'world2';
</script>
{#if snippetLoaded1}
<p>Hello {name1}, {name2}</p>
{/if}
{/snippet}
<script>
let name = 'world1';
let snippetLoaded = $state(false);
$effect(() => snippetLoaded = true);
</script>
<div>
{@render myChild(snippetLoaded, name)}
</div>
and to
{#snippet myChild(snippetLoaded1, name1)}
<script>
let name2 = 'world2';
</script>
{#if snippetLoaded1}
<p>Hello {name1}, {name2}</p>
<input bind:value={name3} />
{/if}
{/snippet}
<script>
let snippetLoaded = $state(false);
let name = 'world1';
let name3 = $state('world3');
$effect(() => {
snippetLoaded = true;
console.log(`name3: `, name3);
});
</script>
<div>
{@render myChild(snippetLoaded, name)}
</div>
yes they can, @sken130 |
@shahul01, no, though scripts are regular ones, which you can place anywhere except root. And svelte things (runes, $store, bindings, etc) won't work for them, and even more, in |
for updating variable, yeah, I guess you just have to send the parent's data directly then. |
What is this: This is a feature request
Description:
Sometimes, putting one component per one file is overkill and hard to maintain, especially when you have a lot of components. Use cases:
Allow multiple-component declarations in one file would help a lot.
Proposal Example:
design.svelte
app.svelte
The text was updated successfully, but these errors were encountered: