Menggunakan Node.js membutuhkan vs. impor / ekspor ES6

930

Dalam sebuah proyek yang saya kolaborasi, kami memiliki dua pilihan sistem modul mana yang dapat kami gunakan:

  1. Mengimpor modul menggunakan require, dan mengekspor menggunakan module.exportsdan exports.foo.
  2. Mengimpor modul menggunakan ES6 import, dan mengekspor menggunakan ES6export

Apakah ada manfaat kinerja untuk menggunakan salah satunya? Apakah ada hal lain yang harus kita ketahui jika kita menggunakan modul ES6 di atas yang Node?

kpimov
sumber
9
node --experimental-modules index.mjsmemungkinkan Anda menggunakan importtanpa Babel dan berfungsi di Node 8.5.0+. Anda dapat (dan harus) juga mempublikasikan paket npm Anda sebagai ESModule asli , dengan kompatibilitas ke belakang untuk requirecara lama .
Dan Dascalescu

Jawaban:

728

Apakah ada manfaat kinerja untuk menggunakan salah satunya?

Perlu diingat bahwa belum ada mesin JavaScript yang secara bawaan mendukung modul ES6. Anda mengatakan sendiri bahwa Anda menggunakan Babel. Konversi Babel importdan exportdeklarasi ke CommonJS ( require/ module.exports) secara default. Jadi, bahkan jika Anda menggunakan sintaks modul ES6, Anda akan menggunakan CommonJS di bawah tenda jika Anda menjalankan kode di Node.

Ada perbedaan teknis antara modul CommonJS dan ES6, mis. CommonJS memungkinkan Anda memuat modul secara dinamis. ES6 tidak mengizinkan ini, tetapi ada API dalam pengembangan untuk itu .

Karena modul ES6 adalah bagian dari standar, saya akan menggunakannya.

Felix Kling
sumber
16
Saya mencoba menggunakan ES6 importdengan requiretetapi mereka bekerja secara berbeda. CommonJS mengekspor kelas itu sendiri sementara hanya ada satu kelas. Ekspor ES6 seperti ada beberapa kelas sehingga Anda harus menggunakan .ClassNameuntuk mendapatkan kelas yang diekspor. Apakah ada perbedaan lain yang sebenarnya mempengaruhi implementasi
Thellimist
78
@ Entei: Sepertinya Anda menginginkan ekspor default, bukan ekspor yang bernama. module.exports = ...;setara dengan export default .... exports.foo = ...setara dengan export var foo = ...;
Felix Kling
10
Perlu dicatat bahwa meskipun Babel pada akhirnya importmentranspilasikan ke CommonJS di Node, yang digunakan bersama Webpack 2 / Rollup (dan bundler lain yang memungkinkan ES6 tree gemetar), dimungkinkan untuk menyelesaikan dengan file yang jauh lebih kecil daripada kode setara Node crunches melalui penggunaan requiretepat karena fakta ES6 memungkinkan analisis statis impor / ekspor. Meskipun ini tidak akan membuat perbedaan untuk Node (belum), tentu bisa jika kode akhirnya akan berakhir sebagai satu bundel browser.
Lee Benson
5
kecuali jika Anda perlu melakukan impor dinamis
chulian
3
Modul ES6 berada di V8 terbaru dan juga tiba di peramban lain di belakang bendera. Lihat: medium.com/dev-channel/…
Nexii Malthus
180

Ada beberapa penggunaan / kemampuan yang mungkin ingin Anda pertimbangkan:

Memerlukan:

  • Anda dapat memiliki pemuatan dinamis di mana nama modul yang dimuat tidak ditentukan sebelumnya / statis, atau di mana Anda memuat modul hanya jika itu "benar-benar diperlukan" (tergantung pada aliran kode tertentu).
  • Memuat sinkron. Itu berarti jika Anda memiliki banyak requires, mereka dimuat dan diproses satu per satu.

Impor ES6:

  • Anda dapat menggunakan impor bernama untuk hanya memuat bagian yang Anda butuhkan secara selektif. Itu bisa menghemat memori.
  • Impor dapat tidak sinkron (dan pada ES6 Module Loader saat ini, sebenarnya) dan dapat bekerja sedikit lebih baik.

Juga, sistem modul Require tidak berbasis standar. Sangat tidak mungkin menjadi standar sekarang karena modul ES6 ada. Di masa depan akan ada dukungan asli untuk Modul ES6 dalam berbagai implementasi yang akan menguntungkan dalam hal kinerja.

Amit
sumber
16
Apa yang membuat Anda berpikir bahwa impor ES6 tidak sinkron?
Felix Kling
5
@FelixKling - kombinasi dari berbagai pengamatan. Menggunakan JSPM (ES6 Module Loader ...) Saya perhatikan bahwa ketika impor mengubah namespace global efeknya tidak diamati di dalam impor lainnya (karena mereka terjadi secara tidak sinkron .. Ini juga dapat dilihat dalam kode yang ditranskripsikan). Juga, karena itu adalah perilaku (1 impor tidak memengaruhi orang lain) tidak ada alasan untuk tidak melakukannya, jadi itu bisa jadi tergantung pada implementasi
Amit
35
Anda menyebutkan sesuatu yang sangat penting: pemuat modul. Meskipun ES6 menyediakan sintaks impor dan ekspor, ES6 tidak menentukan bagaimana modul harus dimuat. Bagian yang penting adalah bahwa deklarasi dapat dianalisis secara statis, sehingga dependensi dapat ditentukan tanpa mengeksekusi kode. Ini akan memungkinkan pemuat modul memuat modul secara sinkron atau asinkron. Tetapi modul ES6 sendiri tidak sinkron atau asinkron.
Felix Kling
5
@FelixKling loader modul ES6 telah ditandai di OP jadi saya menganggapnya relevan dengan jawabannya. Saya juga menyatakan bahwa berdasarkan pengamatan, async adalah perilaku saat ini, serta kemungkinan di masa depan (dalam implementasi apa pun) sehingga merupakan hal yang relevan untuk dipertimbangkan. Apakah Anda pikir itu salah?
Amit
10
Saya pikir penting untuk tidak mengacaukan sistem modul / sintaksis dengan pemuat modul. Misalnya jika Anda mengembangkan untuk simpul, maka Anda kemungkinan mengkompilasi modul ES6 require, jadi Anda tetap menggunakan sistem modul dan loader Node.
Felix Kling
41

Keuntungan utama adalah sintaksis:

  • Lebih banyak deklaratif / sintaksis kompak
  • Modul ES6 pada dasarnya akan membuat UMD (Universal Module Definition) usang - pada dasarnya menghilangkan perpecahan antara CommonJS dan AMD (server vs browser).

Anda tidak mungkin melihat manfaat kinerja dengan modul ES6. Anda masih membutuhkan pustaka tambahan untuk menggabungkan modul-modul, bahkan ketika ada dukungan penuh untuk fitur ES6 di browser.

snozza
sumber
4
Bisakah Anda menjelaskan mengapa seseorang membutuhkan bundler bahkan ketika browser memiliki dukungan modul ES6 penuh?
E. Sundin
1
Permintaan maaf, diedit agar lebih masuk akal. Maksud saya, fitur modul impor / ekspor tidak diterapkan di browser apa pun secara asli. Transpiler masih diperlukan.
snozza
16
Tampaknya agak kontradiktif bagi saya. Jika ada dukungan penuh lalu apa tujuan bundler? Apakah ada sesuatu yang hilang dalam spesifikasi ES6? Apa yang sebenarnya akan dilakukan bundler yang tidak tersedia di lingkungan yang didukung penuh ?
E. Sundin
1
Seperti yang dikatakan @snozza ... "fitur modul impor / ekspor tidak diterapkan di peramban apa pun secara naif. Transpiler masih diperlukan"
robertmain
2
Anda tidak lagi membutuhkan perpustakaan tambahan. Sejak v8.5.0 (dirilis lebih dari setahun yang lalu), node --experimemntal-modules index.mjsAnda dapat menggunakannya importtanpa Babel. Anda dapat (dan harus) juga mempublikasikan paket npm Anda sebagai ESModule asli, dengan kompatibilitas ke belakang untuk requirecara lama . Banyak browser juga mendukung impor dinamis secara asli.
Dan Dascalescu
38

Apakah ada manfaat kinerja untuk menggunakan salah satunya?

Jawaban saat ini adalah tidak, karena tidak ada mesin browser saat ini yang mengimplementasikan import/export dari standar ES6.

Beberapa grafik perbandingan http://kangax.github.io/compat-table/es6/ tidak memperhitungkan ini, jadi ketika Anda melihat hampir semua hijau untuk Chrome, berhati-hatilah.importkata kunci dari ES6 belum diperhitungkan.

Dengan kata lain, mesin browser saat ini termasuk V8 tidak dapat mengimpor file JavaScript baru dari file JavaScript utama melalui arahan JavaScript apa pun.

(Kami mungkin masih beberapa bug saja atau beberapa tahun lagi hingga V8 mengimplementasikannya sesuai dengan spesifikasi ES6.)

Dokumen ini adalah yang kita butuhkan, dan dokumen ini ini yang harus kita patuhi.

Dan standar ES6 mengatakan bahwa dependensi modul harus ada sebelum kita membaca modul seperti dalam bahasa pemrograman C, di mana kita memiliki .hfile (header) .

Ini adalah struktur yang baik dan teruji, dan saya yakin para ahli yang menciptakan standar ES6 memikirkan hal itu.

Inilah yang memungkinkan Webpack atau paket bundler lain untuk mengoptimalkan bundel dalam beberapa kasus khusus , dan mengurangi beberapa dependensi dari bundel yang tidak diperlukan. Tetapi dalam kasus kita memiliki dependensi sempurna ini tidak akan pernah terjadi.

Dibutuhkan waktu hingga import/exportdukungan asli ditayangkan, dan requirekata kunci tidak akan pergi ke mana pun untuk waktu yang lama.

Apa require?

Ini adalah node.jscara memuat modul. ( https://github.com/nodejs/node )

Node menggunakan metode tingkat sistem untuk membaca file. Anda pada dasarnya mengandalkan itu ketika menggunakan require. requireakan berakhir pada beberapa panggilan sistem sepertiuv_fs_open (tergantung pada sistem akhir, Linux, Mac, Windows) untuk memuat file / modul JavaScript.

Untuk memeriksa apakah ini benar, coba gunakan Babel.js, dan Anda akan melihat bahwa importkata kunci akan dikonversi menjadi require.

masukkan deskripsi gambar di sini

prosti
sumber
2
Sebenarnya, ada satu area di mana kinerja dapat ditingkatkan - ukuran bundel. Menggunakan importdalam proses membangun Webpack 2 / Rollup berpotensi mengurangi ukuran file yang dihasilkan oleh 'pohon gemetar' modul / kode yang tidak digunakan, yang mungkin berakhir di bundel akhir. Ukuran file yang lebih kecil = lebih cepat untuk mengunduh = lebih cepat untuk init / dieksekusi pada klien.
Lee Benson
2
alasannya adalah tidak ada browser saat ini di planet bumi memungkinkan import kata kunci secara asli. Atau ini berarti Anda tidak dapat mengimpor file JavaScript lain dari file JavaScript. Inilah sebabnya mengapa Anda tidak dapat membandingkan manfaat kinerja keduanya. Tapi tentu saja, alat seperti Webpack1 / 2 atau Browserify dapat menangani kompresi. Mereka leher ke leher: gist.github.com/substack/68f8d502be42d5cd4942
prosti
4
Anda menghadap 'pohon bergetar'. Tidak ada satu pun di tautan utama Anda yang dibahas adalah pohon goyang. Menggunakan modul ES6 memungkinkannya, karena importdan exportmerupakan deklarasi statis yang mengimpor jalur kode tertentu, sedangkan requirebisa dinamis dan dengan demikian bundel dalam kode yang tidak digunakan. Manfaat kinerja tidak langsung - Webpack 2 dan / atau Rollup berpotensi menghasilkan ukuran bundel yang lebih kecil yang lebih cepat untuk diunduh, dan karenanya terlihat lebih tajam untuk pengguna akhir (peramban). Ini hanya berfungsi jika semua kode ditulis dalam modul ES6 dan karenanya impor dapat dianalisis secara statis.
Lee Benson
2
Saya memperbarui jawaban @LeeBenson, saya pikir jika kita mempertimbangkan dukungan asli dari mesin browser yang belum dapat kita bandingkan. Apa yang berguna sebagai opsi tiga guncangan menggunakan Webpack, dapat juga dicapai bahkan sebelum kita mengatur modul CommonJS, karena untuk sebagian besar aplikasi nyata kita tahu modul apa yang harus digunakan.
prosti
1
Jawaban Anda benar-benar valid, tetapi saya pikir kami membandingkan dua karakteristik yang berbeda. Semua import/export dikonversi menjadi require, diberikan. Tetapi apa yang terjadi sebelum langkah ini dapat dianggap sebagai peningkatan "kinerja". Contoh: Jika lodashditulis dalam ES6 dan Anda import { omit } from lodash, bundel utama HANYA akan berisi 'hilangkan' dan bukan utilitas lain, sedangkan yang sederhana require('lodash')akan mengimpor semuanya. Ini akan meningkatkan ukuran bundel, membutuhkan waktu lebih lama untuk mengunduh, dan karenanya menurunkan kinerja. Ini hanya berlaku dalam konteks browser, tentu saja.
Lee Benson
31

Menggunakan modul ES6 dapat berguna untuk 'pengocokan pohon'; yaitu mengaktifkan Webpack 2, Rollup (atau bundler lain) untuk mengidentifikasi jalur kode yang tidak digunakan / diimpor, dan karenanya tidak membuatnya menjadi bundel yang dihasilkan. Ini secara signifikan dapat mengurangi ukuran file dengan menghilangkan kode yang tidak pernah Anda perlukan, tetapi dengan CommonJS dibundel secara default karena Webpack dkk tidak memiliki cara untuk mengetahui apakah itu diperlukan.

Ini dilakukan dengan menggunakan analisis statis dari jalur kode.

Misalnya, menggunakan:

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

... memberikan bundler petunjuk yang package.anotherParttidak diperlukan (jika tidak diimpor, itu tidak dapat digunakan-kan?), sehingga tidak akan mengganggu bundling itu.

Untuk mengaktifkan ini untuk Webpack 2, Anda perlu memastikan bahwa transpiler Anda tidak mengeluarkan modul CommonJS. Jika Anda menggunakan es2015plug-in dengan babel, Anda dapat menonaktifkannya .babelrcseperti:

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

Rollup dan lainnya dapat bekerja secara berbeda - lihat dokumen jika Anda tertarik.

Lee Benson
sumber
2
juga bagus untuk pengocokan pohon 2ality.com/2015/12/webpack-tree-shaking.html
prosti
25

Ketika datang ke async atau mungkin pemuatan malas, maka import ()jauh lebih kuat. Lihat ketika kita membutuhkan komponen dengan cara asinkron, maka kita menggunakannya importdalam beberapa cara async seperti pada constvariabel yang menggunakan await.

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

Atau jika Anda ingin menggunakannya require(),

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

Masalahnya import()sebenarnya async di alam. Seperti yang disebutkan oleh neehar venugopal di ReactConf , Anda dapat menggunakannya untuk memuat komponen reaksi secara dinamis untuk arsitektur sisi klien.

Juga jauh lebih baik dalam hal Routing. Itu adalah satu hal khusus yang membuat log jaringan mengunduh bagian yang diperlukan ketika pengguna terhubung ke situs web tertentu ke komponen spesifiknya. mis. halaman login sebelum dasbor tidak akan mengunduh semua komponen dasbor. Karena yang dibutuhkan saat ini yaitu komponen login, yang hanya akan diunduh.

Hal yang sama berlaku untuk export: ES6 exportpersis sama dengan untuk CommonJS module.exports.

CATATAN - Jika Anda mengembangkan proyek node.js, maka Anda harus benar-benar menggunakan require()sebagai simpul akan membuang kesalahan pengecualian seolah- invalid token 'import'olah Anda akan menggunakan import. Jadi simpul tidak mendukung pernyataan impor.

UPDATE - Seperti yang disarankan oleh Dan Dascalescu : Sejak v8.5.0 (dirilis September 2017), node --experimental-modules index.mjsAnda dapat menggunakannya importtanpa Babel. Anda dapat (dan harus) juga mempublikasikan paket npm Anda sebagai ESModule asli, dengan kompatibilitas ke belakang untuk requirecara lama .

Lihat ini untuk izin lebih lanjut tempat menggunakan impor async - https://www.youtube.com/watch?v=bb6RCrDaxhw

Temui Zaveri
sumber
1
Jadi akankah persyaratan disinkronkan dan tunggu?
baklazan
1
Dapat dikatakan secara faktual!
Temui Zaveri
15

Yang paling penting untuk diketahui adalah bahwa modul ES6 memang merupakan standar resmi, sedangkan modul CommonJS (Node.js) tidak.

Pada tahun 2019, modul ES6 didukung oleh 84% browser. Sementara Node.js menempatkannya di belakang flag --experimental-modules , ada juga paket simpul yang disebut esm , yang membuat integrasi menjadi mulus.

Masalah lain yang mungkin Anda temui antara sistem modul ini adalah lokasi kode. Node.js mengasumsikan sumber disimpan dalam node_modulesdirektori, sementara sebagian besar modul ES6 digunakan dalam struktur direktori datar. Ini tidak mudah untuk direkonsiliasi, tetapi dapat dilakukan dengan meretas package.jsonfile Anda dengan skrip instalasi sebelum dan sesudah. Berikut ini adalah contoh modul isomorfik dan artikel yang menjelaskan cara kerjanya.

isysd
sumber
8

Saya pribadi menggunakan impor karena, kita dapat mengimpor metode yang diperlukan, anggota dengan menggunakan impor.

import {foo, bar} from "dep";

Nama File : dep.js

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

Penghargaan untuk Paul Shan. Info lebih lanjut .

Chandoo
sumber
1
Pilihan bagus! Apakah Anda juga menerbitkan paket npm Anda sebagai ESModule asli, dengan kompatibilitas ke belakang untuk requirecara lama ?
Dan Dascalescu
6
Anda dapat melakukan hal yang sama dengan mengharuskan!
Suisse
4
const {a,b} = require('module.js'); berfungsi juga ... jika Anda mengekspor adanb
BananaAcid
module.exports = { a: ()={}, b: 22 }- Bagian kedua dari respan @BananaAcid
Seth McClaine
7

Sampai sekarang impor ES6, ekspor selalu dikompilasi ke CommonJS , sehingga tidak ada manfaat menggunakan satu atau yang lain. Meskipun penggunaan ES6 direkomendasikan karena itu harus menguntungkan ketika dukungan asli dari browser dirilis. Alasannya, Anda dapat mengimpor sebagian dari satu file sementara dengan CommonJS Anda harus meminta semua file.

ES6 → import, export default, export

CommonJS → require, module.exports, exports.foo

Di bawah ini adalah penggunaan umum dari mereka.

ES6 standar ekspor

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

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

ES6 ekspor banyak dan impor banyak

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

Module.exports CommonJS

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

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

CommonJS module.exports multiple

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

Tidak yakin mengapa (mungkin optimasi - pemuatan malas?) Apakah berfungsi seperti itu, tetapi saya perhatikan bahwa importmungkin tidak menguraikan kode jika modul yang diimpor tidak digunakan.
Perilaku yang mungkin tidak diharapkan dalam beberapa kasus.

Ambil kelas Foo yang dibenci sebagai ketergantungan sampel kami.

untuk

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

Sebagai contoh:

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

Di samping itu:

index.ts

import Foo from './foo'
typeof Foo; // any use case
// prints "Foo loaded"
100rb
sumber