These sections are called “modules,” of course. You can import and export them as needed.
If that sounds confusing, don’t worry. We’ll define all that in the next few sections. Read on to learn more about the benefits of using modules and how to use them.
The Benefits of Using Modules
Without modular programming, developers would spend hours maintaining, debugging, rewriting, and renaming code. This is incredibly inefficient, especially for bigger projects. So, how do modules prevent these things from happening?
Ease of Maintenance
If you design a module well, there will be fewer dependencies within it. This means that it can grow and change independently of the rest of the codebase.
This is especially useful if you know you’ll have to make changes to a certain section of code. By decoupling it from the rest of the codebase, you’ll only have to make changes to that small section rather than make many changes throughout.
Let’s say you write a piece of code that’ll be useful elsewhere in your project, or later in a different project. It’d be much easier to store this piece of code in a module you can import wherever you want than to rewrite it every time you need it right?
Of course, it would. That’s why modules are optimal for reusability. If you need to change part of this code, editing it at the source module is also much easier than finding every instance and editing it individually.
Avoiding Namespace Pollution
Namespace pollution occurs when you share global variables between pieces of code that share different functions. This can be hard to avoid in those long codebases, and that’s where modules come in. By using modules, function/variable names become unique to each module.
Using an anonymous closure allows you to keep variables private within the closure scope. This hides them from the global namespace and avoids pollution.
You can only use a local variable inside the function that it’s defined, while a global variable can be used anywhere. Within an anonymous closure, you can use both global and local variables without accidentally overwriting a global variable.
Another approach is to use global import (popular with libraries like jQuery). This means passing in global variables as parameters. Here, the global variables are delineated upfront, improving readability.
Importing and Exporting Modules
First, let’s quickly define some keywords.
The import keyword allows you to import variables and functions from another module. The export keyword allows you to label the variables and functions you want to bring (import) into another module. The default keyword specifies the variable, object, class, etc. that you wish to prioritize during import.
Dependencies are relationships between modules. This will become important when considering best practices for coding modules.
The import statement is static. This means that all it does is read live bindings that another module exports. Live bindings are modified by whatever module exported them.
Using the name of the module as the namespace will import all contents of a module. You can also use the names of specific exported objects or values to import just a single export. By separating two or more names with a comma in the namespace, you can import multiple exports.
Dynamic imports allow you to load a module conditionally. However, the standard is a static import, which will evaluate all code from the imported module at load time.
Some situations where you may need to use dynamic imports include:
- Static import will increase load time significantly
- Static import increases memory usage
- Low chance you’ll need the imported module
- The imported module doesn’t exist at the start of the load time
Otherwise, use dynamic imports only when necessary.
You can export modules in two ways. First, through named exports, allowing for zero or more exports per module. Second, through default exports, allowing for one and only one export per module.
If you want to export multiple values, named exports are the way to go. When doing so, make sure the naming of both the imported and exported modules remains the same.
When you wish to make your code default to a single function, use default exports. Unlike named exports, you can only include one default export per module. With default exports, you can name them however you want.
Applying Modules to HTML
The process of applying a module to your HTML page is quite similar to applying a regular script. However, there are a few differences you need to keep in mind.
First, ensure that you define the script as a module. This is easily done by coding type=”module” in the <script> element. Otherwise, you can embed the module code directly into the HTML file as long as you place it within the <script> element.
Second, inside modules in the HTML file, only import and export statements can be used (not regular scripts).
Because these are still found in old scripts, it’s important to understand their basics in case you have to work with or modify older code.
CommonJS is the standard used in Node.js development. It uses the require statement instead of import. It also doesn’t include default exports, so you’ll have to use exclusively named exports when dealing with CommonJS.
Another difference is that the required function is dynamic, while import is static, as we’ve discussed.
Asynchronous Module Definition (AMD) is one of the oldest module systems and was first used by the modular script loader require.js. It was created to declare dependencies for modules and ensure they would load before the execution of the code.
It also allowed developers to specify which of these dependencies should load before the execution of each section of code. This highly boosted performance by reducing the amount of information needed to load before a program could run.
AMD uses the define function to load the dependencies. Since the function executes as soon as the dependencies load, there’s no need for an export function. Everything returned by the define function is exported.
Universal Module Definition (UMD) is compatible with both AMD and CommonJS. The term “universal” is used because its goal is to allow your module to be uploaded by several different loaders.
There are two components to a UMD definition. The first is an immediately invoked function expression (IIFE), and the second is a factory function (which receives the dependencies).
As the name suggests, an IIFE runs the moment it is defined. Here, the IIFE takes two parameters. First, the root element to enable global scope, and second, a factory function, which is simply the module code.
Whether you’re writing your modules natively or through one of the other module types, it’s important to keep best practices in mind. Here are a few to consider to make your code easier for yourself and other developers to read.
Favor Named Exports
Unless there’s a good reason to use default exports, you should use named exports wherever possible. Refactoring default exports is needlessly difficult. Using named exports makes renaming a lot easier.
Don’t Execute Heavy Operations at the Module-Level Scope
At the module level, stick simply to defining functions, variables, objects, and classes (and exporting these components when appropriate). It’s a mistake to perform heavy computation at the module-level scope.
By leaving the decision of when to run heavy operations up to the user, you cut down on unnecessary load time.
Ensure Your Modules Are High Cohesion
How related are the components within your module? Does the module focus on just one task, or multiple?
If the components are highly related and specific to a singular task, the module is classed as high cohesion. This is optimal for an organized, readable module.
Modules that contain unrelated components are considered low cohesion. Low cohesion modules create unnecessary dependencies, which extend run time.
If you notice your code contains low-cohesion modules, simply split them up into several high-cohesion ones. This will improve readability and reduce transitive dependencies.
Favor Absolute Paths Over Long Relative Paths
Avoid including too many parent folders. One is usually fine, but two or more are often unnecessarily confusing. Even though absolute paths are often longer, using them provides a more direct path to the imported module’s location.
How to Manage Your Modules
This is done through something called a dependency graph. This keeps track of the modules in your project and the dependencies that belong to them. It then will bundle all these modules and dependencies into a single file.
In front-end development, it’s rare to use modules outside of bundles. Instead, they’re deployed to the production server packaged within the bundle. This allows developers to put all the modules in the bundle into a regular script without using the statements we defined previously.
Why is Good Front-End Development So Important?
Front-end developers ensure that site users can easily navigate and interact with the site. They do this through a combination of user experience design, programming, and debugging. The easier it is to use your site, the more time potential customers will spend on it.
If you’re still looking for professional help with front-end development, we’ve got you covered. Check out our front-end services page for more information about how we can optimize your site’s code.