How to include a JavaScript file in another JavaScript file
JavaScript has no #include. It has modules, and in the browser it has script
order — and the second one is where most of the errors come from.
import
// helper.mjs
export const greet = n => `hello ${n}`;
// main.mjs
import { greet } from "./helper.mjs";
[helper.mjs body ran]
greet('world') : hello world | version: esm
import
is the answer for anything written today. In the browser it needs
<script type="module">; in Node it needs a .mjs extension or
"type": "module" in package.json.
The body runs once
-> imported three times in total, body printed once. Modules are cached.
[helper.mjs body ran] appears once even though three different places import
it. A module is evaluated the first time it is requested and the result is
cached — which is why a module can safely hold state, and why an import cannot
be used to “re-run” a file.
require
[helper.cjs body ran]
require('./helper.cjs') : hello world | version: cjs
require is the older
CommonJS form. Node-only, never a browser feature, and it caches the same way.
You will keep meeting it in existing code.
import cannot be conditional, import() can
a conditional static import is a SyntaxError
dynamic import() is a function call, so it CAN be conditional:
loaded on demand: esm
A static import is hoisted and resolved before any of the file runs, so it
cannot sit inside an if. That is not a restriction someone invented — it is
what lets a bundler know the dependency graph without executing anything.
When you do need a conditional or lazy load, use
import():
it is an expression, it returns a promise, and it goes anywhere.
In the browser, order decides
execution order : inline-1 -> inline-2
Plain <script> tags run in document order, each blocking the parser until it
finishes. So this happens:
first script saw laterVar as : undefined
The first script cannot see what the second defines. That is the real cause of
most “X is not defined” errors — not a missing file, but a file loaded after
the code that uses it.
defer and async
a.js defer=false async=false
b.js defer=true async=false
c.js defer=false async=true
- plain: blocks parsing, runs immediately
defer: runs after parsing, in document orderasync: runs as soon as it has arrived, in no guaranteed order
async is the one that bites. Two async scripts where one depends on the
other will work on your machine and fail on a slower connection, because the
order is a function of download time.
type : module | the spec defers module scripts even without the attribute
type="module" is deferred whether or not you write defer.
What not to do
The old answer — creating a <script> element and appending it to the DOM —
still circulates. It works, but nothing tells you when it has finished, so the
next line of your code runs before the file has loaded. If you need that shape,
import() gives you a promise to wait on.
About Netcup (advertisement)
The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).