Modules in ES6: An Introduction to Import and Export Basics
To truly make our components modular, we'd ideally have them live in their own files. In the upper scope of that file, the component might define a styles object or helper functions that only the component uses. But we want our component-module to only expose the component itself.
Until ES6, modules were not natively supported in JavaScript. Developers would use a variety of different techniques to make modular JavaScript. Some solutions only work in the browser, relying on the browser environment (like the presence of window). Others only work in Node.js.
Browsers don’t yet support ES6 modules. But ES6 modules are the future. The syntax is intuitive, we avoid bizarre tactics employed in ES5, and they work both in and outside of the browser. Because of this, the React community has quickly adopted ES6 modules.
// Example of a modular JavaScript module (ES6)
export function getTimers() {
// implementation here
}
This Client module only exposed these functions. These are the Client module’s public methods. The file public/js/client.js also contained other functions, like styles definitions or helper functions that only the component uses.
What is Module in ES6?
In earlier apps, we saw modules being used to separate concerns and make components more reusable. For instance, the time tracking app had a Client module that exposed several public methods. This module only shared its functionality with other parts of the app that needed it.
In ES6, modules take on a new form. A module is essentially a file that can contain JavaScript code that can be reused across your application. With ES6, you can write your components as separate files and import them into other files where they're needed. This approach makes your code more modular, reusable, and easier to maintain.
ES6 modules use the import and export statements to define what's available to other parts of your app. You can export values, functions, classes, or even entire objects from a module using the export statement. To import these exported values into another file, you use the import statement.
For example:
// myComponent.js
export function MyComponent() {
// component code here
}
// app.js
import { MyComponent } from './myComponent';
// now you can use MyComponent in your app
This is the basic idea behind ES6 modules. They allow you to write reusable, modular JavaScript code that's easy to maintain and reuse across your application.
Why Use Modules in ES6?
In earlier chapters, we discussed how ES5 modules were often implemented using ad-hoc techniques. These solutions were either browser-specific or Node.js-specific, making it challenging to share code between environments. With the introduction of ES6 modules, developers can now create truly modular JavaScript components that live in their own files.
The benefits of using ES6 modules are twofold. Firstly, they provide a standardized way to manage dependencies and scope in your codebase. This leads to more maintainable and organized projects. Secondly, ES6 modules allow you to control what parts of your code are exposed to the outside world, making it easier to reuse components without worrying about polluting the global namespace.
By using modules, you can encapsulate implementation details within a module and only expose the necessary APIs to the outside world. This makes it easier to test, debug, and maintain your codebase over time.
Understanding ES6 Module Syntax
To create truly modular JavaScript components, we need to define styles or helper functions within the component file itself. However, we want our component-module to only expose the component itself. Prior to ES6, modules were not natively supported in JavaScript, and developers employed various techniques to make modular JavaScript.
ES6 introduces a new syntax for creating modules that is both intuitive and future-proof. It works seamlessly in both Node.js and the browser. The React community has quickly adopted ES6 modules due to their simplicity and flexibility.
In our Modash file, we can use the export statement to define what functionality is exposed from the module. For example:
export { sayHi, sayBye };
This syntax allows us to specify which named exports are available for import by other modules. We can also specify a default export using the export default statement.
A common pattern for libraries is to define all functionality within a single object and then export that object as the default export:
const Greetings = { sayHi, sayBye };
export default Greetings;
This allows us to easily import the entire library without having to specify individual functions.
Importing Modules in ES6
Here is the section content:
When you want your components to be truly modular, we’d ideally have them live in their own files. In the upper scope of that file, the component might define a styles object or helper functions that only the component uses. But we want our component-module to only expose the component itself.
Until ES6, modules were not natively supported in JavaScript. Developers would use a variety of different techniques to make modular JavaScript. Some solutions only work in the browser, relying on the browser environment (like the presence of window). Others only work in Node.js.
However, due to the complexity of module systems, we can’t simply use ES6’s import/export syntax and expect it to “just work” in the browser, even with Babel. More tooling is needed.
Exporting Modules in ES6
In ES6, you can use the export keyword to specify which variables or functions your module should expose. This is called named exports. Let's say you have a file my-component.js that defines a React component:
function MyComponent() {
return <div>Hello, world!</div>;
}
export { MyComponent };
In this example, the MyComponent function is exported using the export keyword. When someone imports your module, they will be able to access the MyComponent function.
Note that you can also export multiple variables or functions at once:
function foo() {}
function bar() {}
export { foo, bar };
This way, you can control which parts of your code are exposed to the outside world.
Using the export Keyword
When we export a module in ES6, we can specify individual named exports using the export keyword. Let's take an example:
const sayHi = () => (console.log('Hi!'));
const sayBye = () => (console.log('Bye!'));
const saySomething = () => (console.log('Something!'));
export { sayHi, sayBye };
This exports the sayHi and sayBye functions, but not the saySomething function. We can then import these named exports in another file:
import { sayHi, sayBye } from './greetings';
If we want to import all of a module's functionality underneath a given namespace, we can use the import * as <Namespace> syntax:
import * as Greetings from './greetings';
Greetings.sayHi(); // -> Hi!
Greetings.sayBye(); // => Bye!
Note that trying to access a function that isn't exported will result in a TypeError.
Difference Between Export and Import
Now that we have our test suite set up, let's explore the difference between export and import in ES6. In Modash.js, we can define a default export like this:
export default function add(a, b) {
return a + b;
}
In another file, such as Modash.test.js, we can then import and use that exported function like this:
import add from './Modash';
test('adds two numbers', () => {
expect(add(2, 3)).toBe(5);
});
Notice that the export default syntax is used to define a single export for our module. This can be thought of as a way to provide a "main entry point" into our module.
On the other hand, we can also use named exports to explicitly specify which parts of our module should be exposed. For example:
export function add(a, b) {
return a + b;
}
export const PI = Math.PI;
In this case, we're exporting two separate things: a add function and a constant PI. We can then import these named exports like this:
import { add, PI } from './Modash';
test('adds two numbers', () => {
expect(add(2, 3)).toBe(5);
});
test('uses PI', () => {
expect(PI).toBe(Math.PI);
});
By using export and import, we can create a clear and concise way to share functionality between different parts of our codebase.
Common Use Cases for Import and Export in ES6
In addition to controlling which parts of our library are exposed to the outside world, we can use named exports to import specific functions or objects from another module. For instance, if we have a greetings module with functions sayHi, sayBye, and saySomething, we could import just those functions into another file like this:
import * as Greetings from './greetings';
Greetings.sayHi(); // -> Hi!
Greetings.sayBye(); // => Bye!
Greetings.saySomething(); // => TypeError: Greetings.saySomething is not a function
This syntax allows us to easily import the entire greetings module and access its functions without having to specify each one individually.
Best Practices for Writing ES6 Modules
When writing modules in ES6, it's essential to follow best practices to ensure your code is modular, reusable, and maintainable. Here are some guidelines to help you write high-quality ES6 modules:
Keep modules small and focused: Each module should have a single responsibility and be responsible for one specific task. This makes it easier to reason about the code and test individual components.
Use clear and descriptive names: Choose names that accurately describe the module's purpose, avoiding ambiguity and confusion.
Export only what's necessary: Only export the necessary variables, functions, or classes from your module. This helps prevent namespace pollution and makes it easier to reuse your code in other projects.
Use default exports wisely: Default exports can be useful for providing a main entry point into your module, but use them sparingly and only when necessary.
// my-module.js
export function getTimers() {
// implementation
}
export function createTimer() {
// implementation
}
By following these best practices, you'll be able to write modules that are easy to understand, test, and maintain. Remember, the goal is to create modular code that can be reused in different contexts without introducing unnecessary complexity or dependencies.
Conclusion: ES6 Modules in Action
Here is the content for the "Conclusion: ES6 Modules in Action" section:
Now that we've explored how to use ES6 modules with Create React App and Webpack, let's summarize our journey. We started by defining components as separate files, which allowed us to define styles or helper functions specific to each component. This modular approach enables us to reuse code more effectively and maintain a clean separation of concerns.
import Modash from './Modash';
const App = () => {
return (
<div>
<h1>Hello World!</h1>
<Modash />
</div>
);
};
By using ES6 modules with Webpack, we can write reusable and maintainable code that's easy to understand and work with. This is just the beginning of our exploration into modular JavaScript development. In future sections, we'll dive deeper into how we can leverage this approach to build more robust and scalable applications.