Four best practices for writing high quality JavaScript modules

Time:2020-10-31

With the es2015 module, you can divide your application code into reusable, encapsulated, and single task focused modules.

That’s good, but how to construct modules? How many functions and classes should a module have?

This article describes four best practices on how to better organize JavaScript modules.

1. Give priority to named export

When I started using JavaScript modules, I used the default syntax to export a single block defined by the module, whether it’s a class or a function.

For example, this is a will moduleGreeterModule programs exported as default values:

// greeter.js
export default class Greeter {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Hello, ${this.name}!`;
  }
}

Over time, I noticed the difficulty of refactoring the default exported class (or function). When you rename the original class, the class name in the consumer module does not change.

To make matters worse, the editor does not provide autocomplete recommendations for the class names to import.

My conclusion is that the default export does not bring obvious benefits. Then I turnedNamed export

Let’sGreeterName it exit, and then look at the benefits:

// greeter.js
export class Greeter {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Hello, ${this.name}!`;
  }
}

With named export, the editor can better rename: every time you change the original class name, all consumer modules also change the class name.

Autocomplete also suggests imported classes:
Four best practices for writing high quality JavaScript modules
So here’s my suggestion:

“Support named module export to benefit from rename refactoring and code completion. “

Note: when using third-party modules such as react and lodash, the default import is usually OK. The default import name is a constant:React_

2. There is no heavy calculation during import

Module level scopes define functions, classes, objects, and variables. The module can export some of these components. That’s it.

// Module-level scope

export function myFunction() {
  // myFunction Scope
}

Module level scopes should not be subject to heavy computation, such as parsing JSON, making HTTP requests, reading local storage, and so on.

For example, the following module configuration resolution comes from global variablesbigJsonStringConfiguration of:

// configuration.js
export const configuration = {
  // Bad
  data: JSON.parse(bigJsonString)
};

This is a problem becausebigJsonStringThe parsing of is done at the module level.bigJsonStringThe parsing of is actually done when the configuration module is imported:

//Bad: parsing when importing modules
import { configuration } from 'configuration';

export function AboutUs() {
  return <p>{configuration.data.siteName}</p>;
}

At a higher level, the role of module level scope is to define module components, import dependencies, and export common components: This is the dependency resolution process. It should be separated from the Runtime: parsing JSON, issuing requests, handling events.

Let’s refactor the configuration module to perform deferred resolution:

// configuration.js
let parsedData = null;

export const configuration = {
  // Good
  get data() {
    if (parsedData === null) {
      parsedData = JSON.parse(bigJsonString);
    }
    return parsedData;
  }
};

becausedataProperty is defined as agetter, so only users can access itconfiguration.dataOnly whenbigJsonString

//Good: no JSON parsing when importing modules
import { configuration } from 'configuration';

export function AboutUs() {
  //JSON parsing is only performed when calling
  return <p>{configuration.data.companyDescription}</p>;
}

Consumers are more aware of when to perform a large operation, and the user may decide to perform the operation when the browser is idle. Alternatively, a consumer might import a module but not use it for some reason.

This provides an opportunity for deeper Performance Optimization: reducing interaction time and minimizing the work of the main thread.

When importing, the module should not perform any heavy work. Instead, users should decide when to perform runtime operations.

3. Use high cohesion module as much as possible

CohesivenessIt describes the degree to which the components in the module are together.

Functions, classes or variables of high cohesion modules are closely related. They focus on a single task.

formatDateThe module is highly cohesive because its functions are closely related and focuses on date formatting:

// formatDate.js
const MONTHS = [
  'January', 'February', 'March','April', 'May',
  'June', 'July', 'August', 'September', 'October',
  'November', 'December'
];

function ensureDateInstance(date) {
  if (typeof date === 'string') {
    return new Date(date);
  }
  return date;
}

export function formatDate(date) {
  date = ensureDateInstance(date);
  const monthName = MONTHS[date.getMonth())];
  return `${monthName} ${date.getDate()}, ${date.getFullYear()}`;
}

formatDate()ensureDateInstance()andMONTHSThey are closely related to each other.

deleteMONTHSorensureDateInstance()Will destroyformatDate()This is a sign of high cohesion.

4. Avoid long relative path

I find it difficult to understand that the path of a module contains one or more parent folders:

import { compareDates } from '../../date/compare';
import { formatDate } from '../../date/format';

// Use compareDates and formatDate

There is a parent selector../It’s usually not a problem, having two or more is often difficult to master.

That’s why I recommend avoiding the parent folder and using the absolute path:

import { compareDates } from 'utils/date/compare';
import { formatDate } from 'utils/date/format';

// Use compareDates and formatDate

Although it sometimes takes longer to write absolute paths, using absolute paths makes the location of the imported modules clear.

In order to reduce the lengthy absolute path, a new root directory can be introduced. For example, this can be implemented using Babel plugin module resolver.

Use absolute paths instead of long relative paths.

5. Conclusion

JavaScript modules are great for splitting your application logic into separate pieces.

By using a named export instead of a default export, you can more easily rename refactoring and editor autocomplete help when importing named components.

useimport {myFunc} from 'myModule'The only purpose of is to import the myfunc component, that’s all.myModuleThe module level scope of should only define classes, functions, or variables that contain a small amount of content.

How many functions or classes should a component have and how should these components be associated with each component? Support highly cohesive modules: their components should be closely related and perform a common task.

Contains many parent folders../It’s hard to understand the path. Reconstruct them as absolute paths.

What JavaScript module best practices do you use?


Original text: https://dmitripavlutin.com/ja…
Author: Dmitri pavlutin
Translator: be an engineer, not a farmer


Pay attention to the official account and receive the latest articles for the first time. If you have a little help, you can like it, like it, collect it, and give a small reward to the author to encourage the author to write more and better articles.

Four best practices for writing high quality JavaScript modules

Recommended Today

Automatic reporting of function errors by AST

preface Some people around me asked me how to implement automatic error monitoringFunction automatically adds error trapping。 Today, let’s talk about how technology works. Let’s talk about the principle first: when the code is compiled, the loader of Babel is used to hijack all function expressions. And then use itAst (abstract syntax tree)Modify the function […]