JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
102: Dynamic Imports
I've noticed a lot of developers treat the import() function as if it's just a more flexible version of the standard import statement. The most common misconception I see is the belief that import() is a synchronous operation that simply returns the module's exports. You'll see people try to write code like this:
const utils = import('./utils.js');
utils.formatDate(new Date()); // ❌ TypeError: utils.formatDate is not a function
If you've tried this, you probably spent twenty minutes scratching your head wondering why utils exists but doesn't contain your functions. The reason is simple: import() does not return the module itself; it returns a Promise. It's an asynchronous request to the browser (or runtime) to go fetch that file from the server and parse it, which takes time. You can't treat a network request as if it happens instantaneously.
Stop treating imports as static declarations and start treating them as Promises
To use a dynamic import, you have to handle it like any other asynchronous operation in JavaScript. You either need to use .then() or, more commonly in modern codebases, async/await. I personally prefer await because it keeps the logic linear and much easier to read when you're dealing with multiple dependencies.
async function loadChart() {
try {
// The browser only downloads this file when this line is hit
const { renderChart } = await import('./analytics-engine.js');
renderChart();
} catch (err) {
console.error("The analytics module failed to load", err);
}
}
Notice that I used destructuring inside the await. This is because the Promise resolves to a module object containing all the exports. If you have a default export, it'll be under a key called default.
Using Code Splitting to kill your "Bundle Bloat"
Now, you might be wondering, "Why bother with this complexity when I can just put import { renderChart } from './analytics-engine.js' at the top of the file?"
The answer is Bundle Size. When you use static imports at the top of your file, your bundler (like Webpack or Vite) lumps everything into one giant JavaScript file. If you have a massive library for generating PDFs or complex data visualizations, every single user has to download that code on the first page load—even if they never click the "Export to PDF" button. That's a waste of their bandwidth and slows down your Time to Interactive (TTI).
Dynamic imports enable "Code Splitting." By wrapping the import in a function, you're telling the bundler: "Put this module in its own separate file. Don't load it until I explicitly ask for it."
Here is a real-world pattern I use frequently for "heavy" components that are hidden behind a user action:
const settingsButton = document.querySelector('#settings-btn');
settingsButton.addEventListener('click', async () => {
// We only load the heavy settings UI logic when the user actually wants it
const { initSettingsMenu } = await import('./settings-ui.js');
initSettingsMenu();
});
I'll give you a pro tip: if you find yourself dynamically importing the same module in five different places, you're doing it wrong. Create a single "loader" function that handles the import and caches the result, or let your module system handle the singleton pattern for you. Don't make the browser re-evaluate the logic more than necessary.
📋 Practical Task
The On-Demand Language Pack Loader
You are building a multi-language site. Instead of loading every translation file (which would be massive for a site supporting 20+ languages), you need to load the translation module only when the user selects a language from a dropdown.
Requirements:
- Create a mock file structure in your mind:
/locales/en.js,/locales/es.js, and/locales/fr.js. Each file should have a default export containing awelcomeMessagestring. - Write a function
async function changeLanguage(langCode). - Inside this function, use a dynamic import to load the file based on the
langCode(e.g.,import('./locales/${langCode}.js')). - Once the module is loaded, update a DOM element with the ID
#welcome-textto display thewelcomeMessagefrom that module. - Implement a
try...catchblock to handle cases where a user might try to load a language code that doesn't exist (resulting in a 404).
There are no comments for now.