JavaScript Internals (521 Qs)•Part 8: Modern JavaScript APIs, WeakRef, Proxy & Performance (464 - 521)
How do you implement a lazy loading pattern for modules?
Lazy loading defers module loading until they're actually needed, reducing initial bundle size and improving load time.
Dynamic import (ES modules):
javascript
// Traditional import - loaded immediately
import { heavyFunction } from './heavy-module.js';
// Dynamic import - loaded on demand
async function loadModule() {
const module = await import('./heavy-module.js');
module.heavyFunction();
}
// Or with destructuring
const { heavyFunction } = await import('./heavy-module.js');
Route-based lazy loading (React):
javascript
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard'));
const Profile = lazy(() => import('./Profile'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
</Router>
);
}
Feature-based lazy loading:
javascript
class FeatureLoader {
constructor() {
this.features = new Map();
}
async loadFeature(name) {
if (this.features.has(name)) {
return this.features.get(name);
}
const module = await import(`./features/${name}.js`);
this.features.set(name, module);
return module;
}
}
const loader = new FeatureLoader();
// Load feature only when needed
button.addEventListener('click', async () => {
const feature = await loader.loadFeature('advanced-charts');
feature.render(data);
});
Intersection Observer lazy loading:
javascript
const observer = new IntersectionObserver((entries) => {
entries.forEach(async (entry) => {
if (entry.isIntersecting) {
const moduleName = entry.target.dataset.module;
const module = await import(`./modules/${moduleName}.js`);
module.init(entry.target);
observer.unobserve(entry.target);
}
});
});
// Observe elements
document.querySelectorAll('[data-module]').forEach(el => {
observer.observe(el);
});
Conditional lazy loading:
javascript
async function loadEditor() {
if (window.innerWidth > 768) {
// Load full editor for desktop
const { FullEditor } = await import('./FullEditor.js');
return new FullEditor();
} else {
// Load mobile editor
const { MobileEditor } = await import('./MobileEditor.js');
return new MobileEditor();
}
}
Prefetching for better UX:
javascript
// Prefetch on hover
link.addEventListener('mouseenter', () => {
import(/* webpackPrefetch: true */ './heavy-module.js');
});
// Preload critical modules after initial load
window.addEventListener('load', () => {
setTimeout(() => {
import(/* webpackPreload: true */ './important-module.js');
}, 1000);
});
Module caching:
javascript
class ModuleCache {
constructor() {
this.cache = new Map();
this.pending = new Map();
}
async load(path) {
// Return cached module
if (this.cache.has(path)) {
return this.cache.get(path);
}
// Return pending promise
if (this.pending.has(path)) {
return this.pending.get(path);
}
// Load module
const promise = import(path).then(module => {
this.cache.set(path, module);
this.pending.delete(path);
return module;
});
this.pending.set(path, promise);
return promise;
}
}
const moduleCache = new ModuleCache();
const module = await moduleCache.load('./module.js');
Webpack code splitting:
javascript
// Magic comments for webpack
const module = await import(
/* webpackChunkName: "my-chunk" */
/* webpackMode: "lazy" */
'./module.js'
);