Nested Imports in Meteor
A unique feature to Meteor is nested imports. It is undocumented, except for several references in the changelog and a short section in the Meteor 1.4 migration guide. Possibly due to this, it seems to not be well known.
According to the specification, ECMAScript modules only allow import/export at the top level. They can not be placed within any blocks, such as in a function or if block. This means that when the main bundle or dynamic imports are loaded, all modules imported in the bundle would have to be executed right away.
After v8 downloads a js file, it goes through a process of parsing, compiling, then executing it. Other js engines are likely similar. According to the v8 blog post The cost of JavaScript in 2019, they found that Post-download, script execution time is now a dominant cost
. This is especially true for mobile devices.
With require
, you can delay or avoid executing a module by placing require
in a function or if block. The module will be downloaded, parsed, and compiled by the web browser, but execution is delayed until the first time it is required. Meteor, Node, Webpack, and many other bundlers that support commonjs modules do this. However, bundlers are not able to tree shake modules imported with require
, and the ECMAScript specification does not provide a way to do this with import
.
ECMAScript modules in Meteor were implemented by Ben Newman with reify. Reify compiles js files to allow using ECMAScript modules within a commonjs environment. Its implementation allows using import statements anywhere in a file. He wrote a document explaining why someone would want to nest imports, along with an ECMAScript Proposal.
Nested imports should not be confused with dynamic imports. Dynamic imports delay downloading a file until it is needed. In contrast, the files imported by nested imports will always be downloaded at the same time as the file with the nested imports. Nested imports reduce execution time, but will not make your client bundle smaller.
// This is a top-level import
// alerts.js is evaluated immediately
import { showAlert } from './alerts.js';
export function reportError() {
// This is a nested import.
// errors.js has already been downloaded, but
// will not be evaluated until the
// first time reportError is called
import sendError from './errors.js';
}
export function showHint () {
// This is a dynamic import.
// hint.js is not downloaded or evaluated until the first time
// showHint is called
import('./hint.js').then(({ showRandomHint }) => {
showRandomHint()
});
}
Since nested imports are not part of the ECMAScript specification, many js parsers do not support it by default.
- Typescript can not parse them. Typescript files must use
require
instead to delay an import or make it conditional. - The Babel parser has an
allowImportExportEverywhere
option. - Acorn also has an
allowImportExportEverywhere
option. - The default parser in eslint, Espree, does not support nested imports. Instead, you can configure eslint to use Babel as the parser:
{
"parser": "babel-eslint",
"parserOptions": {
"allowImportExportEverywhere": true
}
}
Nested imports have successfully been used by many companies to provide a better experience on mobile for their users. At Monti APM, we halved the execution time of our app code with 3 nested imports. To help get started with your app, I've created a package to identify any imports that could be worth nesting.
Have a comment? Please Send me an email
- Next: Exploring the .meteor/local Folder
- Previous: Meteor Up 1.5
Want More? Join my monthly newsletter for new blog posts and news about Meteor and my projects.
Have a Meteor app in production? I run Monti APM for production monitoring to help improve performance and reduce errors.