In this example, import { GetMessage } from "./mymodule.js";
means to import only the GetMessage
function exported from mymodule.js. It also is made a top-level function, so is called with just GetMessage()
alone; there is no object named MyModule
.
Multiple things can be imported with the form: import { FuncA, FuncB, FuncC } from "./file.js";
The original example imported everything and gave it a name. This approach is useful if you know you only need to import some specific things and you don't need absolutely everything.
Import script just to run it
You can also use import "./file.js";
to just load and run a script file. It won't import anything or use its exports in any way.
This can be useful for older libraries which don't export anything and instead just add global variables. In this case after importing the script you can access anything it added to globalThis
. Alternatively it's possible the script does something useful just by running its top-level code, in which case you don't need to import anything, as merely loading and running the script is sufficient.
Default export
If a script only has a single export, or has a main thing that you'll want to import most of the time, that can be made a default export. This means it is exported with export default
instead of just export
. Try the code below for mymodule.js.
export default function GetMessage()
{
return "Hello world from mymodule.js!";
}
Default exports are in turn imported slightly differently. Try the code below for main.js.
import GetMessage from "./mymodule.js";
console.log(GetMessage());
Now preview the project and the message will be logged.
In this case import Thing from "./file.js"
means to import the default export from file.js and give it the name Thing. Note this doesn't use the braces {
and }
in the previous example (which imported exports with specific names), or the *
or as
parts (which import everything).
Don't forget that a script imported this way must specify a default export, otherwise you'll get an error.
Imports are read-only
Everything you import is read-only, as in it cannot be re-assigned. This helps ensure modules always expose a consistent interface.
However sometimes this can pose a bit of a problem. For example if you want to export a variable like this:
export let myVariable = 2;
import { myVariable } from "./mymodule.js";
// TypeError: Assignment to constant variable.
myVariable = 3;
...then after the import
, you cannot change the value of myVariable
. Even though we exported the variable declared with let
, it still acts as if it's really const
.
One way around this problem is to use an object. The object itself cannot be reassigned, but its properties can. Therefore we can use object properties instead of variables. Here's an example showing how this can be used as a way to store groups of related variables in a module. In mymodule.js use:
export default {
score: 100,
lives: 3,
ammo: 50
};
This uses a default export of an object with properties for score, lives and ammo.
Then in main.js use:
import GameVariables from "./mymodule.js";
GameVariables.score += 100;
console.log(`Score: ${GameVariables.score}`);
This imports the default export with the name GameVariables
. That object is itself not re-assignable, but it doesn't matter: its properties can be changed, so the line GameVariables.score += 100
works as expected, resulting in a total score of 200.
This is a useful technique for managing sets of related variables as their own modules. For example you could use this for "global" variables - which wouldn't actually be in global scope (as they're not actually on the global object), but provides a better organised way to share a set of variables across all your code.
What can be exported
You can export pretty much anything from a script file. However since anything that is imported is read-only (i.e. not re-assignable), it's not very useful to export variables, as they will always work as if they were declared with const
. Usually modules export objects, functions and classes, as these are the most useful things to share.