Skip to main content
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: George Murphy
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: George Murphy

Replacing jQuery (110kb) With Umbrella JS (8kb)

By on

The other day, I decided to stop supporting IE11 on my blog. Which means, I should be able to remove a lot of the transpiling that I do in my JavaScript build system (parcel). Which should, in turn, lead to a reduced bundle size. In that vein, I momentarily considered replacing jQuery with native JavaScript constructs. But then I remembered, jQuery has a luxurious API and I'd be darn fool to give that up. Really, what I want is just a smaller jQuery. Enter Umbrella JS by Francisco Presencia, a tiny 8kb (3kb gzipped) alternative with a very familiar, fluent API.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

The version of jQuery that I was using - 3.4.1 - was something like 110kb uncompressed, 34kb compressed. Umbrella JS, on the other hand, is only 8kb uncompressed, and about 3kb compressed. That's a massive savings on size. Of course, there are some sacrifices taken in that reduction. While the Umbrella JS API is similar to the jQuery API, it's not exactly the same. Umbrella JS doesn't have all of the same convenience methods; which means, you either have to update your JavaScript code or author some Umbrella JS plugins.

Also, Umbrella JS doesn't have an AJAX module. Luckily, I just wrote an API client using the fetch() function, which will act as a stand-in for the jQuery.ajax() method.

When converting from jQuery to Umbrella JS, the changes can be made incrementally since jQuery uses $() as its handle and Umbrella JS uses u() as its handle. The only constraint, really, is that you cannot use one instance inside the other. Meaning, you can't pass an Umbrella JS instance to a jQuery method - bad things will happen.

ASIDE: If you're using an ES Module based build-system, you can always import your libraries and rename them at the same time. As such, it doesn't really matter what jQuery and Umbrella JS use as their global references.

With that said, I wanted to put together a simple "Hello World" demo for Umbrella JS so that you can see how close the API is to jQuery. The following code isn't really doing anything meaningful - it's just authoring some Umbrella JS plugins and then calling some DOM (Document Object Model) manipulation methods:

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<title>
		Umbrella JS - A Light-Weight Replacement For jQuery
	</title>
	<link rel="stylesheet" type="text/css" href="./demo.css">
</head>
<body>

	<h1>
		Umbrella JS - A Light-Weight Replacement For jQuery
	</h1>

	<h2>
		Friends
	</h2>

	<ul class="friends">
		<li>Kimmie Karate</li>
		<li>Timmy Tornado</li>
		<li>Pammy Prankster</li>
	</ul>

	<!-- Load scripts. -->
	<script type="text/javascript" src="../../vendor/umbrella/3.2.2/umbrella-3.2.2.min.js"></script>
	<script type="text/javascript">

		// While Umbrella JS doesn't have all the same convenience methods that jQuery
		// has, it is structured in the same way, wherein the Umbrella prototype can be
		// to register instance methods on the Umbrella wrapper. Let's create a few
		// event-based plugins.
		u.prototype.mouseenter = partiallyApply( u.prototype.on, "mouseenter" );
		u.prototype.mouseleave = partiallyApply( u.prototype.on, "mouseleave" );
		// The Umbrella JS philosophy is that CSS shouldn't be touched inline; instead,
		// it should be updated by adding and removing CSS classes. Which is a nice
		// thought in most cases. However, sometimes, you need to perform dynamic,
		// runtime calculations that can't be predefined in static CSS classes. As such,
		// let's create a plugin that easily changes the underlying style properties.
		u.prototype.css = function( styleProps ) {

			return this.each(
				function iterator( node ) {

					Object.assign( node.style, styleProps );

				}
			);

		};

		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //

		// NOTE: There's no real rhyme or reason to what I'm doing here, I'm just using
		// the UmbrellaJS API to make some stuff happen.
		u( "ul.friends" )
			.append( "<li>Billy Bonanza</li>" )
			.find( "li" )
			.css({
				color: "fuchsia",
			})
			.mouseenter(
				function handleMouseenter( event ) {

					u( this ).addClass( "active" );

				}
			)
			.mouseleave(
				function handleMouseleave( event ) {

					u( this ).removeClass( "active" );

				}
			)
			.each(
				function iterator( node ) {

					var wrapped = u( node );
					wrapped.attr( "title", `My groovy friend: ${ wrapped.text() }` );

				}
			)
		;

		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //

		/**
		* I return a function in which the given arguments are automatically prepended to
		* the invocation arguments of the given function reference. The runtime THIS
		* reference is propagated through the internal invocation.
		*/
		function partiallyApply( functionRef, ...appliedArguments ) {

			function proxyFunction( ...restArguments ) {

				return( functionRef.apply( this, [ ...appliedArguments, ...restArguments ] ) );

			}

			return( proxyFunction );

		}

	</script>

</body>
</html>

As I mentioned above, Umbrella JS doesn't have all the convenience methods that jQuery has. But, we can easily replicate those methods through the use of Plugins. Just like jQuery, Umbrella JS works by creating - aka, new()'ing - instances of the Umbrella class constructor. The Umbrella JS API is then defined on the prototype object, which means that all instances of Umbrella JS can access the prototype methods on the this scope. Plugins - in both jQuery and Umbrella JS - then become nothing more than new methods injected into the prototype object, u.prototype.

JAVASCRIPT ALL THE WAY DOWN: Wait, so, it's like, in order to use Umbrella JS more effectively, I have to understand how JavaScript works? And, how prototypal inheritance works? And, how the this bindings works? And, how new() works to create instances? And, how those instances inherit methods? And, how partial-application of Functions can be performed? And, how spread and rest operators work? Yeah, spoiler alert, jQuery and Umbrella JS are JavaScript. It's JavaScript all the way down. Using libraries like this don't mean that you aren't using JavaScript - it just means that you're using abstractions that make your code easier to read, write, and maintain.

Now, if we run this code in a modern browser, we get the following output:

HTML page maniupalated using Umbrella JS, a jQuery-inspired DOM API.

As you can see, using the Umbrella JS API, we were able to add DOM elements, change the style attributes, attach event-listeners, and change the classList during enter/leave interactions. All while using an API that looks almost exactly like jQuery.

Now, I mentioned above that converting from jQuery to Umbrella JS doesn't have to be all-or-nothing. And, in fact, on my blog, I did it in a few steps since some code required more refactoring than other code. As I was doing this, I took note of my overall JavaScript bundle size:

  • jQuery only = 110(40)kb
  • Both libraries = 118(42)kb
  • Umbrella JS only = 10(3)kb

Because Umbrella JS is so small, including it alongside the jQuery library required a negligible increase in bundle size. But then, being able to remove jQuery once my migration was complete lead to a large reduction in bundle size. And, keep in mind that I'm still transpiling down to ES5; once I start using type="module" on my Script tag, the code should get even smaller.

Since "plain text" content is highly compressible, the gzipped size of the different production bundles is not that huge. However, keep in mind that the uncompressed size is a good representation of how much code the browser has to compile, optimize, and execute once it's downloaded. As such, this migration to Umbrella JS should have a meaningful impact on performance, especially on resource-constrained devices.

To be honest, I did spend a little time trying to rewrite my DOM-manipulation code using native DOM methods. But, after 5-minutes, I realized that this was a silly idea. Yes, "you may not need jQuery"; but, there's a lot of things in life that make life better even if you don't need them. It turns out, high-level, fluent APIs are one of those things. And, at 2.8Kb gzipped, the whole bundle-size argument no longer has a place in the decision-making process. And so, I'm very excited to be keeping all the jQuery-value, but using the tiny Umbrella JS library.

Want to use code from this post? Check out the license.

Reader Comments

15,192 Comments

Oh, two things that I forgot to mention in the blog post:

  1. I could not get event-delegation to work properly in the .on() method. Not sure if is a bug in the library; or, if I'm just not understand the documentation (they don't have much in the way of event-delegation examples).

  2. I could not get the u() library to load external JavaScript files when I created it with a Script tag string. Instead, I had to use u(document.createElement("script")). That seemed to work for reasons I don't understand.

15,192 Comments

@All,

In the blog post, I mentioned using type="module" to prevent some tranpsilation. One issue that I ran into is that module scripts, apparently, must adhere to the same-origin policy for CORS (Cross-Origin Resource Sharing). And, since my scripts are served up from a different (CDN) domain, the CORS security will block it with the current settings. As such, I'm sticking with type="text/javascript". That said, my browserslist setting is really what is preventing the tranpiling of code back to ES5.

1 Comments

I tried to run your demo, but nothing worked on mobile, I won't be using it as most of people needs this working for mobile devices, jQuery is big, but works.

Yo be honest your whole website isn't mobile friendly, I am writing this down needing to move to the sides instead of seeing what I am writing, maybe those tips that you do must be tested before you say that is the best thing ever.

2 Comments

Well, you do have an option to build jQuery and get a smaller package. Althought you did present an interesting alternative...

15,192 Comments

@Rafael,

Yeah, my site definitely needs to be more mobile-friendly. This site is really old - it actually predates the notion of being able to view web content on a mobile device. I've been slowly trying to make it more mobile friend - if you think it's bad now you should have seen it a few years ago. Something like 97% of all my traffic comes from desktop devices, so I've been a bit lazy about prioritizing it.

That said, none of the mobile issues should be related to jQuery or my conversion that Umbrella. All those issues should be CSS-related. But, I am surprised that the demo didn't work. Can I ask what browser you are using? I just tried on iOS Safari and iOS Chrome (which I think both just use the same web engine under the hood) and the demo works (you can tell because the text turns pink).

15,192 Comments

@Boco,

Oh, I didn't know that. I vaguely remember that you could do that with jQuery UI - where you didn't have to include all the various UI widgetry; but, I didn't know something like that was also available for the core library. Good tip.

2 Comments

I am not one of them...but senion developers can even manipulate library line by line. On the other hand, if looking at average web page today, one 35kb gzip-ed library is only a drop in a see. Since open source dominates the market, all JS and CSS are totaly overbloated. Hhahaha this week I ran into a webpage which loads more than 50 font files, but only 2 or 3 were used🤣

15,192 Comments

@Boco,

Ha ha, it's definitely a "death by a 1,000 cuts" issue on the web today. Right now, I am just trying to make small, incremental changes to try and improve things. No one change is enough; but, hopefully after many, many changes, things will start to get better 💪

1 Comments

Interertins, but I need jQuery because of its plugins. for example mask, jQueryUI and so on. Clear jQuery is not interesting.

15,192 Comments

@Andrey,

Yeah, it's not exactly a drop-in replacement. So, if you have a lot of plugins, it can be a challenge to upgrade. It's also not easy to mix-and-match Umbrella JS objects with jQuery objects since they have different internal mechanisms.

To be clear, I don't dislike jQuery at all. In fact, I still think jQuery is awesome, which is why I'm not trying to replace anything with "vanilla JS". There's a tremendous amount of value to be gotten out of these simple, fluent APIs.

So, don't feel like there's pressure to change. The only pressure is to provide value for your users. And if you're doing that, then you're doing the right thing 💪

1 Comments

@Ben,

I found this post whilst searching for reasons my umbrellajs event delegation wasn't working. Turns out they have just released an update to fix it as it was a bug in their code.

As for the script tag issue, I also had the same problem. After Googling for a while, I stumbled across this website (not my own) which explained that script tags appended to the DOM get executed by browsers, whereas if they are injected through innerHTML they are not. I don't know the reasoning, but it's a "thing" apparently! https://eager.io/blog/everything-I-know-about-the-script-tag/

15,192 Comments

@Ian,

Yeah, I was talking to Francisco Presencia on Twitter - it sounds like the event-delegation code was a user-submission (not his code), so he didn't know that that part was broken. But, every time I mention something, he seems to turn around and fix it 🤩💪

Thanks for the script-tag link -- I'll take a look.

1 Comments

@Ian,

jQuery's html() method actually attaches any scripts in the markup you pass it for you, getting around the innerHTML issue.

This is also the case for dabby.js, a lightweight ES6 jQuery clone, which at full build weighs in at 18(6)kb.

See https://github.com/hexydec/dabby/

15,192 Comments

@Hexydec,

Very interesting - it's neat how you broke out all of the individual methods. Kind of like how Lodash refactored all of its architecture to allow for individual import calls. Classy approach 👍

Post A Comment — I'd Love To Hear From You!

Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.