Guy Bedford, core contributor and creator of the dynamic module loader system.js, discussed the workflows enabled by import maps. In his talk at ESNEXT this year, Bedford took a historical view while introducing the motivation behind the import map proposal, and linked the feature with the package entry points used in the latest version of node.
Bedford started by reminding the audience that import maps have an extensive history in JavaScript, albeit not a well-known one. In 2009, the file and module loader require.js was among the first projects to introduce a mapping system in the browser. require.js could be configured as follows:
require.config({
baseUrl: '/',
paths: {
'jquery': 'lib/jquery',
'bootstrap': 'lib/bootstrap'
}
])
require(['jquery'], function($){
...
})
The previous code causes the dynamic, lazy loading of the jquery package, from a location that is resolved with the configured paths
and baseUrl
. require.js thus offered functionality similar to that of dynamic imports that are standard in browsers today.
In 2012, the jam
package manager would expose a require function that also loads dependencies lazily by name. Bedford lamented that the project did not get any traction, as the ideas being put forward today largely replicate what was being done then. The Bower package manager, with its bower.json file also released in 2012, did catch on. In 2013, system.js and jspm were released and were focusing on how to bridge require.js with the future of modules, also provided a mapping mechanism for modules. system.js allowed circular references, live bindings, and enabled workflows with native modules that could be run in the browser.
Eventually in 2018, the import maps WICG specifications came to birth. The import maps is implemented by the Chrome browser and is available behind a flag. In 2019, Snowpack was one of the first projects to create an import map for JavaScript natively in development workflows.
Bedford then took a look at the next years. Assuming that the feature would be supported in all browsers, Bedford envisioned an import map configuration file that would be loaded in the browser with a <script type="importmap">
tag:
{
"import": {
"@babel/core": "lib/babel-core.js",
"assert": "lib/assert.js",
"component": "lib/component.js",
"react": "lib/react.js",
"react-dom": "lib/react-dom.js",
}
}
That file could be handled manually or through tooling. In the ideal development workflow, developers can import modules by names and do not have to maintain specifiers.
Bedford sees the export
package.json property playing nicely with import maps. The field is used in Node 14 to describe ES module package entry points:
{
"main": "./main.js",
"exports": {
".": "./main.js",
"./feature": "./src/submodule.js"
}
}
With the previously shown exports
configuration, only the defined subpaths in "exports"
can be imported by a consumer:
import feature from 'es-module-package/submodule';
// Loads ./node_modules/es-module-package/src/submodule.js
While other subpaths will error:
import feature from 'es-module-package/private-module.js';
// Throws ERR_PACKAGE_PATH_NOT_EXPORTED
This allows developers to create an external interface for a node module that is separated from the internal module structure. This information can be parsed and used in IDEs, for optimization purposes, and to generate import maps automatically. Bedford contended that package entry points, subpaths imports and import maps led to better results than tree-shaking:
Using separate feature exports is actually a best practice for package authoring and package consumption. If you are creating a new JavaScript package, instead of lumping everything into one big file, and relying on tree-shaking to magically remove code that was not used, it is actually much better to just import features as separate modules
This is superior because compilers and bundlers can’t always detect reliably the occurrence of side effects [and may include more code than what is actually used to be safe].
Bedford’s full talk is available online and contains plenty of code samples and additional technical details. ESNEXT is a new conference (conducted remotely in July 2020 due to Covid-19) that addresses the big changes that are coming for JavaScript over the next five years.