A static import is a declaration: the bundler follows it and the code ships whether or not it runs. import() is an expression. It returns a promise for the module namespace object, it can sit inside an if or an event handler, and bundlers treat every one as a split point — the module and everything only it needs move into a chunk fetched on first call and cached afterwards.
const t = performance.now();
const math = await import('./math.mjs'); // one network request, once
console.log(Object.keys(math).join(', '));
console.log('add(16, 26) =', math.add(16, 26));
console.log('loaded in', (performance.now() - t).toFixed(2), 'ms');add, default add(16, 26) = 42 loaded in 2.95 ms
The namespace is a Module exotic object whose keys are the named exports plus default, so a default-exported component is math.default, not math.
The specifier must be statically analyzable for a bundler to split on it. import('./routes/' + name + '.js') cannot be resolved at build time, so Rolldown 181,166 and Rollup 141,981 bundle every file the pattern could match; Vite 25,978 's import.meta.glob('./routes/*.jsx') makes that pattern explicit and splittable. Wrap the call in try/catch too: a chunk request fails on a flaky network and, more often, after a redeploy invalidates the hashed filename the page still remembers.