Modules are reusable pieces of code in a file that can be exported and then imported for use in another file.
A modular program is one whose components can be separated, used individually, and recombined to create a complex system.
이런 모듈러 방식 프로그래밍 전략은 각각 다른 로직을 갖고 있는 함수나 데이타를 여러 파일에 나눠 다른파일에 불러들여 재사용 및 변수 이름 콜리젼도 피할수 있으며 읽기도 쉬워 디버깅에 용이하다.
This modular strategy is sometimes called separation of concernsand is useful for several reasons.
By isolating code into separate files, called modules, you can:
There are multiple ways of implementing modules depending on the runtime environment in which your code is executed. In JavaScript, there are two runtime environments and each has a preferred module implementation:
Every JavaScript file that runs in a Node environment is treated as a distinct module. The functions and data defined within each module can be used by any other module, as long as those resources are properly exported and imported.
To create a module, you simply have to create a new file where the functions can be declared. Then, to make these functions available to other files, add them as properties to the built-in module.exports object:
/* converters.js */
function celsiusToFahrenheit(celsius) {
return celsius * (9/5) + 32;
}
module.exports.celsiusToFahrenheit = celsiusToFahrenheit;
module.exports.fahrenheitToCelsius = function(fahrenheit) {
return (fahrenheit - 32) * (5/9);
};
module.exports is an object that is built-in to the Node.js runtime environment. Other files can now import this object, and make use of these two functions, with another feature that is built-in to the Node.js runtime environment: the require() function.
The require() function accepts a string as an argument. That string provides the file path to the module you would like to import.
/* water-limits.js */
const converters = require('./converters.js');
const freezingPointC = 0;
const boilingPointC = 100;
const freezingPointF = converters.celsiusToFahrenheit(freezingPointC);
const boilingPointF = converters.celsiusToFahrenheit(boilingPointC);
console.log(`The freezing point of water in Fahrenheit is ${freezingPointF}`);
console.log(`The boiling point of water in Fahrenheit is ${boilingPointF}`);
When you use require(), the entire module.exports object is returned and stored in the variable converters. This means that both the .celsiusToFahrenheit() and .fahrenheitToCelsius() methods can be used in this program.
We could also use destrucring to be more selective with require()
/* celsius-to-fahrenheit.js */
const { celsiusToFahrenheit } = require('./converters.js');
const celsiusInput = process.argv[2];
const fahrenheitValue = celsiusToFahrenheit(celsiusInput);
console.log(`${celsiusInput} degrees Celsius = ${fahrenheitValue} degrees Fahrenheit`);
With this approach, the remainder of the program works the same way but the program avoids importing a function it does not need. === Better performance.
Named exports
/* dom-functions.js */
const toggleHiddenElement = (domElement) => {
if (domElement.style.display === 'none') {
domElement.style.display = 'block';
} else {
domElement.style.display = 'none';
}
}
const changeToFunkyColor = (domElement) => {
const r = Math.random() * 255;
const g = Math.random() * 255;
const b = Math.random() * 255;
domElement.style.background = `rgb(${r}, ${g}, ${b})`;
}
export { toggleHiddenElement, changeToFunkyColor };
/* dom-functions.js */
export const toggleHiddenElement = (domElement) => {
// logic omitted...
}
export const changeToFunkyColor = (domElement) => {
// logic omitted...
}
The ES6 syntax for importing named resources from modules is similar to the export syntax:
/* 예시
import { exportedResourceA, exportedResourceB } from '/path/to/module.js';
*/
/* secret-messages.js */
import { toggleHiddenElement, changeToFunkyColor } from '../modules/dom-functions.js';
const buttonElement = document.getElementById('secret-button');
const pElement = document.getElementById('secret-p');
buttonElement.addEventListener('click', () => {
toggleHiddenElement(pElement);
changeToFunkyColor(buttonElement);
});
<!-- secret-messages.html -->
<html>
<head>
<title>Secret Messages</title>
</head>
<body>
<button id="secret-button"> Press me... if you dare </button>
<p id="secret-p" style="display: none"> Modules are fancy! </p>
<script type="module" src="./secret-messages.js"> </script>
</body>
</html>
the addition of the attribute type='module' to the < script > element. Failure to do so can cause some browsers to throw an error. For example, in Chrome you might see this error:
Uncaught SyntaxError: Cannot use import statement outside a module.
Inevitably, you will run into a situation where the resources you wish to import share a name with some other value that already exists in your program (or from another imported module).
For example, suppose you had access to two modules, greeterEspanol.js and greeterFrancais.js. Each exports a function called greet():
/* inside greeterEspanol.js */
const greet = () => {
console.log('hola');
}
export { greet };
/* inside greeterFrancais.js */
const greet = () => {
console.log('bonjour');
}
export { greet };
Now, let’s say you wanted to use each of these functions in a program, called main.js, that simulates a conversation between a spanish-speaker and a french-speaker.
import { greet } from 'greeterEspanol.js';
import { greet } from 'greeterFrancais.js';
The code above will throw an error on line 2 due to the fact that the identifier greet has already been defined on line 1. Thankfully, ES6 includes syntax for renaming imported resources by adding in the keyword as. It looks like this:
import { exportedResource as newlyNamedResource } from '/path/to/module'
/* main.js */
import { greet as greetEspanol } from 'greeterEspanol.js';
import { greet as greetFrancais } from 'greeterFrancais.js';
greetEspanol(); // Prints: hola
greetFrancais(); // Prints: bonjour
Now, both of the imported functions can be called within main.js using their new identifiers, greetEspanol() and greetFrancais().
So far, the examples shown have used the named export syntax, in which a module’s resources are exported individually by name.
Every module also has the option to export a single value to represent the entire module called the default export. Often, though not always, the default export value is an object containing the entire set of functions and/or data values of a module.
The syntax for exporting a default object looks like this:
const resources = {
valueA,
valueB
}
export { resources as default };
const resources = {
valueA,
valueB
}
export { resources as default };
At first glance, it looks like the resources object is being exported as a named export. However, the clause as default renames the exported object to default, a reserved identifier that can only be given to a single exported value.
shorthand syntax using export default
const resources = {
valueA,
valueB
}
export default resources;
import importedResources from 'module.js';
Notice that the curly braces are gone from the import statement.
This syntax is actually shorthand for import { default as importedResources } from 'module.js and the imported default value may be given any name the programmer chooses.
It should be noted that if the default export is an object, the values inside cannot be extracted until after the object is imported, like so:
// This will work...
import resources from 'module.js'
const { valueA, valueB } = resources;
// This will not work...
import { valueA, valueB } from 'module.js'
예시:
/* dom-functions.js */
const toggleHiddenElement = (domElement) => {
if (domElement.style.display === 'none') {
domElement.style.display = 'block';
} else {
domElement.style.display = 'none';
}
}
const changeToFunkyColor = (domElement) => {
const r = Math.random() * 255;
const g = Math.random() * 255;
const b = Math.random() * 255;
domElement.style.background = `rgb(${r}, ${g}, ${b})`;
}
const resources = {
toggleHiddenElement,
changeToFunkyColor
}
export default resources;
This default exports object can now be used within secret-messages.js like so:
import domFunctions from '../modules/dom-functions.js';
/* ***Above is same as but above line is shorthand version ***
import { default as domFunctions } from '../modules/dom-functions.js';
*/
const { toggleHiddenElement, changeToFunkyColor } = domFunctions;
const buttonElement = document.getElementById('secret-button');
const pElement = document.getElementById('secret-p');
buttonElement.addEventListener('click', () => {
toggleHiddenElement(pElement);
changeToFunkyColor(buttonElement);
});
The standard import syntax is static and will always result in all code in the imported module being evaluated at load time. In situations where you wish to load a module conditionally or on demand, you can use a dynamic import instead. The following are some reasons why you might need to use dynamic import:
When importing statically significantly slows the loading of your code and there is a low likelihood that you will need the code you are importing, or you will not need it until a later time.
When importing statically significantly increases your program's memory usage and there is a low likelihood that you will need the code you are importing.
When the module you are importing does not exist at load time
When the import specifier string needs to be constructed dynamically. (Static import only supports static specifiers.)
When the module being imported has side effects, and you do not want those side effects unless some condition is true. (It is recommended not to have any side effects in a module, but you sometimes cannot control this in your module dependencies.)
Use dynamic import only when necessary. The static form is preferable for loading initial dependencies, and can benefit more readily from static analysis tools and tree shaking.
To dynamically import a module, the import keyword may be called as a function. When used this way, it returns a promise.
import('/modules/my-module.js')
.then((module) => {
// Do something with the module.
});
//This form also supports the await keyword.
let module = await import('/modules/my-module.js');
// **** 예시 ****
const main = document.querySelector("main");
for (const link of document.querySelectorAll("nav > a")) {
link.addEventListener("click", e => {
e.preventDefault();
import('/modules/my-module.js')
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});
});