Використання Node.js вимагає порівняно з ES6 імпорту / експорту


930

У проекті, над яким я співпрацюю, у нас є два варіанти, яку модульну систему ми можемо використовувати:

  1. Імпорт модулів за допомогою requireта експорт із використанням module.exportsта exports.foo.
  2. Імпорт модулів за допомогою ES6 importта експорт за допомогою ES6export

Чи є якісь переваги від використання одного над іншим? Чи є ще щось, що ми повинні знати, якби ми використовували модулі ES6 над Node?


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

Відповіді:


728

Чи є якісь переваги від використання одного над іншим?

Майте на увазі, що ще немає механізму JavaScript, який би підтримував ES6 модулі. Ви самі сказали, що використовуєте Babel. 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), можна запустити файл, що значно менший за еквівалентний код завдяки використанню requireсаме тому, що ES6 дозволяє статичний аналіз імпорту / експорту. Незважаючи на те, що це не вплине на Node (все ще), це, безумовно, може, якщо код врешті-решт закінчиться як єдиний пакет браузера.
Лі Бенсон

5
якщо вам не потрібно робити динамічний імпорт
чуліан

3
Модулі ES6 є в останній версії V8, а також з'являються в інших браузерах за прапорами. Дивіться: medium.com/dev-channel/…
Nexii Malthus

180

Ви можете скористатися кількома можливостями використання / можливостями:

Потрібно:

  • Ви можете мати динамічне завантаження там, де завантажене ім'я модуля не заздалегідь визначене / статичне, або де умовно завантажуєте модуль, лише якщо це "справді потрібно" (залежно від певного потоку коду).
  • Завантаження синхронне. Це означає, що якщо у вас кілька requires, вони завантажуються та обробляються по черзі.

Імпорт ES6:

  • Ви можете використовувати імпортний імпорт, щоб вибірково завантажувати лише потрібні вам частини. Це може зберегти пам'ять.
  • Імпорт може бути асинхронним (і в поточному завантажувачі модулів ES6, він насправді є) і може працювати трохи краще.

Крім того, система модуля «Вимагати» не базується на стандартах. Зараз існування модулів ES6 навряд чи стане стандартним. В майбутньому буде наявна підтримка модулів ES6 в різних реалізаціях, що буде вигідним з точки зору продуктивності.


16
Чому ви вважаєте, що імпорт ES6 є асинхронним?
Фелікс Клінг

5
@FelixKling - поєднання різних спостережень. Використовуючи JSPM (ES6 Module Loader ...), я помітив, що при імпорті модифікується глобальний простір імен, ефект не спостерігається всередині іншого імпорту (тому що вони відбуваються асинхронно. Це також можна побачити у перекладеному коді). Крім того, оскільки така поведінка (1 імпорт не впливає на інші) немає причин не робити цього, тому це може залежати від реалізації
Amit

35
Ви згадуєте щось дуже важливе: завантажувач модулів. У той час як ES6 забезпечує синтаксис імпорту та експорту, він не визначає, як слід завантажувати модулі. Важлива частина полягає в тому, що декларації можна аналізувати статично, так що залежність можна визначити без виконання коду. Це дозволило б навантажувачу модулів завантажувати модуль синхронно або асинхронно. Але модулі ES6 самі по собі не є синхронними або асинхронними.
Фелікс Клінг

5
@FelixKling ES6 навантажувач модулів позначений в ОП, тож я припускаю, що він робить його відповідним до відповіді. Також я заявив, що на основі спостережень асинхрон - це поточна поведінка, а також можливість у майбутньому (у будь-якій реалізації), тому це необхідно враховувати. Ви вважаєте, що це неправильно?
Аміт

10
Я думаю, що важливо не зв'язувати систему / синтаксис модулів із завантажувачем модулів. Наприклад, якщо ви розробляєте для вузла, ви, швидше за все, збираєте модулі ES6 в requireбудь-якому випадку, тому ви все одно використовуєте модульну систему і завантажувач.
Фелікс Клінг

41

Основними перевагами є синтаксичні:

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

Ви навряд чи зможете побачити якісь переваги в роботі з модулями ES6. Вам все одно знадобиться додаткова бібліотека для з’єднання модулів, навіть якщо в браузері є повна підтримка функцій ES6.


4
Чи можете ви пояснити, чому потрібен постачальник, навіть якщо браузери мають повну підтримку модуля ES6?
Е. Сундін

1
Вибачення, відредаговані, щоб мати більше сенсу. Я мав на увазі, що функція модулів імпорту / експорту не реалізована в будь-яких браузерах. Ще потрібен транспілер.
snozza

16
Мені це здається трохи суперечливим. Якщо є повна підтримка, то яка мета цього постачальника? Щось не вистачає в специфікації ES6? Що б насправді зробив цей постачальник, який не доступний у повністю підтримуваному середовищі ?
Е. Сундін

1
Як сказав @snozza ... "функція модулів імпорту / експорту не реалізована в жодних браузерах наївно. Транспілер все ще потрібен"
robertmain

2
Вам більше не потрібні додаткові бібліотеки. З v8.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
prosti

4
Ви оглядаєте "струшування дерева". Ніде у вашій суті посилання не обговорюється тремтіння дерева. Використання модулів ES6 дозволяє, тому що importі exportстатичні декларації , які імпортують шлях конкретний код, в той час як requireможе бути динамічним і , таким чином , розшарування в коді , який не використовується. Перевага від продуктивності непряме - Webpack 2 та / або збір може потенційно призвести до менших розмірів пакету, які швидше завантажуються, і тому з’являються оснащеннями для кінцевого користувача (браузера). Це працює лише в тому випадку, якщо весь код записаний у модулях ES6, і тому імпорт може бути проаналізований статично.
Лі Бенсон

2
Я оновив відповідь @LeeBenson, думаю, якщо врахувати вбудовану підтримку двигунів браузера, ми поки не можемо порівняти. Що є зручним трьома способами тремтіння за допомогою Webpack, може бути досягнуто ще до того, як ми встановимо модулі CommonJS, оскільки для більшості реальних програм ми знаємо, які модулі слід використовувати.
prosti

1
Ваша відповідь цілком справедлива, але я думаю, ми порівнюємо дві різні характеристики. Все import/export перетворюється на require, надається. Але те, що відбувається до цього кроку, можна вважати підвищенням продуктивності. Приклад: Якщо lodashнаписано в ES6, а ви import { omit } from lodash, остаточний пакет буде ТОЛЬКО містити "опущення", а не інші утиліти, тоді як простий require('lodash')імпортує все. Це збільшить розмір пакета, завантажуватиме більше часу, а отже, знизить продуктивність. Це, звичайно, справедливо лише в контексті браузера.
Лі Бенсон

31

Використання модулів ES6 може бути корисним для «струшування дерева»; тобто дозволяють Webpack 2, збірці (або іншим постачальникам) ідентифікувати кодові шляхи, які не використовуються / імпортуються, і тому не вносять їх у отриманий пакет. Це може значно зменшити розмір файлу, усунувши код, який вам ніколи не знадобиться, але з CommonJS постачається за замовчуванням, оскільки Webpack та ін не мають можливості знати, чи потрібен він.

Це робиться за допомогою статичного аналізу шляху коду.

Наприклад, використовуючи:

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

... надає постачальнику натяк, який package.anotherPartне потрібен (якщо він не імпортований, його не можна використовувати, правда?), тому він не буде турбувати його пакет.

Щоб увімкнути це для Webpack 2, вам потрібно переконатися, що ваш транспілер не виплює модулі CommonJS. Якщо ви використовуєте es2015плагін з babel, ви можете відключити його .babelrcтак:

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

Збірка та інші можуть працювати по-різному - перегляньте документи, якщо вас цікавить.


2
також відмінно підходить для струшування дерев 2ality.com/2015/12/webpack-tree-shaking.html
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.0 (випущена вересня 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-модулів , є також зручний пакет вузлів під назвою esm , який робить інтеграцію плавною.

Інша проблема, з якою ви, швидше за все, зіткнетеся між цими модульними системами, - це розташування коду. Node.js припускає, що джерело зберігається в node_modulesкаталозі, в той час як більшість модулів ES6 розгорнуті в плоскій структурі каталогів. Зробити це непросто, але це можна зробити, взломивши package.jsonфайл зі скриптами до і після встановлення. Ось приклад ізоморфного модуля та стаття, що пояснює, як це працює.


8

Я особисто використовую імпорт, тому що ми можемо імпортувати потрібні методи, члени за допомогою імпорту.

import {foo, bar} from "dep";

Назва файлу: dep.js

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

Кредит йде на Поля Шаня. Більше інформації .



6
ви можете зробити те ж саме з вимагати!
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"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.