Tag: Framework

radEventListener: a Tale of Client-side Framework Performance

React is popular, popular enough that it receives its fair share of criticism. Yet, this criticism of React isn’t completely unwarranted: React and ReactDOM total about 120 KiB of minified JavaScript, which definitely contributes to slow startup time. When client-side rendering in React is relied upon entirely, it churns. Even if you render components on the server and hydrate them on the client, it still churns because component hydration is computationally expensive.

React certainly has its place when it comes to applications requiring complex state management, but in my professional experience, it doesn’t belong in most scenarios I see it used. When even a bit of React can be a problem on devices slow and fast alike, using it is an intentional choice that effectively excludes people with low-end hardware.

If it sounds like I have a grudge against React, then I must confess that I really like its componentization model. It makes organizing code easier. I think JSX is great. Server rendering is also cool—even if that’s just how we say “send HTML over the network” these days.

Still, even though I happily use React components on the server (or Preact, as is my preference), figuring out when it’s appropriate to use on the client is a bit challenging. What follows are my findings on React performance as I’ve tried to meet this challenge in a way that’s best for users.

Setting the scene

Lately, I’ve been chipping away at an RSS feed app side project called bylines.fyi. This app uses JavaScript on both the back and front end. I don’t think client-side frameworks are horrid things, but I’ve frequently observed two things about the client-side framework implementations I tend to run into in my day-to-day work and research:

  1. Frameworks have the potential to inhibit a deeper understanding of the things they abstract, which is the web platform. Without knowing at least some of the lower level APIs that frameworks rely on, we can’t know what projects benefit from a framework, and which projects are better off without one.
  2. Frameworks don’t always provide a clear path toward good user experiences.

You may be able to argue the validity of my first point, but the second point is becoming more difficult to refute. You might remember a little while ago when Tim Kadlec did some research on HTTPArchive about web framework performance, and came to the conclusion that React wasn’t exactly a stellar performer.

Still, I wanted to see if it was possible to use what I thought was best about React on the server while mitigating its ill effects on the client. To me, it makes sense to simultaneously want to use a framework to help to organize my code, but also restrict that framework’s negative impact on the user experience. That required a little experimentation to see what approach would be best for my app.

The experiment

I make sure to render every component I use on the server because I believe that the burden of providing markup should be assumed by the web app’s server, not the user’s device. However, I needed some JavaScript in my RSS feed app in order to get a toggleable mobile nav to work.

The mobile nav toggle functionality. At left, the mobile nav is in the closed state. On the right, it’s in the open state, which overlays the entire screen with the navigation.

This scenario aptly describes what I refer to as simple state. In my experience, a prime example of simple state are linear A to B interactions. We toggle a thing on, and then we toggle it off. Stateful, but simple.

Unfortunately, I often see stateful React components used to manage simple state, which is a trade-off that’s problematic for performance. Though that may be a vague utterance for the moment, you’ll come to find out as you read on. That said, it’s important to emphasize that this is a trivial example, but it’s also a canary. Most developers—I hope—aren’t going to rely solely on React to drive such simple behavior for just one thing on their website. So it’s vital to understand that the results you’re going to see are intended to inform you on how you architect your applications, and how the effects of your framework choices could scale when it comes to runtime performance.

The conditions

My RSS feed app is still in development. It contains no third party code, which makes for easy testing in a quiet environment. The experiment I conducted compared the mobile nav toggle behavior across three implementations:

  1. A stateful React component (React.Component) rendered on the server and hydrated on the client.
  2. A stateful Preact component, also server-rendered and hydrated on the client.
  3. A server-rendered stateless Preact component which was not hydrated. Instead, regular ol’ event listeners provide the mobile nav functionality on the client.

Each of these scenarios were measured across four distinct environments:

  1. A Nokia 2 Android phone on Chrome 83.
  2. A ASUS X550CC laptop from 2013 running Windows 10 on Chrome 83.
  3. An old first generation iPhone SE on Safari 13.
  4. A new second generation iPhone SE, also on Safari 13.

I believe this range of mobile hardware will be illustrative of performance across a broad spectrum of device capabilities, even if it’s slightly heavy on the Apple side.

What was measured

I wanted to measure four things for each implementation in each environment:

  1. Startup time. For React and Preact, this included the time it took to load the framework code as well as hydrating the component on the client. For the event listener scenario, this included only the event listener code itself.
  2. Hydration time. For the React and Preact scenarios, this is a subset of the startup time. Because of issues with remote debugging crashing in Safari on macOS, I couldn’t measure hydration time alone on iOS devices. Event listener implementations incurred zero hydration cost.
  3. Mobile nav open time. This gives us insight into how much overhead frameworks introduce in their abstraction of event handlers, and how that compares to the frameworkless approach.
  4. Mobile nav close time. As it turned out, this was quite a bit less than the cost of opening the menu. I ultimately decided not to include those numbers in this article.

It should be noted that measurements of these behaviors include scripting time only. Any layout, paint, and compositing costs would be in addition to and outside of these measurements. One should take care to remember that those activities compete for main thread time in tandem with scripts that trigger them.

The procedure

To test each of the three mobile nav implementations on each device, I followed this procedure:

  1. I used remote debugging in Chrome on macOS for the Nokia 2. For iPhones, I used Safari’s equivalent of remote debugging.
  2. I accessed the RSS feed app running on my local network on each device to the same page where the mobile nav toggling code could be run. Because of this, network performance was not a factor in my measurements.
  3. Without CPU or network throttling applied, I began recording in the profiler, and reloaded the page.
  4. After page load, I opened the mobile nav and then closed it.
  5. I stopped the profiler, and recorded how much CPU time was involved in each of the four behaviors listed earlier.
  6. I cleared the performance timeline. In Chrome, I also clicked the garbage collection button to free up any memory that may have been tied up by my app’s code from a previous session recording.

I repeated this procedure ten times for each scenario for each device. Ten iterations seemed to get just enough data to see a few outliers while getting a reasonably accurate picture, but I’ll let you decide as we go over the results. If you don’t want a play-by-play of my findings, you can view the results at this spreadsheet and draw your own conclusions, as well as the mobile nav code for each implementation.

The results

I initially wanted to present this information in a graph, but because of the complexity of what I was measuring, I wasn’t certain how to present the results without cluttering the visualization. Therefore, I’ll present the minimum, maximum, median, and average CPU times in a series of tables, all of which effectively illustrate the range of outcomes I encountered in each test.

Google Chrome on Nokia 2

The Nokia 2 is a low-cost Android device with a Snapdragon 212 processor. It is not a powerhouse, but rather a cheap and easily obtainable device. Android usage worldwide is currently around 40%, and though Android device specs vary greatly from one device to the next, low-end Android devices are not rare. This is a problem we must recognize as being one of both wealth and proximity to fast network infrastructure.

Let’s see what the numbers look like for startup cost.

Startup time
React Component Preact Component addEventListener Code
Min 137.21 31.23 4.69
Median 147.76 42.06 5.99
Avg 162.73 43.16 6.81
Max 280.81 62.03 12.06

I believe it says something that it takes, on average, over 160 ms to parse and compile React, and hydrate one component. To remind you, startup cost in this case includes the time it takes for the browser to evaluate the scripts needed for the mobile nav to work. For React and Preact, it also includes hydration time, which in both cases can contribute to the uncanny valley effect we sometimes experience during startup.

Preact fares much better, taking around 73% less time than React, which makes sense considering how tiny Preact is at 10 KiB sans compression. Still, it’s important to note that the frame budget in Chrome is about 10 ms to avoid jank at 60 fps. Janky startup is as bad as janky anything else, and is a factor when calculating First Input Delay. All things considered, though, Preact performs relatively well.

As for the addEventListener implementation, it turns out that parse and compile time for a tiny script with no overhead is unsurprisingly very low. Even at the sampled maximum time of 12ms, you’re barely in the outer ring of the Janksburg Metropolitan Area. Now let’s have a look at hydration cost alone.

Hydration time
React Component Preact Component
Min 67.04 19.17
Median 70.33 26.91
Avg 74.87 26.77
Max 117.86 44.62

For React, this is still in the vicinity of Yikes Peak. Sure, a median hydration time of 70 ms for one component isn’t a big deal, but think about how hydration cost scales when you have a bunch of components on the same page. It’s no surprise that the React websites I test on this device feel more like endurance trials than user experiences.

Preact’s hydration times are quite a bit less, which makes sense because Preact’s documentation for its hydrate method states that it “skips most diffing while still attaching event listeners and setting up your component tree.” Hydration time for the addEventListener scenario isn’t reported, because hydration isn’t a thing outside of VDOM frameworks. Next, let’s take a peek at the time it takes to open the mobile nav.

Mobile nav open time
React Component Preact Component addEventListener Code
Min 30.89 11.94 3.94
Median 43.62 14.29 6.14
Avg 43.16 14.66 6.12
Max 53.19 20.46 8.60

I find these figures a bit surprising, because React commands almost seven times as much CPU time to execute an event listener callback than an event listener you could register yourself. This makes sense, as React’s state management logic is necessary overhead, but one has to wonder if it’s worth it for simplistic, linear interactions.

On the other hand, Preact manages to limit its overhead on event listeners to the point where it takes “only” twice as much CPU time to run an event listener callback.

CPU time involved in closing the mobile nav was quite a bit less at an average approximate time of 16.5 ms for React, with Preact and bare event listeners coming in at around 11 ms and 6 ms, respectively. I’d post the full table for the measurements on closing the mobile nav, but we have a lot left to sift through yet. Besides, you can check out those figures yourself in the spreadsheet I referred to earlier on.

A quick note on JavaScript samples

Before moving onto the iOS results, one potential sticking point I want to address is the impact of disabling JavaScript samples in Chrome DevTools when recording sessions on remote devices. After compiling my initial results, I wondered if the overhead of capturing entire call stacks was skewing my results, so I re-tested the React scenario samples disabled. As it turned out, this setting had no significant impact on the results.

Additionally, because the call stacks were truncated, I was unable to measure component hydration time. Average startup cost with samples disabled vs. samples enabled was 160.74 ms and 162.73 ms, respectively. The respective median figures were 157.81 ms and 147.76 ms. I would consider this squarely “in the noise.”

Safari on 1st Generation iPhone SE

The original iPhone SE is a great phone. Despite its age, it still enjoys devoted ownership owing to its more comfortable physical size. It shipped with the Apple A9 processor which is still a solid contender. Let’s see how it did on startup time.

Startup time
React Component Preact Component addEventListener Code
Min 32.06 7.63 0.81
Median 35.60 9.42 1.02
Avg 35.76 10.15 1.07
Max 39.18 16.94 1.56

This is a big improvement from the Nokia 2, and it’s illustrative of the gulf between low-end Android devices and even older Apple devices with significant mileage.

React performance still isn’t great, but Preact gets us within a typical frame budget for Chrome. Event listeners alone, of course, are blazingly fast, leaving plenty of room in the frame budget for other activity.

Unfortunately, I couldn’t measure hydration times on the iPhone, as the remote debugging session would crash every time I would traverse the call stack in Safari’s DevTools. Considering that hydration time was a subset of the overall startup cost, you can expect that it probably accounts for at least half of the startup time if results from the Nokia 2 trials are any indicator.

Mobile nav open time
React Component Preact Component addEventListener Code
Min 16.91 5.45 0.48
Median 21.11 8.62 0.50
Avg 21.09 11.07 0.56
Max 24.20 19.79 1.00

React does alright here, but Preact seems to handle event listeners a bit more efficiently. Bare event listeners are lightning fast, even on this old iPhone.

Safari on 2nd Generation iPhone SE

In mid-2020, I picked up the new iPhone SE. It has the same physical size as an iPhone 8 and similar phones, but the processor is the same Apple A13 used in the iPhone 11. It is very fast for its relatively low $ 400 USD retail price. Given such a beefy processor, how does it deal?

Startup time
React Component Preact Component addEventListener Code
Min 20.26 5.19 0.53
Median 22.20 6.48 0.69
Avg 22.02 6.36 0.68
Max 23.67 7.18 0.88

I guess at some point there are diminishing returns when it comes to the relatively small workload of loading a single framework and hydrating one component. Things are a little faster on a 2nd generation iPhone SE than its first generation variant in some cases, but not terribly so. I’d imagine that this phone would tackle larger and sustained workloads better than its predecessor.

Mobile nav open time
React Component Preact Component addEventListener Code
Min 13.15 12.06 0.49
Median 16.41 12.57 0.53
Avg 16.11 12.63 0.56
Max 17.51 13.26 0.78

Slightly better React performance here, but not much else. Strangely, Preact seems to take longer on average to open the mobile nav on this device than its first generation counterpart, but I’ll chalk that up to outliers skewing a relatively small dataset. I certainly would not assume the first generation iPhone SE is a faster device based on this.

Chrome on a dated Windows 10 Laptop

Admittedly, these were the results I was most excited to see: how does an ASUS laptop from 2013 with Windows 10 and an Ivy Bridge i5 of the day handle this stuff?

Startup time
React Component Preact Component addEventListener Code
Min 43.15 13.11 1.81
Median 45.95 14.54 2.03
Avg 45.92 14.47 2.39
Max 48.98 16.49 3.61

The numbers aren’t bad when you consider that the device is seven years old. The Ivy Bridge i5 was a good processor in its day, and when you couple that with the fact that it’s actively cooled (rather than passively cooled as mobile device processors are), it probably doesn’t run into thermal throttling scenarios as often as mobile devices.

Hydration time
React Component Preact Component
Min 17.75 7.64
Median 23.55 8.73
Avg 23.12 8.72
Max 26.25 9.55

Preact does well here, and manages to stay within Chrome’s frame budget, and is almost three times faster than React. Things could look quite a bit different if you’re hydrating ten components on the page at startup time, possibly even in Preact.

Mobile nav open time
Preact Component addEventListener Code
Min 6.06 2.50 0.88
Median 10.43 3.09 0.97
Avg 11.24 3.21 1.02
Max 14.44 4.34 1.49

When it comes to this isolated interaction, we see performance that’s similar to high-end mobile devices. It’s encouraging to see such an old laptop still keep up reasonably well. That said, this laptop’s fan spins up often when browsing the web, so active cooling is probably this device’s saving grace. If this device’s i5 was passively cooled, I suspect its performance might drop.

Shallow call stacks for the win

It’s not a mystery as to why it takes React and Preact longer to start up than it does for a solution that eschews frameworks altogether. Less work equals less processing time.

While I think startup time is crucial, it’s probably inevitable that you’ll trade some amount of speed for a better developer experience. Though I’d strenuously argue that we tend to trade too much toward developer experience than user experience far too often.

The dragons also lie in what we do after the framework loads. Client-side hydration is something that I think is far too often abused, and can sometimes be completely unnecessary. Every time you hydrate a component in React, this is what you’re throwing at the main thread:

A React stateful component hydration call stack captured in Chrome DevTools.

Recall that on the Nokia 2, the minimum time I measured for hydrating the mobile nav component was about 67 ms. In Preact—for which you’ll see the hydration call stack below—takes about 20 ms.

A Preact stateful component hydration call stack captured in Chrome DevTools.

These two call stacks aren’t to the same scale, but Preact’s hydration logic is simplified, probably because “most diffing is skipped” as Preact’s documentation states. There’s quite a bit less going on here. When you get closer to the metal by using addEventListener instead of a framework, you can get even faster.

A call stack of event listeners attaching to DOM elements.

Not every situation calls for this approach, but you’d be surprised at what you can accomplish when your tools are addEventListener, querySelector, classList, setAttribute/getAttribute, and so on.

These methods—and many more like them—are what frameworks themselves rely on. The trick is to evaluate what functionality you can safely deliver outside of what the framework provides, and rely on the framework when it makes sense.

A call stack of React firing a click event handler to open a mobile nav.

If this were a call stack for, say, making a request for API data on the client and managing the complex state of the UI in that situation, I’d find this cost more acceptable. Yet, it’s not. We’re just making a nav appear on the screen when the user taps a button. It’s like using a bulldozer when a shovel would be a better fit for the job.

Preact at least strikes the middle ground:

A call stack of Preact firing a click event handler to open a mobile nav.

Preact takes about a third of the time to do the same work React does, but on that budget device, it exceeds the frame budget often. This means opening that nav on some devices will animate sluggishly because the layout and paint work may not have enough time to finish without entering long task territory.

A call stack of a bare event listener opening the mobile nav.

In this case, an event listener is what I needed. It gets the job done seven times faster on that budget device than React.

Conclusion

This is not a React hit piece, but rather a plea for consideration of how we do our work. Some of these performance pitfalls can be avoided if we take care to evaluate what tools make sense for the job, even for apps with a great deal of complex interactivity. To be fair to React, these pitfalls likely exist in many VDOM frameworks, because the nature of them adds necessary overhead to manage all sorts of things for us.

Even if you’re working on something that doesn’t call for React or Preact, but you want to take advantage of componentization, consider keeping it all on the server to start with. This approach means you can decide if and when it’s appropriate to extend functionality to the client—and how you’ll do that.

In the case of my RSS feed app, I can manage this by putting lightweight event listener code in the entry point for that page of the app, and using an asset manifest to put the minimal amount of script necessary in order for each page to work.

Now let’s suppose that you have an app that truly needs what React provides. You have complex interactivity with lots of state. Here are some things you can do to try and get things going a bit faster.

  1. Check all of your stateful components—that is, any component which extends React.Component—and see if they can be refactored as stateless components. If a component doesn’t use lifecycle methods or state, you can refactor it to be stateless.
  2. Then, if possible, avoid sending JavaScript to the client for those stateless components, as well as hydrating them. If a component is stateless, only render it on the server. Prerender components when possible to minimize server response time, because server rendering has its own performance pitfalls.
  3. If you have a stateful component with simple interactivity, consider prerendering/server-rendering that component, and replace its interactivity with framework-independent event listeners. This avoids hydration entirely, and user interactions won’t have to filter through the framework’s state management logic.
  4. If you must hydrate stateful components on the client, consider lazily hydrating components that aren’t near the top of the page. An Intersection Observer that triggers a callback works very well for this, and will give more main thread time to critical components on the page.
  5. For lazily-hydrated components, assess whether you can schedule their hydration during main thread idle time with requestIdleCallback.
  6. If possible, consider switching from React to Preact. Given how much faster it runs than React on the client, it’s worth having the discussion with your team to see if this is possible. The latest version of Preact is nearly 1:1 with React for most things, and preact/compat does a great job of easing this transition. I don’t think Preact is a panacea for performance, but it gets you closer to where you need to be.
  7. Consider adapting your experience to users with low device memory. navigator.deviceMemory (available in Chrome and derived browsers) enables you to change the user experience for users on devices with little memory. If someone has such a device, it’s probable that its processor isn’t so fast either.

Whatever you decide to do with this information, the thrust of my argument is this: if you use React or any VDOM library, you should spend some time investigating its impact on an array of devices. Get a cheap Android device and see how your app feels to use. Contrast that experience with your high-end devices.

Most of all, don’t follow “best practices” if the result is that your app effectively excludes a part of your audience that can’t afford high end devices. Keep pushing for everything to be faster. If our daily work is any indication, this is an endeavor that will keep you busy for some time to come, but that’s OK. Making the web faster makes the web more accessible in more places. Making the web more accessible makes the web more inclusive. That’s the really good work we should all be trying our best to do.

I’d like to express my gratitude to Eric Bailey for his editorial feedback this piece, as well as the CSS-Tricks staff for their willingness to publish it.


The post radEventListener: a Tale of Client-side Framework Performance appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,

Alpine.js: The JavaScript Framework That’s Used Like jQuery, Written Like Vue, and Inspired by TailwindCSS

We have big JavaScript frameworks that tons of people already use and like, including React, Vue, Angular, and Svelte. Do we need another JavaScript library? Let’s take a look at Alpine.js and you can decide for yourself. Alpine.js is for developers who aren’t looking to build a single page application (SPA). It’s lightweight (~7kB gzipped) and designed to write markup-driven client-side JavaScript.

The syntax is borrowed from Vue and Angular directive. That means it will feel familiar if you’ve worked with those before. But, again, Alpine.js is not designed to build SPAs, but rather enhance your templates with a little bit of JavaScript.

For example, here’s an Alpine.js demo of an interactive “alert” component.

The alert message is two-way bound to the input using x-model="msg". The “level” of the alert message is set using a reactive level property. The alert displays when when both msg and level have a value.

It’s like a replacement for jQuery and JavaScript, but with declarative rendering

Alpine.js is a Vue template-flavored replacement for jQuery and vanilla JavaScript rather than a React/Vue/Svelte/WhateverFramework competitor.

Since Alpine.js is less than a year old, it can make assumptions about DOM APIs that jQuery cannot. Let’s briefly draw a comparison between the two.

Querying vs. binding

The bulk of jQuery’s size and features comes in the shape of a cross-browser compatibility layer over imperative DOM APIs — this is usually referred to as jQuery Core and sports features that can query the DOM and manipulate it.

The Alpine.js answer to jQuery core is a declarative way to bind data to the DOM using the x-bind attribute binding directive. It can be used to bind any attribute to reactive data on the Alpine.js component. Alpine.js, like its declarative view library contemporaries (React, Vue), provides x-ref as an escape hatch to directly access DOM elements from JavaScript component code when binding is not sufficient (eg. when integrating a third-party library that needs to be passed a DOM Node).

Handling events

jQuery also provides a way to handle, create and trigger events. Alpine.js provides the x-on directive and the $ event magic value which allows JavaScript functions to handle events. To trigger (custom) events, Alpine.js provides the $ dispatch magic property which is a thin wrapper over the browser’s Event and Dispatch Event APIs.

Effects

One of jQuery’s key features is its effects, or rather, it’s ability to write easy animations. Where we might use slideUp, slideDown, fadeIn, fadeOut properties in jQuery to create effects, Alpine.js provides a set of x-transition directives, which add and remove classes throughout the element’s transition. That’s largely inspired by the Vue Transition API.

Also, jQuery’s Ajax client has no prescriptive solution in Alpine.js, thanks to the Fetch API or taking advantage of a third party HTTP library (e.g. axios, ky, superagent).

Plugins

It’s also worth calling out jQuery plugins. There is no comparison to that (yet) in the Alpine.js ecosystem. Sharing Alpine.js components is relatively simple, usually requiring a simple case of copy and paste. The JavaScript in Alpine.js components are “just functions” and tend not to access Alpine.js itself, making them relatively straightforward to share by including them on different pages with a script tag. Any magic properties are added when Alpine initializes or is passed into bindings, like $ event in x-on bindings.

There are currently no examples of Alpine.js extensions, although there are a few issues and pull requests to add “core” events that hook into Alpine.js from other libraries. There are also discussions happening about the ability to add custom directives. The stance from Alpine.js creator Caleb Porzio, seems to be basing API decisions on the Vue APIs, so I would expect that any future extension point would be inspired on what Vue.js provides.

Size

Alpine.js is lighter weight than jQuery, coming in at 21.9kB minified — 7.1kB gzipped — compared to jQuery at 87.6kB minified — 30.4kB minified and gzipped. Only 23% the size!

Most of that is likely due to the way Alpine.js focuses on providing a declarative API for the DOM (e.g. attribute binding, event listeners and transitions).

Bundlephobia breaks down the two

For the sake of comparison, Vue comes in at 63.5kB minified (22.8kB gzipped). How can Alpine.js come in lighter despite it’s API being equivalent Vue? Alpine.js does not implement a Virtual DOM. Instead, it directly mutates the DOM while exposing the same declarative API as Vue.

Let’s look at an example

Alpine is compact because since application code is declarative in nature, and is declared via templates. For example, here’s a Pokemon search page using Alpine.js:

This example shows how a component is set up using x-data and a function that returns the initial component data, methods, and x-init to run that function on load.

Bindings and event listeners in Alpine.js with a syntax that’s strikingly similar to Vue templates.

  • Alpine: x-bind:attribute="express" and x-on:eventName="expression", shorthand is :attribute="expression" and @eventName="expression" respectively
  • Vue: v-bind:attribute="express" and v-on:eventName="expression", shorthand is :attribute="expression" and @eventName="expression" respectively

Rendering lists is achieved with x-for on a template element and conditional rendering with x-if on a template element.

Notice that Alpine.js doesn’t provide a full templating language, so there’s no interpolation syntax (e.g. {{ myValue }} in Vue.js, Handlebars and AngularJS). Instead, binding dynamic content is done with the x-text and x-html directives (which map directly to underlying calls to Node.innerText and Node.innerHTML).

An equivalent example using jQuery is an exercise you’re welcome to take on, but the classic style includes several steps:

  • Imperatively bind to the button click using $ ('button').click(/* callback */).
  • Within this “click callback” get the input value from the DOM, then use it to call the API.
  • Once the call has completed, the DOM is updated with new nodes generated from the API response.

If you’re interested in a side by side comparison of the same code in jQuery and Alpine.js, Alex Justesen created the same character counter in jQuery and in Alpine.js.

Back in vogue: HTML-centric tools

Alpine.js takes inspiration from TailwindCSS. The Alpine.js introduction on the repository is as “Tailwind for JavaScript.”

Why is that important?

One of Tailwind’s selling points is that it “provides low-level utility classes that let you build completely custom designs without ever leaving your HTML.” That’s exactly what Alpine does. It works inside HTML so there is no need to work inside of JavaScript templates the way we would in Vue or React  Many of the Alpine examples cited in the community don’t even use script tags at all!

Let’s look at one more example to drive the difference home. Here’s is an accessible navigation menu in Alpine.js that uses no script tags whatsoever.

This example leverages aria-labelledby and aria-controls outside of Alpine.js (with id references). Alpine.js makes sure the “toggle” element (which is a button), has an aria-expanded attribute that’s true when the navigation is expanded, and false when it’s collapsed. This aria-expanded binding is also applied to the menu itself and we show/hide the list of links in it by binding to hidden.

Being markup-centric means that Alpine.js and TailwindCSS examples are easy to share. All it takes is a copy-paste into HTML that is also running Alpine.js/TailwindCSS. No crazy directories full of templates that compile and render into HTML!

Since HTML is a fundamental building block of the web, it means that Alpine.js is ideal for augmenting server-rendered (Laravel, Rails, Django) or static sites (Hugo, Hexo, Jekyll). Integrating data with this sort of tooling can be a simple as outputting some JSON into the x-data="{}" binding. The affordance of passing some JSON from your backend/static site template straight into the Alpine.js component avoids building “yet another API endpoint” that simply serves a snippet of data required by a JavaScript widget.

Client-side without the build step

Alpine.js is designed to be used as a direct script include from a public CDN. Its developer experience is tailored for that. That’s why it makes for a great jQuery comparison and replacement: it’s dropped in and eliminates a build step.

While it’s not traditionally used this way, the bundled version of Vue can be linked up directly. Sarah Drasner has an excellent write-up showing examples of jQuery substituted with Vue. However, if you use Vue without a build step, you’re actively opting out of:

  • the Vue CLI
  • single file components
  • smaller/more optimized bundles
  • a strict CSP (Content Security Policy) since Vue inline templates evaluate expressions client-side

So, yes, while Vue boasts a buildless implementation, its developer experience is really depedent on the Vue CLI. That could be said about Create React App for React, and the Angular CLI. Going build-less strips those frameworks of their best qualities.

There you have it! Alpine.js is a modern, CDN-first  library that brings declarative rendering for a small payload — all without the build step and templates that other frameworks require. The result is an HTML-centric approach that not only resembles a modern-day jQuery but is a great substitute for it as well.

If you’re looking for a jQuery replacement that’s not going to force you into a SPAs architecture, then give Alpine.js a go! Interested? You can find out more on Alpine.js Weekly, a free weekly roundup of Alpine.js news and articles.

The post Alpine.js: The JavaScript Framework That’s Used Like jQuery, Written Like Vue, and Inspired by TailwindCSS appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , , , ,
[Top]

Getting Acquainted With Svelte, the New Framework on the Block

For the last six years, Vue, Angular, and React have run the world of front-end component frameworks. Google and Facebook have their own sponsored frameworks, but they might leave a bitter taste for anyone who advocates for an open and unbiased web. Vue is another popular framework that has multiple sponsors, but isn’t run by a single corporation, which may be attractive to some folks.

There’s another player in the framework space that’s gaining attention and operates very much in the same spirit as Vue as far adopting an open MIT license: Svelte.

Svelte has been covered here on CSS-Tricks before, like Ollie Williams’ excellent overview of how it can be used to write more convenient, component-based CSS. This article is going to zoom out a bit and provide a little more context about Svelt, as well as how it differentiates itself from other frameworks, and how to implement it in your own projects.

What makes Svelte different?

I can confidently say that Svelte has been the easiest JavaScript component library to learn and start putting to use in a productive way.

— Jeff Delaney, from Svelte Realtime Todo List with Firebase

OK, so Svelte is a JavaScript component library. But so is React. And Angular. And Vue. What makes Svelte stand out from the bunch?

Svelte is trying to do a few things that are different from the rest:

  1. All the code is compiled ahead of time.
  2. There is no virtual DOM.
  3. CSS scoping is baked in.

Let’s break those down a bit because they significantly distinguish Svelte from other front-end frameworks.

All the code is compiled ahead of time.

Svelte is a compiler, meaning that the code in Svelte files gets converted from an easier-to-write hybrid language that mixes HTML, JavaScript, and CSS into lower-level optimized JavaScript, HTML, and CSS files.

This is very similar to the way C# gets compiled down to bytecode, or how Typescript compiles down to JavaScript. But where traditional compilers tend to go down to one language, Svelte mixes all three.

This makes writing code a lot more flexible, and benefits the client (web browser) as the computation is done when the application is built, not on every browser when the web app is visited.

There is no Virtual DOM.

A DOM (or Document Object Model) is an interface that defines the logical structure of a webpage. It takes HTML and converts it to a structure that can be manipulated and accessed. Chris has a classic post that thoroughly explains it.

The Virtual DOM extends the concept of a DOM by creating a “second” DOM in memory. Like the DOM, this is manipulated and accessed by traditional frameworks (e.g. Angular, Vue, and React). At build, this second “virtual” DOM gets consolidated with the actual DOM, allowing the UI to render.

And what about the Shadow DOM? Well, the Shadow DOM is technically part of the “real” DOM, just in the shadows. As such it is a great tool for isolating chunks of code that don’t leak into or conflict with other elements on the page — a little bit like (but at the same time almost nothing like) an iframe. The shadow DOM is sorta the crux for most component-based front-end frameworks because they leverage the siloed nature of the Shadow DOM to serve specific code to specific elements.

While that isn’t exactly a key selling point of Svelte, it is possible to work with the Shadow DOM experimentally. The Shadow DOM hasn’t really quite caught on in progressive web practices, which is a shame, and probably due to the confusion between drafts and lack of support from IE and Edge.

So, where am I going with all this? The difference between Svelte and other JavaScript frameworks is the lack of a Virtual DOM. That’s important because it contributes to faster apps — faster than frameworks using a Virtual DOM. Yes, the Virtual DOM can be super fast because it only updates parts of the DOM when needed, but as applications grow, the impact of a duplicate DOM stored in memory can have an overall negative impact on performance.

Svelte takes a different approach and does a lot of these heavy calculations at build time. All that heavy lifting in advance, which allows Svelte to surgically insert changes only where needed.

CSS scoping is baked in.

Svelte has built-in styling, which is essential in other modern frameworks. The different between CSS in Svelte and CSS in other frameworks is that Svelte takes the CSS from each component and spits it out to a separate CSS file on build.

A personal gripe I have with most CSS-in-JS approaches is that it seems like an over-engineered solution. Svelte’s approach keeps things lean, vanilla, and encapsulated — while keeping everything where it should be.

For those who love preprocessors, there are plugins, whether it for Sass, Less or Gulp. But since Svelte is still in its infancy, I would recommend using plain ol’ CSS with a minified CSS framework of your choice so you can utilize Svelte’s handy dandy component scoping. 

You could just as easily keep to your usual styling preferences and completely forgo Svelte’s CSS builder. However, I’d argue that is a massive shame, as Svelte’s solution has been extremely clean and enjoyable, at lease in my experience. But anyone who has to work with IE11 (😬) and even older browsers will know that normalizing styles is a must. This is a good place to stop and check out Ollie’s post because he dives much deeper into Svelte’s styling features and advantages.

How Svelte stacks up to other frameworks

We just looked at what how Svelte has a different approach for compiling, interacting with DOM and writing CSS. You might be wondering: how does Svelte compare to other popular frameworks?

There are plenty of comparisons already out there, but suffice to say that Svelte is pretty darn fast. But speed isn’t the only basis for comparison. Instead, let’s do a side-by-side that looks at a broader overview in a format much loved by the development community: a table!

Svelte Vue React Angular (2+)
What is it Compiler Framework Framework Framework
First Commit Nov. 16, 2016 Jul. 29, 2013 May 24, 2013 Sep. 18, 2014
Backing Open source Multiple Sponsors Facebook Google
Community¹ Small Large Massive Large
Satisfaction2 88% 87% 89% 38%

Svelte is in a strong position considering its late entrance and small community. Developer satisfaction is high, while the big three have been seeing recent declines. The Svelte community is small, but growing, and the code is open source which is a huge plus for the overall web community.

Let’s look at an example of using Svelte

I hope that I have convinced you that Svelte is worth at least a try. If so, let’s fire up the terminal and try a real-world examples of an everyday use case: implementing the Intersection Observer. If you’ve ever run a Lighthouse report, it may have been shouted at you for not using passive scroll events. That may be the most boring sentence I have written in my life, but it’s scores points for performance and isn’t overly complicated to do with the Intersection Observer in Svelte.

Let’s skip all the installation and setup stuff because we can avoid it with REPL, the online editor Svelte uses to demonstrate the framework on its site. The standard “Hello world” boilerplate is in there. Go ahead and download the ZIP file of the app, in the upper-right corner of the screen.

Now, unzip the file and cd into the folder from the terminal and run  npm -i to initialize the project. Once that’s done, do npm run build and you’ll get a copy of your lightweight miniature Svelte “Hello, world!” app.

Now we can get into the actual task of adding the IntersectionObserver.

First, we import the code that has already kindly been written by the Svelte team. It’s in the source code of the svelte.dev git repo (the inner cogs of which make for fascinating reading).

<script>   import { onMount } from 'svelte';   export let once = false;   export let top = 0;   export let bottom = 0;   export let left = 0;   export let right = 0;   let intersecting = false;   let container;    onMount(() => {     if (typeof IntersectionObserver !== 'undefined') {       const rootMargin = `$  {bottom}px $  {left}px $  {top}px $  {right}px`;       const observer = new IntersectionObserver(entries => {         intersecting = entries[0].isIntersecting;         if (intersecting && once) {           observer.unobserve(container);         }         }, {           rootMargin       });         observer.observe(container);         return () => observer.unobserve(container);   }    function handler() {     const bcr = container.getBoundingClientRect();     intersecting = (       (bcr.bottom + bottom) > 0 &&       (bcr.right + right) > 0 &&       (bcr.top - top) < window.innerHeight &&       (bcr.left - left) < window.innerWidth     );     if (intersecting && once) {       window.removeEventListener('scroll', handler);     }   }    window.addEventListener('scroll', handler);     return () => window.removeEventListener('scroll', handler);   }); </script>  <style>   div {     width: 100%;     height: 100%;   } </style>  <div bind:this={container}>   <slot {intersecting}></slot> </div>

Stick this in a file called IntersectionObserver.svelte in a src/components folder. Then, reference it from the main Svelte file: App.svelte.

import IntersectionObserver from "../components/IntersectionObserver.svelte";

Now that we have the Intersection Observer available as a component, we can wrap other elements with it.

<IntersectionObserver let:intersecting top={400}>  {#if intersecting}     <section>       This message will Show if it is intersecting     </section>   {:else}     <section>       This message won't Show if it is intersecting     </section>  {/if} </IntersectionObserver>

That’s really it! You can see how the Intersection Observer component allows us to use <IntersectionObserver>  like a wrapper and define where the intersection should trigger, which is 400 pixels from the top in this example. As a reminder, this is all being exported as vanilla JavaScript! Super performant, no funny business. We’re sandwiching JavaScript and HTML together which is cool because we can see what the Intersection Observer is directly affecting, leaving no ambiguity and without being penalized for performance.

The OnMount function is necessary to tell Svelte that this code needs to run within the browser, as the Intersection Observer can’t be figured out ahead of time.

We’ll need to add some styling so that we can experience the observer in action, and we can do that directly in your App.svelte file. This might look super familiar if you have worked with any of the other front-end frameworks:

<style>   .somesection {     display: flex;     align-items: center;     justify-content: center;     width: 100%;     height: 100vh;   }      .somesection.even{     background: #ccc;   }            .content{     text-align: center;     width: 350px;   } </style>

Finally, we can copy and paste our Intersection Observer element four times to create more intersections. That gives us a mini web app that reactively adds and removes content as it comes into view — perfect to use with media, like lazy-loading. Check out a demo of the final result and be sure to crack open DevTools to see the Intersection Observer

Some final thoughts

My personal recommendation is to give Svelte a try. We’ve only scratched the surface of the framework in this article, but having converted my personal website to Svelte, I can confidently say that it is a pleasure to work with. It is performant, has a brilliant VSCode linter, and best of all, is easy to use. It may be small and new on the block, but I have a keen feeling that it is the relief from bloated “Goliath” frameworks, the “David” that frontend-ers have been looking for.

So should you use Svelte in a real project? Comparing risk and reward definitely comes into play. The community is smaller than other frameworks, meaning you’re likely to find less support and fewer tutorials to guide your along. At the same time, Svelte is in its third generation, meaning most of the gremlins should have been driven away, leaving a lean and reliable framework.

As with anything new, common sense rules, try it out with something non-commercial, take it for a spin, and see how you go.

Is there anything else? Funny you should ask! There are two co-projects that live in the Svelte Ecosystem: Sapper and Native. Sapper is a framework that utilizes Svelte for building full web applications, including routing, service workers, and all the good stuff you need to get started. I have used it to rebuild my personal website, and so far, I am a fan. Svelte Native is the most experimental of the Svelte projects, a NativeScript mobile app builder that utilizes Svelte under the hood. I confess that is where my knowledge on the subject ends. Luckily, it has a website with further information.

What do you think? Have you given Svelte a try? Do you think it stacks up to other frameworks? Let’s discuss it in the comments!

  1. Based on a mix of Github Contributions, NPM Downloads and StackOverflow topics
  2. State of JS review 2019

The post Getting Acquainted With Svelte, the New Framework on the Block appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Yet Another JavaScript Framework

On March 6, 2018, a new bug was added to the official Mozilla Firefox browser bug tracker. A developer had noticed an issue with Mozilla’s nightly build. The report noted that a 14-day weather forecast widget typically featured on a German website had all of a sudden broken and disappeared. Nothing on the site had changed, so the problem had to be with Firefox.

Bug 1443630: Implementing array.prototype.flatten broke MooTools' version of it.
A screenshot of the bug report filed with Mozilla.

The problem, the developer noted in his report, appeared to stem from the site’s use of the JavaScript library MooTools.

At first glance, the bug appeared to be fairly routine, most likely a small problem somewhere in the website’s code or a strange coincidence. After just a few hours though, it became clear that the stakes for this one particular bug were far graver than anyone could have anticipated. If Firefox were to release this version of their browser as-is, they risked breaking an unknown, but still predictably rather large number of websites, all at once. Why that is has everything to do with the way MooTools was built, where it drew influence from, and the moment in time it was released. So to really understand the problem, we’ll have to go all the way back to the beginning.

In the beginning

First came plain JavaScript. Released in 1995 by a team at Netscape, Javascript began making its way into common use in the late 90’s. JavaScript gave web developers working with HTML a boost, allowing them to dynamically shift things around, lightly animate content, and add counters and stock tickers and weather widgets and all sorts of interactivity to sites.

By 2005, JavaScript development had become increasingly complex. This was precipitated by the use of a technique we know as Asynchronous JavaScript and XML (Ajax), a pattern that likely feels familiar these days for anyone that uses a website to do something more than just read some content. Ajax opened up the door for application-like functionality native to the web, enabling the release of projects like Google Maps and Gmail. The phrase “Web 2.0” was casually lobbed into conversation to describe this new era of dynamic, user-facing, and interactive web development. All thanks to JavaScript.

It was specifically Ajax that Sam Stephenson found himself coming back to again and again in the early years of the turn of the century. Stephenson was a regular contributor to Ruby on Rails, and kept running into the same issues when trying to connect to Rails with JavaScript using some fairly common Ajax code. Specifically, he was writing the same baseline code every time he started a new project. So he wrote a few hundred lines of code that smoothed out Ajax connections with Rails he could port to all of his projects. In just a few months, a hundred lines turned into many more and Prototype, one of the earliest examples of a full JavaScript framework, was officially released.

A screenshot of the Prototype website.
An early version of the Prototype website that emphasizes its ease of use and class-based structure.

Extending JavaScript

Ruby utilizes class inheritance, which tends to lend itself to object-oriented development. If you don’t know what that means, all you really need to know is that it runs a bit counter to the way JavaScript was built. JavaScript instead leans on what’s known as prototypal inheritance. What’s that mean? It means that everything in JavaScript can be extended using the base object as a prototype. Anything. Even native object prototypes like String or Array. In fact, when browsers do add new functions and features to Javascript, they often do so by taking advantage of this particular language feature. That’s where Stephenson got the name for his library, Prototype.

The bottom line is, prototypal inheritance makes JavaScript naturally forgiving and easily extendable. Its basically possible for any developer to build on top of the core JavaScript library in their own code. This isn’t possible in a lot of other programming languages, but JavaScript has always been a bit of an outlier in terms of its approach to accommodate a much larger, cross-domain developer base.

All of which is to say that Stephenson did two things when he wrote Prototype. The first was to add a few helpers that allowed object-oriented developers like himself to work with JavasScript using a familiar code structure. The second, and far more important here, is that he began to extend existing Javascript to add features that were planned for some point in the future but not implemented just yet. One good example of this was the function document.getElementByClassName, a slightly renamed version of a feature that wouldn’t actually land in JavaScript until around 2008. Prototype let you use it way back in 2006. The library was basically a wish list of features that developers were promised would be implemented by browsers sometime in the future. Prototype gave those developers a head-start, and made it much easier to do the simple things they had to do each and every day.

Prototype went through a few iterations in rapid succession, gaining significant steam after it was included by default in all Ruby on Rails installations not long after its release. Along the way, Prototype set the groundwork for basically every framework that would come after it. For instance, it was the first to use the dollar sign ($ ) as shorthand for selecting objects in JavaScript. It wrote code that was, in its own words, “self-documented,” meaning that documentation was scarce and learning the library meant diving into some code, a practice that is more or less commonplace these days. And perhaps most importantly, it removed the difficulty of making code run on all browsers, a near Herculean task in the days when browsers themselves could agree on very little. Prototype just worked, in every modern-at-the-time browser.

Prototype had its fair share of competition from other libraries like base2 which took the object-oriented bits of Prototype and spun them off into a more compact version. But the library’s biggest competitor came when John Resig decided to put his own horse in the race. Resig was particularly interested in that last bit, the work-in-all-browsers-with-the-same-code bit. He began working on a different take on that idea in 2005, and eventually unveiled a library of his own at Barcamp in New York in January of 2006.

It was called jQuery.

New Wave JavaScript

jQuery shipped with the tagline “New Wave Javascript,” a curious title given how much Resig borrowed from Prototype. Everything from the syntax to its tools for working with Ajax — even its use of a dollar sign as a selector — came over from Prototype to jQuery. But it wasn’t called New Wave JavaScript because it was original. It was called New Wave JavaScript because it was new.

jQuery: New Wave JavaScript. jQuery is a new type of JavaScript library. jQuery is designed to change the way that you write JavaScript.
“New Wave” Javascript

jQuery’s biggest departure from Prototype and its ilk was that it didn’t extend existing and future JavaScript functionality or object primitives. Instead, it created brand new features, all assembled with a unique API that was built on top of what already existed in JavaScript. Like Prototype, jQuery provided lots of ways to interact with webpages, like select and move around elements, connect to servers, and make pages feel snappy and dynamic (though it lacked the object-oriented leanings of its predecessor). Crucially, however, all of this was done with new code. New functions, new syntax, new API’s, hence a new wave of development. You had to learn the “jQuery way” of doing things, but once you did, you could save yourself tons of time with all of the stuff jQuery made a lot easier. It even allowed for extendable plugins, meaning other developers could build cool, new stuff on top of it.

MooTools

It might sound small, but that slight paradigm shift was truly massive. A shift that seismic required a response, a response that incidentally came the very next year, in 2007, when Valerio Proietti found himself entirely frustrated with another library altogether. The library was called script.aculo.us, and it helped developers with page transitions and animations. Try as he might, Proietti just couldn’t get script.aculo.us to do what he wanted to do, so (as many developers in his position have done in the past), he decided to rewrite his own version. An object-oriented developer himself, he was already a big fan of Protoype, so he based his first version off of the library’s foundational principles. He even attempted to coast off its success with his first stab at a name: prototype.lite.js. A few months and many new features later, Proietti transformed that into MooTools.

Like Protoype, MooTools used an object-oriented programming methodology and prototypical inheritance to extend the functionality of core JavaScript. In fact, most of MooTools was simply about adding new functionality to built-in prototypes (i.e. String, Array). Most of what MooTools added was on the JavaScript roadmap for inclusion in browsers at some point in the future; the library was there to fill in the gap in the meantime. The general idea was that once a feature finally did land in browsers, they’d simply update their own code to match it. Web designers and developers liked MooTools because it was powerful, easy to use, and made them feel like they were coding in the future.

MooTools is a compact, modular, Object-oriented JavaScript framework designed to make writing extensible and compatible code easier and faster.
MooTools: Object-oriented, developer-friendly

There was plenty there from jQuery too. Like jQuery, MooTools smoothed over inconsistencies and bugs in the various browsers on the market, and offered quick and easy ways to add transitions, make server requests, and manipulate webpages dynamically. By the time MooTools was released, the jQuery syntax had more or less become the standard, and MooTools was not going to be the one to break the mold.

There was enough similarities between the two, in fact, for them to be pitted against one another in a near-endless stream of blog posts and think-pieces. MooTools vs. jQuery, a question for the ages. Websites sprung up to compare the two. MooTools was a “framework,” jQuery was a “library.” MooTools made coding fun, jQuery made the web fun. MooTools was for Geminis, and jQuery was for Sagittariuses. In truth, both worked very well, and the use of one over the other was mostly a matter of personal preference. This is largely true of many of the most common developer library debates, but they continue on all the same.

The legacy of legacy frameworks

Ultimately, it wasn’t features or code structure that won the day — it was time. One by one, the core contributors of MooTools peeled off from the project to work on other things. By 2010, only a few remained. Development slowed, and the community wasn’t far behind. MooTools continued to be popular, but its momentum had come to a screeching halt.

jQuery’s advantage was simply that they continued on, expanded even. In 2010, when MooTools development began to wind down, jQuery released the first version of jQuery Mobile, an attempt at retooling the library for a mobile world. The jQuery community never quit, and in the end, it gave them the advantage.

The legacy and reach of MooTools, however, is massive. It made its way onto hundreds of thousands of sites, and spread all around the world. According to some stats we have, it is still, to this day, more common to see MooTools than Angular or React or Vue or any modern framework on the average website. The code of some sites were updated to keep pace with the far less frequent, but still occasional, updates to MooTools. Others to this day are comfortable with whatever version of MooTools they have installed. Most simply haven’t updated their site at all. When the site was built, MooTools was the best option available and now, years later, the same version remains.

Array.flatten

Which brings us all the way back to the bug in the weather app that popped up in Firefox in early 2018. Remember, MooTools was modeled after Prototype. It modified native JavaScript prototype objects to add some functions planned but not yet released. In this specific case, it was a method called Array.flatten, a function that MooTools first added to their library way back in 2008 for modifying arrays. Fast forward 10 years, and the JavaScript working group finally got around to implementing their own version of Array.flatten, starting with the beta release of Firefox.

The problem was that Firefox’s Array.flatten didn’t map directly to the MooTools version of Array.flatten.

The details aren’t all that important (though you can read more about it here). Far more critical was the uncomfortable implication. The MooTools version, as it stood, broke when it collided with the new JavaScript version. That’s what broke the weather widget. If Firefox were to release their browser to the larger public, then the MooTools version of flatten would throw an error, and wipe out any JavaScript that depended on it. No one could say how many sites might be affected by the conflict, but given the popularity of MooTools, it wasn’t at all out of the question to think that the damage could be extensive.

Once the bug surfaced, hurried discussion took place in the JavaScript community, much of it in the JavaScript working group’s public GitHub repo. A few solutions soon emerged. The first was to simply release the new version of flatten. Essentially, to let the old sites break. There was, it was argued, a simple elegance to the proposal, backed fundamentally by the idea that it is the responsibility of browsers to push the web forward. Breaking sites would force site owners to upgrade, and we could finally rid ourselves of the old and outdated MooTools versions.

Others quickly jumped in to point out that the web is near limitless and that it is impossible to track which sites may be affected. A good amount of those sites probably hadn’t been updated in years. Some may have been abandoned. Others might not have the resources to upgrade. Should we leave these sites to rot? The safe, forgivable approach would be to retool the function to be either backward or fully compatible with MooTools. Upon release, nothing would break, even if the final implementation of Array.flatten was less than ideal.

Somewhere down the middle, a final proposition suggested the best course of action may simply be to rename the function entirely, essentially sidestepping the issue altogether and avoiding the need for the two implementations to play nice at all.

One developer suggested that the name Array.smoosh be used instead, which eventually lead to the whole incident to be labeled Smooshgate, which was unfortunate because it glossed over a much more interesting debate lurking just under the surface about the very soul of the web. It exposed an essential question about the responsibility of browser makers and developers to provide an accessible and open and forgiving experience for each and every user of the web and each and every builder of the web, even when (maybe especially when) the standards of the web are completely ignored. Put simply, the question was, should we ever break the web?

To be clear, the web is a ubiquitous and rapidly developing medium originally built for sharing text and links and little else, but now used by billions of people each day in every facet of their lives to do truly extraordinary things. It will, on occasion, break all on its own. But, when a situation arises that is in full view and, ultimately, preventable, is the proper course of action to try and pull the web forward or to ensure that the web in its current form continues to function even as technology advances?

This only leads to more questions. Who should be responsible for making these decisions? Should every library be actively maintained in some way, ad infinitum, even when best practices shift to anti-patterns? What is our obligation, as developers, for sites we know have been abandoned? And, most importantly, how can we best serve the many different users of the web while still giving developers new programmatic tools? These are the same questions that we continue to return to, and they have been at the core of discussions like progressive enhancement, responsive design and accessibility.

Where do we go now?

It is impossible to answer all of these questions simply. They can, however, be framed by the ideological project of the web itself. The web was built to be open, both technologically as a decentralized network, and philosophically as a democratizing medium. These questions are tricky because the web belongs to no one, yet was built for everyone. Maintaining that spirit takes a lot of work, and requires sometimes slow, but always deliberate decisions about the trajectory of web technologies. We should be careful to consider the mountains of legacy code and libraries that will likely remain on the web for its entire existence. Not just because they are often built with the best of intentions, but because many have been woven into the fabric of the web. If we pull on any one thread too hard, we risk unraveling the whole thing.

As the JavaScript working group progressed towards a fix, many of these questions bubbled up in one form or another. In the end, the solution was a compromise. Array.flatten was renamed to Array.flat, and is now active in most modern browser releases. It is hard to say if this was absolutely the best decision, and certainly we won’t always get things right. But if we remember the foundational ideals of the web — that it was built as an accessible, inclusive and always shifting medium, and use that as a guide — then it can help our decision-making process. The seems to have been at the core of the case with Smooshgate.

Someday, you may be browsing the web and come across an old site that hasn’t been updated in years. At the top, you may even notice a widget that tells you what the weather is. And it will keep on working because JavaScript decided to bend rather than break.

Enjoy learning about web history with stories just like this? Jay Hoffmann is telling the full story of the web, all the way from the beginning, over on The History of the Web. Sign up for his newsletter to catch up on the latest… of what’s past!

The post Yet Another JavaScript Framework appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]