国产吃瓜黑料

Image

We Removed jQuery

A quick overview of 国产吃瓜黑料's journey to rid its Drupal 7 site of jQuery and use emerging standards

Published: 
Image

New perk: Easily find new routes and hidden gems, upcoming running events, and more near you. Your weekly Local Running Newsletter has everything you need to lace up! .

Disclaimer 1: We actually didn't totally remove jQuery聽as we have a few custom features and pages that use it, but for ~99.7% of our pages, it's gone!

Disclaimer 2: The following method isn't something we recommend if you rely heavily on front-end contrib聽modules, authenticated traffic, or parts of Drupal 7 core that depend on jQuery.

What The Heck Is聽jQuery?

makes writing JavaScript code easier and quicker by abstracting overly complicated JavaScript tasks (HTML document traversal and manipulation, event handling, animation, and Ajax) with an easy-to-use API that works across a multitude of browsers. Originally created by John Resig, jQuery is ubiquitous in front-end development and is used by millions of developers on a daily basis.聽Some even indicate that 97% of the web uses jQuery.

Why Remove jQuery?

I love jQuery. It's what has kept me sane by聽helping me achieve many cross-browser effects throughout the years. I'm forever grateful for all the time I've saved聽and all of the great developers who brought this open-source goodness to the masses. But I started to realize that I (we) often use it as a crutch instead of really understanding what is happening with JavaScript code. So when the following four conditions were met, we decided it was time to remove it.

  1. We adopted a modern modular build system for our custom JavaScript.

  2. We fell below 2% of users using any version of Internet Explorer to view our site.

  3. Our quest for greater page-speed optimizations reached a point were we considered jQuery as unnecessary overhead.

  4. We wanted to be one of the first major consumer-publishing聽websites to be jQuery-free!

The above four conditions were paramount before we even thought about looking into removing jQuery. If, for instance, your site has bigger page-speed issues than a library that adds an additional聽87kb聽(~30kb gzipped) to your page load, you should probably stop reading this and work out those issues, as you'll get greater bang for your buck.

Our Modern Build System

We started using a modern聽build system for our custom JavaScript聽in 2017. Prior聽to that, we聽were already using 聽to minify our CSS and JavaScript聽before we drupal_add_js()聽it on our site, but we found that we were using a lot of repetitive code in our JavaScript files for each of our custom templates. Then we discovered , which allowed us to use modern JavaScript and聽 in order to modularize our frontend code. Each of our custom templates (content types) now had a single custom JavaScript file that we compiled.聽This greatly reduced the amount of overlapping code and taught聽us to think from a functional programming聽perspective rather than spaghetti coding the JavaScript for each of our templates. Our current setup looks something like this.聽

  1. Gulp uses each JavaScript file found in specific ../modules/custom/**/js/src/ directories as an input file.
  2. Rollup聽then compiles聽each input file using Babel and聽adds in necessary pollyfills for ES6聽code.
  3. Rollup then minifies and saves each production-ready JavaScript file in a ../modules/custom/**/箩蝉/诲颈蝉迟/听诲颈谤别肠迟辞谤测.听
  4. Gulp watches our JavaScript files and dependencies.

There are many different build tools that allow you to do the same kinds of things, but we found that Gulp + Rollup聽was faster and more functional than the alternatives. As we started writing聽more performant code, we considered removing jQuery聽as a next step. We also wanted to trade in our jQuery聽plugins for more lightweight options. This forced us to really consider all our JavaScript necessary to run our user experience. View our聽full gulpfile.js at this .

Drupal 7 Dependency Hurdle

As you may know, Drupal 7 shipped with jQuery as a dependency, and the more I searched聽how to remove jQuery from Drupal 7, the more I lost hope. Then I remembered that we had done something similar with one of our progressive web app聽features, 100 Days of Winter, which was built with and used a hook_js_alter() to remove unnecessary scripts such as聽misc/drupal.js from that template.

We ended up with the following hook used in our template.php file.


/**
 * Implements hook_js_alter().
 */
function outside_js_alter(&$javascript) {
  // Remove jQuery dependent scripts and recreate settings.
  if (!path_is_admin(current_path()) && !user_is_logged_in() && empty($javascript['jquery'])) {
    $javascript['custom_settings'] = array(
      'type' => 'inline',
      'scope' => 'head_scripts',
      'weight' => -99.999,
      'group' => 0,
      'every_page' => TRUE,
      'requires_jquery' => FALSE,
      'cache' => TRUE,
      'defer' => FALSE,
      'preprocess' => TRUE,
      'version' => NULL,
      'data' => 'document.querySelector("html").classList.add("js"); var Drupal = {}; Drupal.settings = ' . json_encode(array_merge_recursive(...$javascript['settings']['data'])),
    );
    $remove_scripts = array(
      // Jquery dependent JS to remove.
      'misc/drupal.js',
      'settings',
      'misc/ajax.js',
      'misc/progress.js',
    );
    // Remove it.
    foreach (array_keys($javascript) as $value) {
      if (in_array($value, $remove_scripts) || strpos($value, 'jquery')) {
        unset($javascript[$value]);
      }
    }
  }
  elseif (!empty($javascript['jquery'])) {
    // Remove placeholder.
    unset($javascript['jquery']);
  }
}

This hook allows us to remove jQuery-dependent JavaScript, which Drupal adds, and still allows us to grab important Drupal.settings data in JavaScript. We also built in an override that allows jQuery to be added to the page should we need it using a simple drupal_add_js('jquery'); function. We definitely had to rework any Drupal.attachBehaviors() calls, but we never liked that method in the first place, so there weren't many to begin with. (Your mileage may vary depending on which scripts your site requires.)

The next issue for us was replicating the functionality of Drupal AJAX forms found in聽misc/ajax.js,聽which we accomplished with a custom JavaScript module.聽


/*
 * Drupal ajax.js replacement
 *
 * @description
 *  JS for replicating default drupal ajax form handling.
 *
 */

import extend from './extend.js';
import propegatedEvent from './propegated-event.js';
import stringProcessScript from './string-process-script';

const ajaxCommands = {
  insert(formEl) {
    let targetEl = document.querySelector(this.selector) || formEl;
    switch (this.method) {
      case 'html':
        targetEl.innerHTML = stringProcessScript(this.data, targetEl);
        break;
      case 'append':
        targetEl.insertAdjacentHTML('beforeend', stringProcessScript(this.data, targetEl))
        break;
      case 'prepend':
        targetEl.insertAdjacentHTML('beforebegin', stringProcessScript(this.data, targetEl));
        break;
      case null:
        targetEl.insertAdjacentHTML('afterend', stringProcessScript(this.data, targetEl));
        targetEl.remove();
        break;
      default:
        alertNonExisting(this);
        break;
    }
  },
  settings() {
    if (this.merge) {
      Drupal.settings = extend(Drupal.settings, this.settings);
    }
    else {
      // Don't have a need for this yet (ajax settings)?
      // ajax.settings = response.settings;
    }
  },
  updateBuildId() {
    document.querySelector('input[name="form_build_id"][value="' + this.old + '"]').value = this.new;
  }
}

function alertNonExisting(obj) {
  /*eslint-disable */
  console.log("%cThis ajax command isn't setup yet: " + obj.command + (obj.method ? "-" + obj.method : ""), "color: red");
  console.log(obj);
  /*eslint-enable */
}

export default function(formSelector) {
  formSelector = formSelector || '[data-js-ajax-form]';
  propegatedEvent(document.body, ['submit'], formSelector, function(e){
    e.preventDefault();
    let postData = new URLSearchParams(new FormData(document.querySelector(formSelector))).toString(),
        xhttp = new XMLHttpRequest(),
        msg = document.querySelector(formSelector).parentElement.querySelector('.messages');
    // Remove old messages.
    if (msg) {
      msg.remove();
    }
    // Add callback.
    xhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
        let formData = JSON.parse(this.responseText);
        // Loop through drupal response commands.
        formData.forEach((cmd) => {
          if (ajaxCommands[cmd.command]) {
            ajaxCommands[cmd.command].call(cmd, document.querySelector(formSelector));
          }
          else {
            alertNonExisting(cmd);
          }
        });
      }
    };
    xhttp.open("POST", "/system/ajax", true);
    xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
    xhttp.send(postData + '&ajax_page_state%5Btheme_token%5D=' + Drupal.settings.ajaxPageState.theme_token + '&ajax_page_state%5Btheme%5D=' + Drupal.settings.ajaxPageState.theme);
  })
}

So we didn't replicate all the functionality, but just enough to cover our own聽usage while providing a console.log message, should we need help with additional methods in the future.

Real-World File-Size Savings

Before our jQuery聽removal refactoring, the bundle file on our most popular content type,聽article.min.js,聽was聽139kb聽minified (gzipped聽47kb). After our refactor, our聽jQuery-free code comes in at 50kb聽(gzipped聽15kb).

This might seem trivial, but when you consider that we have over 5 million pageviews to the article聽content type in any given 30 days, it translates to roughly 150GB聽of bandwidth saved. Also we've tested the JavaScript computation time on mobile devices, which accounts for 70% of site traffic, and we found that on average we are saving roughly 177听尘蝉 of processing time for our聽article.min.js聽file compared with the jQuery聽version. That time savings is even larger on older devices and on聽antiquated jQuery plugins, which don't utilize newer browser聽APIs like MutationObserver and Intersection Observer.

Recap And Final Thoughts

Keep in mind this was not just a simple exercise in removing jQuery聽from our bundled code, but also many jQuery聽plugins, which forced us to rewrite the majority of our JavaScript code from the ground up. The entire process has been kind of exhausting, as we had to test nearly every template type against all our browser and device requirements. But we've streamlined our code and reduced our JavaScript footprint by almost聽65%聽on the majority of our bundles, and we've learned valuable insights along the way鈥攅.g., browsers aren't that dissimilar聽when it comes to JavaScript rendering. We also found that the new methods are also the best methods, and that modern browsers are often optimized for such.

Filed to:

Popular on 国产吃瓜黑料 Online