DEV Community

Cover image for Picking apart JavaScript import Syntax
Mike Bifulco
Mike Bifulco

Posted on • Originally published at mike.biful.co

Picking apart JavaScript import Syntax

Note: this is a follow-up to my first post on destructuring. Import syntax uses destructuring pretty liberally, and it can be really confusing for folks who are new to using it. Give my other article a read first if this all seems confusing!

Let's talk about importing dependencies into your node projects. As your work gets more complex, you will inevitably come across syntax like this:

import React, { useState, useEffect } from 'react';
import { Link } from 'gatsby';
import Button from '@material-ui/core/Button';
import moment from 'moment';

import { Layout } from '../components';
Enter fullscreen mode Exit fullscreen mode

At first glance, it's pretty straightforward. We're importing a handful of bits to use in a React component. As you might imagine, though, I've selected these four lines of code because each one is unique. In fact, during my journey as a blossoming Node/React developer, I've found ways to mess up every single one of these.

You're gonna mess this stuff up, too, and that's perfectly fine! For now, I'm here to help.

We're going to look through each one of these, in order of complexity, and I'll do my best to explain what the hell is going on, and the way I think about imports as I work.

Straightforward import syntax - the easiest case

import moment from 'moment';
Enter fullscreen mode Exit fullscreen mode

If you've worked in .Net languages, or Python, or Ruby, or one of many other languages under the sun, this should be second nature to you. I'm calling it out here specifically because some of us may never have seen it before.

What's going on here?

Well, it turns out it's pretty easy. moment is a JavaScript library, which has been included in our node project's package.json file's dependencies or devDependencies. If you're new to node and are unfamilar with package.json, read more about it here.

This line of code creates a reference to everything made available in the moment library, and puts it into what is effectively a variable that we can use to access it. the 'moment' bit (in quotes) is what tells the compiler which library to get. The other one, moment (not in quotes) is the variable. So from here in, we can access moment just like any other variable in this file:

import moment from 'moment';
console.log(
  moment().get('year')
);
// 2019
Enter fullscreen mode Exit fullscreen mode

The un-obvious bit

Behind the scenes, this is just taking everything that is made available by the moment library through export default in its main file , and stuffing it into a variable - and that variable can have any valid name we want!

It may be confusing, but you absolutely could do this, if the this name made more sense to you:

import ThatReallyUsefulDateLibrary from 'moment';
console.log(
  ThatReallyUsefulDateLibrary().get('year')
);
// 2019
Enter fullscreen mode Exit fullscreen mode

Importing a component from somewhere within a library

Next up - this slightly more complex beast:

import Button from '@material-ui/core/Button';
Enter fullscreen mode Exit fullscreen mode

Here we're grabbing the <Button /> component from the @material-ui library. Again, this is fairly straightforward - but it may be helpful to think of this in terms of the structure of the material-ui project. Material-ui exports loads of great stuff, and it's all organized into logical groupings. Think of it a bit like this:

// material-ui exports
const muiExports = {
  core: {
    Button: () => {}, // some component
    TextField: () => {} // another useful component
    // and loads more
  }
}
Enter fullscreen mode Exit fullscreen mode

With the import syntax for Button above, we're telling the compiler to give us a reference to the exported thing called Button, which is found in the @material-ui library under /core/Button. The compiler essentially treats this like the JSON object in the snippet above.

Here's the thing - that also means we can destructure it! 😁. This syntax would also work to import Button:

import { Button } from '@material-ui/core';
Enter fullscreen mode Exit fullscreen mode

That also means we can import multiple things from /core in a single line!

import { Button, TextField} from '@material-ui/core';
Enter fullscreen mode Exit fullscreen mode

Cool, huh? I know this can be confusing, but try to stick with it. It'll all start to make sense to you before long. This brings us to our next example:

Importing a subset of a library by way of destructuring

import { Link } from 'gatsby';

Enter fullscreen mode Exit fullscreen mode

Boom! This should be easy by now. One of the things that Gatsby makes available is their link component. We're importing just that component to use here.

Renaming an import

But what if we already have a component called Link in our project? Or, what if we're making a Legend of Zelda fan-site, and Link is already defined in a component or variable that we can't rename? Well, it turns out renaming something in an import is as easy as renaming something in a destructured statement. We can rename the same component from gatsby like this:

import { Link as GatsbyWebLink } from 'gatsby';

Enter fullscreen mode Exit fullscreen mode

We can also rename one or many destructured imports in a single statement:

import { 
  Link as GatsbyWebLink,
  graphql as graphqlQuery,
  useStaticQuery
} from 'gatsby';

Enter fullscreen mode Exit fullscreen mode

Piece of cake! 🍰

Relative imports

One more quick thing - the compiler knows to look for something you exported if you use a relative path in your import location string:

import { Layout } from '../components';

Enter fullscreen mode Exit fullscreen mode

Just like anywhere else, you can combine and rename things to your heart's content here:

import {
  Layout,
  Button as SuperButton
} from '../components';

Enter fullscreen mode Exit fullscreen mode

Putting it all together

The best isn't alway last, but this is certainly the last example I've got to share today:

import React, { useState, useEffect } from 'react';

Enter fullscreen mode Exit fullscreen mode

If you've been playing along at home, this should all be familiar now - we're grabbing the default export from react, which we've put into the variable react. We also destructured useState and useEffect from the same library. If you're asking yourself "Well couldn't we also access useState as a child of React"? The answer is - well, actually, yeah!

This is perfectly valid

const [loading, setLoading] = React.useState(false);

Enter fullscreen mode Exit fullscreen mode

… but it's not as nice to type or to read as

const [loading, setLoading] = useState(false);

Enter fullscreen mode Exit fullscreen mode

They're both equally functional from an execution standpoint, but the latter is used by convention.

I think that's it.

I think. It turns out this was a really tricky post to write - there's a billion ways to import a file, and there's probably loads of cases I've missed here. There are definitely also performance and bundle size implications for some of the varieties of import syntaxes shown here. While they're absolutely real constraints, and have real implications on your app's performance, I left that discussion for another day - purely for the sake of simplicity.

There's also the not-so-small matter that using import requires a transpiler like Babel or Webpack right now. That's another super-complex universe that I'm not sure i'm equipped to explain in a single blog post. This also means I've skipped showing how any of the above syntax works with require(). There's frankly an exhausting amount to explain here - future improvements to EcmaScript and node will make it all better.

Say hi!

As always, if I've gotten anything wrong here, I'd love to know about it! Drop me a line @irreverentmike. I'd love to hear from you. πŸ‘‹

Credit

The background image used for the cover photo on this post is from Ricardo Viana on Unsplash. Thank you for your work!

Top comments (8)

Collapse
 
vramana profile image
Ramana Venkata

If you're asking yourself "Well couldn't we also access useState as a child of React"? The answer is - well, actually, yeah!

Well not actually if I understand correctly. The correct way to do it will be

import * as React from 'react';

What you wrote works because of bundlers exporting Module as object. But it's not really an object. I don't know the full details.

Consider this code.

export function useState() {
   console.log('useState')
}

export function useState2() {
   console.log('not useState')
}

const React = {
  useState: useState2
};

export default React;

If your import is import React from 'react' and you used React.useState, what would you expect to be logged in the console? This is the best example I can think of.

Collapse
 
irreverentmike profile image
Mike Bifulco • Edited

That's a perfect example, and a great explanation. I think a more thorough explanation is probably due in my post.

Curiosity got the best of me, and I set up a minimal example of what I mentioned in the post... and it turns out it works!

πŸ€” To be honest, the more I think about it, the more confused I am. If you dig into react's source, you can see useState is exported just like your example above, with useState a child of the object that is exported by default. I wonder if there's not also a separate export for useState, too?

Collapse
 
vramana profile image
Ramana Venkata • Edited

Ah! I did not look at React's source. So my argument is wrong as well.

My general point is that you should not use non-default exports from the default export. This may mislead your readers.

import * from React from 'react' would be correct way to do it. But react does not individual exports so it does not apply.

One reason why React may be using default export object is because babel transpiles JSX to React.createElement('div', ...) calls. If the default export is not an object, then we would have write

import { createElement } from 'react'

React team may have felt it's better to write import React from 'react' instead.

Collapse
 
ben profile image
Ben Halpern

Really nice post

Collapse
 
irreverentmike profile image
Mike Bifulco

Thank you, Ben!

Collapse
 
jplindstrom profile image
Johan Lindstrom

Can someone explain what the @ in '@material-ui' is please?

Collapse
 
irreverentmike profile image
Mike Bifulco

Hi Johan - the @ denotes an npm organization.

Collapse
 
gohawks profile image
Chu Xu

Nice post, thanks!