Start Date Release Date Release Versions PR link Tracking Link Stage Teams
1/11/2018 7/16/2018
  • ember-source: v3.3.0
Recommended
  • Framework

Make jQuery optional

Summary

For the past Ember has been relying and depending on jQuery. This RFC proposes making jQuery optional and having a well defined way for users to opt-out of bundling jQuery.

Motivation

Why we don't need jQuery any more

One of the early goals of jQuery was cross-browser normalization, at a time where browser support for web standards was incomplete and inconsistent, and Internet Explorer 6 was the dominating browser. It provided a useful and convenient API for DOM traversal and manipulation as well as event handling, that hid the various browser differences and bugs from the user. For example document.querySelector wasn't a thing at that time, and browsers were using very different event models (DOM Level 0, DOM Level 2 and IE's own proprietary model).

But this level of browser normalization is not required anymore, as today's browsers all support the basic DOM APIs well enough. Even more so that the upcoming Ember 3.0 will drop support for all versions of Internet Explorer except 11.

Furthermore Ember users will need to directly traverse and modify the DOM or manually attach event listeners in very special cases only. Most of these low level interactions are taken care of by Ember's templates and its underlying Glimmer rendering engine, as well as action helpers or the component's event handler methods.

So having jQuery included by default does not provide that much value to users most of the time, and Ember itself is expected to be fully functional and tested without jQuery, presumably for the upcoming 3.0 stable release.

What are the drawbacks of bundling jQuery

The major drawback is the increased bundle size, which amounts to ~29KB (minified and gzipped). This not only increases the loading time, but also parse and compile times, thus increasing the total time to interactive. This is especially true for mobile devices, where slow connectivity and weak CPU performance is not uncommon.

Having jQuery not included will improve the suitability of Ember for mobile applications considerably. Even if the raw number is not that huge, it all adds up. And it plays together with other efforts to make leaner Ember builds possible, like enabling tree shaking with the new Module API, moving code from core to addons (e.g. the Ember.String deprecation) or the "Explode RFC". In that regard removing the dependency on jQuery is a rather low hanging fruit with an high impact.

But this is already possible, why this RFC?

There is indeed a somewhat quirky way to build an app without jQuery even today. Although this happens to work, it is not sufficient to consider this officially supported for these reasons:

  • Ember itself must be fully tested to work without jQuery
  • the public APIs that depend on and/or expose jQuery need to have some well defined behavior when jQuery is not available
  • there should be a way to technically opt-out (other than fiddling with vendorFiles) that is easier to use, understand and maintain
  • addons should mostly default to not use jQuery, to make removing jQuery practically possible for their consuming apps

Detailed design

Remove internal jQuery usage

As of writing this, there are major efforts underway to remove and cleanup the Ember codebase and especially its tests from jQuery usage. Having a way to fully test Ember without jQuery is a prerequisite to officially support jQuery being optional. When this is done, it will enable a "no jQuery" mode, that will make it not use jQuery anymore, but only native DOM APIs.

Add an opt-out flag

There should be a global flag that will toggle the optional jQuery integration (true by default). When this is disabled, it will make Ember CLI's build process not include jQuery into the vendor.js bundle, and it will explicitly put Ember itself into its "no jQuery" mode.

The flag itself will not be made a public API. Rather it will be handled by a privileged addon, that will allow to disable the integration flag, thus to opt out from jQuery integration. This approach is in line with RFC 278 and RFC 280, to allow for some better implementation flexibility.

Introduce @ember/jquery package

Currently Ember CLI itself is importing jQuery into the app's vendor.js file. To decouple it from this task, and to allow for some better flexibility in the future, the responsibility for importing jQuery is moved to a dedicated @ember/jquery addon.

To not create any breaking changes, Ember CLI will have to check the app's dependencies for the presence of this addon. If it is not present, it will continue importing jQuery unless the jQuery integration flag is disabled. If it is present, it will stop importing jQuery at all, and delegate this responsibility to the addon.

To nudge users to install @ember/jquery when they need jQuery, some warning/deprecation messages should be issued when the addon is not installed and the integration flag is either not specified or is set to true. To ease migration the addon should be placed in the default blueprint (until an eventual more aggressive deprecation of jQuery). Only in the case the app is actively opting out of jQuery integration the addon is not needed.

The addon itself has to make sure the Ember CLI version in use is at least the one that introduced the above mentioned logic, to prevent importing jQuery twice.

Assertions for jQuery based APIs

Apart from testing (see below), Ember features some APIs that directly expose jQuery, which naturally cannot continue to work without it. For these APIs some assertions have to be added when running in "no jQuery" mode (and not in production), that provide some useful error messages for the developer:

  • Ember.$() should throw an assertion stating that jQuery is not available.
  • this.$() in components should throw an assertion stating that jQuery is not available and that this.element and native DOM APIs should be used instead.

Introducing ember-jquery-legacy and deprecating jQuery.Event usage

Event handler methods in components will usually receive an instance of jquery.Event as an argument, which is very similar to native event objects, but not exactly the same. To name a few differences, not all properties of the native event are mapped to the jQuery event, on the other hand a jquery event has a originalEvent property referencing the native event.

The updated event dispatcher in Ember 3.0 is capable of working without jQuery (similar to what ember-native-dom-event-dispatcher provided for Ember 2.x). When jQuery is not available, it will naturally not be able to pass a jquery.Event instance but a native event instead. This creates some ambiguity for addons, as they cannot know in advance how the consuming app is built (with or without jQuery).

For code that does not rely on any jQuery.Event specific API, there is no need to change anything as it will continue to work with native DOM events.

But there are cases where jQuery specific properties have to be used (when jQuery events are passed). This is especially true for the originalEvent property, for example to access TouchEvent properties that are not exposed on the jQuery.Event instance itself. So there has to be a way to make the code work with either jQuery events or native events being passed to the event handler (especially important for addons). Moreover this should be done in a way that uses native DOM APIs only, to support the migration away from jQuery coupled code.

To solve this issue another addon ember-jquery-legacy will be introduced, which for now will only expose a single normalizeEvent function. This function will accept a native event as well as a jQuery event (possibly distinguishing between those two modes at build time, based on the jQuery integration flag), but will always return a native event only.

This will allow addon authors to work with both event types, but start to only use native DOM APIs:

import Component from '@ember/component';
import { normalizeEvent } from 'ember-jquery-legacy';

export default Component.extend({
  click(event) {
    let nativeEvent = normalizeEvent(event);
    // from here on use only native DOM APIs...
  }
})

To encourage addon authors to refactor their jQuery coupled event code, the use of jQuery.Event specific APIs used for jQuery events passed to component event handlers should be deprecated and a deprecation message be shown when accessing them (e.g. event.originalEvent). Care must be taken though that this warning will not be issued when normalizeEvent has to access originalEvent.

Also for apps that do not want to transition away from jQuery and would be overloaded with unnecessary warnings, the deprecations should be silenced when the jQuery integration flag is explicitly set to true (and not just true by default). By doing so users effectively state their desire to continue using jQuery, thus any needless churn should be avoided for them.

Testing

Ember's test harness has been based on jQuery for a long time. Most global acceptance test helpers like find or click rely on jQuery. For integration tests the direct use of jQuery like this.$('button').click() to trigger events or assert the state of the DOM is still the standard, based on this.$() returning a jQuery object representing the rendered result of the tests render call.

To be able to reliably run tests in a jQuery-less world, we need to run our tests without jQuery being included, so our test harness has to work without jQuery as well.

Fortunately this is well underway already. ember-native-dom-helpers introduced native DOM test helpers for integration and acceptance tests as an user space addon. The recent acceptance testing RFC 268 provides similar test helpers, implemented in the @ember/test-helpers package, and envisages deprecating the global test helpers.

However while the existing jQuery based APIs are still available, when these are used without jQuery they have to throw an assertion with some meaningful error message:

  • global acceptance test helpers that expect jQuery selectors (which are a potentially incompatible superset of standard CSS selectors)

  • this.$() in component tests, provided currently by @ember/test-helpers in moduleForComponent and setupRenderingTest

In both cases the error message should state that jQuery is not available and that the native DOM based test helpers of the @ember/test-helpers package should be used instead.

The transitioning to these new test helpers can be eased through a codemod. For ember-native-dom-helpers there already exists ember-native-dom-helpers-codemod, which could be adapted to the very similar RFC 268 based interaction helpers in @ember/test-helpers.

Implementation outline

The following outlines how a possible implementation of the jQuery integration flag could look like. This is just to provide some additional context, but is intentionally not meant to be normative, to allow some flexibility for the actual implementation.

The addon that will handle the flag is expected to be ember-optional-features, which will read from and write to a config/optional-features.{js,json} file. This will hold the jquery-integration flag (amongst others). This flag in turn will be added to the EmberENV hash, which will make Ember go into its "no jQuery" mode when set to false.

Ember CLI and the @ember/jquery addon will also look for jquery-integration in this configuration file, and will opt-out of importing jQuery when this file is present and the flag is set to false.

How we teach this

Guides

The existing "Managing Dependencies" chapters in the Ember Guides as well as on ember-cli.com provide a good place to explain users how to set the jQuery integration flag by means of the mentioned privileged addon that handles this flag.

The section on components should be updated to remove any eventually remaining references to this.$, to not let users fall into the trap of creating an implicit dependency on jQuery by "accidental" use of it. These should be changed to refer to their native DOM counterparts like this.element or this.element.querySelector().

The section on acceptance tests will have been updated as per RFC 268 to use the new @ember/test-helpers based test helpers instead of the jQuery based global helpers.

The section on component tests should not use this.$() anymore as well, and instead also according to RFC 268 use this.element to refer to the component's root element, and use the new DOM interaction helpers instead of jQuery events triggered through this.$().

Deprecation guide

The deprecation warnings introduced for using jQuery.Event specific APIs should explain the use of the normalizeEvent helper function to migrate towards native DOM APIs on the one side, and on the other side the effect of setting the jQuery integration flag to explicitly opt into jQuery usage thus suppressing the warnings.

Addon migration

One of the biggest problems to easily opt-out of jQuery is that many addons still depend on it. Many of these usages seem to be rather "accidental", in that the full power of jQuery is not really needed for the given task, and could be fairly easily refactored to use only native DOM APIs.

For this reason this RFC encourages addon authors to not use jQuery anymore and to refactor existing usages whenever possible! This certainly does not apply categorically to all addons, e.g. those that wrap jQuery plugins as components and as such cannot drop this dependency.

ember-try

ember-try, which is used to test addons in different scenarios with different dependencies, should provide some means to define scenarios without jQuery, based on the jQuery integration flag introduced in this RFC.

Furthermore the Ember CLI blueprint for addons should be extended to include no-jQuery scenarios by default, to make sure addons don't cause errors when jQuery is not present.

emberobserver.com

It would be very helpful to have a clear indication on emberobserver.com which addons depend on jQuery and which not. This would benefit users as to know which addons they can use without jQuery, but also serve as an incentive for authors to make their addons work without it.

Given the jQuery integration flag introduced in this RFC, this paves the way to automatically detect addons that are basically declaring their independence from jQuery by having this flag set to false (in their own repository).

Drawbacks

Churn

A vast amount of addons still depend on jQuery. While as far as this RFC is concerned no jQuery based APIs will be deprecated and the default will still be to include jQuery, addons are nevertheless encouraged to remove their dependency on jQuery, which will add some considerable churn to the addon ecosystem. As of writing this, there are:

Among these are still some very essential addons like ember-data, which still relies on $.ajax, see #5320.

A good amount of that churn can be mitigated by having a codemod that migrates tests (see "Testing" above).

Alternatives

Continue to depend on jQuery.

Unresolved questions

None so far.