Skip to content
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

Closed
clitetailor opened this issue Jun 3, 2019 · 58 comments
Closed

Multiple components in one file #2940

clitetailor opened this issue Jun 3, 2019 · 58 comments

Comments

@clitetailor
Copy link

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:

  • Styled components.
  • Private component (Component for local use only)

Allow multiple-component declarations in one file would help a lot.

Proposal Example:

design.svelte

{#def Input}
  <input class="input" {...$$props} />

  <style>
    .input {
      background: blue;
    }
  </style>
{/def}

{#def Button}
  <button class="button" {...$$props}>
    <slot></slot>
  </button>

  <style>
    .button {
      background: blue;
    }
  </style>
{/def}

app.svelte

<Button>Hi</Button>

<script>
  import { Button, Input } from './design.svelte'
</script>
@qmzik
Copy link

qmzik commented Jun 3, 2019

I like the idea about private components

@kylecordes
Copy link

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?

@bketelsen
Copy link

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

@Conduitry
Copy link
Member

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.

@PierBover
Copy link

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.

@benwinding
Copy link

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 <ng-template>, which is used to recreate things within the same component, kind of like a private subcomponent...

Would the native html <template> tag be an alternative? Or would that break browser compatibility or something?

@antony
Copy link
Member

antony commented Aug 31, 2019

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.

@PierBover
Copy link

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.

@stavalfi
Copy link

stavalfi commented Sep 3, 2019

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.

@yinonc

@benwinding
Copy link

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:

  • Easier to maintain
  • Easier to reuse and copy
  • Easier to determine dependencies
  • Encourage simpler components

I'm sticking with single file components! ✊

@swyxio
Copy link
Contributor

swyxio commented Sep 5, 2019

kudos for publicly changing your mind :)

@PierBover
Copy link

Easier to maintain

I guess it's very subjective but IMO less files and less folders makes projects much easier to navigate and reason about.

Encourage simpler components

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!

@PaulMaly
Copy link
Contributor

PaulMaly commented Sep 5, 2019

@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>

@saintech
Copy link

@PaulMaly, does this work OK with SSR in Sapper?

@PaulMaly
Copy link
Contributor

@saintech I believe it should.

@droidMakk
Copy link

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.

awkwardly acceptable while trying to maintain a clean code structure.

@milahu
Copy link
Contributor

milahu commented Aug 25, 2020

edit: i have to admit, a top level switch is a bad solution for sub-components

the solution is <svelte:self>

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 ....
assuming the dead code is harming performance

to optimize such code, we would need "set once" props, like

export init set_me_only_once;

proposing the new pseudo type init
behaving similar to the C preprocessor macros #define and #if

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
(problem is, rollup is not aware of components?)

<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
so im using a type prop to switch the component variant
to avoid maintaining two components with 90% same code
(no, i dont want to use a shared.js file)

svelte could optimize this with a space-time-tradeoff ("function unswitching")
by cloning the component for every init value
and optimize each subtype for its init value
yay component inheritance unswitching

maybe this can be implemented as a preprocessor
if svelte allows a preprocessor to create new components (*.svelte files)
and transform component calls like <C t="1"/> to <C_t_1/>

edit: this probably only makes sense
for deep recursion like {#if level < 2500} (unswitched)
or calling <svelte:self> like 50 000 times (unswitched)
cos hot code (like a mousemove handler) can be optimized already
and cold code optimization is an unnecessary/premature optimization

@judehunter
Copy link

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.

@milahu
Copy link
Contributor

milahu commented Mar 6, 2021

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 WidgetCollection component acts as a namespace that contains multiple sub-components,
and inside that namespace, components can call sibling components. (also if they are defined later.)
does that make sense?

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

  • extract subcomponents from component source ({#component} blocks)
    (no support for nested subcomponents)
  • in a parent component:
    • for each subcomponent, add
      import ThisComponent__SubComponent from './ThisComponent__SubComponent.svelte';
    • replace subcomponent calls with calls to the pseudo component:
      <SubComponent><ThisComponent__SubComponent>
  • call svelte.compile on parent + sub components
  • optional: move subcomponents back into parent

@milahu
Copy link
Contributor

milahu commented Mar 6, 2021

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 SubComponent is not defined or App is not defined ...
just hit reload until it works ; )

@KevsRepos
Copy link

@milahu This looks like a good attempt, gotta try that out! like!

@JuanDouek
Copy link

JuanDouek commented Jun 7, 2021

What about implementing something like this:

<script>
  import { Button, Input } from './*';
</script>

<Button/>
<Input/>

Because Svelte is a compiler it can be transform into:

<script>
  import Button from './Button.svelte';
  import Input from './Input.svelte';
</script>

Also:

<script>
  import Widgets from './*';
</script>

<Widgets.Button/>
<Widgets.Input/>

@alfosua
Copy link

alfosua commented Aug 28, 2021

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 Private and Public prefixes are just for making it clear they are as accessible as they say. I don't think it could be very useful to make sub-components public, but if done correctly and in the right circumstances, it could work. If making sub-components public is needed, maybe it should be explicitly specified:

<!--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 export from statements will do.

/* 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';

@yahao87
Copy link

yahao87 commented Dec 2, 2021

I also suffered a lot in Vue with this problem.
As the size of the project grew, more and more files made maintenance difficult.
I am sorry that this discussion ended like this.
I tried to do Svelte, but gave up because of this problem.
I look forward to seeing this discussion resume someday.
I will return to React. T.T

@intensr
Copy link

intensr commented Sep 15, 2022

@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>

hey this is all i need but it doesnt really work with typescript (index.ts instead of index.js)
any clue to get this working with typescript?

@gutbk201
Copy link

gutbk201 commented Dec 1, 2022

I agree support of multiple components in one file, as well as private component.
let's replace component by function and think about it.

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.

  1. write B,C and D into one function.
  2. write functions for each things, ends up three functions in one file.
  3. write one file for one function.

For separate of concerns, the best choice is 2, but you can't pick it. Only 1. and 3. are left.
For lazy and convenient, you would pick 1. Drawback is, the function is cumbersome, hard to maintain. Other programmers would have difficulty reading the 60lines of code as well. Since you can't pick option 2., it encourages programmers to do this anyway, as somebody did above.

Let's replace back the term component to function, write one file for one component.
Picking option 3, great thing is better code readability.Bad thing is namespace pollution.
To write new feature, selecting a right place from nested folders is viable if number of components is below 100. But more than that, the difficulty increases fast. You are likely to search by a key word you known to find the right path. When it's more than 200, you end up searching the right key, it still shows up lots of similar names of file. Imagine what would happen if it's more than 500.
Also, number of components increase faster if you follow the rules one component one file and one component do one thing at the same time.

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.

@GermanJablo
Copy link

If you want to stick with single-filed components, I don't see how this would stop you from doing so

+1.
I think this should be the focus of the discussion, instead of discussing personal opinions about why you like it or why you don't. Why not allow everyone to do what they want?

@lafkpages

This comment was marked as off-topic.

@sken130
Copy link

sken130 commented Jun 5, 2023

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?

@GermanJablo
Copy link

GermanJablo commented Jul 21, 2023

Proof of concept

I'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 imports and exports. That's not to mention that in the world of JS, you can export constants or variables. An abstraction that does not translate well to functions.

As an additional note, in the component-party fork I linked to above, I used .jsx for "Svelte 2" solely because the highlighting of the code improves readability. But as you can see, it's not a valid .jsx, it's still using the characteristic Svelte syntax.

Summary of arguments in favor of multiple components per file

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

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.

2. This will not prevent you from continuing to use one component per file

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

For those who argue that 1 component per file is a good practice, why not use the same logic to do 1 function per file?

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.

@arkmech
Copy link

arkmech commented Aug 21, 2023

Example simplified React example to illustrate concept (These markups have lots of classes or can have multiple elements). This pattern keeps the markup at the end very clean where you only concerned with layout. Components aren't complicated enough for their own file, but complicated enough to be stored in its own variable.

So ya'll telling me that I need to create a separate component for titleMarkup, descriptionMarkup, and actionMarkup?

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>
  );

@arkmech
Copy link

arkmech commented Aug 21, 2023

Multiple components per file in some way needs to be implemented.
For those that say, we want to keep Svelte simple. Ok, then just don't use the feature when its implemented.
For those that want to use the feature, its there when you need it...

Both parties are satisfied if multiple components per file is implemented.

@chientrm

This comment was marked as off-topic.

@zachlankton
Copy link

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.

https://github.com/Rezact/Rezact

@subhasish-smiles
Copy link

Will be possible with new upcoming svelte 5?

@danielo515
Copy link

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.
The reason this why this is trivial in other libraries, not even needing anything from the library itself, just JS syntax, and as oppose in Svelte it will require new syntax is because this decisions, but there is now way back unless they decide to greatly simplify in a future release.

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.
This is a necessity, and many people already explained many reasons.
Something as simple as having the same UI section of a component appear in several places already requires this. And no, I don't want to create a separate component and have to come up with a nice name that makes clear it is a internal component and not a general component to use, my IDE does not differentiate, and I don't want to see 5k components when fuzzy jumping to a part of my application.

@wporoslo
Copy link

wporoslo commented Jan 5, 2024

Will be possible with new upcoming svelte 5?

Snippets enable this to some extent https://svelte-5-preview.vercel.app/docs/snippets

At least the "private" component part

@Jojoshua
Copy link

Jojoshua commented Feb 11, 2024

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

@subhasish-smiles
Copy link

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!

@nicksulkers
Copy link

While I still appreciate something like function components, I find that Svelte 5 Snippets sufficiently meet my needs.
Any component that requires more than a Snippet usually deserves a file of it's own.

@WhyFenceCode
Copy link

WhyFenceCode commented Apr 11, 2024

New Idea: Compile time variant flags/item flagging:

Pros / Cons

Reasons

  • Allows for multi components per file
  • Allows for variants
  • Allows for dev only components
  • Allows for different compile in different situations

Downsides

  • One more thing to learn - I don't think this is a big one, I'm new to this and it seems so easy
  • A little bit more work
  • Slower compile

Example Usage Idea:

App.svelte

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

Buttons.svelte

<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}

Proposal

Add the def:[id] flag that gets evaluated to multiple versions at compile, with the <[Element]:[id]> getting used in the html to point towards these and resolving to those versions on compile as well. This would allow for complex structures with a complex structure. While there is more syntax in my mind that is trivial.

Final Thing

Should I make this a new issue? It's only partially related, as it has other uses.

@Antonio-Bennett
Copy link

@WhyFenceCode make it a new issue for sure

@tgf9
Copy link

tgf9 commented May 21, 2024

One very important detail about Svelte 5 Snippets is that you can't bind to snippet parameters.

Cannot reassign or bind to snippet parameter

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.

<script>
  let newPerson = $state({firstName: "", lastName: ""});
  let existingPerson = $state({firstName: "John", lastName: "Smith"});
</script>

{#snippet field(label, value)}
  <div>
    <label>{label}</label>
    <input type="text" bind:value={value} />
  </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>

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 SpecialUniqueForm.svelte and SpecialUniqueField.svelte. These are names for Svelte, not for humans.

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.

@nicksulkers
Copy link

@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>

@tgf9
Copy link

tgf9 commented May 27, 2024

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! 😅 🙏

@sken130
Copy link

sken130 commented Aug 15, 2024

Can snippets have script inside?

@nicksulkers
Copy link

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

@shahul01
Copy link

shahul01 commented Jan 18, 2025

Although a hacky way,
svelte docs doesn't show that you can use script and maybe even style tags inside snippets

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 bind:value dont pass arguments to child,
just use it inside the snippet
like @nicksulkers said #2940 (comment)

		
{#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

@7nik
Copy link
Contributor

7nik commented Jan 18, 2025

@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 let a = { foo: 42 } the brackets will be treated as a place for a value substitution.

@shahul01
Copy link

@7nik

for updating variable, yeah, I guess you just have to send the parent's data directly then.
bind:value etc works only when getting variable from parent directly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests