After playing around with ES6 features, I found a glitch in ES6 modules. You can't re-export your imports the way you can in other languages with native import/export support.
Here's what I mean:
// Meta/index.jsx
import Title from './Title';
export Title;
Seems reasonable, right? Yet it throws a syntax error.
Sure, it could be a bug in Babel.js, but I don't think it is. If I 'm reading the ECMAScript2015 Specification correctly, then you were never meant to do this. Which is even worse.
After an hour of figuring out why Babel won't compile my code, I spent another hour or two figuring out why I shouldn't have expected my code to compile in the first place. Let's take a look.
Babel throws a syntax error like this:
Version: webpack 1.11.0 Time: 662ms [0] multi main 40 bytes 0 [built] [1 error]
- 7 hidden modules
ERROR in ./src/components/H1BGraph/Meta/index.jsx Module build failed: SyntaxError: /Users/Swizec/Documents/random-coding/react-d3js-experiment/src/components/H1BGraph/Meta/index.jsx: Unexpected token (7:18) 5 | import Title from './Title'; 6 |
7 | export Title; | ^ 8 |
This is not a helpful error. Not only is this a reasonable thing to do, it's also a very common thing to do in other languages with native module support. For example, when your organize files into a directory.
In this case, there is a Title
and a Description
. Both components inherit from a base component called BaseMeta
, but are used separately. It makes sense to put these three files and some helpers in a directory called Meta
.
Forcing programmers to write import Title from './Meta/Title'
breaks the abstraction. You shouldn't have to know how components and classes internal to Meta
are organized. Writing import Title from './Meta’
should be possible.
Right?
Sure, one could argue that both of these components fall under a common Meta
should be used through a parent <Meta>
component. Then you could use Meta
and not worry about importing either Title
or Description
. In many ways that’s true.
But the Meta
component should be able to expose any of its internal components for external use. If JavaScript or Babel disagree with the architecture, then they should complain about the architecture and not throw a syntax error.
To make that work, we need a dirty hack like this:
// Meta/index.jsx
import { Title as T } from "./Title";
export class Title extends T {}
Ugly, right?
We use an aliased import from ./Title
only to turn around and export a new Title
component that extends T
, but adds nothing of its own.
We have to do this because ES6 imports are constants — creating a new class with the same name as already exists would throw an error. Re-exporting Title
with a different name would spread aliased imports through our codebase like a virus.
I wish none of this silliness was necessary, but it is.
Maybe the answer lies in what this code compiles to. Perhaps somewhere in the resulting ES5 code there is a deeper reason why JavaScript can't re-export like everyone else.
When Babel is done, our import→export looks like this:
function(module, exports, __webpack_require__) {
// Ugly but works :D
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var _Title = __webpack_require__(8);
var Title = (function (_T) {
_inherits(Title, _T);
function Title() {
_classCallCheck(this, Title);
_get(Object.getPrototypeOf(Title.prototype), 'constructor', this).apply(this, arguments);
}
return Title;
})(_Title.Title);
exports.Title = Title;
;
/***/ }
Damn, that's a lot of code! Remember writing all of this every time you wanted class inheritance? Me neither, screw class inheritance if this is what it takes. No wonder everyone is so excited about ES6 modules.
Let's look at the key part: where the Title
class is defined as a featureless extension of T
. This part:
var _Title = __webpack_require__(8);
var Title = (function (_T) {
_inherits(Title, _T);
function Title() {
_classCallCheck(this, Title);
_get(Object.getPrototypeOf(Title.prototype), "constructor", this).apply(
this,
arguments
);
}
return Title;
})(_Title.Title);
exports.Title = Title;
Ok, that's pretty familiar. If a human wrote the first line, it would be something like var Foo = require('Foo’)
. require()
gives a class, and we assign it to a variable.
Then we create a closure, which wraps a scope around a function. Think of this function as a class factory — it takes a class and creates a new extended class.
To produce these new classes, the closure creates a function called Title
. This function acts as an object constructor, just like functions normally do in JavaScript. It uses _get
to perform some magic that's beyond me. I *think* it calls the function on itself while also calling the superclass's constructor, but I'm not sure.
Just to keep things interesting, Babel uses this opportunity to take advantage of JavaScript's hoisting behavior, where functions that aren't assigned to a variable are always global inside the current scope. The code calls _inherits
on the Title
function, but before it's created. From the looks of _inherits
, it can replace the subclass's prototype with the superclass's object instance.
It’s weird, but it works. Praise the gods of open source that Babel writes this magic for us.
class X extends Y
is not only easier to write than this JavaScript soup, but also easier (read: possible) to understand.
Now we know what export class X extends Y {}
transpiles into, but we still haven't answered the main question: Why can't we just re-export?
I don't think it's because JavaScript couldn't handle something like this:
var Title = (function (_T) {
// ...
})(__webpack_require__(8));
While this isn't the cleanest code, it *should* work. But take a look at the last line in the earlier example, the line that says exports.Title = Title
.
If we don’t tell the code what to export as, how will it know? It won't. Without a name, the compiled code would look like this:
exports = webpack_require(8);
With that, we can only re-export a single module's worth of code. Which does work by the way, you can always write export default X
.
If you want to re-export multiple modules, then you'll have to stick to that ugly workaround. I think it's silly, but it's the best JavaScript can do until gets reflection, and we're finally able to answer the question "Hey, what did the programmer name this variable?"
I think that's coming in ES7. We'll see.
PS: the official way to re-export modules in ES6 looks like this:
// to export a member
export { encrypt as en } from "lib/crypto";
// or to export everything
export * from "lib/crypto";
But that still looks weird, doesn't play well with default exports, and doesn't solve the “preserve the name” problem.
Thanks to Jure Čuhalev, Anže Pečar, Will Fanguy, Coletta Teske, and David Baird for reading draft versions of this article
Continue reading about There's a "bug" in ES6 modules
Semantically similar articles hand-picked by GPT-4
- How to debug unified, rehype, or remark and fix bugs in markdown processing
- Livecoding #20: You can’t extend an object
- How to debug unified, rehype, or remark and fix bugs in markdown processing
- Arcane JavaScript knowledge still useful
- Don't Worry; It's a Compiler Bug
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️