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

Literally undefined class #3283

Closed
cvlab opened this issue Jul 23, 2019 · 23 comments
Closed

Literally undefined class #3283

cvlab opened this issue Jul 23, 2019 · 23 comments
Labels

Comments

@cvlab
Copy link
Contributor

cvlab commented Jul 23, 2019

There is a very small bug. When there is undefined class value and some styles, the element will get literally undefined class.

<script>
	let className
</script>

<h1 class={ className }>Hello world!</h1>

<style>
	h1 {
		color: red
	}
</style>

will produce

<h1 class="undefined svelte-1wsvnfu">Hello world!</h1>

PS: For my components I like nice way to define combination of external and internal classes, just to use true instead of concatenation:

<button
  class={ $$props.class || '' }
  class:MyButton={ true }
>
Hi
</button>
@Conduitry Conduitry added the bug label Jul 23, 2019
@Conduitry
Copy link
Member

When we're walking through the AST and want to update a node containing class="{something}" to include the scoping class, I guess we need to do something other than just change it to class="{something} svelte-abc123". Using class="{something == null ? '' : something} svelte-abc123" would probably be the most correct.

@bre30kra69cs
Copy link
Contributor

1

I think i can fix it

@daltondowning
Copy link

Similarly, I often mix static and dynamic classes. Ideally, I'd be able to write the following:

<script>
  let dynamicClass = undefined;
</script>

<h1 class={"fixedClass" + dynamicClass}></h1>

With a desired output of:

<h1 class="fixedClass"></h1>

The closest way to achieve a similar result right now is with a bit of extra logic:

<h1 class={"fixedClass" + " " + (dynamicClass || "")}></h1>

@Conduitry
Copy link
Member

That's unrelated to the bug this issue is about, and is something we don't want to do. We're not going to mess with what things like + mean in certain contexts. The general rule is that if an attribute is set to a null or undefined value, it won't be present in the markup. That the style scoping classes mess with this rule is a bug. That "hello " + undefined in javascript is "hello undefined" is not a bug.

@daltondowning
Copy link

Understood. Thanks for the clarification, @Conduitry.

@Conduitry Conduitry assigned Conduitry and unassigned Conduitry Jul 29, 2019
@bre30kra69cs
Copy link
Contributor

bre30kra69cs commented Jul 31, 2019

@Conduitry Hello! I work on "Literally undefined class #3283" and found a possible solution. We may add 2 new code string in compiler/../Attribute class, but we add condition from image in the COMPILED file (more precisely "create_fragment")(screens only as example). I understand your comment correct (you placed info about possible resolution strategy in the issue)? It's possible to open mr, but i don't want to spend your time on mr review in a situation where I may misunderstand the main idea about svelte / bug.

Thanks

image

@deviprsd
Copy link

deviprsd commented Aug 1, 2019

Either svelte could make use of just another small library like classnames or clsx or create it's own similar resolver function, rather than generating (class || '') for every class and just exploding the bundle size or totally leave this to the developers to handle it on their own with proper warning in the documentation.

Then there is the case of (ctx.btn + ctx.primary) as shown by @bre30kra69cs, if both are undefined or any other falsy value, it may result in a NaN or something else. I don't think svelte should be encouraging addition of falsy values either.

@cvlab
Copy link
Contributor Author

cvlab commented Aug 1, 2019

@deviprsd it should remove only null and undefined values, not all falsy.

@deviprsd
Copy link

deviprsd commented Aug 1, 2019

@cvlab it should remove all falsy values unless passed as string, I have tried to use a variable containing false and it prints class="ui false", not that .false can't be a class but by that logic so can't .undefined. Then take 0, it's falsy, you wouldn't want it to be a class name, moreover css isn't happy with class names starting in numbers. W3 specification

P.S. The proposed fix (dynamicClass || "") will remove all falsy values.

@cvlab
Copy link
Contributor Author

cvlab commented Aug 1, 2019

@deviprsd I mean @Conduitry fix, it not remove all falsy values.

There are many attributes in html elements, for some attr="false" or attr="0" is expected result, and svelte use same rules for almost all attributes - #1434

@deviprsd
Copy link

deviprsd commented Aug 1, 2019

There are many attributes in html elements, for some attr="false" or attr="0" is expected result, and svelte use same rules for almost all attributes - #1434

@cvlab True, but this particular issue and it's related pr fix is for class attr and not other attributes, hence I stand by my reasoning.

@cvlab
Copy link
Contributor Author

cvlab commented Aug 1, 2019

@deviprsd Why only class? Many attributes have some expected values or types, if to do checks for all types of attributes, svelte will became bigger and slower. For me remove of undefined and null is best solution.

@deviprsd
Copy link

deviprsd commented Aug 1, 2019

@cvlab lets say I had an app with multiple classes or attributes, and taking @Conduitry fix

class="{class1 == null && class1 == undefined ? '' : class1} svelte-123app"
class="{class2 == null && class2 == undefined ? '' : class2} svelte-124app"
class="{class3 == null && class3 == undefined ? '' : class3} svelte-125app"
...
class="{class50 == null && class50 == undefined ? '' : class3} svelte-200app"

Doesn't this make the app bigger and also adds a lot of checks if you had a big app?

I prefer logical 'or' to ternary operator because className token may be a function with side effects and we will call function twice - (@bre30kra69cs )

I agree with this too, what if there is a helper function? class="{h(class3) == null && h(class3) == undefined ? '' : helper(class3)}, this could have serious side effects. So, we go with (class1 || '') but this removes all falsy values.

@cvlab
Copy link
Contributor Author

cvlab commented Aug 1, 2019

@deviprsd You need only
class="{class1 == null ? '' : class1} svelte-123app" because undefined == null is true.
In case of function my way will be to use an variable with result and to call function only one time.

@deviprsd
Copy link

deviprsd commented Aug 1, 2019

class="{class1 == null ? '' : class1} svelte-123app" because undefined == null is true.

First of all, https://www.stevefenton.co.uk/2018/04/catching-null-and-undefined-without-side-effects/ there is performance overhead, it's obviously not much but why avoid optimization when there can be other ways

In case of function my way will be to use an variable with result and to call function only one time.

Your way is not everyone's way, no app is the same

@fbasso
Copy link

fbasso commented Aug 1, 2019

Is it not possible to simply try: class="{class1 || ''}" ?

Or even better :

<script>
	let class1 = ''; // default value to empty
</script>

then:

class="{class1}"

?

@cvlab
Copy link
Contributor Author

cvlab commented Aug 1, 2019

@deviprsd I did not see performance overhead, in your article. It promotes the use of the statement if (x == null).

Do you know another way to save result of function?

@deviprsd
Copy link

deviprsd commented Aug 1, 2019

the == comparison, which allows type coercion (AKA type juggling)

It does, but it also says that, and I did say the overhead is not much but we should look for other ways first

Do you know another way to save result of function?

Yes, (x = helper(y)) == null ? '' : x, but do you see how it's already getting complex.

@cvlab
Copy link
Contributor Author

cvlab commented Aug 1, 2019

@deviprsd I'm not sure, but == null it's special case, and it's optimised very good.

No, it's not complex, because for var || '' you have to at least 2 other place when you need to change code to more complex

export function attr(node: Element, attribute: string, value?: string) {
and in case of spread attribute <div { ...{ class: undefined} }></div>

@deviprsd
Copy link

deviprsd commented Aug 1, 2019

I'm not sure, but == null it's special case, and it's optimised very good.

No it isn't, there is some type coercion as undefined is a type and null is an object

No, it's not complex, because for var || '' you have to at least 2 other place when you need to change code to more complex

I don't think either of the solutions is a good idea, you are not thinking in the perspective of a big app.

@cvlab
Copy link
Contributor Author

cvlab commented Aug 1, 2019

No it isn't, there is some type coercion as undefined is a type and null is an object

I looked right now, in V8 == null is an special case and it's optimised

I don't think either of the solutions is a good idea, you are not thinking in the perspective of a big app.

Sure, you are welcome to propose good ideas.

@bre30kra69cs
Copy link
Contributor

@deviprsd @cvlab we have option to catch "func token" in class name if we calculate them and set as argument in helper func. So i added this and resolve all falsy values problems.

Rich-Harris added a commit that referenced this issue Aug 3, 2019
Rich-Harris added a commit that referenced this issue Aug 3, 2019
@Rich-Harris
Copy link
Member

fixed in 3.6.11, thanks!

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

No branches or pull requests

7 participants