Please Enqueue Responsibly – Ethan Clevenger



Please Enqueue Responsibly – Ethan Clevenger

0 0


please-enqueue-responsibly

A talk on script and style management in WordPress

On Github ethanclevenger91 / please-enqueue-responsibly

Please Enqueue Responsibly

Ethan Clevenger

A bit about me

beerPilgrimage ethanclevenger.com ethanclevenger91 @ethanclevenger eclev91 Hey, I’m Ethan Clevenger. I’m a frontend/backend web developer, designer, what-have-you from Des Moines, IA. I work for a company up there called Webspec Design where I do a lot of WordPress work, but also some custom stuff working with MVCs like Laravel. I run a website called BeerPilgrimage, I write for an new online publication called Finding the Forest, and I also do some freelance, I have a couple open-source libraries and contribute to others, so on and so forth. I’ve got one WordPress plugin out there, Picture Element Thumbnails, that does responsive images for WordPress. You might be saying “Well isn’t that built into core now?” to which I would say that the conversation on the difference between responsive images and art-directed responsive images is a topic for another talk, because today I’m here to talk about scripts, styles, requests and performance.

"Beginner"

  • Internet - self-taught, chasm of best-practices
  • Talking about "basics" at WordCamp
  • Everyone's a beginner at something
  • Consider the requirements of your project
  • Don't do something because I said so

Agh, I don't want to say don't use the word beginner, because there is such a thing, but see, the web is a funny industry in that, and this might be a lot of you, it’s very easy to teach yourself, start doing stuff for clients, never have a code review and do things totally “wrong” but be wildly successful doing it. So you end up with practices that someone like me might take for granted that you’ve never even thought about and, similarly, things you take for granted that I’ve never thought of, unlike many other industries where you pretty much have to have a formalized education.

So we come to WordCamp and we're like "Ah, the pinnacle of WordPress!" But we're having conversations, which we totally should, about “basic” things like wp_enqueue_scripts, which many people may be super familiar with and a lot of others may have never heard of. Conversely, I doubt they have presentations about “basic” things, like giving booster shots, at pediatrics conventions. The long and short of that whole spiel is that this talk is labeled intermediate but it's really for everybody and you can take it as far as you want. We're all beginners at something and you should never feel bad for not knowing something. Don't let someone say "Oh you don't know that, are you even a web developer?"

So today I’m going to chat with you about scripts and styles and give you lots of advice, but you should remember a couple things. The first is that I’m working on my site and you’re working on yours, so take everything I say and then use your best judgement. You know your projects better than I do. The second thing to remember is that “because some guy at WordCamp said so” is a terrible reason to do anything. Especially for people who do client work, you should always be asking why you’re doing something, so I’m actually going to start off talking about why this stuff is important and make a pitch for the relevence of my talk today.

Your site should be fast.

  • Bounce at 4-ish seconds, at mercy of computer between 1 and 10
  • Speed analyzers red
  • All content loaded - not TTFB
*https://www.nngroup.com/articles/website-response-times/

Your site should be fast. This isn't new information to any of you I'm sure - there are piles of articles out there on the web that say bounce rates skyrocket after four-ish seconds. According to Nielsen Norman, your users not only bounce, but start to feel "at the mercy of the computer" beginning after just one second, and bounce definitely after 10 seconds.

And I am talking about users here, so like your site is loaded fast. Not just time-to-first-byte or stuff starts popping in. If ads are still loading, your site isn't done as far as I'm concerned, especially if they're bumping content around and marring up the scroll. Sorry.

If you’ve ever submitted a slow site to Google’s page speed analyzer, you’ll notice it gives you big red boxes as feedback, and that’s pretty much the same for any service that does page speed analytics. That’s because slow is, generally, bad.

Requests

  • Any time a site gets a resource from a server
  • FIFO, HOL-blocking
  • Images are a lost cause

So we're probably all on board with that. Here's the less-well-known part - why is your site slow? There can be lots of reasons, but generally, it has to do with requests - that is, any time your site gets a resource from a server.

Requests to any one server, using HTTP/1.1, are FIFO - first-in-first-out. Results must be returned in the order they were received. Sometimes this results in head-of-line blocking, and even when it doesn’t, there’s still a line. This means, generally, that more requests are more detrimental to the load speed of your website than big requests. Not that this totally excuses you for having 10MB images on your site, as that’s bad for a host of other reasons, not limited to site speed.

And if you’ve been on the internet lately, you might have noticed that images are in vogue. They’re engaging, they tell stories - people will even sell you pretty but generic ones to stuff into your website.

Unfortunately, these are the requests we have the least ability to optimize - they tie your hands. As a WordPress theme developer, you’re probably creating spaces for the end user to upload images, and they can drop as many as they want into the WYSIWYG. Every one of those is a single request - you can’t generate fancy spritesheets and have totally arbitrary images.

So if images are out there trashing our websites, we can at least cut our losses by making our CSS and JS efficient. If you aren't sold on the idea of making a fast website and doing it by getting smart with our requests, your probably not going to get much from this talk.

For everyone else, I’m going to apply this specifically to WordPress. The first thing we can do is start using these magic functions `wp_enqueue_script` and `wp_enqueue_style`.

wp_enqueue_script, wp_enqueue_style

  • Alert WordPress to scripts and styles your site will require
  • Dependency management

How many of you are already familiar with these functions?

Awesome, I had hoped so/this talk might go from 0 to advanced real quick then. Alternatively, how many of you are currently putting script and link tags right into your theme template files?

I feel like some of you raised your hands for both. I thought those were mutually exclusive…

So what do `wp_enqueue_script` and `wp_enqueue_style` do? These are functions that alert WordPress to the existence of scripts and styles that the site is going to require, where they should be dropped in, what version if the file it is and if it depends on any other scripts or styles. Then, when you call `wp_head()` in your header or `wp_footer()` in your footer, it’s going resolve those dependencies and drop in your script and link tags for you.

Why do we use these? The dependency management is nice, and it keeps us from accidentally loading up some files more than once. It also keeps our template files template-y - focused on layout rather than making sure our scripts and styles are loaded in the right place in the right order from page to page to page. Let's look at some examples.


Okay, so what's wrong here? First we've linked in our theme's styles, which is probably fine. Next, oh, well looks like we wanted to use WordPress's dashicons on the front end. Hope they don't move those on us. Next we've got our theme's scripts, once again, pretty unremarkable, and finally, ah, yes. We've loaded jQuery from a CDN like everyone tells us to, but there are a couple issues. First issue is our own scripts probably depend on jQuery but we've loaded them out of order. Furthermore, some plugin author is probably going to load WordPress's jQuery, so then we'll have two versions loaded together, which never pans out. Finally, you generally don't need to put your script tags in your header and hold up the rest of the site. That's called render-blocking content.

class Plz {
	function __construct() {
		add_action('wp_enqueue_scripts', [$this, 'wp_enqueue_scripts']);
	}

	function wp_enqueue_scripts() {
		wp_enqueue_script(
			'google-maps',
			'https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap'
		);
		wp_enqueue_script(
			'theme-scripts',
			get_template_directory_uri() .'/js/scripts.js',
			['jquery'],
			filemtime(get_template_directory().'/js/scripts.js'),
			true
		);
		wp_enqueue_style(
			'theme-styles',
			get_template_directory_uri() . '/style.css',
			false,
			filemtime(get_template_directory().'/style.css')
		);
		wp_enqueue_style('dashicons');
	}
}
$plz = new Plz();

					

So like good PHP programmers, we're going to wrap our functions file or our plugin in a class and not clutter the global namespace. Then we can hook into the wp_enqueue_scripts action with our own function and queue up some stuff. You'll see we give our resource a handle and then a URL. In our second script here you'll see I've included a third argument, which is dependencies. Our theme's scripts depend on jQuery, so WordPress won't load them without jQuery. Then we've got our theme styles, and you can see here I haven't passed any dependencies, but I have passed a function for the write time of the file - this is a fun way to cache-bust your resources. Finally, WordPress also has already registered a handle for dashicons so we can enqueue that by itself. I'll explain registering a bit later.

Cool, okay, so what other WordPress stuff can we do? I vote we move jQuery to the footer. By default, WordPress sticks it in the header - gross. If your site depends on jQuery for the layout to be right or takes so long to load that the user is going to do something that should fire some jQuery logic before the page is loaded, you’ve got other problems. You might rethink your layout. This is also a great intro to manipulating third-party plugins’ scripts and styles - another handy advantage of these functions. Here’s how we can do that:

wp_deregister_script( 'jquery' );
wp_enqueue_script( 'jquery', includes_url( '/js/jquery/jquery.js' ), false, false, true );
					

Once again, WordPress already registers a handle for jQuery. You'll see with jQuery I've actually deregistered WordPress's default handle and re-registered it. The fifth argument is to load the resource in the footer so it isn't render-blocking. Not sure why this isn't default WordPress behavior, but I'd call it best practice. We're using the same file and handle so that when plugin developers enqueue jQuery, it doesn't get enqueued twice.

Enqueueing in Plugins

  • wp_enqueue_script is indiscriminate
  • wp_register_script
  • Enqueue at entry point

Various plugins you install to your site or that you develop are going to need their own scripts and styles. "Yeah! So I’ll just hook into wp_enqueue_scripts() and enqueue my scripts!" Don’t do that, probably.

By actually enqueuing your scripts on the enqueue scripts action, you're telling WordPress to load that script or style on every single page on the site. This probably isn't necessary. Fortunately, there’s a solution.

wp_enqueue_script() and wp_enqueue_style() come with a couple of related functions called wp_register_script() and wp_register_style(). These functions take the same arguments as their enqueue counterparts, but don’t actually tell WordPress to spit them out. Instead, it saves them for later, when you can call wp_enqueue_script() using the handle you registered and queues it up then - like dashicons and jQuery earlier. So the way this ends up working is you can register your script and then, at your plugin's entry point, be that a shortcode or a template file or a function call, enqueue the relevant scripts and styles. This prevents your scripts and styles from being loaded on every page, in particular those that your plugin isn't actually acting upon.

Unfortunately, a lot of plugins don’t do this. I’d like to talk about one in particular that has a lot of the market despite being, in more than just request efficiency, generally really frustrating to work with as a dev. Say hi to NextGen Gallery.

So before you freak out, NextGen actually addressed this issue. According to the changelog, it was February of last year. That being said, this plus some other behavior I'm not super fond of kept me away from NextGen long enough that I wasn't around to hear it was fixed. It's also a great lesson to be learned.

Here's the story: at the point in time where this was a problem, NextGen loaded about 13 styles and scripts to do some of its basic gallery things. Now even if that's only showing up on one page, that's a lot. But here's the kicker - you could spin up a default WordPress site, base theme, not touch any settings. Install NextGen, don't even set up a gallery or upload photos. If you checked out the network tab on your home page, there were those 13 scripts and styles. On every page. Whew.

Like I said, they've cleaned it up, which is awesome, and if you peruse the changelog, they've been making a lot of efforts at being performant, which is nifty.

Here's another, less dramatic example - Contact Form 7. To this day, Contact Form 7 makes 3 requests on every page on your site - 2 scripts, dutifully in the footer, and one stylesheet. Let's go ahead and demo that.

Demo Time

Clean up Contact Form 7 Scripts and Styles

There's even a blog post on the developer's website telling you how to turn it off. I don't know the intricacies of how Contact Form 7 works, but I'd like to think this could be avoided out of the box.

Level 2

Optimization

Thus far, we've talked about ways that WordPress allows us to make sure that the actual task of making a request is done intelligently - only when necessary and without duplication of resources. Now let's see what we can do with our code to make the files themselves more efficient.

CSS Preprocessors

First let's talk about CSS preprocessors. How many people in the room are writing vanilla CSS? Alright, a few of you. Many of you may be familiar with these and just don't care and for others, maybe this is news to you.

There are a couple of big CSS extension languages out there called LESS and Sass, or SCSS. Neither of them are technically required for using CSS preprocessors, but they sure are nice. They let us declare variables, use imports, nest selectors, define "functions", lots of fun stuff. Explore them if you haven't.

But the browser can't read any of these extension languages, so we use CSS preprocessors. I'm familiar with a few I've listed here, and your neighbor probably has their favorite. These will compile your scss or your less into browser-friendly CSS. In regards to our talk today, I'm going to talk about two big things we get out of these: concatenation and minification.

Open WordCamp demo theme

So here's a pretty basic WordPress theme. I've got a header, footer, index, functions and styles. Really about the bare minimum, you can ignore some of that other stuff for now. Now I can open my styles.css file and you see I've got some basic styles here, and if we open my functions.php file, you'll see I've enqueued it. Let's hop on the front of the site and, sure enough, I've got a pretty straightforward "Hello World". Great.

How about I want to load up another CSS file? I'm a good dev, I like to keep my code compartmentalized. Atomic design and all that. So I hop into my css folder directory and I've got a new styles.css here. Let's add something silly, like make my background orange, and then go over to functions and enqueue that. Refresh and the background is orange. Well that _works_, but now we're up a request. Yuck. How can we avoid that? Well we can put all our styles in one file, but that becomes unmanageable. How about a CSS extension language?

For this example, I'm using my extension of choice, scss. I'll hop into my scss directory, set up a new file here and copy over the code from my original stylesheet. Now if you're using a tool like Prepros, you can drag your theme right in here and it's going to automatically find your scss and compile it as soon as we save. Let's go back to our file, save, and sure enough, we get a quick notification that says everything went well. And if we open up our css folder, we see its contents have been replaced with the results of the compilation. Couldn't be much easier. Let's go over to functions and enqueue this sheet instead of the default one. Perfect.

Now let's get back to compartmentalized code: I want several files, but just one request. Well with scss, that becomes super easy. I can create a new directory here called partials, create a new file, for now we'll call it foo.scss or something, and we can make our background orange right here. Now back in our main styles.css, I can @import my other stylesheet right underneath my other styles. Let's save and refresh, sure enough, we've got an orange background.

And that scales very very well. I can also configure PrePros to minify my files when it's done so that I end up with the smallest filesize possible. It'll do the same kind of thing for Javascript files, too - concatenation and minification, and compilation if you're using something like CoffeeScript.

Level 3

Task Runners

  • Gulp
  • Grunt

Now personally, I'm not much for these big GUI apps. They do what they do well, but there's a lot more out there you can do with your files of all kinds, and for that, you're going to need something more extensible. The solution is task runners. These have gotten super popular recently. These are command line tools generally based on Node.js, which is like server-side javascript. Gulp and Grunt are the two big ones, and I'm going to give you a quick look at Gulp by getting it configured to do the same stuff that PrePros was doing. This is going to be a super-fast crash course and you should definitely look for tutorials on getting started with this stuff. I'm just here to convince you it's awesome.

First, you'll want to set up a package.json that says your project requires gulp. [Explain package.json] It does this via a tool called npm, which stands for node package manager. You can Google that for more information about getting set up.

Next let's check out our gulpfile.js

So gulp operates on the idea of plugins, all of which are also maintained on npm. I'm gone ahead and installed gulp-sass, which will compile our scss for us. Let's go ahead and pause PrePros, wipe our CSS directory to make sure I'm not fooling you, and then from the command line we can run `gulp sass`. Our CSS file is generated and boom, refresh, same result. You can see I've also set up a watch task, so we can have gulp watch for file changes just like PrePros was doing.

How about minification? Auto-prefixing? All done via other gulp plugins that I've already installed. I'll go ahead and uncomment the autoprefixer stuff here, save, check out our CSS and our flexbox is set for a variety of browsers. Same for minification.

What else can gulp do?

  • Spritesheets
  • Prune CSS
  • Optimize images
  • JavaScript

Gulp can be extended to do all kinds of stuff with your CSS - create spritesheets, compare against your markup and prune unused selectors, it's awesome. It'll also do your Javascript, minify images - you can do a lot with this thing.

Last thing on gulp, I'm going to show you the gulpfile I like to use for production. I've actually further abstracted gulp behind a wonderful library from Laravel called Elixir. Let's open that up and take a look at what it's doing really quickly.

CDN vs Self-hosted

All right, so the last thing I want to talk about today is CDNs. Raise your hand if you aren't familiar with CDNs. Real quick, a CDN or content-delivery network is a strategy for dispersing commonly-used resources across the world to minimize latency and downtime for delivery. If you've ever used a third-party front-end library, you've probably seen getting started instructions that inclue fetching from a CDN like MaxCDN, Google, there are a bunch. There are a variety of advantages to this strategy.

CDN Advantages

  • Server potentially closer to the user
  • Potential pre-cache
  • One less request in your server's pipeline
  • Generally, high uptime

CDNs are awesome for a few reasons. The first is that the CDN's location is likely closer to your user and hosted on a pretty kicking server, so the latency is likely to be low.

There's also the advantage that this could be the same resources that a variety of sites around the internet are using, including ones your user has already visited. That means, at least on the first page load, there's a possibility that the file is already cached on the user's browser, which is good news for you. If not, their browser wil cache it after that and hopefully it'll benefit someone else down the line.

Remember when we talked about that HTTP pipeline and how you can only have 4 concurrent connections to any one server? With CDNs, you take a request out of your stack and put it in another. Neato.

And finally, these are companies that make their money by providing high uptime and low latency across a distributed network, so they do it pretty well.

CDN disadvantages

  • Uptime? Who cares?
  • I'm lazy and upgrade-happy
  • No CSS optimization
  • Giving up control
  • $

This all sounds pretty good. Any disadvantages? Some.

So first, I don't really care about uptime if the alternative is hosting it myself. If my site is down, it's not going to matter if I can fetch Bootstrap from the CDN, my site is down. If my site is up, I'll be able to fetch my own site from my own server. Similarly, let's say the CDN _did_ go down - now my site may load without the basic CSS framework. If you've been thoughtful about it, this may fall back to something useable, but that's not always true, so now it just looks like your site is broken. Personally, I'd rather all or nothing.

Next, I really like to upgrade libraries. If I'm doing work on an existing client site, the first thing I often do is an npm update and get all my front-end libraries up-to-date before building, testing locally and pushing out. I like to think this minimizes bugs and potential security holes, though with front-end libraries that isn't usually a concern. With CDNs, the URL you're hitting is generally versioned, but I don't really care to figure out which version is the latest and then bump all my URLs manually. By the same token, I don't want to use the "latest" URL if the CDN offers it, because then any updates hit the live site without my oversight.

CSS optimization. Let's go back to that scss file we were working with earlier. You'll notice here I've commented out a _lot_ of bootstrap rather than just @include bootstrap. I often only use the grid - some of this stuff I never comment out on any sites I work on. This makes sure my CSS is only as big as it needs to be. With a CDN, it's all or nothing. Now a lot of libraries only have one file, so it really doesn't make a difference like it does here with Bootstrap or other CSS frameworks.

Giving up control. While unlikely, there's a possibility that your CDN is compromised or goes rouge or whatever and suddenly you're served up alternative or even malicious files. Or maybe they're collecting referrer information and using it to track your users? The former is unlikely and the latter is a small drop in a pool of questionable big data practices, but if it's something you're worried about, a CDN probably isn't for you. A tinfoil hat, however, might be.

Finally, if you're looking to host your own resources on a CDN, that's going to cost money. If that's something you or your client have budgeted for, great, and if not, this may not be an option.

As with most of the stuff I've talked about today, use your best judgement. A CDN is great for some situations and not so much for others. To be honest, Google Fonts and Typekit are the only two that I regularly use, as I prefer to bundle all my resources together for a single request in most other situations.

In Review

  • Enqueue your requests, and enqueue them conditionally
  • Make fewer requests
  • Optimize your resources
  • Task runners are your friend
  • Consider a CDN
And that about wraps it up. Let's review real quickly what we went over today: [blah]. Any questions?