Closed clitetailor closed 5 years ago
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 <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?
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.
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.
@yinonc
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! :fist_raised:
kudos for publicly changing your mind :)
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!
@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.
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.
edit: i have to admit, a top level switch is a bad solution for sub-components
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 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
{#component}
blocks)import ThisComponent__SubComponent from './ThisComponent__SubComponent.svelte';
<SubComponent>
→ <ThisComponent__SubComponent>
svelte.compile
on parent + sub componentsthis 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 ; )
@milahu This looks like a good attempt, gotta try that out! like!
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/>
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';
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
I will return to React
solidjs could help
get
functionsexample (playground)
import { render, For } from "solid-js/web";
import { createSignal, children } from "solid-js";
import { createStore } from "solid-js/store";
function Button(props) {
const getChildren = children(() => props.children);
const getOnClick = () => props.onClick;
return (
<button type="button" onClick={getOnClick()}>
{getChildren()}
</button>
);
}
function App() {
const [store, setStore] = createStore({ count: 0 });
const increment = () => {
setStore('count', store.count + 1);
console.log(`store.count=${store.count}`);
};
return (
<Button onClick={increment}>
{store.count}
</Button>
);
}
render(() => <App />, document.getElementById("app")!);
I have to agree with @clitetailor that this feature should be added to Svelte. If you want to stick with single-filed components, I don't see how this would stop you from doing so, but for some of us, it would be really useful to be able to create subcomponents in one file thereby reducing the need for redundant files.
For example, let's I want to create two loading button variations in LoadingButton.svelte
one with a circular loader and another with a progress bar beneath, that would mean two files having just a few lines of varying code.
I agree that multiple components per file are beneficial.
I was also hoping for finding a way to bundle multiple components within a single file @Conduitry . I agree that the typical case 1-1 is sufficient but my reasoning for this feature is so that you can easily read and understand a section of large html in the template.
Splitting it up into modular components would improve readability. Again, this would be for sections that are not large/meaningful enough to live in a new file but when encapsulated provide value for the reader.
Actually, in between, I actually think having multiple components per file is unnecessary. Back then, when I tried to find a solution for that and stumbled across this issue, I was just switching from react to svelte. So I was just used to have multiple components per file, especially react taught me to keep components as small as possible. That is even one recommendation on the react documentation.
So I had the misconception, that a component should really only do one very single thing. A component should be reusable, yes, but that doesnt mean that we must split it up until its so tiny, that it actually becomes unreadable, as you cant see any correlations anymore.
So I got used to single file components and they work perfectly.
Really need this feature. It'll allow for more freedom.
Bump, would like to be able to define multiple components in one file
I occasionally find myself looking for a feature like this.
Creating many small files for basically just repeated html/css (that are only used in a single component anyway) and having to open them when reading through is kind of a hassle.
If used well, subcomponents could definitely boost productivity and readability.
@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?
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.
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.
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?
@stavalfi
moving from react to selve
Bro really said "selve" 💀
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?
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.
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.
@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.
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
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.
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 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>
);
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.
The best component is no component.
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. 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.
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
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. Any component that requires more than a Snippet usually deserves a file of it's own.
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}
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.
Should I make this a new issue? It's only partially related, as it has other uses.
@WhyFenceCode make it a new issue for sure
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