Bare Import Specifiers and Import Maps in JavaScript

This article explains what bare import specifiers are, why native browser JavaScript environments cannot resolve them by default, and how import maps bridge this gap. Modern web browsers natively support ECMAScript modules (ESM), but their resolution mechanism differs fundamentally from server-side runtimes like Node.js. By understanding bare specifiers and implementing import maps, developers can use clean, package-style import syntax directly in the browser without relying on build tools or bundlers.

What Are Import Specifiers?

An import specifier is the string literal that follows the from keyword in a JavaScript import statement. In native ECMAScript modules, specifiers generally fall into three categories:

  1. Relative specifiers: Start with ./ or ../ (e.g., import { utils } from './utils.js';).
  2. Absolute specifiers: Start with / or a full URL protocol (e.g., import { api } from '/scripts/api.js'; or import { lib } from 'https://cdn.example.com/lib.js';).
  3. Bare specifiers: Strings that contain no path indicator or protocol (e.g., import React from 'react'; or import dayjs from 'dayjs';).

A bare import specifier does not explicitly tell the runtime where to locate the file on the network or filesystem; it only provides the name of a module.

Why Browsers Cannot Resolve Bare Specifiers

In Node.js, bare import specifiers are resolved using a file-system lookup algorithm that automatically searches through nested node_modules directories. This process involves multiple synchronous file system checks until the target file is found.

Native web browsers do not have direct access to a local filesystem and must fetch resources asynchronously over HTTP/HTTPS. If browsers attempted to mimic Node.js resolution, they would have to guess file paths and make dozens of speculative network requests for every import. This would result in severe latency, network waterfalls, and failed requests. Consequently, the native browser ES module specification strictly requires explicit URLs or relative paths for all module imports and throws a TypeError whenever it encounters an unmapped bare specifier.

How Import Maps Solve the Problem

An import map is a JSON structure embedded directly in an HTML document using a <script type="importmap"> tag. It instructs the browser’s module loader on how to resolve specific module specifiers to real URLs.

Example of an Import Map

<script type="importmap">
{
  "imports": {
    "lodash": "https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.js",
    "components/": "/src/components/"
  }
}
</script>

<script type="module">
  // The browser uses the import map to resolve 'lodash' to the CDN URL
  import debounce from 'lodash';
  
  // Resolves 'components/button.js' to '/src/components/button.js'
  import { Button } from 'components/button.js';
</script>

Key Benefits of Import Maps