How is JavaScript library bloat mitigated with Web Components?

2.4k views Asked by At

As someone who has tried to find a way to help content authors develop and maintain big web sites by creating (HTML) components for years, I'm really excited to see web components gaining tracction at w3c, google and mozilla. But it seems to me that, there is no measure against javascript library bloat in the specifications.

Say that I develop component A which has a dependency for underscore.js and want to use components B and C which have dependencies on lodash.js version 1.*, etc.
I don't see any way to flag dependencies and library versions. This could lead to huge library bloat when we talk about web sites with multiple teams and stake holders.

The current solution is to standardize on a wholesale client framework for the entire web site, globally. This is difficult when you have invested substantial resources in different server-side frameworks like LifeRay (java), EpiServer (.net), Django (python) etc. each with preferred client-side libraries.

I see web components as a mean to decouple server-side frameworks from client-side code, but the omission of client-side dependency handling is worrisome.

Is it in the specifications and I have missed it, or is there a strategy to mitigate this issue , that I'm not aware of?

[THE LIBRARIES MENTIONED ARE JUST EXAMPLES. THE QUESTION IS AGNOSTIC TO FRAMEWORK, LIBRARY AND SERVER-SIDE LANGUAGE]

UPDATE Thanks to all for answering. I'm surprised no one mention Mozilla X-Tag or Google Polymer which has been all the hype lately. I completely buy into the idea of shadow DOM, scoped styles, custom elements etc. but nowhere do I see any mention of how to deal with JavaScript dependencies. As @Daniel-Baulig correctly writes HTML Imports doesn't mention JavaScript at all. I acknowledge this question is almost impossible to answer. However, I think @Daniel-Bailig came the closest, when he mention ES6 Modules. I personally think that we will find a sustainable solution somewhere between ES6 Modules and require.js.

8

There are 8 answers

1
Daniel Baulig On BEST ANSWER

In the current W3C spec there does not seem to be a specific way to define dependencies or even version them. Components are expected to not make use of any libraries/dependencies or to be tightly coupled with them.

This means that each major library will probably bring their own set of components with them that expect those libraries to have been loaded.

Maybe ES6 Modules provide help in this regard, but then again they currently also don't provide any versioning mechanisms.

That all said the spec is in a pretty early stage and is likely to change. Bringing the dependencies issue up with the spec authors might bring that topic to the table and might even solved before the spec solidifies. In the end using different libraries to perform the same task within a single code base has always been and will continue to be a problem in software development, regardless of platform and language. You will just have to agree upon which frameworks/libraries to use within your codebase, even if that means locking you out of others.

Also, if you are interested in developing independent components for the web already today you might want to take a look at the React library

2
cstruter On

This is an issue that's been bothering me as well for a while, esp when faced with maintaining code that's been touched by numerous developers. You often encounter multiple JS libraries (some of which essentially does the same thing) included in one solution, not to mention even different versions of the same framework used in one solution.

The or rather "a" potential solution that I am looking at is to create a type of mediator framework.

The basic idea is to code "against" the mediator (never accessing/using a js library directly, but using it via the mediator), thereby essentially making the code agnostic (decoupled from its "parent" library) and including a mediation implementation underneath.

This wont address my/our immediate problem or bloat, but whatever web component I write will be able to run cross framework.

Here is a little proof of concept: Tagger Mediator POC

E.g. mediators included for:

JQuery (1.9.1)

Mootools (1.4.5)

Prototype (1.7.1.0)

YUI (3.10.3)

Dojo (1.9.1)

Ext (3.4.0)

Zepto (1.0)

But nothing stops anyone to create their own mediation framework, which "abstracts" other mediators, hmmm, so potentially something that can contribute to the bloat as well (making things worse rather than better).

I guess its up to you to set your own standards ;)

1
RononDex On

I never heard of standardising javascript frameworks. However with HTML5 some parts that used to need javascript frameworks in earlier versions of HTML have now been added as a standard feature to HTML5 (for example the tag <canvas>, rounded borders, shadow effects, ...), which is a first step in solving what you are mentioning.

To be honest I don't think that this is ever going to happen, or at least not in the near future. All those frameworks out there have their own purposes, advantages and disadvantages. Until you can build a huge javascript library that somehow combines them all there will always be different libraries used all over the web.

Another important point is the competition between the different libraries that keeps the javascript market growing and come up with new innovations. IF you would make a standard javascript library that all would use, you would also eliminate the competition between the different frameworks that keeps innovation and progress going.

2
Ben On

Library bloat isn't necessarily mitigated by using web components, in fact you can trim down libraries by using custom builds (link is for Lo-Dash, but other popular libraries have build steps). Some kind of automation here can be very useful, i.e. a tool that can scan through your source code and generate a custom build based on what functions you are using.

I think with the rise of npm such libraries are becoming less and less relevant. Lo-Dash is a good example of this because its functions are being released on npm as standalone modules, but you also have things like Sizzle, the CSS selector engine that jQuery uses. If you look hard enough you can find lots of plugins that are being written without jQuery as a dependency, or it is in a project's roadmap to remove dependencies, or someone has already forked the project to remove its dependency on another library. For example, Exoskeleton, a completely underscore & jQuery free version of Backbone.

I don't think we're going to see another popular utility library such as jQuery or underscore; with npm we can simply choose the modules that we want, and fork projects that depend on these large libraries to rewrite them using only the modules that they need, or a completely dependency free version.

With AMD and requirejs, this is already a reality. You can define some source code's dependencies; instead of linking to monolithic libraries, in your code you can state that this component only needs for example microajax instead of the whole jQuery library:

define(['microajax'], function(microAjax) {
    microAjax("/resource/url", function (res) {
      alert (res);
    });
});

Check out the microjs website for more helper libraries like this one.

As far as web components go, I'd hope that the authors of these write their components in such a way so that they don't require massive libraries like jQuery that can do everything. And if they do I would also hope that I could fork them and trim out all the unnecessary parts myself. :-)

Edit: This 24ways article is a good primer on the rise of native JS features that will eventually supersede jQuery. It's worth mentioning that jQuery was written at a time where JavaScript implementations were wildly different; but as standardisation has risen and APIs have become more consistent, the need for a wrapper around native functionality has diminished somewhat. For example, now we have querySelectorAll:

// jQuery
$('.counter').each(function (index) {
  $(this).text(index + 1);
});

// Vanilla JavaScript
var counters = document.querySelectorAll('.counter');
[].slice.call(counters).forEach(function (elem, index) {
  elem.textContent = index + 1;
});
0
Paul Sweatte On

Say that I develop component A which has a dependency for underscore.js and want to use components B and C which have dependencies on lodash.js version 1.*, etc. I don't see any way to flag dependencies and library versions.

There's an old ECMA spec from 1999 (ECMA-290) that specified a component mechanism which included elements and attributes for dependencies and library versions:

<component
name="com.mycompany.selectnav"
displayname="SelectNav"
src="selectnav.js"
env="client"
hint="Navigational Select List"
version="1.0"
needsform="yes">
</component>

For dependencies, use the customizer element. For versioning, use the meta element. For example:

<customizer type="ecmascript" url="http://com.com/foo">
  <meta name="version" value="1.1b"/>
</customizer>

A JSON encoding for modern implementation would look like:

{
"component":
 {
 "name":"com.mycompany.selectnav",
 "displayname":"SelectNav",
 "src":"selectnav.js",
 "env":"client",
 "hint":"Navigational Select List",
 "version":"1.0",
 "needsform":"yes",
 "customizer":
   {
   "type":"ecmascript",
   "url":"http://com.com/foo",
   "meta":
     {
     "name":"version",
     "value":"1.1b"
     }
   }
 }
}

Lifecycle callbacks integrated with a CDN API, an API checker, and an on-demand script loader would be another option:

createdCallback => check API URL => createElement for dependent script tags => onload event for dependent script tags => appendChild for dependent script tags

Subsetting HTML as in the following projects is a solution which has been tried numerous times:

References

0
balupton On

We just try and keep our web component javascript to a bare minimal, keeping their footprint down very low. So instead of using underscore for whatever, we'd just use the native ES6 equivalents (considering the existing use web components, this isn't much of a stretch).

We then package our web components with one html file, one css file, and one js file. They can actually be made up of multiple files, but our build process takes care of that. Our javascript file is bundled with Browserify, allowing us to use npm modules, while have them nicely packaged within our web app.

This offers really nice tiny, but blackbloxed web components, that can share common modules without any conflicts, and without having to worry about AMD overhead, just simple commonjs for everything.

If we ever do need to bundle a heavy library across multiple components, we would either make that library external and pass it in, leaving the inclusion up the application (this is how you have to do it with backbone.js, due to backbone.js using instanceof instead of duck typing, making multiple backbone.js instances talking to each other impossible), or have the web components bundle it in using a browserify cdn server like wzrd.in — however, much better for the parent web app to handle large deps, as if the web components use different cdns, then you have a issue.

4
Jesse Hattabaugh On

I've had similar misgivings about web-components myself, but then I started working with third-party React components. They have the same problems in that they bring along all their own dependencies. But worse than that, because React doesn't like living with other versions of React, React components must list React as a Peer Dependency. This means that you are forced to use the same version of React as your third-party components.

So my conclusion is that this is in fact a feature of web-components! Every Custom Element can have it's own dependencies entirely independent from the rest of your app. I consider third-party components to be the main use-case for Web Components; where the consumer wants to be able to use a component with as little friction as possible. Sure there will be some duplicate code, but the component consumer doesn't need to know or care. I think that component authors will deal with this is by using smaller modules in their components. For example, instead of using React they could use something like Snabbdom.

If you are building an entire application out of web-components that you control, you can still use a bundling tool like Browserify or WebPack to manage your dependencies. These will allow you to override certain modules so that you can even force third-party components to use the same versions as the rest of your app (see: https://www.npmjs.com/package/aliasify). That might break some modules, but that's life.

0
Edderd On

IMHO the issue you described always exists in frontend and not directly related to web components, but web components do make this even worse.

Say if you rely on component A and B which all relies on lodash, unless A and B both set lodash to peer dependencies (which makes them look like plugins like most jquery plugins do), then you can include one copy of lodash, otherwise, A and B are meant like black box to you. Though bundlers may do some post-build processing like tree shaking/dead code elimination to help mitigate this, but this could never 100% work, e.g. A and B both rely on two different versions of lodash.

Why web components make this even worse? the goal of WC is to provide a framework agnostic way to reuse UI components, say if i use vue and a bunch of vue components, my bundled js ends up with these components and a single vue runtime cuz vue is a peer dep in these components. Now if i use WC and a bunch of WC components, i will most likely end up with a larger bundled js. For example, can create a simple lit-element WC component with a simple button, then build the project and see how the bundle js is much larger. Some framework may take a different approach, vue for example, still requires you to add vue as an external framework to run vue based WC, which doesn't save you one byte if you switch vue to WC. Also, sharing styles is another problem in WC in terms of reducing bundle size.

My company uses bootstrap and react, our app is bundled with 1 bootstrap, 1 react and many external react components, which already ends up to be 3.5mb js/css, i doubt we can do the same thing in WC in 3.5mb.