LOAD EVERYTHING EVERYWHERE ALL AT ONCE

Load resources in JavaScript using code that works in all universes environments, using import.meta.resolve(…) and friends.

PART 1
EVERYTHING

Modern Javascript apps load a variety of resources:

PART 2
EVERYWHERE

Modern JavaScript apps are now run or processed by a variety of environments: All the listed examples are compatible with JavaScript module code (“ESM”), and some of them require it.

PART 3
ALL AT ONCE

When authoring an app or a library, it is now common to publish JavaScript module code that is meant to run in all environments directly, without any polyfills or other translation. Further, it is also common to publish packages that import or export resource files (like CSS or WASM), either directly or through JavaScript code.

It is time-consuming to author and debug multiple life code paths for different environments, which makes it difficult to publish a reliable library with such resources.

Further, if you publish a library, other developers may run it through other bundlers and/or re-publish your code, possibly passing through multiple bundlers. There is currently no implementation that is handled consistently by all bundlers, which means it is actually impossible to write code that always works at the moment.

Therefore, it is important for such authors to have a standard way to reference and import a given resource, so that a single implementation works across all environments at once.

PART 4
import.meta.resolve

When code and resources for in a JavaScript codebase are stored in a folder structure, there is now a standard way to refer to resolve the path of a relative resource, which can then be loaded in the appropriate way: import.meta.resolve(…)

Although there is special syntax for some resources (such as static imports), this is by far the most flexible option. The following examples work in all modern browsers:

// JS const libPath = import.meta.resolve("./lib/dynamic.js"); const lib = await import(libPath); // JSON const dataPath = import.meta.resolve("./data.json"); const data = await (await fetch(dataPath)).json(); console.log(data); // CSS const cssPath = import.meta.resolve("./themes/solarized.css"); const link = document.body.appendChild(document.createElement("link")); link.rel = "stylesheet"; link.href = cssPath; // Image const imagePath = import.meta.resolve("./icons/errorsaurus.svg"); document.body.appendChild(document.createElement("img")).src = imagePath; // Web worker const workerPath = import.meta.resolve("./client/worker.js"); const worker = new Worker(workerPath, { type: "module" }); worker.postMessage("hi"); // WASM const wasmPath = import.meta.resolve("./code.wasm"); const wasmModule = WebAssembly.compileStreaming(fetch(wasmPath), /* imports */); console.log(wasmModule.exports.foo()); // Arbitrary resources const resourcePath = import.meta.resolve("./resource"); const response = await fetch(resourcePath); // binary console.log(await response.arrayBuffer()); // text console.log(await response.text());
It is also possible to use this to import CSS and JSON directly in browsers that support import assertions:

// JSON const dataPath = import.meta.resolve("./data.json"); const data = await import(dataPath, { assert: { type: "json" } }); console.log(data); // CSS const cssPath = import.meta.resolve("./themes/solarized.css"); const cssModule = await import(cssPath, { assert: { type: "css" } }); document.adoptedStyleSheets = [cssModule.default];
When bundlers process JavaScript code and other resources in a way that can move them into different folders than originally, they should look for import.meta.resolve(…) calls that use relative paths (i.e. strings that begin with ./ or ../), and update such calls to any new relative path.

PART 5
new URL

Unfortunately, import.meta.url(…) is not implemented in all environments yet, and is not technically specified as part of JavaScript.

However, many environments support similar functionality using the URL constructor, with import.meta.url as the second parameter:

const workerPath = new URL("./client/worker.js", import.meta.url).href; const worker = new Worker(workerPath, { type: "module" });
For more information, consult this comparison.

Similar to import.meta.resolve(…), when bundlers process JavaScript code and other resources in a way that can move them into different folders than originally, they should look for new URL(…, import.meta.url) calls that use relative paths for the first argument (i.e. strings that begin with ./ or ../), and update such calls to any new relative path.

Additionally, it can be useful for bundlers to rewrite import.meta.resolve(…) to new URL(…, import.meta.url).href when outputting code to environments that may not support the former, particularly if the code may be used by other bundlers that do not support it yet. However, the ecosystem will reach a reliable equilibrium once all major tools preserve import.meta.resolve(…) by default.

PART 6
Static imports

In all JavaScript module environments, it is possible to import additional JavaScript using a static import declaration:

import { name } from "./lib/dynamic.js";
There are also proposed standards for static import of CSS and JSON in JavaScript using import assertions, which are available in some environments. However: Therefore, it is still valuable to support import.meta.resolve(…) and new URL(…, import.meta.url) for code that can handle all resource paths, today and into the future.

PART 7
Compatibility dashboard

Each environment was tested using this code and these commands. Each entry has two result markers, one for supporting each of the following (in order): Legend:
Environment Uses JavaScript JSON Image Web worker WASM
Browsers
Chromium
111
V8 ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
Firefox
114
SpiderMonkey ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
Safari
16.4
JavaScriptCore ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
Runtimes
node
20.6.0
V8 ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
deno
1.27.2
V8 ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
bun
0.5.7
JavaScriptCore ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
Bundlers
esbuild
0.5.7
❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌
Rollup
3.20.2
❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌
Webpack
5.76.3
❌ ❌ ❌ ✅ ❌ ✅ ❌ ❌ ❌ ❌
swcpack
0.1.62
❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌
Parcel2
2.8.3
swc ❌ ❌ ❌ ❌ ❌ ✅ ❌ ✅ ❌ ❌
Vite2
4.2.1
Rollup and
esbuild
❌ ❌ ❌ ✅ ❌ ✅ ❌ ❌ ❌ ❌

1 Requires a custom config to build production code that uses relative imports

Note: The markers here are not meant to imply that projects have done anything wrong by lacking support. Some of them have to support a wide variety of configurations, and it may not be easy to support ESM-only features. However, this dashboard does serve as a rough measure of how widely usable these resource loading conventions are.