Sử dụng Node.js yêu cầu nhập / xuất ES6


930

Trong một dự án tôi đang hợp tác, chúng tôi có hai lựa chọn về hệ thống mô-đun nào chúng tôi có thể sử dụng:

  1. Nhập mô-đun bằng cách sử dụng requirevà xuất bằng cách sử dụng module.exportsexports.foo.
  2. Nhập mô-đun bằng ES6 importvà xuất bằng ES6export

Có bất kỳ lợi ích hiệu suất để sử dụng cái này hơn cái kia không? Có bất cứ điều gì khác mà chúng ta nên biết nếu chúng ta sử dụng các mô-đun ES6 trên các nút Node không?


9
node --experimental-modules index.mjscho phép bạn sử dụng importmà không cần Babel và hoạt động trong Node 8.5.0+. Bạn cũng có thể (và nên) xuất bản các gói npm của mình dưới dạng ESModule gốc , với khả năng tương thích ngược cho requirecách cũ .
Dan Dascalescu

Câu trả lời:


728

Có bất kỳ lợi ích hiệu suất để sử dụng cái này hơn cái kia không?

Hãy nhớ rằng chưa có công cụ JavaScript nào hỗ trợ các mô-đun ES6. Bạn nói rằng bạn đang sử dụng Babel. Babel chuyển đổi importexportkhai báo thành CommonJS ( require/ module.exports) theo mặc định. Vì vậy, ngay cả khi bạn sử dụng cú pháp mô-đun ES6, bạn sẽ sử dụng CommonJS dưới mui xe nếu bạn chạy mã trong Nút.

Có sự khác biệt về kỹ thuật giữa các mô-đun CommonJS và ES6, ví dụ CommonJS cho phép bạn tải các mô-đun một cách linh hoạt. ES6 không cho phép điều này, nhưng có một API được phát triển cho điều đó .

Vì các mô-đun ES6 là một phần của tiêu chuẩn, tôi sẽ sử dụng chúng.


16
Tôi đã cố gắng sử dụng ES6 importvới requirenhưng họ làm việc khác nhau. CommonJS tự xuất lớp trong khi chỉ có một lớp. Xuất ES6 giống như có nhiều lớp, do đó bạn phải sử dụng .ClassNameđể có được lớp xuất. Có sự khác biệt nào khác thực sự ảnh hưởng đến việc thực hiện không
Thellimist

78
@Entei: Có vẻ như bạn muốn xuất mặc định, không phải xuất có tên. module.exports = ...;tương đương với export default .... exports.foo = ...tương đương với export var foo = ...;
Felix Kling

10
Điều đáng chú ý là mặc dù Babel cuối cùng đã chuyển importsang CommonJS trong Node, được sử dụng cùng với Webpack 2 / Rollup (và bất kỳ trình đóng gói nào khác cho phép cây ES6 rung), có thể kết thúc với một tệp nhỏ hơn đáng kể so với mã Node tương đương thông qua việc sử dụng requirechính xác thực tế ES6 cho phép phân tích tĩnh nhập / xuất. Mặc dù điều này sẽ không tạo ra sự khác biệt cho Node (nhưng) chắc chắn có thể nếu mã cuối cùng sẽ kết thúc như một gói trình duyệt duy nhất.
Lee Benson

5
trừ khi bạn cần thực hiện nhập động
chulian

3
Các mô-đun ES6 nằm trong động cơ V8 mới nhất và cũng đang đến các trình duyệt khác đằng sau cờ. Xem: Medium.com/dev-channel/ Kẻ
Nexii Malthus

180

Có một số cách sử dụng / khả năng bạn có thể muốn xem xét:

Yêu cầu:

  • Bạn có thể tải động trong đó tên mô-đun được tải không được xác định trước / tĩnh hoặc ở nơi bạn chỉ tải một cách có điều kiện mô-đun nếu nó "thực sự cần thiết" (tùy thuộc vào dòng mã nhất định).
  • Đang tải là đồng bộ. Điều đó có nghĩa là nếu bạn có nhiều requires, chúng được tải và xử lý từng cái một.

Nhập khẩu ES6:

  • Bạn có thể sử dụng nhập khẩu có tên để chỉ tải có chọn lọc những phần bạn cần. Điều đó có thể tiết kiệm bộ nhớ.
  • Nhập có thể không đồng bộ (và trong Trình tải mô-đun ES6 hiện tại, thực tế là như vậy) và có thể thực hiện tốt hơn một chút.

Ngoài ra, hệ thống mô-đun Yêu cầu không dựa trên tiêu chuẩn. Hiện tại rất khó có thể trở thành tiêu chuẩn khi các mô-đun ES6 tồn tại. Trong tương lai sẽ có hỗ trợ riêng cho các Mô-đun ES6 trong các triển khai khác nhau, điều này sẽ có lợi về mặt hiệu suất.


16
Điều gì khiến bạn nghĩ rằng nhập ES6 không đồng bộ?
Felix Kling

5
@FelixKling - kết hợp các quan sát khác nhau. Sử dụng JSPM (Trình tải mô-đun ES6 ...) Tôi nhận thấy rằng khi nhập đã sửa đổi không gian tên toàn cục, hiệu ứng không được quan sát bên trong các nhập khác (vì chúng xảy ra không đồng bộ .. Điều này cũng có thể được nhìn thấy trong mã được dịch mã). Ngoài ra, vì đó là hành vi (1 lần nhập không ảnh hưởng đến người khác) nên không có lý do gì để không làm như vậy, vì vậy nó có thể phụ thuộc vào việc thực hiện
Amit

35
Bạn đề cập đến một cái gì đó rất quan trọng: bộ nạp mô-đun. Mặc dù ES6 cung cấp cú pháp nhập và xuất, nhưng nó không xác định cách các mô-đun nên được tải. Phần quan trọng là các khai báo có thể phân tích tĩnh, do đó các phụ thuộc có thể được xác định mà không cần thực thi mã. Điều này sẽ cho phép trình tải mô-đun tải mô-đun đồng bộ hoặc không đồng bộ. Nhưng bản thân các mô-đun ES6 không đồng bộ hoặc không đồng bộ.
Felix Kling

5
Trình tải mô-đun ESF @FelixKling đã được gắn thẻ trong OP vì vậy tôi cho rằng nó làm cho nó phù hợp với câu trả lời. Ngoài ra tôi đã nói rằng dựa trên các quan sát không đồng bộ là hành vi hiện tại, cũng như khả năng trong tương lai (trong bất kỳ triển khai nào) vì vậy đó là một điểm liên quan để xem xét. Bạn có nghĩ nó sai không?
Amit

10
Tôi nghĩ điều quan trọng là không kết hợp hệ thống / cú pháp mô-đun với trình tải mô-đun. Ví dụ: nếu bạn phát triển cho nút, thì requiredù sao bạn cũng có thể biên dịch các mô-đun ES6 , vì vậy bạn vẫn đang sử dụng hệ thống mô-đun và trình tải của Node.
Felix Kling

41

Những ưu điểm chính là cú pháp:

  • Cú pháp khai báo / rút gọn hơn
  • Các mô-đun ES6 về cơ bản sẽ làm cho UMD (Định nghĩa mô-đun phổ quát) trở nên lỗi thời - về cơ bản sẽ loại bỏ sự phân ly giữa CommonJS và AMD (máy chủ so với trình duyệt).

Bạn không thể thấy bất kỳ lợi ích hiệu suất nào với các mô-đun ES6. Bạn vẫn sẽ cần một thư viện bổ sung để đóng gói các mô-đun, ngay cả khi có hỗ trợ đầy đủ cho các tính năng ES6 trong trình duyệt.


4
Bạn có thể làm rõ lý do tại sao một người cần một bộ đóng gói ngay cả khi các trình duyệt có hỗ trợ mô-đun ES6 đầy đủ?
E. Sundin

1
Lời xin lỗi, chỉnh sửa để có ý nghĩa hơn. Tôi có nghĩa là tính năng mô-đun nhập / xuất không được triển khai trong bất kỳ trình duyệt nào. Một transpiler vẫn được yêu cầu.
snozza

16
Có vẻ như một chút mâu thuẫn với tôi. Nếu có sự hỗ trợ đầy đủ thì mục đích của gói là gì? Có thiếu thứ gì trong thông số ES6 không? Bộ đóng gói thực sự sẽ làm gì mà không có sẵn trong một môi trường được hỗ trợ đầy đủ ?
E. Sundin

1
Như @snozza đã nói ... "tính năng mô-đun nhập / xuất không được triển khai trong bất kỳ trình duyệt nào một cách ngây thơ. Bộ chuyển mã vẫn được yêu cầu"
robertmain

2
Bạn không còn cần bất kỳ thư viện bổ sung. Vì v8.5.0 (được phát hành hơn một năm trước), node --experimemntal-modules index.mjscho phép bạn sử dụng importmà không cần Babel. Bạn cũng có thể (và nên) xuất bản các gói npm của mình dưới dạng ESModule gốc, với khả năng tương thích ngược cho requirecách cũ . Nhiều trình duyệt cũng hỗ trợ nhập khẩu động .
Dan Dascalescu

38

Có bất kỳ lợi ích hiệu suất để sử dụng cái này hơn cái kia không?

Câu trả lời hiện tại là không, bởi vì không có công cụ trình duyệt hiện tại nào thực hiện import/exporttừ tiêu chuẩn ES6.

Một số biểu đồ so sánh http: // Khangax.github.io/compat-table/es6/ không tính đến điều này, vì vậy khi bạn thấy hầu hết các màu xanh lục cho Chrome, hãy cẩn thận. importtừ khóa từ ES6 chưa được đưa vào tài khoản.

Nói cách khác, các công cụ trình duyệt hiện tại bao gồm V8 không thể nhập tệp JavaScript mới từ tệp JavaScript chính thông qua bất kỳ chỉ thị JavaScript nào.

(Chúng tôi có thể vẫn chỉ là một vài lỗi hoặc cách đó vài năm cho đến khi V8 thực hiện theo thông số kỹ thuật ES6.)

Tài liệu này là những gì chúng ta cần, và tài liệu này là những gì chúng ta phải tuân theo.

Và tiêu chuẩn ES6 nói rằng các phụ thuộc mô-đun nên có trước khi chúng ta đọc mô-đun như trong ngôn ngữ lập trình C, nơi chúng ta có .hcác tệp (tiêu đề) .

Đây là một cấu trúc tốt và được thử nghiệm tốt, và tôi chắc chắn rằng các chuyên gia tạo ra tiêu chuẩn ES6 đã nghĩ đến điều đó.

Đây là những gì cho phép Webpack hoặc các gói gói khác tối ưu hóa gói trong một số trường hợp đặc biệt và giảm một số phụ thuộc từ gói không cần thiết. Nhưng trong trường hợp chúng ta có sự phụ thuộc hoàn hảo thì điều này sẽ không bao giờ xảy ra.

Nó sẽ cần một thời gian cho đến khi import/exporthỗ trợ bản địa đi vào hoạt động và requiretừ khóa sẽ không đi đến đâu trong một thời gian dài.

require

Đây là node.jscách để tải các mô-đun. ( https://github.com/nodejs/node )

Nút sử dụng các phương thức cấp hệ thống để đọc tệp. Bạn cơ bản dựa vào đó khi sử dụng require. requiresẽ kết thúc trong một số cuộc gọi hệ thống như uv_fs_open(phụ thuộc vào hệ thống cuối, Linux, Mac, Windows) để tải tệp / mô-đun JavaScript.

Để kiểm tra xem điều này có đúng không, hãy thử sử dụng Babel.js và bạn sẽ thấy importtừ khóa sẽ được chuyển đổi thành require.

nhập mô tả hình ảnh ở đây


2
Trên thực tế, có một lĩnh vực mà hiệu suất có thể được cải thiện - kích thước bó. Việc sử dụng importtrong quy trình xây dựng Webpack 2 / Rollup có thể có khả năng làm giảm kích thước tệp kết quả bằng cách sử dụng các mô-đun / mã không sử dụng 'cây lắc', điều này có thể xuất hiện trong gói cuối cùng. Kích thước tệp nhỏ hơn = nhanh hơn để tải xuống = nhanh hơn để khởi tạo / thực thi trên máy khách.
Lee Benson

2
lý do là không có trình duyệt hiện tại trên trái đất cho phép import từ khóa nguyên bản. Hoặc điều này có nghĩa là bạn không thể nhập tệp JavaScript khác từ tệp JavaScript. Đây là lý do tại sao bạn không thể so sánh lợi ích hiệu suất của hai. Nhưng tất nhiên, các công cụ như Webpack1 / 2 hoặc Browserify có thể xử lý việc nén. Họ là cổ để cổ: gist.github.com/substack/68f8d502be42d5cd4942
prosti

4
Bạn đang nhìn 'cây rung chuyển'. Không nơi nào trong liên kết ý chính của bạn là cây lắc được thảo luận. Việc sử dụng các mô-đun ES6 cho phép nó, bởi vì importexportlà các khai báo tĩnh nhập một đường dẫn mã cụ thể, trong khi đó requirecó thể là động và do đó bó trong mã không được sử dụng. Lợi ích về hiệu suất là gián tiếp-- Webpack 2 và / hoặc Rollup có thể có khả năng dẫn đến kích thước gói nhỏ hơn để tải xuống nhanh hơn và do đó xuất hiện nhanh hơn cho người dùng cuối (của trình duyệt). Điều này chỉ hoạt động nếu tất cả các mã được viết trong các mô-đun ES6 và do đó nhập khẩu có thể được phân tích tĩnh.
Lee Benson

2
Tôi đã cập nhật câu trả lời @LeeBenson, tôi nghĩ rằng nếu chúng tôi xem xét hỗ trợ riêng từ các công cụ trình duyệt, chúng tôi chưa thể so sánh được. Điều gì có thể là tùy chọn lắc ba tiện dụng khi sử dụng Webpack, cũng có thể đạt được ngay cả trước khi chúng tôi đặt các mô-đun CommonJS, vì đối với hầu hết các ứng dụng thực tế, chúng tôi biết nên sử dụng mô-đun nào.
prosti

1
Câu trả lời của bạn là hoàn toàn hợp lệ, nhưng tôi nghĩ chúng ta đang so sánh hai đặc điểm khác nhau. Tất cả import/export được chuyển đổi thành require, cấp. Nhưng những gì xảy ra trước bước này có thể được coi là tăng cường "hiệu suất". Ví dụ: Nếu lodashđược viết bằng ES6 và bạn import { omit } from lodash, gói cuối cùng sẽ CHỈ chứa 'bỏ qua' và không phải các tiện ích khác, trong khi đơn giản require('lodash')sẽ nhập mọi thứ. Điều này sẽ tăng kích thước gói, mất nhiều thời gian hơn để tải xuống và do đó làm giảm hiệu suất. Điều này chỉ hợp lệ trong bối cảnh trình duyệt, tất nhiên.
Lee Benson

31

Sử dụng các mô-đun ES6 có thể hữu ích cho 'rung cây'; tức là cho phép Webpack 2, Rollup (hoặc các gói khác) xác định các đường dẫn mã không được sử dụng / nhập khẩu và do đó không đưa nó vào gói kết quả. Điều này có thể giảm đáng kể kích thước tệp của nó bằng cách loại bỏ mã bạn sẽ không bao giờ cần, nhưng với CommonJS được gói theo mặc định vì Webpack et al không có cách nào để biết liệu nó có cần thiết hay không.

Điều này được thực hiện bằng cách sử dụng phân tích tĩnh của đường dẫn mã.

Ví dụ: sử dụng:

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

... cung cấp cho bộ đóng gói một gợi ý package.anotherPartkhông bắt buộc (nếu không được nhập, nó không thể được sử dụng - phải không?), Vì vậy nó sẽ không làm phiền việc gói nó.

Để kích hoạt tính năng này cho Webpack 2, bạn cần đảm bảo rằng bộ chuyển đổi của bạn không phun ra các mô-đun CommonJS. Nếu bạn đang sử dụng es2015trình cắm với babel, bạn có thể tắt nó theo cách .babelrctương tự:

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

Rollup và những người khác có thể hoạt động khác nhau - xem tài liệu nếu bạn quan tâm.


2
cũng rất tốt cho cây lắc 2ality.com/2015/12/webpack-tree-shaking.html
prosti

25

Khi nói đến async hoặc có thể lười tải, thì import ()mạnh hơn nhiều. Xem khi chúng tôi yêu cầu thành phần theo cách không đồng bộ, sau đó chúng tôi sử dụng thành phần này theo cách không đồng bộ importnhư trong constbiến sử dụng await.

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

Hoặc nếu bạn muốn sử dụng require()thì

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

Điều import()thực sự là không đồng bộ trong tự nhiên. Như được đề cập bởi neehar venugopal trong ReactConf , bạn có thể sử dụng nó để tải động các thành phần phản ứng cho kiến ​​trúc phía máy khách.

Ngoài ra, đó là cách tốt hơn khi nói đến Routing. Đó là một điều đặc biệt giúp nhật ký mạng tải xuống một phần cần thiết khi người dùng kết nối với trang web cụ thể với thành phần cụ thể của nó. ví dụ trang đăng nhập trước khi bảng điều khiển sẽ không tải xuống tất cả các thành phần của bảng điều khiển. Bởi vì những gì cần thiết hiện tại tức là thành phần đăng nhập, chỉ sẽ được tải xuống.

Tương tự như vậy đối với export: ES6 exporthoàn toàn giống với CommonJS module.exports.

LƯU Ý - Nếu bạn đang phát triển dự án node.js, thì bạn phải sử dụng nghiêm ngặt require()vì nút sẽ đưa ra lỗi ngoại lệ như invalid token 'import'thể bạn sẽ sử dụng import. Vì vậy, nút không hỗ trợ báo cáo nhập khẩu.

CẬP NHẬT - Theo đề xuất của Dan Dascalescu : Kể từ v8.5.0 (phát hành tháng 9 năm 2017), node --experimental-modules index.mjscho phép bạn sử dụng importmà không cần Babel. Bạn cũng có thể (và nên) xuất bản các gói npm của mình dưới dạng ESModule gốc, với khả năng tương thích ngược cho requirecách cũ .

Xem phần này để biết thêm thông tin về nơi sử dụng nhập không đồng bộ - https://www.youtube.com/watch?v=bb6RCrDaxhw


1
Vì vậy, yêu cầu sẽ được đồng bộ hóa và chờ đợi?
baklazan

1
Có thể nói thực tế!
Gặp Zaveri

15

Điều quan trọng nhất cần biết là các mô-đun ES6 thực sự là một tiêu chuẩn chính thức, trong khi các mô-đun CommonJS (Node.js) thì không.

Năm 2019, các mô-đun ES6 được hỗ trợ bởi 84% trình duyệt. Trong khi Node.js đặt chúng phía sau cờ --experimental-mô-đun , thì cũng có một gói nút tiện lợi được gọi là esm , giúp việc tích hợp trở nên trơn tru.

Một vấn đề khác bạn có thể gặp phải giữa các hệ thống mô-đun này là vị trí mã. Node.js giả định nguồn được giữ trong một node_modulesthư mục, trong khi hầu hết các mô-đun ES6 được triển khai trong cấu trúc thư mục phẳng. Đây không phải là dễ dàng để điều hòa, nhưng nó có thể được thực hiện bằng cách hack package.jsontệp của bạn với các kịch bản cài đặt trước và sau. Dưới đây là một ví dụ mô-đun đẳng cấu và một bài viết giải thích cách thức hoạt động của nó.


8

Cá nhân tôi sử dụng nhập khẩu bởi vì, chúng tôi có thể nhập các phương thức, thành viên cần thiết bằng cách sử dụng nhập khẩu.

import {foo, bar} from "dep";

Tên tệp : dep.js

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

Tín dụng cho Paul Shan. Thêm thông tin .



6
bạn có thể làm tương tự với yêu cầu!
Suisse

4
const {a,b} = require('module.js'); cũng hoạt động ... nếu bạn xuất ab
BananaAcid

module.exports = { a: ()={}, b: 22 }- Phần thứ hai của hô hấp @BananaAcid
Seth McClaine

7

Kể từ bây giờ nhập ES6, xuất luôn được biên dịch sang CommonJS , do đó không có lợi ích khi sử dụng cái này hay cái khác. Mặc dù việc sử dụng ES6 được khuyến nghị vì nó sẽ thuận lợi khi hỗ trợ riêng từ các trình duyệt được phát hành. Lý do là, bạn có thể nhập các phần từ một tệp trong khi với CommonJS, bạn phải yêu cầu tất cả các tệp.

ES6 → import, export default, export

CommonJS → require, module.exports, exports.foo

Dưới đây là cách sử dụng phổ biến của những người.

Mặc định xuất ES6

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

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

ES6 xuất nhiều và nhập nhiều

// 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 CommonSS.exports

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

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

Module CommonJS. Thể hiện nhiều

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

Không chắc chắn tại sao (có thể tối ưu hóa - lười tải?) Nó hoạt động như vậy, nhưng tôi đã nhận thấy rằng importcó thể không phân tích mã nếu các mô-đun nhập khẩu không được sử dụng.
Mà có thể không được mong đợi hành vi trong một số trường hợp.

Hãy ghét lớp Foo làm phụ thuộc mẫu của chúng tôi.

foo.ts

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

Ví dụ:

chỉ số

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

chỉ số

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

chỉ số

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

Mặt khác:

chỉ số

import Foo from './foo'
typeof Foo; // any use case
// prints "Foo loaded"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.