The broken promise of Web Components
dmitriid.com
dmitriid.com
“This was cumbersome. This was limiting. You could not prototype easily and quickly. You could not easily escape the limitations of UI libraries. etc. etc. etc.”
It would have been far more interesting to look at whether the broadest claims were even true, or why people ran into problems using various libraries. Every time I've done a test, React is significantly slower than using standard JavaScript + DOM but that's the wrong question to ask when you really care about whether it's a) fast enough and b) helping you write code faster, reducing maintenance or sharing costs, etc.
All I got from this is that using React makes life easier than calling the DOM APIs directly every time, which is technically true but doesn't say anything about e.g. how WebComponents compare when using abstraction following the best practices of software developers since something like the 1950s.
The “easy-peasy” example is:
['Hello', 'world'].map((text) => {
return <p><span>{text}</span></p>
})
The alternative given is: ['Hello', 'world'].forEach(text => {
const p = document.createElement('p')
const span = document.createElement('span')
span.textContent(text)
p.appendChild(span)
MyComponent.appendChild(p)
})
Since the first example requires 140KB of React code plus a ton of setup code to get to that it's either careless or dishonest to compare the two without acknowledging the possibility of using a helper function or two to simplify the second one, as JavaScript developers have been doing routinely for multiple decades.Again, not a slam on React, just this kind of unhelpful advocacy. It'd be a lot more interesting to compare real examples with multiple libraries and talk about how the different choices impact debugging, performance, isolation, etc.
You cannot buy into Web Components without buying into at least Polymer to supply you with data binding.
If you don't like React's size, there are slimmer options: Inferno, Preact etc.
Why? You can use Polymer and not its data binding. In fact you can use its polyfills and deal with mostly the standard based APIs. X-Tags and many others also use Polymer's polyfills.
DOM APIs are awkward as hell. But you can create Web Components and handle the data binding yourself. It's actually pretty easy to roll your own data binding. Now whether you should is another matter.
> This gets progressively worse as your components grow in complexity. Imagine adding a span around the text in p?
In this case you simply type in the span inside the template tag similar to what you have in the React JSX and then just importNode the contents into your webcomponent.
Nope, it's not. It's a thin XML-like DSL which translated into Javascript calls: http://bit.ly/2nC6fdu
This is completely and utterly false. What are you basing your assertion on?
SkateJS's TodoMVC uses JSX[1]. Does that also imply that all Web Components need to use JSX? I don't see any logic in your claims.
[1]: https://github.com/skatejs/todomvc/blob/skatejs/examples/ska...
> it's either careless or dishonest to compare the two without acknowledging the possibility of using a helper function or two to simplify the second one, as JavaScript developers have been doing routinely for multiple decades.
To quote from the article:
--- start quote ---
DOM APIs are horrible, cumbersome, awkward and clunky. Polymer and others are bravely trying to use DOM APIs only, but even they resort to innerHTML anywhere they don’t have to put on a show (tests, for example). When Web Components take root, the web will be flooded with less performant innerHTMLs and possibly re-implementations of snabbdoms and virtual-doms (obviously incompatible)
--- end quote ---
Similarly, if people are building reusable components it's not clear why using different virtualdom libraries or not using one at all is a problem. Doesn't sound software engineering practice recommend a well-defined public interface rather than deeply interconnecting them like that? Besides, this is the web: what are the odds that you wouldn't quickly hit a problem where component X depends on v1.2.3 but component Y requires v2.3.4?
I can't see any fundamental reason why generating the DOM with React would be faster than generating the DOM with Web Components - They're doing the same thing; it's a matter of time before they even out (if they haven't already).
I think that Web Components would end up faster ultimately due to native browser support (more room to optimise).
We do think it helps to create maintainable apps though, and it is fast enough for practical use cases despite its immutable design. That's why we use it at Facebook.
jQuery code is well known to be hard to maintain when it changes things based on element-types, classes and id's nested in each other.
var widget = require('./widget');
widget.foo('bar');
And the widget handles the jQuery'ing for you. module.exports = {
foo: function(s) { $('.widget').text(s) }
}- it breaks immediately if there is more than one '.widget' on the page
- your app will get out-of-sync DOM updates because different parts will flush changes to the DOM at different times (React is DOM = f(state, time), so if multiple changes happen in f, they all be flushed to DOM in one go)
* Inconsistencies or duplicated code between initial render and updates.
* Having to write code for N^2 transitions between N valid states instead of N valid states themselves.
* Harder to ensure DOM operation order across different parts of the codebase doesn’t produce jank.
Sure, this is all possible to solve, but in my experience I just ended up with a crappy version of React.
Honest question, because I use Javascript just infrequently enough to have to look this up every time (and it's possible the reported solutions are old), is there a good concise universal way (or two, at most) to determine the type of the variable I may have? It seems it's always a tossup between typeof, instanceof, == null and == undefined, and to my eyes that's way too many.
If the type you're testing for is not one of the builtin literal types, you can just use instanceof.
I guess an alternative is foo.constructor and foo.constructor.name, which will give you a string if you want that.
That's why many dynamic languages allow access to the type through a string representation.
Best just to see if it quacks, or barks :-)
(i.e. - does the object define a given function/"method")
EDIT: Of course, I forget that arrays also have `typeof` "object"... Again, probably best to just start using length and indexing and hope for the best.
What about automatic semicolon insertion (ASI)? This makes it impossible to internalize how the JS implementation will interpret your code without memorizing a bunch of special cases, and makes "newline" semantically significant even if you only intend it for formatting your code.
It's never going to be fixed because of backwards compatibility. I can put up with a lot of language peculiarities, but I don't like wondering if some random "return" statement in my code is going to misbehave because of extra whitespace.
This is enough reason for me to keep looking for an ideal language.
Remember the bad old days of BASIC? You only needed that extra delimiter (colon, rather than semi) when you were going to put multiple statements on one line :-)
Yeah, minifiers are often too dumb to accept valid input text :-(
I'd be happy to treat JS as "statements end with a newline, not semicolon", if that were consistently true.
The paradox of Javascript: Do statements end in a semicolon, or newline? If you answer either way, you're sometimes wrong. As far as I can tell this is the only misfeature of JS that is really unfixable.
['Hello', 'world'].forEach( text => MyComponent
.appendChild(document.createElement('p'))
.appendChild(document.createElement('span'))
.textContent = text
)I'd be interested in seeing those tests.
React apps are usually built with many layers of components that returns another components. And with standard Web API, there will be significantly more DOM nodes because each component is a DOM node.
You can check it out yourself: https://localvoid.github.io/uibench/
I highly recommend the author's article about it: https://medium.com/@localvoid/how-to-win-in-web-framework-be...
It's gotten to the point where if you list JS frameworks, you can slip in 2-3 that don't exist and I don't think anybody would notice.
No, it's gotten to the point that any attempt to do so is doomed to failure. ;)
The article makes the usual criticism that web components doesn't have a databinding spec. It's true, it's a short coming. But the lack of a databinding spec doesn't make Shadow DOM any less useful. The two are unrelated.
The author really likes JSX, but didn't bother to google "jsx web components"; if he had he might have come across this: https://skatejs.gitbooks.io/skatejs/content/
There are some real things to criticize about web components. Shadow DOM makes it hard to theme your app. Custom elements has some warts in regards to the way that some events are composed. But the article doesnt make these criticisms.
> Shadow DOM makes it hard to theme your app.
Polymer uses CSS custom properties for this and it's not even hard, it's pretty cool, it's like you're making a CSS API for theming <your-element>.
Could you please link to a reference? I've never heard of JavaScript attributes and I can't find any reference on MDN.
element.foo = 123;
vs element.setAttribute('foo', '123');> Shadow DOM makes it hard to theme your app
The styling is a little different from normal but not too bad after getting the hang of it. There's usually a few components for colors, fonts, and common app styles (see paper-styles [1]). A few other components can provide CSS helper classes (e.g. Polymer's spin on flexbox [2]). CSS properties make tweaking component styles pretty easy, which makes the components more reusable. There are probably other styling tricks that'll pop up over time too.
(you don't have to use Polymer, I picked it because it's what I'm most familiar with)
[1] https://www.webcomponents.org/element/PolymerElements/paper-...
[2] https://www.webcomponents.org/element/PolymerElements/iron-f...
I have a bigger rant about web components, and polymer and interesting enough, I believe - none except one - of the article owner's comments matter much.
Here's another rant and what currently sucks at web components (for us)
1. Still no adequate browser support coverage (OP's point) - Google polymer team keeps the mood up, is excited and does great things but we're still waiting
-- for Firefox to support it, (which declined to implement html imports). I kind of think Mozilla is sabotaging the efforts for some political reason after reading their comments.
-- for Safari 10.2 to support it natively
-- for Edge to care.
Unless all the browsers support the v1 spec, we end up being just another JS framework and the Polyfill webcomponent-lite.js itself becomes a framework in competition with vue, react, riot, and others.
2. Web components (especially) polymer ones are not a good citizen inside an HTML page next to others.
-- Due to component scoping (which is ok), style scoping in the main document css (custom style) (which is weird) and DOM and shadow dom, you end up feeling hopeless at some point.
-- I just want to use the paper-toggle and I end up downloading 100s of files.
3. The opposite of "it just works" ( as someone wrote somewhere, kind of, like you invented the opposite of it just works.)
- Polymer, and the paper, iron and other libraries are cool, but they somehow fail silently and often. It's hard to figure out what went wrong.
4. Polymer team is trying to replicate the cool other JS tech and tools, and it just lags behind. (ES6 in polymer 2.0, the still beta app router, bower, gulp and OMG.js ....)
5. The iron, and then paper, and other elements are too heavily interdependent and no other leaner library or collection appears in sight.
- A lot of nested imports
- and the hopeless the wait for HTTP/2 and the future web of 2020s.
I will write later what can be done.
And then... They are really not different from React/Angular/<add your own> :)
How is that anything like React or Angular that really don't let you interop with other libraries out of the box?
1. A very lighweight set of touch-first self-contained UI components collection
Carousel, navigation bars, card, switch (toggle), a range slider, calendar/time picker, badge, rating and some. They must be self-contained and light-weight. All the set of iron-, paper-, vue, (material, mintui, and even Bootstrap set available.
It must be so easy, and so cooperative that WordPress themes must be able to use those components with 2 lines of imports. If the old web starts using it, not the mobile app web, it ll be easier to force other browsers to support it natively.
2. Full browser coverage and elimination of polyfill.
Next to Chrome/Opera and Chromium++, Safari 10.2. Then HTML import support issue (ES6 modules?) with both Safari and Firefox remain as still big obstacles as well as Edge's uncertain future support/
3. Mobile App-like components and features only for the advanced apps,
- Router, caching, service workers.. - Better debugging and error messages. - Better failover. - Here competition with angular, vue, react, riot and others seems hard to win, and this must be a separate battle.
So, "declarative custom components" have fully devolved into "JS-only framework that pretends to be DOM and adds weird limitations"
DOMNodes are these hot data structures when they're linked into the DOM tree. Writing (and even sometimes reading) from DOMNodes can cause whole page reflows or paints to occur, sometimes synchronously. The advice to "touch the DOM as little as possible" stems from the fact that the DOM has so many performance pitfalls.
The DOM is so damned awkward that it's faster to maintain a virtual DOM tree, diff changes to it on the next tick with those of the previous, and then only modify the real DOM tree with those changes like React does. Couple that to the fact that React's DOM update strategy is tuned for speed and they can hide/handle most weird DOM object creation/insertion rules, and overall you wind up with a less awkward system.
If I compose my page entirely out of Web Components, the chief benefit I see is good looking markup representative of what's on the page.
If I were to have designed a component system from DOM, I would have added a lightweight iframe-like tag that could be instantiated from a script/css/markup template package. Performance-wise, I thought that having a nested element's box be laid out and painted independently from the parent page could mitigate some of the global ripple effects of touching the DOM. Of course, this lite iframe's box itself would need to live in the parent page and its dimensions would have to factor into the page flow, and that could have global effects. I suspect there could be clever JavaScript or event system additions that would help limit the reflow and paint damage there.
Sorry, but it's not. I don't know what it is that you think React does, but it makes those same awkward DOM API calls. React is an abstraction layer and abstraction has a price. Having React call .appendChild is not faster than you calling .appendChild yourself.
Yeah, you can do this all on your own, but you get it with React for free. Abstraction does not necessarily have a price.
No, it doesn't. Not unless you do one of two things: 1) yield to the event loop and wait for a refresh tick or 2) query layout information.
The usual perfomance cliff is when you do #2 interleaved with appendChild. Because then the browser really does need to reflow.
With React, what you get is the ability to queue up all your appends but keep asking for the _old_ layout information, without taking your appends into account. Sometimes that's what you want, sometimes it's not. It really depends on why you're asking for the layout information.
React also uses a couple of other tricks, like attaching one event handler to the top of the DOM doc and fire fake events (they call it synthetic) to avoid having many handlers in the page. Or queueing udpates and merging them so that if many updates arrive at a very close time, you update only the DOM once.
Vue.js and Angular 2 do the same. Although Vue.js is my favorite right now because it's so much more flexible and easier to use.
As you say, one can rig up DOM updates through VanillaJS calls that are more efficient than React if you're a clever masochist.
If sub-optimal accesses to the DOM is where most of the time is spent in an application, then using an abstraction like React to manage the DOM really can make the system faster AND more manageable, as you say.
> The advice to "touch the DOM as little as possible" stems from the fact that the DOM has so many performance pitfalls.
What are those performance pitfalls?The only ones I know are that if you read something related to the layout size (offsetHeight), it might cause layout recalculation. And obviously if you modify how element looks like (innerHTML, changing class, etc).
This doesn't seem like much (assuming I dit not forget something). The first one is not obvious through.
http://jankfree.org/ is a good resource for causes of slowdown.
https://gist.github.com/paulirish/5d52fb081b3570c81e3a
My favorite of the doozies is _reading_ from some properties on mouse events will cause a reflow.
The example of passing dynamic styles to a web component is a good one: since attributes have to be strings, you have to handle serialization somewhere rather than have it Just Work, like it does with most modern JavaScript frameworks.
Web Components work ok as "leaf" nodes: then it's just a component with string props/attributes.
The inverse is nigh impossible: Web Components can only deal with strings, so you would end up with yet another incompatible wrapper à la Polymer if you want to do anything remotely complex.
Of course you can pass non-strings to DOM elements. Open your development console and type document.createElement('div').user = {name: 'Dmitri'}. Did you get an error?
Web Components can be arbitrarily complex and accept arbitrarily complex properties or children.
Did you even try Web Components before spewing falsehoods?
It's not a framework or a library, but rather a set of rules and practices of how to do things. It will sounds somewhat weird, but I'll list them anyway. Maybe someone will find it useful:
- Overall mental model: libraries don't implement components, but rather change the behavior of certain elements on the page.
- No library keeps state in JavaScript variables. DOM is your model.
- All configuration is also done through DOM with liberal use of CSS selectors to reference things.
- No library has any direct dependencies on any other.
- Things that need to react to changes should (if at all possible) use timers. If that's not possible, they should register global event handlers, rather than attach them to individual components.
While this might horrify some people, it actually forced me to come up with rather nice, abstract behaviors that capture many common features I had to commonly implement. Also, this plays well with the notion of progressive enhancement. I recommend everyone to try it on some new web-based project of low to moderate complexity. Ir really does simplify a lot things.
http://quickenloans.github.io/Behaviors.js/
The code will look extremely non-idiomatic to most frontend engineers. Treat it as a thought experiment (which it is). The important thing isn't the particular implementation, but the ideas I outlined above. There are other, often simpler, ways to implement the same concepts.
One property of these libraries that might not be immediately obvious is that they "automatically" adapt to changes on the page. So you can add an element with a behavior via AJAX or dev tools and it will actually work. This is why using timers and global events is a part of that list.
Another thing is that none of these libraries have an API or modify the global JS namespace. This is part of the thought experiment not directly related to the main concepts. It drives some of the complexity.
Edit: Fixed the link.
Kinda used it as an attempt to address some bigger questions around Web Components I've seen come up a bunch. Curious what you all think.
Assignment and composition - by CSS, and only by CSS, Sciter's specific prototype property:
.myComponent {
prototype: MyComponent url(components/my.tis);
color: red;
...
}
where MyComponent is the name of script class in components/my.tis script file. That MyComponent looks like as class MyComponent : Element
{
function attached() {
// called when DOM element gets this class
// a.k.a. DOM constructor
// content initialization, if needed:
this.$content( <button.prev/>
<button.next/> );
}
function detached() {
// called when DOM element looses this class
// a.k.a. DOM destructor
}
function method() {
// some public method
}
property prop(v) {
// some public property
get { ... }
set { ... }
}
event mousedown (evt) {
// default mousedown event handler
}
event mouseup (evt) {
// default mouseup event handler
}
event click $(button.next) {
// click on its button.next
}
event click $(button.prev) {
// click on its button.prev
}
// etc
}
To use such component:1. include CSS where the binding is defined (prototype above) 2. In markup to define matching element:
<div.myComponent />
Nothing too complicated and pretty effective. Could be done even 18 years ago: http://www.w3.org/TR/1999/WD-becss-19990804#behaviorTake a look: https://github.com/wisercoder/uibuilder
Here's an example with the latest wc spec: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sl...
But as far as I understand from the spec, sadly, you would still need a separate DSL that would leverage iterations, conditionals and other templating operations on top of web components.
The below from the article is ridiculous and HORRIBLY INNACURATE:
['Hello', 'world'].forEach(('text') => {
const p = document.createElement('p')
const span = document.createElement('span')
span.textContent(text)
p.appendChild(span)
MyComponent.appendChild(p)
})`
In RiotJs you would build a web component this way: <text-breaker-upper>
<p each={this.input_str as out_text}>
<span>{out_text}</span>
</p>
<script>
this.input_str = input.split(" ");
</script>
</text-breaker-upper>
Then you declare it ON THE PAGE. <text-breaker-upper
input_str="Hello World">
<text-breaker-upper>
Web components much more intuitive, reusable, better separation of concerns, less bloat, and more elegant than React..no bastardization mixing of JSX, CSS, and JS, and how the web (and any sane technology) was meant to be.The sooner this JS front end framework B.S. wasteland nightmare is behind us, the gournd salted on where it stood, and we laugh at JS like we laugh at COBOL.... the better in my opinion.
To me they are fine for when you want to compose things by nesting them but bad at any other kind of composition.
But if I had to pick my favourite, I'd have to go with Polymer because:
- It's simpler (fewer dependencies).
- Declaring styling and layout is cleaner (it's easier to visualise a component's appearance and to modify it during debugging because layout and styling are kept separate from a component's behavior logic). When it comes to debugging, styling/layout issues almost always happen independently from behaviour issues so it makes sense to keep them separate in the source.
- It works better with browsers' native development tools.
- No need for a compile step (and associated complexity that comes with it as your app grows).
It's not fair to pretend that React bypasses the DOM entirely. Ultimately, when the user clicks on a button, they are interacting with a DOM element, so the event still has to pass through the DOM layer. The DOM is still the API whether you like it or not.
This is happening at other levels of the web with the HTTP/1 —> HTTPS/2 transition. We're patching over shortcomings in the DOM right now with JavaScript, but it's easy to see the appeal of a native solution to some of these issues that doesn't require a facade over the actual thing that people interact with.
Using the DOM + scripting pattern with a DOM more suited to "rich app" development has been tried multiple times as plugins (FLEX and Silverlight come to mind). But the gravitational pull of zero-install + standards crushed them. We also have in progress what is essentially a reinvention of the JVM idea (WebAssembly) but without the UI layer to go with it. That may be successful in its narrow niche, or it may try to overreach into the UI layer and fail like all the others…too soon to tell.
Stuff like Bootstrap needs them to enrich their "top down" approach with CSS and HTML so you don't have to mess around with either their source OR CSS classes and "their" HTML structure everywhere to get the components right. You add their CSS & (Web Component) JS at the top and use their custom components as the "leafs" of your sites. I wrote about it here: https://dev.to/kayis/web-components-for-your-custom-elements
The other frameworks like React, Angular, Cycle or Ember will then become the Data-flow layer of the app.
None of these frameworks like dom being changed from underneath them, you will run into all sort of weirdness.
I mean, in React, the whole app is basically a component. It wouldn't make any sense to replicate this with Web Components.
But yes, I read a few times that the React devs don't consider React a "data flow framework", so they decide when to re-render and stuff. Cycle is more in line with this thinking.
Not really - React needs a large run time, and it's really not a good fit for an existing app where you need a true component you can pop in and use within a Java or Rails app.
Web Components also need a runtime as soon as you need anything sufficiently complex. Such a simple example as a ToDo List cannot run without Polymer's weird data-binding implementation: https://github.com/PolymerLabs/todo-list
There seem to be two main criticisms here:
1. Web Components don't provide a DSL for creating DOM trees. This is true, because Web Components don't provide a DSL for creating DOM trees! That's and entirely separate concern that can be addressed by a library[1] or by a future, more-ergonomic tree construction API in the DOM.
2. The author doesn't like Polymer's templates. Which is, nearly as unrelated to Web Components as the first complaint, because it's also the same as the first complaint. Polymer's template system allows you to create DOM trees, and automatically wire together properties and events. That's it. You can do this without using custom elements at all.
You can also use any number of ways to construct trees: JSX, DSLs, HTML templates and cloning, string templates, etc. It's no different from any other framework including React.
And then he repeats Sebastian Markbåge's, creator of React, tired complaint that Web Components can only consume string data. This is FUD, pure and simple. Web Components are Elements, and Elements are Objects with properties. Every Web Components framework relies on this for data-binding or setting "props".
And... that's it. That's basically the extent of the critique.
The author could have actually tried to compare React and Web Components and he would have seen that:
1. The component models are very similar, there are analogs for most lifecycle events.
2. Web Components don't prescribe state management or dataflow, and there are indeed functional, unidirectional, immutable (by convention), Web Components libraries and patterns out there.
3. Shadow DOM fixes nearly everything that css-modules address in a native and performant way.
Web Components aren't a be-all-end-all standard. They are a few fixes to the platform that should have been there from the beginning, namely that you should be able to extend the HTML vocabulary (Custom Elements), you should be able to encapsulate some DOM and styling (Shadow DOM, which most browsers already had for their own UIs), and that you should be able to ship and parse _potential_ DOM (<template>). That's it. Everything else is either a library responsibility and/or TDB. Web Components are a foundation to make it easier to create frameworks, libraries, and an base-level interop story that answers some, but not all, critical, simple questions:
How do I write a widget? : Extend HTMLElement How do I keep other scripts and styles from interfering with my widget's internals? : Put the internals in a ShadowRoot. How do I scope my own styles? : Put them in a ShadowRoot How do I instantiate my widget: Use a tag in markup, call createElement(), or call it's constructor.
Anyone coming to the web platform as a new developer now doesn't have to sift though 100 frameworks and contradictory tutorials for such simple questions, the platform provides answers, as it should have from the beginning.
[1]: See Hyperscript as a React-like example https://github.com/hyperhype/hyperscript
The complaint in the article is that Web Components don't _also_ add a nice API fro creating DOM trees.
This is true, but not relevant. The ideas of a component model and tree creation DSL are orthogonal.
Hyperscript (the JS library) is a nice API for creating DOM trees, that's similar to React's createElement(). You can use it with or without Web Components.
Here's that library. JSX for Web Components: https://github.com/wisercoder/uibuilder
;)
Instead, it's hack on top of hack.
The thing though, JSX doesn't return DOM elements. It's a very thin DSL on top of React.createElement calls. Since it's Javascript all the way down, you get a number of benefits:
- you are not constrained by limitations such as "attributes can only be strings" or "children can only be DOM Elements". It's all Javascript expressions
- React knows the return value. It can do efficient diff'ing against its internal DOM representations and only update the actual DOM as needed (same goes for other virtual dom implementations such as snabbdom, virtual-dom etc.)
XBL (2001) and its successor XBL2 (2007) was proposed by Mozilla as a companion to their XUL user-interface language
https://blogs.windows.com/msedgedev/2015/07/14/bringing-comp...
When all you can pass to an attribute is a string, it's very hard to seriously talk about useful extensible custom components on the web :)
Here's the docs for an element that has some arbitrarily complex properties and function-values properties, that you can set from plain JS or Polymer's template syntax or Angular's, etc... https://www.polymer-project.org/2.0/docs/api/elements/Polyme...
It also takes a template and stamps it out several times based on the complex data, like "higher-order components".
It's very hard to seriously talk about web components with someone so willfully ignorant about them.
DOM APIs are a much bigger problem than just WCs, but are a part of the WC's problem as well