Phasing out jQuery

Rupert Angermeier
Trayn Engineering
Published in
3 min readJul 5, 2018


There was a time, when it was considered good practice among frontend developers to use jQuery for everything. Back in that time we started to build the Trayn web app. Since then, a lot of things changed, among them what are considered good practices. Well, best practices in JavaScript land change every second month or so, but good is good enough for me in that case.

In the ongoing struggle to modernize our code base, I recently started to phase out jQuery. What do I mean with phasing out? The app is too big to rewrite all code that relies on jQuery in one go. But since the base functionality of the app (session management, routing, localization, etc.) was already modernized in the last months, only the detail views (there are a lot of them) are using jQuery and various of its plugins. So the plan is to port/modernize one of these views after the other.

The gloomy road to a golden code base. Photo by Sven Wilhelm on Unsplash

But why?

Why removing jQuery at all? It’s still working…

Well, it’s not just jQuery we want to remove, it’s more about plugins and frameworks on top of it and — more specifically — the way we use them. Huge parts of these views use outdated versions of can.js, Bootstrap, and FullCalendar, and updating all that code at once seems harder to handle than migrating view-by-view to a more modern component based architecture.

Taming our jQuery Plugins

Using jQuery plugins together with Webpack can be tricky, since they usually register them on whatever jQuery instance they get thrown at. Because Webpack is putting everything into its own scope, each plugin would end up on a different jQuery instance. To handle this, Webpack provides the externals configuration option, which can be used to exclude some packages from the build, if they are loaded from somewhere else. This allows us to use one global jQuery instance and not breaking any plugin.

Additionally, we’re copying the minified jQuery file to our output directory for later use:

// webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
externals: {
jquery: 'jQuery'
plugins: [
new CopyWebpackPlugin([{ from: 'node_modules/jquery/dist/jquery.min.js'}])

To jQuery or not to jQuery?

This leaves the challenge of actually loading jQuery when it’s needed. After trying some things, I decided to do this in our routing layer, which uses Vue Router. I added a boolean flag jquery to the meta field of routes that are still using legacy code. We check for it in the beforeEach hook: if the flag is present, navigation is blocked until jQuery is loaded. We can’t use import() or require() here, because we instructed Webpack to treat jQuery as an external dependency, so we’re using scriptjs:

import $script from 'scriptjs'const ensurejQuery (route) => new Promise((resolve) => {
if(route.matched.some(record => record.meta.jquery)) {
$script('/jquery.min.js', resolve)
} else {
router.beforeEach((to, from, next) => {


Using this approach, we were able to defer loading jQuery until it is actually needed. We now need to maintain those jquery flags in our route config when we modernize views, but we didn’t need to adapt any of our legacy code and reduced the download size of the modernized parts of our web app.

And I used the word “jQuery” only 15 times in this article (not counting titles and code samples) without inventing any aliases for it.