How to export a function from a module in JavaScript

How to export a function from a module in JavaScript

When you’re working with modules in JavaScript, exporting by name is your go-to strategy. It’s reliable, straightforward, and it keeps your code organized. When you export functions, variables, or classes by name, you make it clear what is available for import in other files. This clarity can save you a lot of headaches down the line.

Take a look at this simple example:

export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

In this case, you have two functions, add and subtract, that can be imported individually in another module. This way, if you only need one of them, you can import just what you need, which can help keep your bundle size smaller and your code cleaner.

To import these functions, you would write:

import { add, subtract } from './math.js';

console.log(add(5, 3)); // Outputs: 8
console.log(subtract(5, 3)); // Outputs: 2

Notice that you’re pulling in only what you need. This selective import is not just a matter of preference; it can lead to better maintainability. If you or someone else is reading the code later, it’s immediately obvious which pieces of functionality are being utilized.

Exporting by name also plays nicely with the tree-shaking capabilities of contemporary bundlers like Webpack or Rollup. When you export everything by name, the bundler can analyze your code and eliminate any unused exports, making your final build leaner.

Here’s another example where you might want to export multiple items at once:

const PI = 3.14;
const E = 2.71;

export { PI, E };

In this case, you are grouping related constants together. This approach is particularly useful when you have a collection of constants or utility functions that belong together logically. It keeps everything tidy and makes it easier to manage.

However, it’s important to keep in mind that with great power comes great responsibility. Over-exporting can lead to confusion about what’s actually being used. It’s wise to regularly audit your exports to ensure you’re not cluttering the namespace with unnecessary exports that no one is using.

So, stick to named exports as your bread and butter, but also be conscious of the balance between usability and clarity. It’s tempting to throw everything into the export basket, but that can lead to chaos in large codebases. Aim for simplicity and legibility, and your future self will thank you.

The siren song of the default export

Now, let’s shift gears and talk about the default export. The default export can be alluring, almost seductive in its simplicity. When you have a single thing that you want to export from a module, default exports can make your code look cleaner at first glance. The syntax is straightforward, and when you import, you can give it any name you want. This flexibility can be particularly appealing when you’re building a library or a component that has a clear primary purpose.

Here’s how you would create a default export:

export default function multiply(a, b) {
  return a * b;
}

In this case, you’re exporting a single function, multiply, as the default export. When you import this function in another module, it looks like this:

import multiply from './math.js';

console.log(multiply(5, 3)); // Outputs: 15

Notice how you don’t need the curly braces when importing a default export. This can make it seem cleaner, but there’s a catch. The flexibility of naming can lead to confusion. If multiple developers are working on the same codebase, it’s easy for imports to become inconsistent, as each developer might choose different names for the same module. This inconsistency can obscure the code’s intent.

Furthermore, default exports don’t play as nicely with tree shaking as named exports do. When you have multiple exports, bundlers can easily determine what’s being used and what’s not. With a default export, it becomes less clear, and you might end up including more code than necessary in your final bundle.

Here’s a scenario where default exports might be particularly tempting:

const defaultConfig = {
  baseUrl: "https://api.example.com",
  timeout: 5000,
};

export default defaultConfig;

While it’s convenient to export a single configuration object, if you ever need to add more configurations, you might find yourself wishing you had opted for named exports instead. The moment you start mixing default and named exports in the same module, you introduce potential confusion. For example, if you were to add another export:

export const apiKey = "12345";

Now, you’re left with a module that has both a default export and a named export. This duality can lead to uncertainty about how to import the module correctly. Should you import the default and the named export together? Or should you just stick with one? The decision can bog down development and lead to errors.

As you can see, while default exports can seem appealing, they come with their own set of problems. It’s essential to weigh the benefits against the potential pitfalls. You might find that the clarity and structure provided by named exports outweigh the initial allure of default exports. The choice can significantly affect how maintainable and understandable your code becomes, especially in larger projects where multiple developers are involved.

Ultimately, the decision between named and default exports is not just a matter of syntax; it’s a matter of maintaining sanity in your codebase. The temptation of default exports can lead you down a path of confusion and inconsistency. So, while it might be easy to succumb to the siren song of the default export, remember that there are trade-offs to consider that can impact the long-term health of your code.

As you navigate these waters, keep in mind that clarity is key. Opting for named exports can lead to a more coherent structure, especially as your application grows. The battle between named and default exports is not just a stylistic choice; it’s a fundamental aspect of how you design your modules and interact with your codebase. And as you well know, in programming, clarity often trumps convenience.

Named versus default is a battle for your sanity

Let’s make this concrete, because the pain caused by default exports isn’t some abstract architectural principle; it’s a real-world, time-wasting, soul-crushing problem. Imagine you’re six months into a project. The codebase is sprawling. You wrote a module early on with a default export you called process. Now you realize that’s a terrible, generic name. It should be parseCustomerRecord. You decide to refactor it.

If you used a default export, your task is now a nightmare. Here’s your original module:

// file: customer-parser.js
export default function process(record) {
  // ... logic to parse a customer record
}

Because the importer gets to name it whatever they want, your codebase is now littered with variations. You have to hunt every single one down manually.

// file: signup-flow.js
import customerProcessor from './customer-parser.js';
// ...
customerProcessor(data);

// file: billing-system.js
import parse from './customer-parser.js';
// ...
parse(customerData);

// file: admin-panel.js
import TheThingThatDoesTheStuff from './customer-parser.js'; // Some joker on your team did this at 2 AM.
// ...
TheThingThatDoesTheStuff(record);

You can’t just use your IDE’s “Rename Symbol” feature. It won’t work. It doesn’t know about all those arbitrary names. You have to perform a project-wide text search for './customer-parser.js', manually inspect every single import, and pray you don’t miss one. That’s not high-value engineering work. That’s digital ditch-digging.

Now, ponder the alternative. If you had just used a named export from the beginning, life would be so much better.

// file: customer-parser.js
export function process(record) {
  // ... logic to parse a customer record
}

When you want to rename process to parseCustomerRecord, you put your cursor on the function name, hit F2, type the new name, and press Enter. Done. Your IDE instantly and safely updates every file that uses it.

// file: signup-flow.js
import { parseCustomerRecord } from './customer-parser.js';

// file: billing-system.js
import { parseCustomerRecord } from './customer-parser.js';

// file: admin-panel.js
import { parseCustomerRecord } from './customer-parser.js';

This isn’t just a minor convenience. That’s the difference between a five-second, zero-risk refactor and a thirty-minute bug hunt. Your tools are there to help you, but they can only do so much if you’re writing code in a way that actively resists automation. Default exports are a wrench in the gears of your development environment.

Furthermore, this fight is about discoverability. When you type import { | } from './utils', your IDE will pop up a helpful list of all the named exports available in utils.js. It’s like an API reference right in your editor. With a default export, you get nothing. You just have to know, through tribal knowledge or by opening the file, that a default export exists and what it represents. This friction, this constant need to context-switch and go look things up, is a tax on your focus.

The absolute worst-case scenario is when a module mixes both default and named exports. It’s a sign of a confused module that doesn’t have a clear purpose, and it forces that confusion onto every developer who has to use it.

// file: api-helpers.js
export function setAuthHeader(token) {
  // ...
}

export default function makeRequest(endpoint, options) {
  // ...
}

Now what? How do you import from this? You force every consumer of your module to stop and think about the “correct” way to do it.

import makeRequest, { setAuthHeader } from './api-helpers.js';

This looks clean, but it’s inconsistent. Why is one function special? Establishing a simple, non-negotiable rule is the only way to win this battle. The rule is: use named exports. Always. Make it a linting error to use a default export. It removes ambiguity, makes your code easier to search and refactor, and lets your tools do their job. Your future self, and your entire team, will be grateful.

Source: https://www.jsfaq.com/how-to-export-a-function-from-a-module-in-javascript/


You might also like this video

Comments

No comments yet. Why don’t you start the discussion?

    Leave a Reply