Odd that they chose to use the unsafe "eval", where there's a drop-in, safe(r) replacement with
```
const properties = Object.keys(injections);
const values = Object.values(injections);
htmlString = new Function(...properties, 'return `' + templateString + '`')(...values);
```
Other than that weirdness, this is a pretty sweet little library! I've already built my own version of it, so it's nice to see a lot of the same techniques I used re-applied here. Definitely feels like doing something right if other people are thinking along the same lines!
I hope this more project sees more utility than mine! I backpedaled on the idea of 'wrapping' custom web components after I figured that learning my abstractions wasn't any different than learning native web component abstractions (which I couldn't justify). I definitely think there are good uses for this type of thing, but it was very hard to scale it to app level and not have to build data-oriented abstractions which started mildly coupling components (on the interface level, rather than as a requirement for rendering). So once I started breaking down the components abstract enough pieces that they were useful as standalone components, I wasn't using any of my custom functionality, so I just migrated it all back to native, not-extended-from-my-library components.
I'm very eager to see mature projects using these kinds of wrappers, though, because I do think there are ways to push the native web-component experience further, and I think these are the kinds of projects that help with that.
Thank you for your kind words about Hyphen, catapart! And that is a very sweet little thing, the code. I like your bloody clever little code hack, that is pretty cool.
I might consider including it. Would you like to make the PR? If you don't want to get involved more than that, I get it! No worries. But I mean this you do care about this, and this is your work. It's very clever!
My concern is it will fail in some cases that with(state)/eval works in, and require annoying edge-case patching to support cases we already support. For dubious security gains (I don't see the risk of eval here--if anyone can point so specifics I'd value learning!)
As to why I didn't choose it, I didn't see it (!!), but I also didn't think I should bother trying to get all properties and values because there are some that can slip through (say state contained some prototype chain and had properties up the chain, these will not be returned by keys). So I just went with with(state), because I know that's safe regarding that. with(state)/eval is simple, neat and elegant. Also I just like the aesthetics of the simpler shorter code. And I guess I like to live dangerously, hahahaha! So those reasons are why I chose it.
But your way is very clever! And it looks very clean the way you present it above.
I love to see these new, like, tight, idiomatic ways of using JavaScript to push the language. I love what you've done, it's really cool! :)
Also, it's good to hear about your experience with that. I encourage you to keep working on your library. I mean, a diversity of approaches is valuable. Even if it's just we're researching and learning and progressing toward something, through our collective effort, even if none of it is actually the destination. Although with multiple demands on your time, I'm sure it makes sense to pull back if you don't see it as worth it.
If you want to contribute more of your really clever ideas and experience to Hyphen that would be cool. Maybe not everything is a fit but we could certainly learn from you. Up to you!
Thanks for your cool comment on this. High value! Have a good day :)
Oh! Ha ha! Nice! I'm glad you like the code! I'd love to tell you it was the product of a journey of discovery that embedded deep magics in a (possibly) one-liner... but it's actually recommended (for the most part) on MDN's documentation about eval (and why not to use it).
On that topic, though, my concerns about eval are this:
Developer A is using my web components along with advertising that uses library X. Library X is actually malware that tries to harvest user data, but it does provide analytics, so it is silent malware that the advertisers nor Dev A can account for. Since Library X is loaded in an iFrame, it can't execute any code that will be local to Dev A's domain; everything will originate from the ad's domain. But since Dev A is using my web components, which use eval, he can query the dom of the iFrame's parent, get a reference to my component, and then inject the attack in a place that will trigger a re-render. The re-render runs the eval which still creates the html, but it also executes code to harvest user data both from localstorage/browser storage and using any tokens that Dev A's app provides for server access. It then stores a string of malware in the browsers local storage to execute every time the page refreshes.
Now, to be clear, I haven't tested this scenario in over a decade. There may be protections, now, that prevent this kind of thing. But that's what my functional testing would be based around: whether or not a third-party can start executing code from my domain. If you can prove that doesn't happen, then I would have much less concerns about security.
That aside, I understand your concerns about edge-cases. I can tell you that I haven't done any rigorous testing, in that regard. I used that method for my web components, but I was specifically trying to "not do anything fancy". Where that took me was trying to write loop executions and extracting object properties (like you mention; it's an operation in order to get the [correct] property values out of an object, instead of just the member variables). So even while getting somewhat robust, I didn't have any issues, but there are definitely some annoying things and some stuff to work around.
Something that jumps out at me is a weird issue with getting raw DOM text. What I would like to do is query my components for a template element, read the contents of that template element as raw text, and then provide injections for any of the string template variables that are present. The problem, though, is that there is no way to get the RAW text from a DOM element (at least that I could find), without something crazy like a direct text import (heavy cost; latency/bandwidth). If you try to read the text (innerHTML/textcontent/etc) of an element whose content is "My variable name is ${var1}, today.", you will get the string "My variable name is , today."
So my work-around was to leave off the $, and just regex for "handlebar" notation (ex: "{var1}"), which I would then prefix just before the template rendering. Not exactly a cheap solution, but fast enough for my work, anyway.
As far as contribution, I'm not saying "no", but I'm sure you can imagine I'm working on my own projects. To that end, I'll ping you when the results of my tortured process are public, just in case it's helpful to you (but, again, this project doesn't have a web-component wrapper lib; it's just pure native components). But that's just to say that I don't have a ton of bandwidth, at the moment, to actually contribute code in any standard worth receiving it. That said, it's obviously an area which I'm interested in, so I'm certainly open to being productive! As long as you're good with a slow pace, I'm happy to make some pull requests.
In the TS version, you can see the code where I extract the property values, as well as the (pretty buggy) loop functionality. I'm happy to discuss any of it, so if there's an app you're more real-time active on (it's Discord for my default, but I'm open to most apps), just let me know and we can get into it.
Anyway, thanks for the high-quality response! Easy to take criticism poorly, so I appreciate that you found it useful or informational in some way! Definitely helps me to have a good day, so I hope you do the same!
Edited to add: sorry! I meant to be explicit about this: you are more than welcome to harvest any code from that gist! If any of it is checked in, anywhere, it's licensed under CC0 so you are way in the clear to use it. Just because I might be too busy for a PR doesn't mean I don't want you using "my" code. Let's be real honest - it's a melting pot of stack overflow answers and HN comments. I can't really take particular credit for any of it. So by all means copy and paste or chop and screw to your heart's content! And, of course, if you have any questions about the whys or hows for any of it, just let me know!
The custom element spec definitely only deals with the mechanics of when are where to run your component's lifecycle code - it says nothing about data. So your choices are basically property accessors, which are interoperable, but require prop-drilling for global-ish data, or something proprietary like a state management library.
The Context protocol provides tree-scoped ambient data in an interoperable way. It's implemented by Lit and FAST (I believe). It doesn't replace a data store, but it's often used to provide data stores to components, and at least reduce some coupling.
Ah, good to know! Hadn't really looked too far into component render batching, but I'm glad there are efforts underway!
I just meant that once I was rendering out each component without having to have it's parent know about its sub-components, I didn't have much use for global contexts or passing state to a component, itself. Auto-re-rendering isn't as compelling as just setting whatever data on the attribute change, or via a child element insertion/removal. Child element maintenance requires a lookup, instead of just re-rendering with the new state, but the DOM is really good at querying, so you can just look them up.
And so, once you remove auto-re-rendering (first render is still arcane, but it's still a pretty simple code snippet to accommodate templating), and state-based re-render triggering, there's not a lot that is added to the web component that isn't just a "different" way to do it, instead of demonstrably "better".
Of course, everyone's mileage may vary! Just my two cents after working with them for a few years. And my work has pushed me toward a "simpler is better" approach, which isn't universal. For people trying to make very complex web components that can benefit from batching tree renders, no amount of breakup will help (and my assumption is that it will actually hurt a lot? Web Components don't feel "cheap" as far as instantiation goes). So it's nice to have people working on these kinds of things so that we can one day get a drop in DAW web-component, or a performant "spread-sheet" component.
That's a good point re CSP, while I don't want to restrict it, I also don't want to restrict developers. For example, unsafe-inline may even be required as we use inline event handler syntax. But complying with a requirement to remove unsafe-inline that some may have, would produce a worse developer experience, as you could no longer write inline handlers.
I could implement turning those inlines into addEventListener calls, as I've done in good.html, but I just want to keep everything simple here, at least for now. Trade offs, ha! :)
Will `new Function` also be covered by the same unsafe-eval CSP restriction? I know I could just search, but I suspect more discussion with you will be fruitful.
Thanks for your concern around possible naming conflicts!
Actually the name is deliberate. A homage to jquery, at first I even considered calling this library JCustom. I think the logic is cool, as Hyphen aspires to be a kind of layer atop custom elements, as JQuery was a layer atop the DOM (and MUCH more!).
Yet, the age of JQuery has passed. It's light has gone out of the world, and John Resig has retreated to the West with the Elves.
I don't think it's really an issue.
Also, it's time to consider that passing the torch of '$' is ok. Moreover, perhaps it was never not ok. As in, the existence of a library that binds to a tiny symbol should not restrict others from using the same symbol. Otherwise it would be kind of like 'variable name squatting'--not good! Dollars for all, I say! Who's with me???
So while I get your concern about conflicts there's always been ways to avoid namespace conflicts, such that these avoidance functions have been built into the libraries themselves, in the form of: noConflict().
I think that's sufficient.
And, not that it's the best guide of best practice, but for a long time Chrome, Safar and Firefox have included $, $$ and so on functions in their consoles, as part of the Web Console API.
I get you might be concerned about this, so you can easily modify this tiny base class of Hyphen to call the base whatever you like. Reasonably, there's plenty of names that could be good for a base class, given whatever it is that you might want! :)
For me, tho, I think $ is a beautiful symbol to name it after so I'm sticking with that. JQuery can bind to other symbols, but it's also not as common these days. I'm not worried about it.
Do you think I should be worried about this? If so why?
Obvious if you've been around long enough. I wouldn't be surprised if the majority of people reading this have never actually worked with jquery, only heard the bedtime horror stories.
(I mean, leaving aside the fact that it's utterly nondescriptive...)
Me neither. "An elegant custom element base class" could relate to any programming language. I skim the page and see angle brackets - so it's browser-related.
Great - but for what? Is this client-side, server-side? Does it relate to a specific framework or approach?
I don't blame the project. They know their audience. It's the HN policy where extra context in the post title is forbidden that hurts stuff like this.
This is correct. It's a wrapper around web components, kind of like Lit (which they credit in the acknowledgements).
Web components are cool, but the spec is bare. This means, a lot of rote work gets custom implemented. There are a lot of frameworks popping up, trying to "standardize" this.
It's really clear to me. (And I did account for the subsequent README commits since your comment). But that's probably because I already know Web Components well. I'm in the market.
In the following line...
> Hyphen - A custom element base class for great developer ergonomics.
...I would recommend adding a link on "custom element" that points to a definition somewhere. This might make it easier for skimmers to parse the meaning of this opening line.
``` const properties = Object.keys(injections);
const values = Object.values(injections);
htmlString = new Function(...properties, 'return `' + templateString + '`')(...values);
```
Other than that weirdness, this is a pretty sweet little library! I've already built my own version of it, so it's nice to see a lot of the same techniques I used re-applied here. Definitely feels like doing something right if other people are thinking along the same lines!
I hope this more project sees more utility than mine! I backpedaled on the idea of 'wrapping' custom web components after I figured that learning my abstractions wasn't any different than learning native web component abstractions (which I couldn't justify). I definitely think there are good uses for this type of thing, but it was very hard to scale it to app level and not have to build data-oriented abstractions which started mildly coupling components (on the interface level, rather than as a requirement for rendering). So once I started breaking down the components abstract enough pieces that they were useful as standalone components, I wasn't using any of my custom functionality, so I just migrated it all back to native, not-extended-from-my-library components.
I'm very eager to see mature projects using these kinds of wrappers, though, because I do think there are ways to push the native web-component experience further, and I think these are the kinds of projects that help with that.
I modified it a little to fit:
I might consider including it. Would you like to make the PR? If you don't want to get involved more than that, I get it! No worries. But I mean this you do care about this, and this is your work. It's very clever!My concern is it will fail in some cases that with(state)/eval works in, and require annoying edge-case patching to support cases we already support. For dubious security gains (I don't see the risk of eval here--if anyone can point so specifics I'd value learning!)
As to why I didn't choose it, I didn't see it (!!), but I also didn't think I should bother trying to get all properties and values because there are some that can slip through (say state contained some prototype chain and had properties up the chain, these will not be returned by keys). So I just went with with(state), because I know that's safe regarding that. with(state)/eval is simple, neat and elegant. Also I just like the aesthetics of the simpler shorter code. And I guess I like to live dangerously, hahahaha! So those reasons are why I chose it.
But your way is very clever! And it looks very clean the way you present it above.
I love to see these new, like, tight, idiomatic ways of using JavaScript to push the language. I love what you've done, it's really cool! :)
Also, it's good to hear about your experience with that. I encourage you to keep working on your library. I mean, a diversity of approaches is valuable. Even if it's just we're researching and learning and progressing toward something, through our collective effort, even if none of it is actually the destination. Although with multiple demands on your time, I'm sure it makes sense to pull back if you don't see it as worth it.
If you want to contribute more of your really clever ideas and experience to Hyphen that would be cool. Maybe not everything is a fit but we could certainly learn from you. Up to you!
Thanks for your cool comment on this. High value! Have a good day :)
On that topic, though, my concerns about eval are this: Developer A is using my web components along with advertising that uses library X. Library X is actually malware that tries to harvest user data, but it does provide analytics, so it is silent malware that the advertisers nor Dev A can account for. Since Library X is loaded in an iFrame, it can't execute any code that will be local to Dev A's domain; everything will originate from the ad's domain. But since Dev A is using my web components, which use eval, he can query the dom of the iFrame's parent, get a reference to my component, and then inject the attack in a place that will trigger a re-render. The re-render runs the eval which still creates the html, but it also executes code to harvest user data both from localstorage/browser storage and using any tokens that Dev A's app provides for server access. It then stores a string of malware in the browsers local storage to execute every time the page refreshes.
Now, to be clear, I haven't tested this scenario in over a decade. There may be protections, now, that prevent this kind of thing. But that's what my functional testing would be based around: whether or not a third-party can start executing code from my domain. If you can prove that doesn't happen, then I would have much less concerns about security.
That aside, I understand your concerns about edge-cases. I can tell you that I haven't done any rigorous testing, in that regard. I used that method for my web components, but I was specifically trying to "not do anything fancy". Where that took me was trying to write loop executions and extracting object properties (like you mention; it's an operation in order to get the [correct] property values out of an object, instead of just the member variables). So even while getting somewhat robust, I didn't have any issues, but there are definitely some annoying things and some stuff to work around.
Something that jumps out at me is a weird issue with getting raw DOM text. What I would like to do is query my components for a template element, read the contents of that template element as raw text, and then provide injections for any of the string template variables that are present. The problem, though, is that there is no way to get the RAW text from a DOM element (at least that I could find), without something crazy like a direct text import (heavy cost; latency/bandwidth). If you try to read the text (innerHTML/textcontent/etc) of an element whose content is "My variable name is ${var1}, today.", you will get the string "My variable name is , today."
So my work-around was to leave off the $, and just regex for "handlebar" notation (ex: "{var1}"), which I would then prefix just before the template rendering. Not exactly a cheap solution, but fast enough for my work, anyway.
As far as contribution, I'm not saying "no", but I'm sure you can imagine I'm working on my own projects. To that end, I'll ping you when the results of my tortured process are public, just in case it's helpful to you (but, again, this project doesn't have a web-component wrapper lib; it's just pure native components). But that's just to say that I don't have a ton of bandwidth, at the moment, to actually contribute code in any standard worth receiving it. That said, it's obviously an area which I'm interested in, so I'm certainly open to being productive! As long as you're good with a slow pace, I'm happy to make some pull requests.
In the meantime, though, I did go ahead and upload some of my attempts as a gist: https://gist.github.com/catapart/64c993533b64089f337825d33a5...
In the TS version, you can see the code where I extract the property values, as well as the (pretty buggy) loop functionality. I'm happy to discuss any of it, so if there's an app you're more real-time active on (it's Discord for my default, but I'm open to most apps), just let me know and we can get into it.
Anyway, thanks for the high-quality response! Easy to take criticism poorly, so I appreciate that you found it useful or informational in some way! Definitely helps me to have a good day, so I hope you do the same!
Edited to add: sorry! I meant to be explicit about this: you are more than welcome to harvest any code from that gist! If any of it is checked in, anywhere, it's licensed under CC0 so you are way in the clear to use it. Just because I might be too busy for a PR doesn't mean I don't want you using "my" code. Let's be real honest - it's a melting pot of stack overflow answers and HN comments. I can't really take particular credit for any of it. So by all means copy and paste or chop and screw to your heart's content! And, of course, if you have any questions about the whys or hows for any of it, just let me know!
The Web Components Community Group (WCCG) is offering something of a third way with the community protocols: https://github.com/webcomponents-cg/community-protocols
The Context protocol provides tree-scoped ambient data in an interoperable way. It's implemented by Lit and FAST (I believe). It doesn't replace a data store, but it's often used to provide data stores to components, and at least reduce some coupling.
I just meant that once I was rendering out each component without having to have it's parent know about its sub-components, I didn't have much use for global contexts or passing state to a component, itself. Auto-re-rendering isn't as compelling as just setting whatever data on the attribute change, or via a child element insertion/removal. Child element maintenance requires a lookup, instead of just re-rendering with the new state, but the DOM is really good at querying, so you can just look them up.
And so, once you remove auto-re-rendering (first render is still arcane, but it's still a pretty simple code snippet to accommodate templating), and state-based re-render triggering, there's not a lot that is added to the web component that isn't just a "different" way to do it, instead of demonstrably "better".
Of course, everyone's mileage may vary! Just my two cents after working with them for a few years. And my work has pushed me toward a "simpler is better" approach, which isn't universal. For people trying to make very complex web components that can benefit from batching tree renders, no amount of breakup will help (and my assumption is that it will actually hurt a lot? Web Components don't feel "cheap" as far as instantiation goes). So it's nice to have people working on these kinds of things so that we can one day get a drop in DAW web-component, or a performant "spread-sheet" component.
Anyone caring about CSP headers can't use this project and that anyone should be everyone even though the CSP header is PITA to setup.
I could implement turning those inlines into addEventListener calls, as I've done in good.html, but I just want to keep everything simple here, at least for now. Trade offs, ha! :)
Will `new Function` also be covered by the same unsafe-eval CSP restriction? I know I could just search, but I suspect more discussion with you will be fruitful.
Honestly it's a construct I've never seen out in the wild so I am not an expert.
Actually the name is deliberate. A homage to jquery, at first I even considered calling this library JCustom. I think the logic is cool, as Hyphen aspires to be a kind of layer atop custom elements, as JQuery was a layer atop the DOM (and MUCH more!).
Yet, the age of JQuery has passed. It's light has gone out of the world, and John Resig has retreated to the West with the Elves.
I don't think it's really an issue.
Also, it's time to consider that passing the torch of '$' is ok. Moreover, perhaps it was never not ok. As in, the existence of a library that binds to a tiny symbol should not restrict others from using the same symbol. Otherwise it would be kind of like 'variable name squatting'--not good! Dollars for all, I say! Who's with me???
So while I get your concern about conflicts there's always been ways to avoid namespace conflicts, such that these avoidance functions have been built into the libraries themselves, in the form of: noConflict().
I think that's sufficient.
And, not that it's the best guide of best practice, but for a long time Chrome, Safar and Firefox have included $, $$ and so on functions in their consoles, as part of the Web Console API.
I get you might be concerned about this, so you can easily modify this tiny base class of Hyphen to call the base whatever you like. Reasonably, there's plenty of names that could be good for a base class, given whatever it is that you might want! :)
For me, tho, I think $ is a beautiful symbol to name it after so I'm sticking with that. JQuery can bind to other symbols, but it's also not as common these days. I'm not worried about it.
Do you think I should be worried about this? If so why?
(I mean, leaving aside the fact that it's utterly nondescriptive...)
Perhaps a 1-2 sentence explanation, plus some code examples, would help.
Great - but for what? Is this client-side, server-side? Does it relate to a specific framework or approach?
I don't blame the project. They know their audience. It's the HN policy where extra context in the post title is forbidden that hurts stuff like this.
That was silly of me! I’m sorry, I’ll correct that. Thank y’all for pointing that out! :)
But don't quote me on that, I just use Vue for a living
Web components are cool, but the spec is bare. This means, a lot of rote work gets custom implemented. There are a lot of frameworks popping up, trying to "standardize" this.
Hyphen appears to one of these.
In the following line...
> Hyphen - A custom element base class for great developer ergonomics.
...I would recommend adding a link on "custom element" that points to a definition somewhere. This might make it easier for skimmers to parse the meaning of this opening line.
What do you think of this? I'd love to hear your thoughts on how it could be improved in the issues or discussions, or just here! :)
Was that part added later?
Deleted Comment