When magic fails — a programmer grows

Yonatan Kra
WalkMe Engineering
9 min readApr 26, 2018

--

Here’s a story about how failures can lead you to a pull request in a very popular open source project.

I learn a lot from my failures. Actually (cliché alert!!!), failure is one of the best ways to learn (so says G-d).

In this post, I will share with you the hard-fought wisdom I attained in tackling the numerous issues I faced learning webpack. Be prepared to learn about not only some k-cka$$ webpack features, but also the many ways I am growing as a developer.

Background

When using the latest JavaScript (JS) version’s (we will call it “ES2017” from now on) features (e.g., async/await, classes, promises), not all browsers will be able to serve the app (I’m looking at you, IE…). We would certainly want to use the lastest version of JS because, well, it’s cool. This is why we need a transpiler. An ES2017 transpiler takes your ES2017 code and translates it so that older browsers can serve it as well.

ES2017 transpiler brings Babel to mind (although this article does raise a good question about it). In theory, if we use the all-powerful webpack babel-loader, we should be good to go on all browsers all the way back to IE8, right? It’s called Magic!

We’ll continue working with the app we built previously in this former post and extended in this subequent former post. If you’re new to the party, you can clone the app from GitHub and follow along with this tutorial.

Cloned? Cool. Go to the lazy-load branch and we’ll move on from there.

Let’s install Babel and webpack’s babel-loader:

npm i -D babel-loader@8.0.0-beta.0 @babel/core @babel/preset-env

And inside the webpack.config.js, we add another rule for JavaScript (js) files:

{
test: /\.js$/,
loader: 'babel-loader',
}

That looks promising. Now, all we need to do is run webpack with the new config, and we should be good to go, right? So…

npm run build

And…………...

We’ve crashed our build process!

What happened?! The babel loader tried to parse our import statements, that’s what happened. What’s its problem? It’s something called dynamic import(). Long story can be found here. Short story below:

Short story

static imports are imports that are made at the head of a file, and their input is a string. It looks like this:

import * as _ from ‘lodash’;
import 'jquery';

These imports are processed pre-runtime.

dynamic imports are imports that can be written anywhere and are called during runtime. They look like this:

const _ = import('lodash');
import('jquery');

You can call them conditionally, hence lazy loading code:

if (route == 'awesome-page') {
template = import('../templates/awesome-page.html');
}

Note that import is not a function, and nor is it an object…

How do we solve the dynamic imports issue for Babel?

If our app has no lazy loading components, we can just switch to static imports and it should work. In addition, if we’ve used module.exports (the Node.js way), we would need to change module.exports to export default(the ES2017 way). Once you have made the changes, you should see that your dist folder (if you build and do not use the dev-server) has fewer files. This is because static import doesn’t create new files for lazy loading.

Seems like the problem is solved... Any questions?

Just one question, ma’am

Thank you for the question! Yes — you in the middle row! I’ll repeat the question so everyone can hear it:

What if we want to use both Babel AND dynamic import/lazy loading awesomeness?

Great question! In the current app we have a dynamic import (which is actually a spec that’s already implemented in Chrome 63). What we really want is to use lazy loading with dynamic import…

Now — why would we want to do that? I’ll just regurgitate what Google has to say about it (why reinvent the wheel, right?):

In some cases, it’s useful to:

- import a module on-demand (or conditionally) — a.k.a lazy load

- compute the module specifier at runtime

- import a module from within a regular script (as opposed to a module)

None of those are possible with static import.

Dynamic import is an awesome feature of our app; it doesn’t load the fireworks until we actually need them:

Line 15 uses dynamic import

So, to recap: Our build breaks, because using dynamic imports with Babel doesn’t work out of the box (though it does with webpack without Babel). There is a neat plugin that prevents this breakage. It’s surprisingly (and horribly) called:

babel-plugin-syntax-dynamic-import

We just need to install the plugin:

npm i -D babel-plugin-syntax-dynamic-import

And add it to our babel-loader rule:

{
test: /\.js$/,
loader: 'babel-loader',
options: {
plugins: ['syntax-dynamic-import'],
}
}

Now once we build we have lazy loading with webpack and Babel. Need proof? See for yourself:

Notice how the new bundle is lazy loaded after the phrase is entered!

You can check it out yourself in the babel-with-dynamic-import branch. Just checkout and build (or use the dev server).

It’s still not working on IE

Of course it’s not working on IE. IE doesn’t have promises! It’s third grade math! Or rather, a very non-obvious fact about Babel.

Here it is: Babel is very light by default, but has extensions that help you add extra compatibility tailored to your target browser.

Now we need two more things:

  1. @babel/preset-env — which we already installed when we installed Babel. This will take care of converting arrow functions to regular functions, among other things (it does much more, but in our case, this is all that we need from it). All we need to do is add it to our babel-loader config.
{
test: /\.js$/,
loader: 'babel-loader',
options: {
plugins: ['syntax-dynamic-import'],
presets: ["@babel/env"]
}
}

2. babel-polyfill — would take care of several things like promises for instance:

npm i -S babel-polyfill

And add the polyfill to the app itself:

entry: ['babel-polyfill', './src/app.js'],

Now you can open it in Chrome, Firefox and IE. Check out the relevant branch on github. Check out the commit changes.

Optimizations

Now that we got everything running, let’s see what optimizations we can (or should) do to our app.

Babel Optimizations — Part I

While building, we might see something like:

[BABEL] Note: The code generator has deoptimised the styling path/to/a/file as it exceeds the max of 500KB.

This usually happens when Babel is trying to parse a node module. You might see this error when installing Lodash, for instance, and trying to load it. Let’s go one step further and exclude node modules from our Babel parser:

{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
plugins: ['syntax-dynamic-import'],
presets: ["@babel/env"]
}
}

That was easy! Optimization is FUN!

Babel Optimizations — Part II

By default, Babel injects some code to every file created. However, there is an option in Babel that prevents this behavior and instead causes Babel to require (lazy load) this code instead of in-lining it in every file (or chunk) of our app.

Here’s what the official repository says about this:

A promising title!

The instructions seem easy enough. Let’s try following them:

Easy peasy

We already know how to do that!

npm i -D babel-plugin-transform-runtime & npm i -S babel-runtime

Now change the babel-loader configuration (the plugins property) as the instructions dictate:

{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
plugins: ["@babel/plugin-transform-runtime", 'syntax-dynamic-import'],
presets: ["@babel/env"]
}
}

Now we can go ahead and merrily build the app. Right?…

We crashed our build process again!

The app crashed! OMG — what happened?

Module build failed: Error: Cannot find module ‘@babel/plugin-transform-runtime

How very nice of Babel to tell me that. I glance furtively at my copied config and notice that, while I am indeed using plugins: [“@babel/plugin-transform-runtime”], I've installed babel-plugin-transform-runtime. Let's change it to: plugins: [“babel-plugin-transform-runtime”].

Let’s build and see what happens (fingers double-crossed!)…

Module build failed: TypeError: this.setDynamic is not a function

By now, I’m left with only a fraction of the hair I had at the beginning of the day (and I didn’t get a haircut…). As a last resort, I turn to my trusty hair-preservation doctor: Google.

WTF?!? Only two results?

Ok — now I’m really upset. I’ve no alternative but to use the coveted but feared almighty doomsday weapon: Stack Overflow!

I posted my question and awaited an answer, to no avail, and it continued to irk me; how is it possible it isn’t working?!?

I took that oft-repeated and indispensable life-advice to pull myself up by my own bootstraps: I researched my question, found the answer, and answered my own question on Stack Overflow. Here’s the result:

https://stackoverflow.com/questions/48366243/webpack-babel-loader-runtime-module-build-failed-typeerror-this-setdynamic-is/

After a few hours of research, it all boiled down to this simple commit in a very simple pull request: https://github.com/babel/babel-loader/pull/566/commits/430b3ffee5ae9d11d1ed57858322e761ba81d67b

If you don’t understand the git-diff language, let me explain: The problem was that I’ve installed the new version of Babel and babel-loader, but the instruction on their website was to install the old runtime plugin… so the fix was to do this:

npm install @babel/plugin-transform-runtime -D & npm install @babel/runtime -S

Hopefully my pull request will be accepted so I can showcase it in my resume (“Contributed to Babel…”).

Summary

When I just started programming I always looked up to those perfect programmers who gave a talk or shared an awesome, problem-free tutorial. I learned a lot from them, but was actually also held back by them.

The most important lessons I’ve learned since then came from a talk in which the presenter was live-coding. There were so many errors in the code, and it was not only funny, but very memorable.

While this article is ostensibly about webpack technical content, the real takeaway should be the learning process we underwent to find a solution to our problem. I’ve shared with you how I learned to add Babel to my webpack project, and It was NOT a smooth process.

I hope you now know how to add Babel to your project. And, even more importantly, I hope that you learned not to feel threatened by (temporary) failures, and instead use them to grow.

It’s OK to feel frustrated.

It’s OK to lament that you’ve spent lots of time just solving this one damn error that refused to go away.

It’s A MUST to feel during the process of learning.

Look what G-D has to say about it.

The only way you can lose is by actually give up and squandering the opportunity to advance your own tech education.

The more that things break for you, the more likely you are to find something wrong in a 3rd-party code. You could either report them (file an issue on GitHub) or fix them yourself (send a pull request). Either way, you win eternal glory!

So get out there and fail. And please write about it, so others can learn, fail and write about it just like you.

--

--