Korzystanie z Node.js wymaga importu / eksportu ES6

930

W projekcie, nad którym współpracuję, mamy dwie możliwości wyboru systemu, z którego możemy korzystać:

  1. Importowanie modułów za pomocą requirei eksportowanie za pomocą module.exportsi exports.foo.
  2. Importowanie modułów za pomocą ES6 importi eksportowanie za pomocą ES6export

Czy są jakieś korzyści związane z wydajnością wynikające z używania jednego nad drugim? Czy jest coś jeszcze, co powinniśmy wiedzieć, gdybyśmy mieli używać modułów ES6 zamiast Node?

kpimov
źródło
9
node --experimental-modules index.mjspozwala używać importbez Babel i działa w węźle 8.5.0+. Możesz (i powinieneś) również publikować swoje pakiety npm jako natywny ESModule , ze starą kompatybilnością wsteczną require.
Dan Dascalescu

Odpowiedzi:

728

Czy są jakieś korzyści związane z wydajnością wynikające z używania jednego nad drugim?

Pamiętaj, że nie ma jeszcze silnika JavaScript, który natywnie obsługuje moduły ES6. Sam powiedziałeś, że używasz Babel. Babel importi tak konwertuje i exportdeklaruje domyślnie na CommonJS ( require/ module.exports). Więc nawet jeśli używasz składni modułu ES6, będziesz używać CommonJS pod maską, jeśli uruchomisz kod w węźle.

Istnieją techniczne różnice między modułami CommonJS i ES6, np. CommonJS pozwala na dynamiczne ładowanie modułów. ES6 na to nie pozwala, ale w tym celu opracowano interfejs API .

Ponieważ moduły ES6 są częścią standardu, skorzystałbym z nich.

Felix Kling
źródło
16
Próbowałem korzystać ES6 importz requireale pracował inaczej. CommonJS eksportuje samą klasę, podczas gdy istnieje tylko jedna klasa. ES6 eksportuje, tak jak istnieje wiele klas, więc musisz użyć, .ClassNameaby uzyskać eksportowaną klasę. Czy są jakieś inne różnice, które faktycznie wpływają na wdrożenie
Thellimist
78
@Entei: Wydaje się, że chcesz domyślnego eksportu, a nie eksportu nazwanego. module.exports = ...;jest równoważne z export default .... exports.foo = ...jest równoważne z export var foo = ...;
Felix Kling,
10
Warto zauważyć, że chociaż Babel ostatecznie transponuje się importdo CommonJS w węźle, używanego wraz z pakietem Webpack 2 / Rollup (i dowolnym innym programem pakującym, który pozwala na wstrząsanie drzewem ES6), możliwe jest, aby skończyć z plikiem, który jest znacznie mniejszy niż równoważny kod Node chrupie poprzez zastosowanie requiredokładnie ze względu na fakt, że ES6 umożliwia statyczną analizę importu / eksportu. Chociaż nie ma to znaczenia dla Node (jeszcze), z pewnością może, jeśli kod ostatecznie skończy jako pojedynczy pakiet przeglądarki.
Lee Benson
5
chyba że musisz wykonać import dynamiczny
chulian
3
Moduły ES6 są w najnowszej wersji V8, a także pojawiają się w innych przeglądarkach za flagami. Zobacz: medium.com/dev-channel/…
Nexii Malthus
180

Istnieje kilka zastosowań / możliwości, które warto rozważyć:

Wymagać:

  • Możesz mieć ładowanie dynamiczne tam, gdzie nazwa załadowanego modułu nie jest predefiniowana / statyczna, lub gdy warunkowo ładujesz moduł tylko wtedy, gdy jest „naprawdę wymagany” (w zależności od określonego przepływu kodu).
  • Ładowanie jest synchroniczne. Oznacza to, że jeśli masz wiele requires, są one ładowane i przetwarzane jeden po drugim.

Import ES6:

  • Możesz użyć nazwanego importu, aby selektywnie załadować tylko potrzebne części. To może zaoszczędzić pamięć.
  • Import może być asynchroniczny (w obecnym module ładującym ES6 tak naprawdę jest) i może działać nieco lepiej.

Ponadto system modułowy Wymagaj nie jest oparty na standardzie. Jest mało prawdopodobne, aby stał się standardem, skoro istnieją moduły ES6. W przyszłości będzie dostępna natywna obsługa modułów ES6 w różnych implementacjach, co będzie korzystne pod względem wydajności.

Amit
źródło
16
Co sprawia, że ​​uważasz, że import ES6 jest asynchroniczny?
Felix Kling
5
@FelixKling - połączenie różnych obserwacji. Za pomocą JSPM (moduł ładujący ES6 ...) zauważyłem, że podczas importowania zmodyfikowanej globalnej przestrzeni nazw efekt nie jest obserwowany wewnątrz innych importów (ponieważ występują one asynchronicznie. Można to również zobaczyć w transpilowanym kodzie). Ponadto, ponieważ takie zachowanie (1 import nie wpływa na innych), nie ma powodu, aby tego nie robić, więc może być zależne od implementacji
Amit
35
Wspominasz coś bardzo ważnego: moduł ładujący. Chociaż ES6 zapewnia składnię importu i eksportu, nie określa, w jaki sposób moduły powinny być ładowane. Ważną częścią jest to, że deklaracje są analizowane statycznie, dzięki czemu można określić zależności bez wykonywania kodu. Pozwoliłoby to modułowi ładującemu ładować moduł synchronicznie lub asynchronicznie. Ale same moduły ES6 nie są synchroniczne ani asynchroniczne.
Felix Kling
5
Moduł ładujący @FelixKling ES6 został oznaczony w OP, więc zakładam, że ma on znaczenie dla odpowiedzi. Stwierdziłem również, że w oparciu o obserwacje asynchroniczne jest bieżące zachowanie, a także możliwość w przyszłości (w dowolnej implementacji), więc warto wziąć to pod uwagę. Myślisz, że to źle?
Amit
10
Myślę, że ważne jest, aby nie łączyć systemu / składni modułu z modułem ładującym. Np. Jeśli tworzysz dla węzła, prawdopodobnie i tak kompilujesz moduły ES6 require, więc i tak używasz systemu modułu i modułu ładującego.
Felix Kling
41

Główne zalety są składniowe:

  • Bardziej deklaratywna / kompaktowa składnia
  • Moduły ES6 w zasadzie sprawią, że UMD (Universal Module Definition) stanie się przestarzałe - zasadniczo usunie schizmę między CommonJS i AMD (serwer kontra przeglądarka).

Z modułami ES6 raczej nie zauważysz żadnych korzyści w zakresie wydajności. Nadal będziesz potrzebować dodatkowej biblioteki, aby spakować moduły, nawet jeśli przeglądarka ma pełne wsparcie dla funkcji ES6.

snozza
źródło
4
Czy możesz wyjaśnić, dlaczego potrzebny jest pakiet, nawet jeśli przeglądarki mają pełną obsługę modułu ES6?
E. Sundin,
1
Przeprosiny, zredagowane dla większego sensu. Miałem na myśli, że funkcja importowania / eksportowania modułów nie jest natywnie zaimplementowana w żadnej przeglądarce. Nadal wymagany jest transpiler.
snozza
16
Wydaje mi się to trochę sprzeczne. Jeśli istnieje pełne wsparcie, to jaki jest cel pakietu? Czy czegoś brakuje w specyfikacji ES6? Co właściwie zrobiłby pakiet, który nie byłby dostępny we w pełni obsługiwanym środowisku ?
E. Sundin
1
Jak powiedział @snozza ... „funkcja importu / eksportu modułów nie jest naiwnie zaimplementowana w żadnej przeglądarce. Nadal wymagany jest transpiler”
robertmain,
2
Nie potrzebujesz już żadnych dodatkowych bibliotek. Od wersji 8.5.0 (wydanej ponad rok temu) node --experimemntal-modules index.mjspozwala używać importbez Babel. Możesz (i powinieneś) również publikować swoje pakiety npm jako natywny ESModule, ze starą kompatybilnością wstecznąrequire . Wiele przeglądarek obsługuje również import dynamiczny .
Dan Dascalescu
38

Czy są jakieś korzyści związane z wydajnością wynikające z używania jednego nad drugim?

Obecna odpowiedź brzmi „nie”, ponieważ żaden z obecnych silników przeglądarek nie implementuje import/exportstandardu ES6.

Niektóre tabele porównawcze http://kangax.github.io/compat-table/es6/ nie biorą tego pod uwagę, więc kiedy widzisz prawie wszystkie zielenie dla Chrome, bądź ostrożny. importsłowo kluczowe z ES6 nie zostało uwzględnione.

Innymi słowy, obecne silniki przeglądarek, w tym V8, nie mogą importować nowego pliku JavaScript z głównego pliku JavaScript za pomocą żadnej dyrektywy JavaScript.

(Być może będziemy jeszcze za kilka błędów lub za kilka lat, dopóki V8 nie wdroży tego zgodnie ze specyfikacją ES6).

Tego dokumentu potrzebujemy, a tego dokumentu musimy przestrzegać.

A standard ES6 mówi, że zależności modułu powinny istnieć, zanim przeczytamy moduł, tak jak w języku programowania C, w którym mieliśmy .hpliki (nagłówki) .

Jest to dobra i sprawdzona struktura, i jestem pewien, że eksperci, którzy stworzyli standard ES6, mieli to na uwadze.

Dzięki temu Webpack lub inne programy pakujące pakiety mogą zoptymalizować pakiet w niektórych szczególnych przypadkach i zmniejszyć niektóre zależności od pakietu, które nie są potrzebne. Ale w przypadkach, w których mamy doskonałe zależności, nigdy się to nie wydarzy.

Potrzebne będzie trochę czasu, zanim import/exportnatywna obsługa zostanie uruchomiona, a requiresłowo kluczowe nigdzie nie zniknie.

Co to jest require?

Jest to node.jssposób na ładowanie modułów. ( https://github.com/nodejs/node )

Węzeł używa metod na poziomie systemu do odczytu plików. Zasadniczo polegasz na tym podczas korzystania require. requirezakończy się wywołaniem systemowym, takim jak uv_fs_open(zależy od systemu końcowego, Linux, Mac, Windows), aby załadować plik / moduł JavaScript.

Aby sprawdzić, czy to prawda, spróbuj użyć Babel.js, a zobaczysz, że importsłowo kluczowe zostanie przekonwertowane na require.

wprowadź opis zdjęcia tutaj

prosti
źródło
2
W rzeczywistości istnieje jeden obszar, w którym można poprawić wydajność - rozmiar pakietu. Użycie importw procesie kompilacji pakietu Webpack 2 / Rollup może potencjalnie zmniejszyć rozmiar wynikowego pliku przez „wstrząsanie drzewem” nieużywanych modułów / kodu, które w przeciwnym razie mogłyby skończyć w ostatecznym pakiecie. Mniejszy rozmiar pliku = szybsze pobieranie = szybsze uruchamianie / uruchamianie na kliencie.
Lee Benson
2
rozumowanie było takie, że obecna przeglądarka na Ziemi nie pozwala na import słowo kluczowe natywnie. Lub oznacza to, że nie można zaimportować innego pliku JavaScript z pliku JavaScript. Dlatego nie można porównywać korzyści wydajnościowych tych dwóch. Oczywiście narzędzia takie jak Webpack1 / 2 lub Browserify radzą sobie z kompresją. Są od jednego
prosti
4
Pomijasz „drżenie drzewa”. Nigdzie w twoim linku nie jest poruszone drżenie drzew. Korzystanie z modułów ES6 pozwala, bo importi exportsą statyczne deklaracje, które importują ścieżkę kodu specyficzny, natomiast requiremoże być dynamiczny, a więc pakiet w kodzie, który nie jest używany. Korzyść z wydajności jest pośrednia - Webpack 2 i / lub pakiet zbiorczy mogą potencjalnie skutkować mniejszymi rozmiarami pakietów, które są szybsze do pobrania, a zatem wydają się być szybsze dla użytkownika końcowego (przeglądarki). Działa to tylko wtedy, gdy cały kod jest zapisany w modułach ES6, dlatego import może być analizowany statycznie.
Lee Benson,
2
Zaktualizowałem odpowiedź @LeeBenson, myślę, że jeśli weźmiemy pod uwagę natywną obsługę silników przeglądarek, nie możemy jeszcze porównać. To, co przydaje się jako przydatna opcja potrząsania za pomocą pakietu Webpack, można również osiągnąć, zanim jeszcze ustawimy moduły CommonJS, ponieważ dla większości rzeczywistych aplikacji wiemy, jakie moduły należy zastosować.
prosti
1
Twoja odpowiedź jest całkowicie poprawna, ale myślę, że porównujemy dwie różne cechy. Wszystko import/export jest konwertowane na require, przyznane. Ale to, co dzieje się przed tym krokiem, można uznać za zwiększenie wydajności. Przykład: Jeśli lodashjest napisane w ES6, a ty import { omit } from lodash, ostateczny pakiet będzie zawierał TYLKO „pomiń”, a nie inne narzędzia, podczas gdy prosty require('lodash')zaimportuje wszystko. Zwiększy to rozmiar pakietu, pobieranie potrwa dłużej, a tym samym zmniejszy wydajność. Jest to oczywiście ważne tylko w kontekście przeglądarki.
Lee Benson,
31

Korzystanie z modułów ES6 może być przydatne do „wstrząsania drzewem”; tzn. umożliwiając Webpack 2, pakietowi zbiorczemu (lub innym programom pakującym) identyfikowanie ścieżek kodu, które nie są używane / importowane, a zatem nie wchodzą w wynikowy pakiet. Może to znacznie zmniejszyć rozmiar pliku poprzez wyeliminowanie kodu, którego nigdy nie potrzebujesz, ale z CommonJS jest domyślnie dołączany do pakietu, ponieważ Webpack i inni nie mają możliwości dowiedzenia się, czy jest potrzebny.

Odbywa się to za pomocą analizy statycznej ścieżki kodu.

Na przykład za pomocą:

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

... daje programowi pakującemu wskazówkę, która package.anotherPartnie jest wymagana (jeśli nie zostanie zaimportowana, nie będzie można jej użyć - prawda?), więc nie będzie kłopotać się jej pakowaniem.

Aby włączyć to dla Webpack 2, musisz upewnić się, że twój transpiler nie wyrzuca modułów CommonJS. Jeśli używasz es2015wtyczki z Babel, możesz ją wyłączyć w następujący .babelrcsposób:

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

Pakiet zbiorczy i inne mogą działać inaczej - wyświetl dokumenty, jeśli jesteś zainteresowany.

Lee Benson
źródło
2
również świetnie nadaje się do
prosti
25

Jeśli chodzi o asynchroniczne lub leniwe ładowanie, to import ()jest o wiele bardziej wydajny. Zobacz, kiedy wymagamy komponentu w sposób asynchroniczny, a następnie używamy importgo w sposób asynchroniczny, jak w constzmiennej zmiennej await.

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

Lub jeśli chcesz użyć require(),

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

Rzecz jest w import()rzeczywistości asynchroniczna. Jak wspomniał neehar venugopal w ReactConf , możesz go użyć do dynamicznego ładowania komponentów reagujących na architekturę po stronie klienta.

Jest także o wiele lepszy, jeśli chodzi o routing. Jest to jedna szczególna rzecz, która powoduje, że dziennik sieciowy pobiera niezbędną część, gdy użytkownik łączy się z określoną witryną internetową do określonego komponentu. np. strona logowania przed pulpitem nawigacyjnym nie pobierałaby wszystkich składników deski rozdzielczej. Ponieważ to, co jest potrzebne, tj. Komponent logowania, zostanie tylko pobrany.

To samo dotyczy export: ES6 exportsą dokładnie takie same jak dla CommonJS module.exports.

UWAGA - Jeśli opracowujesz projekt node.js, musisz ściśle używać, require()ponieważ węzeł zgłosi błąd wyjątku, tak invalid token 'import'jakbyś go użył import. Zatem węzeł nie obsługuje instrukcji importu.

AKTUALIZACJA - zgodnie z sugestią Dana Dascalescu : Od wersji 8.5.0 (wydanej we wrześniu 2017 r.) node --experimental-modules index.mjsPozwala używać importbez Babel. Możesz (i powinieneś) również publikować swoje pakiety npm jako natywny ESModule, ze starą kompatybilnością wstecznąrequire .

Zobacz to, aby uzyskać więcej informacji o tym, gdzie używać importów asynchronicznych - https://www.youtube.com/watch?v=bb6RCrDaxhw

Poznaj Zaveri
źródło
1
Czy więc będzie wymagana synchronizacja i czekanie?
baklazan
1
Można powiedzieć rzeczowo!
Spotkaj się Zaveri
15

Najważniejsze, aby wiedzieć, że moduły ES6 są rzeczywiście oficjalnym standardem, podczas gdy moduły CommonJS (Node.js) nie są.

W 2019 r. Moduły ES6 są obsługiwane przez 84% przeglądarek. Podczas gdy Node.js umieszcza je za flagą --experimental-modules , istnieje również wygodny pakiet węzłów o nazwie esm , który sprawia, że ​​integracja jest płynna.

Innym problemem, na który prawdopodobnie natkniesz się między tymi systemami modułów, jest lokalizacja kodu. Node.js zakłada, że ​​źródło jest przechowywane w node_moduleskatalogu, podczas gdy większość modułów ES6 jest rozmieszczonych w płaskiej strukturze katalogów. Nie jest to łatwe do pogodzenia, ale można to zrobić poprzez zhakowanie package.jsonpliku za pomocą skryptów przed i po instalacji. Oto przykładowy moduł izomorficzny i artykuł wyjaśniający, jak to działa.

isysd
źródło
8

Ja osobiście używam importu, ponieważ możemy zaimportować wymagane metody członków za pomocą importu.

import {foo, bar} from "dep";

Nazwa pliku: dep.js

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

Podziękowania należą się Paulowi Shanowi. Więcej informacji .

chandoo
źródło
1
Świetny wybór! Czy publikujesz również swoje pakiety npm jako natywny moduł ESM, ze starą kompatybilnością wstecznąrequire ?
Dan Dascalescu
6
możesz zrobić to samo z wymaganiem!
Suisse
4
const {a,b} = require('module.js'); działa również ... jeśli eksportujesz aib
BananaAcid
module.exports = { a: ()={}, b: 22 }- Druga część @BananaAcid odpowiada
Seth McClaine
7

W chwili obecnej importowanie ES6 eksport jest zawsze kompilowany do CommonJS , więc nie ma żadnych korzyści z używania jednego lub drugiego. Chociaż zalecane jest użycie ES6, ponieważ powinno być korzystne, gdy natywna obsługa przeglądarek zostanie wydana. Powodem jest to, że możesz importować częściowe z jednego pliku, podczas gdy w CommonJS musisz wymagać całego pliku.

ES6 → import, export default, export

CommonJS → require, module.exports, exports.foo

Poniżej znajduje się ich powszechne użycie.

Domyślny eksport ES6

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

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

ES6 eksportuje wiele i importuje wiele

// 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

Moduł CommonJS. Eksportuje wiele

// 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
Hasan Sefa Ozalp
źródło
0

Nie jestem pewien, dlaczego (prawdopodobnie optymalizacja - opóźnione ładowanie?) Działa w ten sposób, ale zauważyłem, że importnie można przeanalizować kodu, jeśli nie są używane importowane moduły.
Czego nie można oczekiwać w niektórych przypadkach.

Weźmy znienawidzoną klasę Foo jako naszą przykładową zależność.

foo.ts

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

Na przykład:

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"
})();

Z drugiej strony:

index.ts

import Foo from './foo'
typeof Foo; // any use case
// prints "Foo loaded"
100 tys
źródło