Использование Node.js требует ES6 импорта / экспорта

930

В проекте, над которым я работаю, у нас есть два варианта использования модульной системы:

  1. Импорт модулей с использованием requireи экспорт с использованием module.exportsи exports.foo.
  2. Импорт модулей с использованием ES6 importи экспорт с использованием ES6export

Есть ли преимущества в производительности при использовании одного над другим? Есть ли что-то еще, что мы должны знать, если бы мы использовали модули ES6 вместо узлов?

kpimov
источник
9
node --experimental-modules index.mjsпозволяет использовать importбез Babel и работает в Node 8.5.0+. Вы можете (и должны) также публиковать свои пакеты npm как нативный ESModule с обратной совместимостью по старому require.
Дан Даскалеску

Ответы:

728

Есть ли преимущества в производительности при использовании одного над другим?

Имейте в виду, что еще нет движка JavaScript, который изначально поддерживает модули ES6. Вы сами сказали, что используете Вавилон. В любом случае Babel по умолчанию конвертирует importи exportобъявляет в CommonJS ( require/ module.exports). Так что даже если вы используете синтаксис модуля ES6, вы будете использовать CommonJS под капотом, если будете запускать код в Node.

Существуют технические различия между модулями CommonJS и ES6, например, CommonJS позволяет динамически загружать модули. ES6 не позволяет этого, но для этого существует API .

Поскольку модули ES6 являются частью стандарта, я бы их использовал.

Феликс Клинг
источник
16
Я пытался использовать ES6 importс, requireно они работали по-другому. CommonJS экспортирует сам класс, пока существует только один класс. ES6 экспортирует, как будто есть несколько классов, поэтому вы должны использовать, .ClassNameчтобы получить экспортируемый класс. Есть ли другие различия, которые на самом деле влияют на реализацию
Thellimist
78
@Entei: похоже, вы хотите экспорт по умолчанию, а не именованный экспорт. module.exports = ...;эквивалентно export default .... exports.foo = ...эквивалентно export var foo = ...;
Феликс Клинг
10
Стоит отметить, что хотя Babel в конечном итоге переносится importна CommonJS в Node, который используется вместе с Webpack 2 / Rollup (и любым другим упаковщиком, который позволяет встряхивать дерево ES6), возможно, что файл окажется значительно меньше, чем эквивалентный код, который Node хрустит благодаря использованию requireименно из- за того, что ES6 позволяет проводить статический анализ импорта / экспорта. Хотя это и не будет иметь никакого значения для Node (пока), это, безусловно, может, если код в конечном итоге окажется в виде единого пакета браузера.
Ли Бенсон
5
если вам не нужно делать динамический импорт
chulian
3
Модули ES6 находятся в последней версии V8 и также поставляются в других браузерах за флагами. См: medium.com/dev-channel/...
Nexii Мальтус
180

Есть несколько вариантов использования / возможностей, которые вы можете рассмотреть:

Требуется:

  • Вы можете иметь динамическую загрузку, когда имя загруженного модуля не является предопределенным / статическим, или когда вы условно загружаете модуль, только если он «действительно требуется» (в зависимости от определенного потока кода).
  • Загрузка синхронная. Это означает, что если у вас есть несколько requires, они загружаются и обрабатываются по одному.

ES6 Импорт:

  • Вы можете использовать именованный импорт, чтобы выборочно загрузить только те части, которые вам нужны. Это может сохранить память.
  • Импорт может быть асинхронным (и в текущем ES6 Module Loader это действительно так) и может работать немного лучше.

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

Amit
источник
16
Что заставляет вас думать, что импорт ES6 является асинхронным?
Феликс Клинг
5
@FelixKling - комбинация различных наблюдений. Используя JSPM (ES6 Module Loader ...), я заметил, что когда импорт изменяет глобальное пространство имен, эффект не наблюдается внутри других импортов (потому что они происходят асинхронно. Это также можно увидеть в переданном коде). Кроме того, так как это поведение (1 импорт не влияет на другие), нет причин не делать этого, поэтому это может зависеть от реализации
Amit
35
Вы упоминаете что-то очень важное: модуль загрузки. Хотя ES6 предоставляет синтаксис импорта и экспорта, он не определяет, как следует загружать модули. Важной частью является то, что объявления являются статически анализируемыми, так что зависимости могут быть определены без выполнения кода. Это позволит загрузчику модулей загружать модуль синхронно или асинхронно. Но модули ES6 сами по себе не являются синхронными или асинхронными.
Феликс Клинг
5
Модуль загрузки @FelixKling ES6 был помечен в OP, поэтому я полагаю, что это делает его релевантным ответу. Также я заявил, что на основе наблюдений асинхронность является текущим поведением, а также возможностью в будущем (в любой реализации), поэтому это важный момент для рассмотрения. Вы думаете, что это неправильно?
Амит
10
Я думаю, что важно не связывать модульную систему / синтаксис с загрузчиком модулей. Например, если вы разрабатываете для узла, то, скорее всего, вы компилируете модули ES6 в requireлюбом случае, поэтому вы все равно используете систему модулей и загрузчик Node.
Феликс Клинг
41

Основные преимущества синтаксические:

  • Более декларативный / компактный синтаксис
  • Модули ES6 в основном сделают UMD (Universal Module Definition) устаревшим - по сути, устранит раскол между CommonJS и AMD (сервер против браузера).

Вы вряд ли увидите какие-либо преимущества в производительности с модулями ES6. Вам по-прежнему понадобится дополнительная библиотека для объединения модулей, даже если в браузере имеется полная поддержка функций ES6.

snozza
источник
4
Не могли бы вы уточнить, зачем нужен пакет, даже если браузеры имеют полную поддержку модуля ES6?
Е. Сундин
1
Извинения, отредактированные, чтобы иметь больше смысла. Я имел в виду, что функция импорта / экспорта модулей изначально не реализована ни в одном браузере. Транспортер все еще требуется.
Snozza
16
Это кажется мне немного противоречивым. Если есть полная поддержка, тогда какова цель пакета? Чего-то не хватает в спецификации ES6? Что бы на самом деле делал упаковщик, который недоступен в полностью поддерживаемой среде ?
Е. Сундин
1
Как сказал @snozza ... «Функция модулей импорта / экспорта не реализована ни в каких браузерах наивно. Транспортер все еще требуется»
robertmain
2
Вам больше не нужны дополнительные библиотеки. Начиная с версии 8.5.0 (выпущенной более года назад), можно node --experimemntal-modules index.mjsиспользовать importбез Babel. Вы можете (и должны) также публиковать свои пакеты npm как нативный ESModule с обратной совместимостью по старому require. Многие браузеры также поддерживают динамический импорт .
Дан Даскалеску
38

Есть ли преимущества в производительности при использовании одного над другим?

Текущий ответ - нет, потому что ни один из текущих браузерных движков не реализует import/exportстандарт ES6.

Некоторые таблицы сравнения http://kangax.github.io/compat-table/es6/ не учитывают это, поэтому, когда вы видите почти все зеленые цвета для Chrome, просто будьте осторожны. importКлючевое слово от ES6 не было учтено.

Другими словами, современные браузерные движки, включая V8, не могут импортировать новый файл JavaScript из основного файла JavaScript через любую директиву JavaScript.

(У нас может быть всего несколько ошибок или годы, пока V8 не реализует это в соответствии со спецификацией ES6.)

Этот документ - то, что нам нужно, и этот документ - то, что мы должны соблюдать.

И в стандарте ES6 сказано, что зависимости модуля должны существовать до того, как мы прочитаем модуль, как на языке программирования C, где у нас были (заголовочные) .hфайлы.

Это хорошая и хорошо протестированная структура, и я уверен, что эксперты, создавшие стандарт ES6, имели это в виду.

Это то, что позволяет Webpack или другим пакетным пакетам оптимизировать пакет в некоторых особых случаях и уменьшить некоторые зависимости от пакета, которые не нужны. Но в тех случаях, когда у нас есть идеальные зависимости, этого никогда не произойдет.

Потребуется некоторое время, прежде чем import/exportвстроенная поддержка requireначнет действовать, и ключевое слово никуда не денется в течение длительного времени.

Что такое require?

Это node.jsспособ загрузки модулей. ( https://github.com/nodejs/node )

Узел использует системные методы для чтения файлов. Вы в основном полагаетесь на это при использовании require. requireзавершится некоторым системным вызовом, например uv_fs_open(зависит от конечной системы, Linux, Mac, Windows) для загрузки файла / модуля JavaScript.

Чтобы убедиться, что это правда, попробуйте использовать Babel.js, и вы увидите, что importключевое слово будет преобразовано в require.

введите описание изображения здесь

прости
источник
2
На самом деле, есть одна область, где производительность может быть улучшена - размер пакета. Использование importв процессе сборки Webpack 2 / Rollup может потенциально уменьшить размер получаемого файла путем «встряхивания дерева» неиспользуемых модулей / кода, которые в противном случае могут оказаться в конечном пакете. Меньший размер файла = быстрее загружать = быстрее инициализировать / выполнять на клиенте.
Ли Бенсон
2
Рассуждения были о том, что в настоящее время браузер на планете Земля не позволяет import ключевое слово изначально Или это означает, что вы не можете импортировать другой файл JavaScript из файла JavaScript. Вот почему вы не можете сравнить преимущества производительности этих двух. Но, конечно, такие инструменты, как Webpack1 / 2 или Browserify, могут справиться со сжатием. Они шеи на шею: gist.github.com/substack/68f8d502be42d5cd4942
Прости
4
Вы пропускаете «тряску деревьев». Нигде в вашей ссылке не обсуждается дрожание деревьев. Использование модулей ES6 делает это возможным, поскольку importи exportявляются статическими объявлениями, которые импортируют определенный путь кода, тогда как requireмогут быть динамическими и, следовательно, связываться в коде, который не используется. Выигрыш в производительности косвенный - Webpack 2 и / или Rollup могут потенциально привести к меньшему размеру пакета, который быстрее загружается и, следовательно, кажется более быстрым для конечного пользователя (браузера). Это работает, только если весь код написан в модулях ES6 и, следовательно, импорт может быть подвергнут статическому анализу.
Ли Бенсон
2
Я обновил ответ @LeeBenson, думаю, если мы рассмотрим встроенную поддержку от браузерных движков, мы пока не можем сравнивать. То, что является удобным вариантом трех встряхиваний с помощью Webpack, также может быть достигнуто даже до того, как мы установим модули CommonJS, поскольку для большинства реальных приложений мы знаем, какие модули следует использовать.
Просто
1
Ваш ответ полностью действителен, но я думаю, что мы сравниваем две разные характеристики. Все import/export переделано require, предоставлено. Но то, что происходит перед этим шагом, можно считать повышением производительности. Пример: если lodashнаписано в ES6, и вы import { omit } from lodash, окончательный комплект будет содержать ТОЛЬКО «Пропустить», а не другие утилиты, тогда как простое require('lodash')импортирует все. Это увеличит размер пакета, займет больше времени для загрузки и, следовательно, снизит производительность. Конечно, это действительно только в контексте браузера.
Ли Бенсон
32

Использование модулей ES6 может быть полезно для «тряски деревьев»; т.е. позволяя Webpack 2, Rollup (или другим упаковщикам) идентифицировать пути кода, которые не используются / не импортируются, и, следовательно, не попадают в результирующий пакет. Это может значительно уменьшить размер файла за счет исключения кода, который вам никогда не понадобится, но с CommonJS по умолчанию связан, потому что Webpack и другие не имеют возможности узнать, нужен ли он.

Это делается с помощью статического анализа пути кода.

Например, используя:

import { somePart } 'of/a/package';

... дает сборщику подсказку, которая package.anotherPartне требуется (если она не импортирована, ее нельзя использовать, верно?), так что она не будет мешать ее объединению.

Чтобы включить это для Webpack 2, вам нужно убедиться, что ваш транспортер не выплевывает модули CommonJS. Если вы используете es2015плагин с babel, вы можете отключить его .babelrcследующим образом:

{
  "presets": [
    ["es2015", { modules: false }],
  ]
}

Свернуть и другие могут работать по-другому - просматривать документы, если вы заинтересованы.

Ли Бенсон
источник
2
также отлично подходит
prosti
25

Когда дело доходит до асинхронной или, может быть, ленивой загрузки, то import ()она гораздо мощнее. Посмотрите, когда нам требуется компонент асинхронным способом, тогда мы используем importего некоторым асинхронным способом, как при constиспользовании переменной await.

const module = await import('./module.js');

Или если вы хотите использовать require()тогда,

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

Дело в том, import()что на самом деле это асинхронный характер. Как упоминает neehar venugopal в ReactConf , вы можете использовать его для динамической загрузки реагирующих компонентов для архитектуры на стороне клиента.

Также это намного лучше, когда речь идет о маршрутизации. Это единственная особенность, которая заставляет сетевой журнал загружать необходимую часть, когда пользователь подключается к определенному веб-сайту к его конкретному компоненту. Например, страница входа перед панелью управления не будет загружать все компоненты панели мониторинга. Потому что то, что нужно текущему, то есть компоненту входа, которое будет загружено только.

То же самое касается export: ES6 exportточно так же, как для CommonJS module.exports.

ПРИМЕЧАНИЕ. - Если вы разрабатываете проект node.js, вы должны строго использовать его, так require()как узел выдаст ошибку исключения, как invalid token 'import'если бы вы использовали import. Таким образом, узел не поддерживает операторы импорта.

ОБНОВЛЕНИЕ - Как предлагает Дэн Даскалеску : начиная с версии 8.5 (выпущено в сентябре 2017 г.), node --experimental-modules index.mjsвы можете использовать importбез Babel. Вы можете (и должны) также публиковать свои пакеты npm как нативный ESModule с обратной совместимостью по старому require.

Посмотрите это, чтобы больше узнать, как использовать асинхронный импорт - https://www.youtube.com/watch?v=bb6RCrDaxhw.

Познакомьтесь с Завери
источник
1
Так будет ли синхронизация и ожидание?
Баклазан
1
Могу сказать по факту!
Познакомьтесь с Завери
15

Самое важное, что нужно знать, это то, что модули ES6 действительно являются официальным стандартом, а модули CommonJS (Node.js) - нет.

В 2019 году модули ES6 поддерживаются 84% браузеров. В то время как Node.js помещает их за флаг --experimental-modules , есть также удобный пакет узлов, называемый esm , который делает интеграцию гладкой.

Другая проблема, с которой вы можете столкнуться между этими модульными системами, - это местоположение кода. Node.js предполагает, что источник хранится в node_modulesкаталоге, в то время как большинство модулей ES6 развернуто в плоской структуре каталогов. Это нелегко согласовать, но это можно сделать, взломав package.jsonфайл с помощью сценариев до и после установки. Вот пример изоморфного модуля и статья, объясняющая, как он работает.

isysd
источник
8

Я лично использую импорт, потому что мы можем импортировать необходимые методы, члены, используя импорт.

import {foo, bar} from "dep";

Имя файла : dep.js

export foo function(){};
export const bar = 22

Кредит идет на Пола Шана. Более подробная информация .

chandoo
источник
1
Хороший выбор! Вы также публикуете свои пакеты npm как нативный ESModule с обратной совместимостью по старому require?
Дан Даскалеску
6
вы можете сделать то же самое с require!
Suisse
4
const {a,b} = require('module.js'); работает также ... если вы экспортируете aиb
BananaAcid
module.exports = { a: ()={}, b: 22 }- Вторая часть @BananaAcid отвечает
Сет МакКлейн
7

На данный момент импорт ES6, экспорт всегда компилируется в CommonJS , поэтому использование того или другого не имеет смысла . Хотя использование ES6 рекомендуется, так как оно должно быть выгодно, когда выпущена встроенная поддержка браузеров. Причина в том, что вы можете импортировать партиалы из одного файла, в то время как с CommonJS вам потребуется весь файл.

ES6 → import, export default, export

CommonJS → require, module.exports, exports.foo

Ниже приводится общее использование тех.

ES6 экспорт по умолчанию

// hello.js
function hello() {
  return 'hello'
}
export default hello

// app.js
import hello from './hello'
hello() // returns hello

ES6 экспортировать несколько и импортировать несколько

// hello.js
function hello1() {
  return 'hello1'
}
function hello2() {
  return 'hello2'
}
export { hello1, hello2 }

// app.js
import { hello1, hello2 } from './hello'
hello1()  // returns hello1
hello2()  // returns hello2

CommonJS module.exports

// hello.js
function hello() {
  return 'hello'
}
module.exports = hello

// app.js
const hello = require('./hello')
hello()   // returns hello

CommonJS module.exports несколько

// hello.js
function hello1() {
  return 'hello1'
}
function hello2() {
  return 'hello2'
}
module.exports = {
  hello1,
  hello2
}

// app.js
const hello = require('./hello')
hello.hello1()   // returns hello1
hello.hello2()   // returns hello2
Хасан Сефа Озальп
источник
0

Не уверен, почему (возможно, оптимизация - ленивая загрузка?) Это работает так, но я заметил, что importможет не анализировать код, если импортированные модули не используются.
Который не может быть ожидаемым поведением в некоторых случаях.

Возьмите ненавистный класс Foo в качестве нашего примера зависимости.

foo.ts

export default class Foo {}
console.log('Foo loaded');

Например:

index.ts

import Foo from './foo'
// prints nothing

index.ts

const Foo = require('./foo').default;
// prints "Foo loaded"

index.ts

(async () => {
    const FooPack = await import('./foo');
    // prints "Foo loaded"
})();

С другой стороны:

index.ts

import Foo from './foo'
typeof Foo; // any use case
// prints "Foo loaded"
l00k
источник