To explore what modules are, we should take a peek at a common feature from other languages: the import. Consider the following class in Java, adapted from a tutorial at www.javatpoint.com/java-swing.
import javax.swing.*; public class SwingHelloWorld { public static void main(String[] args) { JFrame f=new JFrame(); JButton b=new JButton("click"); 1 b.setBounds(130,100,100, 40); 2 f.add(b); 3 f.setSize(400,500); 4 f.setLayout(null); f.setVisible(true); } }
In this Java-based example, we’re programmatically creating a button and placing it in a window. If you don’t know Java, this looks pretty easy, right? We could actually do something pretty similar and concise with HTML and JS. The difference with JS is that we would be using the document namespace to create our button:
document.createElement('button');
Have you ever thought about all of the methods we use every day from document or window? There are lots, and even though it can be a bit overwhelming, it’s manageable once you get used to it. These top-level, or global, objects are designed to control the DOM and your visual elements within. Meanwhile, there are other global objects that deal with other concerns. We print logs to our console with console.log and can parse JSON with JSON.parse. We also have a top-level Math object, which we can use to do trigonometry, create random numbers, and more.
When you think about all of these top-level objects that we, as JS developers, should just know, it can seem a bit chaotic. Alternately, when you consider the Java example in listing A.16, you’ll notice objects like JFrame and JButton to create the window and button, respectively—but where did those objects come from?
To answer this, consider that graphic interfaces aren’t necessarily something that Java developers do. Many do, but many will be happy doing all backend work. Given the wide breadth of everything Java needs to do and deal with when it comes to third-party libraries, Java, as well as most other languages, has an import feature.
Note the import javax.swing.*; at the top of the class. This is actually shorthand. To be more concise, we could expand this to be
import javax.swing.JFrame; import javax.swing.JButton;
Using the .* syntax imports all classes or nested classes in javax.swing and makes them accessible by their name in the class you imported them in, which is why JButton has the smarts to create a visual button.
Until now, browser-based JS has never had a built-in way to manage external dependencies other than by using a <script> tag. Third-party libraries like require.js have tried to fill this gap, but this was never adopted as a specification. Now, though, we officially have the native JS feature of modules. In order to use modules, which enable imports just like other languages, there’s a small bit of setup.
First, let’s prepare a little JS to be usable as an importable module. In a separate JS file, we can write just a few lines:
export default function demo() { console.log('demo'); }
Breaking this down, it’s obvious that we are defining a function named demo that logs “demo.” The keyword export is what makes this function able to be imported. The keyword default is simply declaring to any JS that imports this script that this function is the default variable, object, or function that is used when importing the script.
To be a little clearer, let’s look at how we import in the following listing. To do so, we need to declare that the <script> tag that we’re using is of type module.
<script type="module"> 1 import DemoModule from "./moduledemo.js"; 2 DemoModule(); </script>
We can simply import the few lines of JS we just made. The name DemoModule is a made-up name in this case. With this import, we could call what we import most anything we wanted, like in figure A.6. Because we’ve declared our function as default in the imported JS, we don’t need any further specificity.
We do need a bit more specificity if there are multiple things to import from a JS file, like the example in the next listing.
export function hi() { 1 console.log('hi'); } export function bye() { 2 console.log('bye'); }
Before, we could use shorthand and make up any name we wanted. In the next listing, we need to use the real names of the functions we defined in the modules as we import them.
<script type="module"> import { hi, bye } from "./multiplemoduledemo.js"; 1 hi(); 2 bye(); </script>
That’s not to say we couldn’t invent our own names if we really wanted to. To accomplish this, we can use the as modifier.
<script type="module"> import { hi as SomeName, bye as SomeOtherName } from "./multiplemoduledemo.js"; 1 SomeName(); SomeOtherName(); </script>
Lastly, we can simply scope both the hi and bye methods to an object with the as modifier.
<script type="module"> import * as Greeting from 1 "./multiplemoduledemo.js"; Greeting.hi(); Greeting.bye(); </script>
Modules are fantastic for use in Web Components. Chapter 5 details how they can be used to keep your Web Components completely self-reliant, managing all of their own dependencies.