Понимание модулей JavaScript: всестороннее погружение в импорт ES6 и импорт CommonJS для начинающих и средних

В мире JavaScript структура и организация кода претерпели значительные изменения с момента появления ES6. Он предоставил нам множество новых возможностей для написания кода, который можно использовать повторно и которым легче управлять. Среди преобразующих изменений, внесенных в существующие функции, выделяется модульная система. Однако до ES6 CommonJS доминировала в качестве доминирующей модульной системы, которая по-прежнему широко используется, особенно в Node.js.

Чтобы писать оптимизированный код, важно понимать основные различия между импортом ES6 и CommonJS. В этом сообщении блога мы рассмотрим несколько различий между ними и рассмотрим их принципы работы, синтаксические различия, а также их соответствующие преимущества и ограничения.

Синтаксические различия

Между импортом ES6 и CommonJS есть несколько различий, которые мы рассмотрим на следующих примерах:

Синтаксис импорта ES6:

import { module1, module2 } from 'module';
import defaultModule from 'module';

Синтаксис импорта CommonJS:

const { module1, module2 } = require('module');
const defaultModule = require('module');

В ES6 ключевое слово import используется для импорта определенных именованных экспортов или экспорта по умолчанию из модуля. В CommonJS функция require используется для импорта модулей.

Статическое и время выполнения разрешение импорта

Разрешение статического импорта (модули ES6):

  • Импорт разрешается во время разбора модуля перед выполнением кода.
  • Загрузчик модулей анализирует операторы импорта и разрешает их соответствующим модулям.
  • Пути в операторах импорта оцениваются статически, что позволяет выполнять статический анализ и оптимизацию. Статический анализ — это процесс анализа кода без его выполнения, обычно на этапе компиляции или синтаксического анализа.
  • Обеспечивает эффективное встряхивание дерева и удаление мертвого кода с помощью упаковщиков.
  • Поддерживает статический анализ и лучшую оптимизацию производительности.
  • Поддерживает динамический импорт, при котором путь к модулю можно определить во время выполнения. Он возвращает обещание, которое разрешается в объект модуля.
function loadModule() {
  return import('./module').then((module) => {
    // Use the module object
  });
}

loadModule();

Разрешение импорта во время выполнения (модули CommonJS):

  • Импорт разрешается во время выполнения по мере выполнения кода. Это означает, что модули загружаются и выполняются блокирующим образом во время выполнения программы.
  • Путь к модулю определяется динамически на основе значения оператора require во время выполнения.
  • Позволяет выполнять условный или вычисляемый импорт, который объявляет оператор require в любом месте кода.
  • Это может усложнить статический анализ и оптимизацию для сборщиков.

Взаимодействие с Node.js

Синтаксис импорта ES6 несовместим напрямую со средой Node.js. Нам нужно использовать транспилятор, такой как Babel, для преобразования синтаксиса ES6 в синтаксис CommonJS или использовать инструмент, такой как esm или ts-node, для транспиляции во время выполнения.

Чтобы использовать синтаксис импорта ES6 в среде Node.js без транспиляции, мы также можем включить флаг --experimental-modules (для расширения файла .mjs).

node --experimental-modules app.js

Живые привязки против кэшированных значений

В JavaScript концепции Live Bindings и Cached Values связаны с тем, как импорт обрабатывается в модулях ES6 (import/export) и модулях CommonJS (require/module.exports) соответственно.

В ES6 modules импорт создает живые привязки. Это означает, что импортированные значения по существу являются ссылками на исходные экспортированные значения. Если экспортированное значение изменится, импортированное значение отразит это изменение. Он устанавливает динамическую связь между импортированными и экспортируемыми значениями, что позволяет выполнять оперативные обновления.

// moduleA.mjs
export let count = 0;

export function increment() {
  count++;
}
// moduleB.mjs
import { count, increment } from './moduleA.js';

console.log(count); // Output: 0

increment();
console.log(count); // Output: 1

В этом случае переменная count в moduleB.mjs является активной привязкой к экспорту count в moduleA.mjs. Если count обновляется в moduleA.mjs, изменение будет отражено в moduleB.mjs. Такое динамическое поведение характерно для живых креплений.

В CommonJS modules импорт представляет собой кэшированные значения. Когда модуль импортируется, требуемые значения оцениваются и кэшируются. Любые последующие изменения в импортированном значении не будут отражены обратно, а кэшированное значение будет использоваться каждый раз при доступе к нему.

// moduleA.js
let count = 0;

function increment() {
  count++;
}

module.exports = {
  count,
  increment
};
// moduleB.js
const { count, increment } = require('./moduleA.js');

console.log(count); // Output: 0

increment();
console.log(count); // Output: 0

В этом примере moduleB.js требует функций count и increment из moduleA.js. Однако при импорте создается кэшированное значение, а не динамическая привязка. Следовательно, даже если вызывается increment, последующий вызов count в moduleB.js все равно даст исходное значение, поскольку импортированные значения не обновляются.

Понимание поддержки браузеров

Браузеры имеют встроенную поддержку модулей ES6 (синтаксис импорта/экспорта), но не имеют встроенной поддержки модулей CommonJS (синтаксис require/module.exports) напрямую. Однако есть способы использовать модули CommonJS в среде браузера, используя такие инструменты, как упаковщики или транспилеры.

При использовании модулей CommonJS в браузере можно использовать сборщики, такие как Webpack или Rollup, для преобразования и объединения модулей CommonJS в формат, понятный браузеру.

Вопросы производительности

При сравнении соображений производительности импорта ES6 и CommonJS необходимо учитывать несколько факторов:

  • Загрузка и синтаксический анализ:- ES6 modules анализируются асинхронно, что означает, что они могут загружаться параллельно с другими модулями, тем самым сокращая время первоначальной загрузки. И наоборот, CommonJS modules анализируются синхронно, и они будут блокировать выполнение кода до тех пор, пока модуль не будет полностью загружен. Как следствие, это может привести к замедлению начальной загрузки, особенно при загрузке нескольких больших модулей.
  • Разрешение зависимостей. Статическая природа ES6 modules позволяет более эффективно разрешать зависимости в процессе сборки. Зависимости можно анализировать и разрешать во время компиляции, что позволяет таким инструментам, как сборщики, оптимизировать размер пакета и исключить неиспользуемый импорт. Напротив, CommonJS modules динамически разрешаются во время выполнения. Это динамическое разрешение приводит к небольшому снижению производительности, поскольку процесс разрешения модуля включает поиск требуемого модуля в файловой системе или кэше.
  • Встряхивание дерева:ES6 modules поддержка статического анализа, позволяющая встряхивать дерево, чтобы уменьшить размер пакета и повысить производительность за счет удаления неиспользуемого кода. Напротив, CommonJS modules не поддерживает статический анализ и, следовательно, естественно не способствует встряхиванию дерева. Сборщикам сложно избавиться от неиспользуемых экспортов из модулей CommonJS, что приводит к увеличению размеров пакетов.

Заключение

В заключение, импорт ES6 предлагает более современный и выразительный синтаксис, статическое разрешение импорта и лучшую поддержку браузера. У них также есть потенциал для лучшей оптимизации производительности. Однако импорт CommonJS является стандартом в Node.js и обеспечивает разрешение импорта во время выполнения. Выбор между ними будет зависеть от конкретной среды, инструментов и требований проекта.