The Case for JS Modules

I originally posted some of this content here: Split javascript files in media into modules

The patch on that ticket breaks up the Backbone classes in media-models.js, media-views.js, media-audiovideo.js, and media-grid.js into modules and injects them via Browserify on build/watch into a built file. Let’s start at the beginning.

Brain overload

Files that are 1000s of lines long are hard to consume. We try to alleviate this by adding copious amounts of docs. Still, it’s a lot to look at. Ideally, we would break our files into smaller modules and then somehow join them together in a build process.

It is common practice to serve (sometimes very large) minified files for JS and CSS that have concatenated many smaller files together and uglify’d (minified/obfuscated) them. It is no longer common or best practice to develop with huge files. We can learn a lot from emerging front end development trends, especially those from the Node/NPM community. In some cases, we can even share code.

We’ll use Media as the main culprit, but this could apply to any “manifest” – a term I use to describe files that contain the entire public API for a feature. Something like media-views.js, it might be nice to bounce from view to view in the same file, provided you know exactly what you are looking at, what depends on what, etc.

I have found, it is completely overwhelming for almost everyone. It would be great if each discreet piece could be viewed in isolation with its dependencies clearly stated.

There are many ways to accomplish the splitting of large files. I want to focus on 2 of the most common.

Vocabulary

Backbone is one of a growing number of MV* frameworks for JavaScript. A large majority of the code related to media either belongs to a handful of Models or to the increasingly large library of Views and View Templates.

Views are the building blocks for the presentation of Media (you know, “the Media Modal” or 4.0’s “Media Grid”).

The main canvas on which these Views are stitched together are called Frames, which are themselves Views – tilting our use of Backbone more towards MVP, P standing for Presenter.

We have Controllers, which are called States, but they belong to a Frame (Presenter! also a View!), so anyways…. for now….

When we create new UIs, we are more than likely adding new Views/Templates, or updating existing Views.

If we wanted to move from one large file to many files that each contain a class, we would create Modules.

Grunt is a task runner. We use Grunt to build our src directory into our build directory.

Require

Require is a great tool for converting AMD modules into built files. Require leans on Dependency Injection in its syntax:

define([
    'models/taco',
    'models/burrito',
    'controllers/meal'
], function (Taco, Burrito, Meal) {
    var Dinner = Meal.extend({
        // taco-related code
    });
    return Dinner;
});

This syntax works great, unless you have way more dependencies. Refactoring code could unwind a module that has a lot of dependencies, but if you are just trying to convert legacy classes into a module, Require starts to get a little weird. Concrete example: Frames have a TON of dependencies.

Require becomes a Grunt task to make one big file by recursing the dependency tree in an initial manifest. Require, by default, loads JS asynchronously, which can cause race conditions with plugins or themes that expect code to be registered on $(document).ready() or window.onload.

Require works even if you don’t build via Grunt.

Browserify

Browserify is a tool that allows you to use Node-style modules and run them in a browser without changing from the Node syntax. Browserify requires a build for this to work.

Using our example from above, this is the syntax for Browserify:

var Taco = require( './models/taco.js' ),
    Burrito = require( './models/burrito.js' ),
    Meal = require( './controllers/meal.js' ),
    Dinner;

Dinner = Meal.extend({
    // taco-related code
});

module.exports = Dinner;

Browserify leans more towards the Service Locator pattern.

Browserify scans the abstract syntax tree (AST) of your JS code to compile dependencies. Your modules themselves get wrapped in their own scope like so:

(function (require, module, exports) { 
    .....YOUR_MODULE..... 
})

After spending a lot of time messing around with both: I think we should use Browserify.

Converting “Legacy” Code

The media JS code is some of the most “modern” code in WordPress, but it still clunkily lives in huge files. To convert the code into modules, we need to make a lot of individual files (one for each Backbone class).

We also need to make sure we maintain the existing wp.media namespaces for backwards compatibility. We don’t want any existing functionality to change, we just want to build the files differently.

Even though the code is defined differently, wrapped in a new scope, and looks different when “built”, we can still maintain our current API design: what is publicly accessible now will remain publicly accessible.

In the patch

Disclaimer: this patch is for experimentation only. It will go stale probably before this post is published. It works, but it is only a playground for now. If this moves forward, it will be a laborious Subversion process to create a bunch of new files.

I have added a folder to wp-includes/js, media, that contains the modules and the built manifests. My patch adjusts script-loader.php to use these new paths.

media contains the following files/folders:

controllers/
models/
routers/
utils/
views/ (with another round of subfolders)
audio-video.manifest.js
grid.manifest.js
models.manifest.js
views.manifest.js

The build pipeline

If you are following along with that patch and want to see this in action, run in the project root:

npm install

Afterwards, run:

grunt watch

*.manifest.js files get built into *.js files when you change a file in media/*, provided you are running the grunt watch task. The watcher will automatically call browserify:media and uglify:media when those files change. This allows you to run your site from src or build, and you will still get Browserify’d files. SCRIPT_DEBUG will either run *.js or *.min.js, just like any other minified JS in core.

This is a proposal

I would like to hear feedback from the overall community and certainly from our fair share of JS-trained ninjas. A common reason to *not* do something like this is the barrier to entry for new developers. I would argue in this case that the code becomes MORE readable and understandable. I was shocked myself to see how much simpler it was to absorb one piece at a time once the code was laid out in modules.

#backbone, #js, #media

Audio / Video 2.0 – codename “Disco Fries”

Some history:

I wanted to do a Make post on my wants for Audio / Video in 3.9 to solicit feedback and spark some discussion about what the community wants / needs / doesn’t want / doesn’t need. Adding audio / video in 3.6 was a great first step, but there are some things we can do to continue to modernize Media and give our huge user base even more ways to display and manage their content. We can also make some changes that help developers navigate the new world of MediaElement.js, Backbone, and Underscore.

First Things First: New Icons

#26650 Replace media file type icons with Dashicons

There are some lingering icons in the admin that don’t look as pretty as their MP6ify’d brethren

Document the “new” Media code introduced in 3.5

In 3.5, we overhauled Media. @Koop produced some beautiful code, and a LOT of it. Raise your hand if you’ve ever dived in and tried to program against it? Raise you hand if you understand how all of it works? Me neither. As a community, we need to help each other learn what it is and what it does. Documentation will go a long way in getting us all on the same page. Do we have a documentation standard for JS? We need one. While this isn’t the easiest place to start, it is a necessary one. I would be happy to spend time on this, as I have spent many hours recently reading the code and learning how it works. The main files: media-editor.js, media-views.js, media-models.js

Support subtitles for Video

#26628 Use the content of a video shortcode when provided.

This ticket speaks for itself, and already has a patch.

Generate audio/video metadata on-demand

#26825 Add ability to generate metadata for audio / video files on-demand

Add “Playlist” and “Video Playlist” shortcodes

#26631 Add a “playlist” shortcode

Adding inline players for audio and video was a great first step. How do I add music to my site? Just upload an MP3 and drop the URL on a line by itself. Done. Or use the audio shortcode. This works most of the time, but can be a little clunky if you want to share an album of your tunes. MediaElement doesn’t “support” playlists out of the box, but MediaElement is JavaScript, and with JavaScript and little UI elbow grease, we can EASILY support playlists.

My ticket already contains a patch, but is still considered a work in progress. I think the playlist shortcode should produce markup that does the following:

  • Works out of the box with any existing theme: the HTML should be semi-bulletproof. Many of the Player libraries make heavy use of DIVs instead of items that might be overridden easily with CSS: LIs and the like.
  • Gives the developer total control if they want to write their own implementation
  • Exposes enough data to the page so the themer/dev can make their own decision regarding display of album cover, track meta, captions, etc.

My current implementation drops data onto the page for each playlist inline. A wrapper div “.wp-playlist” will have a script tag in it with type=”application/json”. I do this so that if ‘wp-playlist.js’ is unenqueue’d, the developer still has the data necessary to write their own implementation. The data is reachable in JS like so:

var data = $.parseJSON( el.find('script').html() );

My current UI for playlist is a basic one, and uses Backbone Views to render the tracklist on load and update the “current” playing track view. There are 2 camps of people when it comes to “JS on the frontend” – one who doesn’t like it (others) and one who says “who cares” (me). One of the reasons I am posting this at the beginning is so we can flesh out issues like this.

Abstract Gallery logic into “Collection” logic that Galleries, Playlists, etc can use with minimal registration code

I have already done a first pass at this in the playlist shortcode patch. It goes like this: a “gallery” is really a “collection” of attachments of type “image.” A “playlist” is really a “collection” of attachments of type “audio.” So they should extend or be instances of a “collection”-type class. Currently, the Gallery code has to be dupe’d. By abstracting this code, Gallery, Playlist, Video Playlist, + any other “collection” of media type can be registered minimally.

Other Ideas

  • In our playlist JS code, emit events that others can hook into – maybe a video playlist is: News clip, ad, news clip, ad, etc. When emitting events before / after an ad, the dev could disable next/prev buttons
  • Make a playlist embeddable on other sites via an iframe or embedded markup
  • Register an endpoint for audio / video that will expose the “embed code” via oEmbed

Thoughts?

#audio, #backbone, #jquery, #media, #mediaelement, #underscore, #video