Categories
JavaScript

How to Select and Manipulate DOM Elements

To make our web pages dynamic, we have to manipulate the elements on the page so that we can get the dynamic effects. The web browser’s standard library allows us to do this. There are multiple methods in the document object, which is the object which represents the items on our page. It has the whole DOM tree with all the elements we can get and then manipulate. In this article, we’ll look at a few methods to get elements from the page.

getElementsByClassName

The document.getElementsByClassName method lets us get a list of DOM objects on the page by their class name. Whatever object that has the given class will be returned by this method. It takes one argument which is a string with one or more class names separated by a space. It’s a method that’s part of any DOM element, which means it can also be called on elements that are returned by this method.

A NodeList object is returned, which is an array-like object. It has a length property and can be looped through by a for loop or a for...of loop, but array methods like forEach aren’t included in this object.

The simplest example would be getting the elements from the following HTML:

<p class='foo'>
  foo
</p>
<p class='foo'>
  bar
</p>

Then in our JavaScript code, we can get the elements with the foo class by writing the following:

const foo = document.getElementsByClassName('foo');

Once we have that we have access to the elements, so we can manipulate them by assigning values to its properties. For example, if we want to change the style, we can set the value of the properties in the style property.

We can write something like the following to set the color of the text of both elements with the foo class to red:

const foo = document.getElementsByClassName('foo');
for (const f of foo) {
  setTimeout(() => {
    f.style.color = 'red';
  }, 2000)
}

Also, we can pass in more than one class name to the method. They have to be separated by a space. It’ll select the elements with all the classes. For example, if we add a bar to the first p element as follows:

<p class='foo bar'>
  foo
</p>
<p class='foo'>
  bar
</p>

Then we can write the following to change the first p element’s text color to red after 2 seconds:

const foo = document.getElementsByClassName('foo bar');
for (const f of foo) {
  setTimeout(() => {
    f.style.color = 'red';
  }, 2000)
}

The style property has lots of properties. Pretty much any CSS option can be set by assigning a value to a property of the style property.

getElementsByTagName

We can select all the elements with the given tag name by using the document.getElementsByTagName method.

It accepts a string with the tag name and returns a NodeList object with all the elements with the given tag. As with any NodeList object, we can loop through by a for loop or a for...of loop, but array methods like forEach aren’t included in this object.

For example, we can write the following code to set all the p elements to red after 2 seconds given the following HTML code:

<p>
  foo
</p>
<p>
  foo
</p>
<div>
  baz
</div>

We can write:

const pElements = document.getElementsByTagName('p');
for (const p of pElements) {
  setTimeout(() => {
    p.style.color = 'red';
  }, 2000)

}

to set them to red since we passed in p to the method. We should get that the first 2 elements are red after 2 seconds. We can see that the div element stays black since it’s not a p element.

getElementsByTagNameNS

The getElementsByTagNameNS is very similar to the getElementsByTagName method, but it takes 2 arguments instead of one. The first is a string with the XML namespace of the HTML document, and the second is the same as getElementsByTagName ‘s first argument, which is the tag name.

The namespace is the XML namespace, which is part of the syntax for defining a web page with XHTML instead of HTML.

The return value is the same as getElementsByTagName , which is a NodedeList object with all the elements with the given tag and XML namespace.

For example, if we have the following HTML:

<html xmlns="[http://www.w3.org/1999/xhtml](http://www.w3.org/1999/xhtml)">
  <head>
    <title>Foo</title>
  </head>
  <body>
    <p>
      foo
    </p>
    <p>
      foo
    </p>
    <div>
      baz
    </div>
    <script src="main.js"></script>
  </body>
</html>

Then we can write the following JavaScript code in main.js to make the text of the p elements red after 2 seconds:

const pElements = document.getElementsByTagNameNS('[http://www.w3.org/1999/xhtml'](http://www.w3.org/1999/xhtml%27), 'p');

for (const p of pElements) {
  setTimeout(() => {
    p.style.color = 'red';
  }, 2000)

}

We have to name the HTML file with the .xhtml extension to make it this method work since it’s only used for XHTML documents.

Photo by Will Smith on Unsplash

getElementById

The document.getElementById method lets us get a DOM object on the page by ID. We can use it by passing in a string with the ID of the element that we want to select. Since there can only be one element with a given ID per page, it’ll only get one element and return that.

For example, if we have the following HTML:

<p id='foo'>
  foo
</p>

Then we can set the color to red after 2 seconds by writing the following code:

const foo = document.getElementById('foo');
setTimeout(() => {
  foo.style.color = 'red';
}, 2000)

querySelector

Selecting elements to manipulate are clumsy with the methods above since we can only select elements by ID or class name. With the querySelector method, we can use any CSS selector to select an element.

It takes one argument which is a string with the CSS selector for the elements that we want to select. It returns an element object that is the first element that is selected by the CSS selector. It’ll throw a syntax error if the selector we pass in is invalid.

Special characters must be escaped with a backslash.

For example, if we have the following input:

<input type='text'>

Then we want to set a value to it by writing the following code:

const input = document.querySelector('input[type="text"]');

input.value = 'abc';

We can escape special characters in CSS selectors like in the following example. If we have an input with the ID abc , which has the special character slash:

<input type='text' id="abc">

Then we can change its values like in the following code:

const input = document.querySelector('#abc');

input.value = 'abc';

Notice that we have 4 slashes to represent the slash character in the string.

If our element has a colon in the selector, like:

<input type='text' id="ab:c">

Then we can write:

const input = document.querySelector('#ab:c');

input.value = 'abc';

The colon is escaped by adding 2 slashes before it.

querySelectorAll

Like the querySelector method, the querySelectorAll takes a CSS selector string for the elements that we want to select. However, it returns a NodeList object with all the elements that match the CSS selector.

For example, if we can write the following HTML code:

<p class='foo'>
  foo
</p>
<p class='foo'>
  foo
</p>
<p class='baz'>
  baz
</p>

Then we can select all the elements with the class .foo and set the text to red after 2 seconds by writing the following code:

const fooElements = document.querySelectorAll('.foo');

for (const f of fooElements) {
  setTimeout(() => {
    f.style.color = 'red';
  }, 2000)
}

Using the Methods Above with Non-Document Elements

We can use all these methods on non-document DOM objects. For example, if we have the following HTML code:

<div id='bar'>
  <p class='foo'>
    foo
  </p>
  <p class='foo'>
    foo
  </p>
  <p class='baz'>
    baz
  </p>
</div>

Then we can select the bar element with any method and, which returns the bar element DOM object. Then we can use any method include the same one as we use before to select another element in the returned object.

For example, with the above HTML, we can write the following to select all the p elements with the class foo and set their text to red after 2 seconds:

const fooElements = document.querySelector('#bar').querySelectorAll('.foo');

for (const f of fooElements) {
  setTimeout(() => {
    f.style.color = 'red';
  }, 2000)
}

If we replace querySelector with some method that returns NodeList objects, then we’ve to loop through them with the for or for...of loop or get theme element by index.

Most of the methods above are very useful for getting DOM elements and manipulating them. However, we can probably ignore getElementByTagNameNS since we don’t write XHTML files much anymore since HTML 5 was released.

Categories
JavaScript Basics

Introduction to Web Workers

To run tasks that take a long time to complete, we need a way to run them in the background.

Web workers are the way to do that. They live in their own context within the browser. Therefore, they don’t have access to the DOM and have their own global variable, self.

A web worker can’t access the window object, but we still use many things that are parts of it like WebSockets and IndexedDB.

In this article, we’ll look at how to create a basic web worker that can send messages between workers and other scripts.


Characteristics of Web Workers

Web workers allow us to send messages between them and other scripts. Non-worker scripts can listen to messages that are emitted by workers by attaching handlers for messages.

To send messages, we use the postMessage() method. The message can be accessed via the Message event’s data property. Message event objects are passed into the event handler of the onmessage handler.

Workers can spawn new workers, as long as they are hosted within the same origin as the parent page. Workers may also use XmlHttpRequest for network I.O, with the exception that the responseXML and channel attributes on XMLHttpRequest always return null.

In addition to dedicated workers, there are also other kinds of workers:

  • Shared workers are workers that can be used by multiple scripts in different windows, IFrames, etc, as long as they’re in the same domain as the worker. They are more complex than dedicated workers in that scripts must communicate via an active port.
  • Service workers act as proxy services that sit between web apps, the browser, and the network. They are intended to create effective offline experiences. They intercept network requests and take appropriate actions based on whether the network is available.
  • Chrome workers are a Firefox-only type of worker that we can use when developing add-ons and want to use workers in extensions.
  • Audio workers provide the ability to direct scripted audio processing to be done inside a web worker context.

Photo by David Siglin on Unsplash


Creating a Simple Dedicated Worker

We can create a dedicated worker by creating a simple worker script and then have other scripts communicate with it.

For example, we can create a dedicated worker that takes values from two inputs and then compute the result by adding them and sending it back to the screen.

First, we create the HTML for getting the input values as follows:

Then, in main.js, we add keyup event handlers to the inputs to send the values to our dedicated worker as follows:

We also add the onmessage event handler to receive the message from the worker, which we’ll add to worker.js to get the message back from it in the following piece of code:

worker.onmessage = e => {  
  result.textContent = e.data;  
};

Finally, we create the worker code in worker.js as follows:

The code above gets the message from main.js, where we called postMessage. Whatever we passed to postMessage will be in the e parameter and we can get the data from there.

Then, in this file, we get the two numbers we passed in with the data property on the first line of the function.

We then check if both numbers are numbers and if they are, we add them together and then send the sum back to main.js with postMessage after we compute the sum and put the result in the workerResult string.

In the end, we should get something like the following:

As long as the dedicated worker script is in the same domain as our other scripts, it’ll run.

The Worker constructor also takes an option object with the following options:

  • type: A string specifying the type of worker to create. The value can be classic or module and defaults to classic.
  • credentials: A string specifying the type of credentials to use for the worker. The value can be omit (no credentials required), same-origin, or include. If it’s not specified, the type is class, then omit is the default.
  • name: A string specifying the identifying name for the DedicatedWorkerGlobalScope representing the scope of the worker. It’s mainly used for debugging.

The constructor will throw a SecurityError if it’s not allowed to start workers, like if the URL is invalid or the same-origin policy is violated.

  • NetworkError is raised if the MIME type of the worker script is incorrect.
  • SyntaxError is raised is the worker’s URL can’t be parsed.

Creating dedicated workers is simple. They let us run background tasks. Communication between workers and other scripts is by sending messages back and forth.

Web workers have their own context so they can’t use the window object or access the DOM.

Categories
JavaScript JavaScript Basics

Handy JavaScript Shorthands

With the latest versions of JavaScript, the language has introduced more syntactic sugar. In this article, we’ll look at handy shortcuts that are easy to read from versions of JavaScript new and old.

In this article, we’ll look at the ternary operator, declaring multiple variables, arrow functions, default parameter values, and more.

Ternary Operator

We can write an if...else statement in a concise way by using the ternary operator.

Instead of writing:

const x = 20;
let grade;

if (x >= 50) {
  grade = "pass";
} else {
  grade = "fail";
}

We can write:

const x = 20;
let grade = (x >= 50) ? "pass" : "fail";

They both check if x is bigger than or equal to 50 then assign the string 'pass' if it’s true and fail otherwise.

We can also write nested if statement with the ternary operator as follows:

const x = 20;
let grade = (x >= 50) ? "pass" : (x >= 25) ? "good fail" : 'bad fail';

This is the same as:

const x = 20;
let grade;
if (x >= 50) {
  grade = "pass";
} else {
  if (x >= 25) {
    grade = "good fail";
  } else {
    grade = "bad fail";
  }
}

Setting Default Value

We can set a default value if a variable is falsy by writing:

let x;
let y = x || 10;

This is the same as:

let x;
let y;
if (x === undefined || x === null || x === 0 || x === '' || isNaN(x)) {
  y = 10;
}

Because x || 10 means that if x is falsy, which means that x is undefined, null, 0, empty string, or NaN, then we assign 10 to y, which is the same as:

if (x === undefined || x === null || x === 0 || x === '' || isNaN(x)) {
  y = 10;
}

Shorthand for Declaring Multiple Variables

We can declare multiple variables by writing:

let x = y = z = 5;

This is the same as writing:

let x = 5;
let y = 5;
let z = 5;

It works by first assigning 5 to z, then the value of z to y, and finally the value of y to x .

If Truthy Shorthand

A shorthand for checking if something is truthy is JavaScript, which is anything other than undefined, null, 0, empty string, or NaN, is the following:

if (x){
 console.log('x is truthy')
}

The code above checks if x is truthy, then we execute the console.log if it is.

For…Of Loop Shorthand

Since ES6, we can use the for...of loop to loop through variables in an array or array-like object, which includes things like Maps, Sets, the arguments object, generators, iterators, and any object with the [Symbol.iterator] method.

We can write:

let fruits = ['apple', 'orange', 'grape'];
for (let fruit of fruits) {
  console.log(fruit);
}

This is cleaner than using a regular for loop with indexes, and it also works other iterable objects. For example, we can use it with generators:

let fruits = function*() {
  yield 'apple';
  yield 'orange';
  yield 'fruits';
}

for (let fruit of fruits()) {
  console.log(fruit);
}

Photo by Robin Mantz on Unsplash

Array.forEach

We can use the Array.forEach method to loop through an array, although it’s slower than loops.

To use it, we can write something like the following code:

let fruits = ['apple', 'orange', 'grape'];

fruits.forEach((fruit, index) => console.log(fruit));

Decimal Base Exponents

We can specify exponential numbers instead of writing out the whole number out with all the trailing zeroes.

For example, if we have:

1e0

for 1

1e1

for 10

1e2

for 100

1e3

for 1000 and so on.

Digit Separators

The latest browsers let us use an underscore to separate digits to make them easy to read. For example, we can write

100_000_000

for 100 million. Underscores can be placed anywhere we choose.

Object Property Shorthand

Instead of writing:

const foo = 1,
  bar = 2;
const obj = {
  foo: foo,
  bar: bar
};

We can write:

const foo = 1,
  bar = 2;
const obj = {
  foo,
  bar
};

The 2 pieces of code are exactly the same.

Arrow Functions

If an arrow function is only one line, then we don’t need the braces and we can return a value from it without using the return keyword.

For example:

() => 1

is the same as:

() => {
  return 1
}

We can use arrow functions if we don’t care about the value of this since arrow functions don’t change the this value inside the function.

Default Parameter Values

We can specify default parameter values in ES6. For example, we can write:

const add = (a = 1, b = 2) => {
  return a + b
}

This is the same as:

const add = (a, b) => {
  if (typeof a === 'undefined') {
    a = 1;
  }

  if (typeof b === 'undefined') {
    b = 1;
  }
  return a + b
}

The shorthands above are mostly from ES6. This version of JavaScript provides lots of handy shortcuts which lets us write code easier and reading just as easy.

for...of loop is very useful since it can loop through arrays and array-like objects. No other loops can do that.

Digit separators are more recent and available in the latest browsers only.

Categories
TypeScript

Introduction to TypeScript Interfaces — Object Literals and Function Types

The big advantage of TypeScript over plain JavaScript is that it extends the features of JavaScript by adding functionality that ensures the type safety of our program’s objects. It does this by checking the shape of the values that objects take on.

Checking the shape is called duck typing or structural typing. Interfaces are one way to fill the role naming data types in TypeScript. It’s very useful for defining contracts within our code in TypeScript programs. In the last article, we looked at how to define a TypeScript interface and adding required and optional properties to it. In this article, we’ll continue from the previous article and look at other properties of TypeScript interfaces.

Excess Property Checks

Object properties get extra checks when they’re being assigned to a variable with the type designated by an interface. This also applies to object literals that we pass into functions as arguments. For example, the following code wouldn’t be compiled by the TypeScript compiler and give us an error:

interface Person{
  name: string
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}`);
}

greet({ name: 'Joe', foo: 'abc' });

The excess property check done by the TypeScript compiler will reject the code since we have an extra foo property that isn’t defined in the Person interface, so add it in the object in the parameter would fail because of TypeScript’s excess property checks for object literals. Assigning the same object literal to a variable will also fail. For example, if we have the following code:

interface Person{
  name: string
}
const greet = (person: Person) => {
  console.log(`Hello, ${person.name}`);
}
const person: Person = { name: 'Joe', foo: 'abc' };
greet(person);

We would get the error “Type ‘{ name: string; foo: string; }’ is not assignable to type ‘Person’. Object literal may only specify known properties, and ‘foo’” if we try to compile the code with the TypsScript compiler or look at the code at a text editor that supports TypeScript. However, we can use the type assertion operator as to designate the type of the object literal as we like it. So if we’re sure that the object literal if of the type Person even though it has a foo property in it, we can write the following code:

interface Person{
  name: string
}
const greet = (person: Person) => {
  console.log(`Hello, ${person.name}`);
}
const person: Person = { name: 'Joe', foo: 'abc' } as Person;
greet(person);

With the code above, the TypeScript compiler won’t complain of any issues. It’ll just assumes that the object literal is of type Person even though it has a foo property. However, we do have some properties that are dynamic or may only sometimes appear, we can also add a dynamic property to our TypeScript interfaces like in the following code:

interface Person{
  name: string,
  [prop: string]: any
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);
}

const person: Person = { name: 'Jane', age: 20 };
greet(person);

In the code above, we added:

[prop: string]: any

to our Person interface. The line above means that the type Person can have any other property other than name . The property name is a string, which is the case for the dynamic property names in JavaScript, and these dynamic properties can take on any value since specified the any type for the dynamic property. As we can see, we have the following line:

const person: Person = { name: 'Jane', age: 20 };

where our object literal has the age property but it’s not explicitly defined in our interface definition. This is because we have the dynamic property after the name property. The [prop: string] is called the index signature.

We can also get around the excess property check for object literals by assigning a variable to another variable. For example, if we have the following code:

interface Person{
  name: string
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);
}

const person: Person = { name: 'Jane', age: 20 };
greet(person);

which wouldn’t compile and run because of the excess property check, we can get around it by assigning the person constant to a new variable or constant that doesn’t have a type designated to it like we do below:

interface Person{
  name: string
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}`);
}

const person = { name: 'Jane', age: 20 };
greet(person);

The person constant doesn’t have a type designated to it so the excess property check for object literals won’t be run.

The excess property check is recommended to be enforced for simple objects like the ones we have above. For more complex, dynamic objects, we can use the ways we outline above to get around the checks to get the code running. However, do be aware that most excess property errors are actually typos in our code, so they’re legitimate bugs that should be fixed.

Photo by Max Baskakov on Unsplash

Function Types

With TypeScript interfaces, we can also define the signature of functions by designating the data type for each parameter and the return type of the function. This prevents us from passing in parameters that have the wrong data type or forgetting to pass in arguments into our function calls, and also ensures that our function always have the same return type and we won’t be returning things that we don’t expect in our code.

We can define a interface for designating the parameter and return data types of our function, and the function signature like we do in the code below:

interface GreetFn{
  (name: string, age: number): string
}

const greet: GreetFn = (name: string, age: number) => {
  return `Hello, ${name}. You're ${age} years old`;
}

console.log(greet('Jane', 20));

The code above has the function greet that follows the function signature defined on the left side of the colon in the GreetFn interface and the return data type on the right side of the interface, so the code will run and produce output from the console.log statement in the last line. We should get ‘Hello, Jane. You’re 20 years old’. If we designate our greet function with the type GreetFn but our function signature or return type stray away from the ones designated in the GreetFn interface then we’ll get errors. For example, if we have:

interface GreetFn{
  (name: string, age: number): string
}
const greet: GreetFn = (name: string, age: number, foo: any) => {
  return `Hello, ${name}. You're ${age} years old`;
}
console.log(greet('Jane', 20));

Then we’ll get the error message “Type ‘(name: string, age: number, foo: any) => string’ is not assignable to type ‘GreetFn’.(2322)“ since our parameter list doesn’t match the signature listed in the interface. Likewise, if our function’s return type doesn’t match the one we defined in the interface, we’ll also get an error. For example if we have the following code:

interface GreetFn{
  (name: string, age: number): string
}
const greet: GreetFn = (name: string, age: number) => {
  return 0;
}
console.log(greet('Jane', 20));

We’ll get the error “Type ‘(name: string, age: number) => number’ is not assignable to type ‘GreetFn’. Type ‘number’ is not assignable to type ‘string’.” This means that the greet function must return a string since we specified that the type of the greet function is GreetFn .

Function parameters are checked one at a time, so the TypeScript compiler infers the position of the type of a parameter by its position even though no types are designated by us when we define our function. For example, the following will still work even though we didn’t specified the type of our parameters explicitly:

interface GreetFn{
  (name: string, age: number): string
}
const greet: GreetFn = (name, age) => {
  return `Hello, ${name}. You're ${age} years old`;
}
console.log(greet('Jane', 20));

If we pass in something with the wrong data type according to the interface we defined like in the code below, we’ll get an error:

interface GreetFn{
  (name: string, age: number): string
}
const greet: GreetFn = (name, age) => {
  return `Hello, ${name}. You're ${age} years old`;
}
console.log(greet('Jane', ''));

When we try to compile the code above, we’ll get the error “Argument of type ‘“”’ is not assignable to parameter of type ‘number’.(2345)“. This means that TypeScript is smart enough to infer the type by its position. Type inference is also done for the return type, so if we write the following code:

interface GreetFn{
  (name: string, age: number): string
}
const greet: GreetFn = (name, age) => {
  return 0;
}
console.log(greet('Jane', 20));

Then we’ll get the error “Type ‘(name: string, age: number) => number’ is not assignable to type ‘GreetFn’. Type ‘number’ is not assignable to type ‘string’.(2322)” so the code won’t compile.

Excess property checks for object literals are useful since it’s much harder for us to add wrong properties or typos into our code when we’re assigning object literals or passing them in as arguments of functions. We can get around it with type assertion or assigning to a variable with different types or no types. We can also define interfaces for functions to define the expected parameters for a function and also the expected return type for them.

Categories
JavaScript JavaScript Best Practices

JavaScript Best Practices — Modules and Identifiers

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Minimum and Maximum Identifier Lengths

We should make identifier descriptive so they definitely shouldn’t be too short.

It also shouldn’t be too long.

If it’s too short, it’s probably not descriptive enough.

If it’s too long, then it has redundant information and takes up too much space.

So we shouldn’t write:

const x = 5;

since we don’t know what x is.

But we can write something like const numApples = 1; .

Location of Arrow Function Bodies with Implicit Returns

We should put arrows in a logical location.

For instance, we can write:

(foo) => bar;

or

(foo) => (bar);

or

(foo) => bar => baz;

They are conventional and logical.

It’s better than these examples:

(foo) =>  
  bar;

or:

(foo) =>  
  (bar);

The first examples are more compact than these ones.

Default Imports

We’ve have a default export for a default import to work.

For instance, we should write:

foo.js

export default function () { return 100 }

Then we should import it by writing:

import foo from './foo';

For CommonJS modules, we can write:

foo.js

module.exports = function () { return 100 }

Then we can import it by writing:

const foo = require('./foo');

Repeated Imports and Exports

We can only have one default export in each module.

For instance, we can’t write:

export default class Foo { /*...*/ } 
export default class Bar { /*...*/ }

We have to remove one of them.

If we remove one:

export default class Foo { /*...*/ }

Then we can write:

import Foo from './foo';

Export Statements that Come Before Other Statements

We shouldn’t have export statements that come before other statements.

For instance, we don’t want to fix statements like:

const bool = true
export default bool
const str = 'foo'

Moving all exports to the bottom would make it easier to read.

Use of File Extension within the Import Path

When we write imports, we don’t need an extension for JavaScript files.

For instance, instead of writing:

import bar from './foo/bar.js';

We write:

import bar from './foo/bar';

However, we should put the extension for JSON import.

For instance, we can write:

import bar from './bar.json';

We may also do the same thing for JavaScript files that don’t have the .js extension.

For instance, we can write:

import Component from './Component.jsx';

for JSX files.

Putting Imports First

We should put import statements first.

For instance, we should put them at the top.

Instead of writing:

import foo from './foo'
doSomething(foo)
import bar from './bar'

We write:

import foo from './foo';  
import bar from './bar';
doSomething(foo);

Grouping Exports

We can group exports together to make finding them easier.

For instance, we can write:

const first = 'foo';  
const second = 'bar';
export {  
  first,  
  second,  
}

With CommonJS modules, we can write:

const test = {};  
test.foo = true  
test.bar  = true
module.exports = test;

We also grouped the exports together.

Named Exports

We can import named module members in various ways.

Given that we have:

export const foo = "foo"

We can write:

import { foo } from './foo'

Also, we can write:

export { foo as bar } from './foo'

to export it as bar .

We can also import it as bar:

import { foo as bar } from './foo'

Newline After Import

After a group of imports, we can put a new line to separate the imports from the rest of the code.

For instance, we can write:

import defaultExport from './foo'  
import { bar } from 'bar'  
  
const foo = 'bar';

or:

const foo = require('./foo')  
const bar = require('./bar')  
  
const baz = 1;

for CommonJS modules.

No Absolute Paths

We shouldn’t use absolute paths for imports because it’ll only work on the current computer’s current folder strucutre.

If we move it anywhere, it won’t work.

So we shouldn’t write:

import f from '/foo';  
import bar from '/some/path';

or:

const f = require('/foo');  
const bar = require('/some/path');

Instead, we write:

import f from './foo';  
import bar from './some/path';

or:

const f = require('./foo');  
const bar = require('./some/path');

Conclusion

We should write our imports and exports properly.

Never import with absolute paths.

And we should group them together.

Identifier lengths shouldn’t be too long or short.