Welcome to the second chapter of the release-notes tutorial!
In the previous chapter, Command Registration, we learned how to create a "menu" for our application. We defined the identity of our command without actually running the code.
Now, we need to answer a critical question: How do we retrieve the actual code only when we need it?
Imagine you are a repair person. You have a massive toolbox containing a hammer, a drill, a saw, a wrench, and 50 other heavy tools.
You are called to a job to simply tighten one screw.
In our software:
release-notes if the user actually asks for it. This keeps our CLI startup lightning fast.To achieve this "lightweight" behavior, we use a special JavaScript feature called Dynamic Imports.
Usually, you see imports at the very top of a file. This is a Static Import.
// Static Import (The "Heavy" way)
// The computer loads this file IMMEDIATELY, whether we need it or not.
import { run } from './release-notes.js'
A Dynamic Import happens inside the code, usually inside a function. It returns a specific file only when that line of code is executed.
// Dynamic Import (The "Lazy" way)
// The computer loads this file ONLY when this function runs.
const loadMyFile = () => import('./release-notes.js')
Let's revisit our index.ts file from Chapter 1 and focus specifically on the load property. This is where the magic happens.
// --- File: index.ts ---
const releaseNotes: Command = {
// ... name and description ...
// This is the Lazy Loading implementation:
load: () => import('./release-notes.js'),
}
Explanation:
() => ....import('./release-notes.js')../release-notes.js is not read when the application starts. It sits there waiting.
What actually happens when a user types my-app release-notes?
index.ts to see the command exists, but it ignores release-notes.js for now.release-notes.load property we wrote above and executes that function.release-notes.js, and brings it into memory.Here is the process visualized:
How does the core system handle this? Since reading a file from the disk (or network) takes a tiny bit of time, the import() function returns a Promise.
A Promise is like a receipt at a fast-food restaurant. It says, "I don't have your burger (code) yet, but hold this receipt, and I will give it to you in a moment."
Here is a simplified view of how the CLI Core handles this receipt:
// --- File: core-runner.ts (Simplified) ---
async function runCommand(command: Command) {
// 1. We call the load function we defined in index.ts
// The 'await' keyword means "wait for the file to finish loading"
const module = await command.load()
// 2. Once loaded, we look for the main function inside that file
// (We will write this 'run' function in the next chapter)
const runFunction = module.default
// 3. Execute the logic!
await runFunction()
}
Explanation:
await command.load(): This triggers our lazy loader. The system pauses here for a split second to fetch the code.module.default: When we import a file, we usually get the default export from it.release-notes.js is never touched, saving memory.In this chapter, we learned that Lazy Module Loading is about efficiency.
import()) inside our load function.Now that we have successfully loaded our code file, we need to define what goes inside it. Since our command needs to fetch data from the internet (which takes time), we need to learn how to handle code that doesn't finish immediately.
Let's dive into the logic file in the next chapter: Asynchronous Command Handler.
Generated by Code IQ