El uso de Node.js requiere vs. ES6 importación / exportación

930

En un proyecto en el que estoy colaborando, tenemos dos opciones sobre qué sistema de módulos podemos usar:

  1. Importando módulos usando require, y exportando usando module.exportsy exports.foo.
  2. Importar módulos con ES6 importy exportar con ES6export

¿Hay algún beneficio de rendimiento al usar uno sobre el otro? ¿Hay algo más que deberíamos saber si tuviéramos que usar módulos ES6 sobre nodos?

kpimov
fuente
99
node --experimental-modules index.mjste permite usar importsin Babel y funciona en el Nodo 8.5.0+. También puede (y debe) publicar sus paquetes npm como ESModule nativo , con compatibilidad con versiones anteriores require.
Dan Dascalescu

Respuestas:

728

¿Hay algún beneficio de rendimiento al usar uno sobre el otro?

Tenga en cuenta que todavía no hay un motor de JavaScript que admita de forma nativa los módulos ES6. Tú mismo dijiste que estás usando Babel. Babel convierte importy exportdeclara a CommonJS ( require/module.exports ) de forma predeterminada de todos modos. Entonces, incluso si usa la sintaxis del módulo ES6, usará CommonJS debajo del capó si ejecuta el código en Node.

Existen diferencias técnicas entre los módulos CommonJS y ES6, por ejemplo, CommonJS le permite cargar módulos dinámicamente. ES6 no permite esto, pero hay una API en desarrollo para eso .

Como los módulos ES6 son parte del estándar, los usaría.

Felix Kling
fuente
16
He intentado utilizar ES6 importcon requirepero trabajaron de manera diferente. CommonJS exporta la clase en sí mientras solo hay una clase. ES6 exporta como si hubiera varias clases, por lo que debe usar .ClassNamepara obtener la clase exportada. ¿Hay alguna otra diferencia que realmente
afecte
78
@Entei: Parece que desea una exportación predeterminada, no una exportación con nombre. module.exports = ...;es equivalente a export default .... exports.foo = ...es equivalente a export var foo = ...;
Felix Kling
10
Vale la pena señalar que, aunque Babel finalmente se transpila importa CommonJS en Node, utilizado junto con Webpack 2 / Rollup (y cualquier otro paquete que permita sacudir el árbol ES6), es posible terminar con un archivo que es significativamente más pequeño que el código equivalente Node crunches mediante el uso requireexacto debido al hecho de que ES6 permite el análisis estático de importación / exportación. Si bien esto no hará una diferencia para Node (todavía), ciertamente puede hacerlo si el código finalmente va a terminar como un paquete de navegador único.
Lee Benson el
55
a menos que necesite hacer una importación dinámica
chulian
3
Los módulos ES6 están en el último V8 y también están llegando a otros navegadores detrás de las banderas. Ver: medium.com/dev-channel/…
Nexii Malthus
180

Hay varios usos / capacidades que puede considerar:

Exigir:

  • Puede tener una carga dinámica donde el nombre del módulo cargado no está predefinido / estático, o donde carga condicionalmente un módulo solo si es "realmente necesario" (dependiendo de cierto flujo de código).
  • La carga es sincrónica. Eso significa que si tiene múltiples correos requireelectrónicos, se cargan y procesan uno por uno.

Importaciones ES6:

  • Puede usar importaciones con nombre para cargar selectivamente solo las piezas que necesita. Eso puede ahorrar memoria.
  • La importación puede ser asíncrona (y en el actual ES6 Module Loader, de hecho lo es) y puede funcionar un poco mejor.

Además, el sistema del módulo Requerir no está basado en estándares. Es muy poco probable que se convierta en estándar ahora que existen módulos ES6. En el futuro habrá soporte nativo para módulos ES6 en varias implementaciones, lo que será ventajoso en términos de rendimiento.

Amit
fuente
16
¿Qué te hace pensar que las importaciones de ES6 son asíncronas?
Felix Kling
55
@FelixKling: combinación de varias observaciones. Usando JSPM (ES6 Module Loader ...) noté que cuando una importación modifica el espacio de nombres global, el efecto no se observa dentro de otras importaciones (porque ocurren de forma asíncrona ... Esto también se puede ver en el código transpilado). También, ya que ese es el comportamiento (1 importación no afecta a los demás) no hay razón para no hacerlo, así que podría ser dependiente de la implementación
Amit
35
Mencionas algo muy importante: el cargador de módulos. Si bien ES6 proporciona la sintaxis de importación y exportación, no define cómo se deben cargar los módulos. La parte importante es que las declaraciones son estáticamente analizables, por lo que las dependencias se pueden determinar sin ejecutar el código. Esto permitiría que un cargador de módulos cargue un módulo de forma síncrona o asíncrona. Pero los módulos ES6 por sí mismos no son síncronos ni asíncronos.
Felix Kling
55
El cargador del módulo @FelixKling ES6 fue etiquetado en el OP, así que supongo que lo hace relevante para la respuesta. También dije que, en base a observaciones, la sincronización es el comportamiento actual, así como la posibilidad en el futuro (en cualquier implementación), por lo que es un punto relevante a considerar. ¿Crees que está mal?
Amit
10
Creo que es importante no combinar el sistema / sintaxis del módulo con el cargador de módulos. Por ejemplo, si desarrolla para el nodo, es probable que esté compilando módulos ES6 de requiretodos modos, por lo que está utilizando el sistema de módulos de Node y el cargador de todos modos.
Felix Kling
41

Las principales ventajas son sintácticas:

  • Más sintaxis declarativa / compacta
  • Los módulos ES6 básicamente harán que UMD (Universal Module Definition) quede obsoleto, esencialmente elimina el cisma entre CommonJS y AMD (servidor vs navegador).

Es poco probable que vea beneficios de rendimiento con los módulos ES6. Aún necesitará una biblioteca adicional para agrupar los módulos, incluso cuando haya compatibilidad total con las funciones de ES6 en el navegador.

snozza
fuente
44
¿Podría aclarar por qué uno necesita un paquete incluso cuando los navegadores tienen soporte completo para el módulo ES6?
E. Sundin
1
Disculpas, editadas para tener más sentido. Quise decir que la función de módulos de importación / exportación no está implementada en ningún navegador de forma nativa. Todavía se requiere un transpilador.
snozza
16
Me parece un poco contradictorio. Si hay soporte completo , ¿cuál es el propósito del paquete? ¿Falta algo en la especificación ES6? ¿Qué haría realmente el bundler que no esté disponible en un entorno totalmente compatible ?
E. Sundin
1
Como dijo @snozza ... "la función de módulos de importación / exportación no está implementada en ningún navegador de forma ingenua. Todavía se requiere un transpilador"
robertmain
2
Ya no necesita bibliotecas adicionales. Desde v8.5.0 (lanzado hace más de un año), node --experimemntal-modules index.mjste permite usarlo importsin Babel. Se puede (y debe) también publicar sus paquetes MNP ESModule nativa, la compatibilidad hacia atrás de la vieja requiremanera. Muchos navegadores también admiten importaciones dinámicas de forma nativa.
Dan Dascalescu
38

¿Hay algún beneficio de rendimiento al usar uno sobre el otro?

La respuesta actual es no, porque ninguno de los motores de navegador actuales se implementa import/exportdesde el estándar ES6.

Algunos cuadros de comparación http://kangax.github.io/compat-table/es6/ no tienen esto en cuenta, así que cuando vea casi todos los verdes para Chrome, tenga cuidado. importLa palabra clave de ES6 no se ha tenido en cuenta.

En otras palabras, los motores de navegador actuales, incluido V8, no pueden importar archivos JavaScript nuevos desde el archivo JavaScript principal a través de ninguna directiva JavaScript.

(Es posible que aún nos falten algunos errores o que falten años para que V8 lo implemente de acuerdo con la especificación ES6).

Este documento es lo que necesitamos, y este documento es lo que debemos obedecer.

Y el estándar ES6 decía que las dependencias del módulo deberían estar allí antes de leer el módulo como en el lenguaje de programación C, donde teníamos (encabezados) .h archivos .

Esta es una estructura buena y bien probada, y estoy seguro de que los expertos que crearon el estándar ES6 lo tenían en cuenta.

Esto es lo que permite a Webpack u otros paquetes de paquetes optimizar el paquete en algunos casos especiales y reducir algunas dependencias del paquete que no son necesarias. Pero en casos tenemos dependencias perfectas, esto nunca sucederá.

Necesitará algo de tiempo hasta que se import/exportactive el soporte nativo, y la requirepalabra clave no irá a ningún lado durante mucho tiempo.

¿Qué es require?

Esta es la node.jsforma de cargar módulos. ( https://github.com/nodejs/node )

El nodo utiliza métodos de nivel de sistema para leer archivos. Básicamente confías en eso cuando lo usas require. requirefinalizará en alguna llamada al sistema como uv_fs_open(depende del sistema final, Linux, Mac, Windows) para cargar el archivo / módulo de JavaScript.

Para comprobar que esto es cierto, intente usar Babel.js y verá que la importpalabra clave se convertirá en require.

ingrese la descripción de la imagen aquí

prosti
fuente
2
En realidad, hay un área donde el rendimiento podría mejorarse: el tamaño del paquete. El uso importen un proceso de compilación de Webpack 2 / Rollup puede reducir potencialmente el tamaño del archivo resultante al 'agitar el árbol' de los módulos / código no utilizados, que de lo contrario podrían terminar en el paquete final. Tamaño de archivo más pequeño = más rápido para descargar = más rápido para iniciar / ejecutar en el cliente.
Lee Benson el
2
El razonamiento era que ningún navegador actual en el planeta Tierra permite la import palabra clave de forma nativa. O esto significa que no puede importar otro archivo JavaScript desde un archivo JavaScript. Es por eso que no puede comparar los beneficios de rendimiento de estos dos. Pero, por supuesto, herramientas como Webpack1 / 2 o Browserify pueden manejar la compresión. Están cuello a cuello: gist.github.com/substack/68f8d502be42d5cd4942
prosti
44
Estás pasando por alto el 'movimiento de los árboles'. En ninguna parte de su enlace esencial se discute el movimiento de los árboles. El uso de módulos ES6 permite, porque importy exportson declaraciones estáticas que importan una ruta de código específico, mientras que requirepuede ser dinámica y, por tanto agrupar en código que no se utiliza. La ventaja de rendimiento es indirect-- webpack 2 y / o el paquete acumulativo puede potencialmente resultar en tamaños de paquete más pequeñas que son más rápidas para descargar, y por lo tanto aparecerá más ágil para el usuario final (de un navegador). Esto solo funciona si todo el código está escrito en módulos ES6 y, por lo tanto, las importaciones pueden analizarse estáticamente.
Lee Benson el
2
Actualicé la respuesta @LeeBenson, creo que si consideramos la compatibilidad nativa de los motores de navegador aún no podemos comparar. Lo que viene como una práctica opción de tres sacudidas usando el Webpack, también se puede lograr incluso antes de configurar los módulos CommonJS, ya que para la mayoría de las aplicaciones reales sabemos qué módulos se deben usar.
prosti
1
Su respuesta es totalmente válida, pero creo que estamos comparando dos características diferentes. Todo import/export se convierte a require, concedido. Pero lo que sucede antes de este paso podría considerarse una mejora del "rendimiento". Ejemplo: si lodashestá escrito en ES6 y usted import { omit } from lodash, el paquete final SOLO contendrá 'omitir' y no las otras utilidades, mientras que un simple require('lodash')importará todo. Esto aumentará el tamaño del paquete, llevará más tiempo descargarlo y, por lo tanto, disminuirá el rendimiento. Esto solo es válido en un contexto de navegador, por supuesto.
Lee Benson
32

El uso de módulos ES6 puede ser útil para 'sacudir árboles'; es decir, habilitar Webpack 2, Rollup (u otros paquetes) para identificar rutas de código que no se usan / importan y, por lo tanto, no se incluyen en el paquete resultante. Esto puede reducir significativamente el tamaño de su archivo al eliminar el código que nunca necesitará, pero con CommonJS se incluye de forma predeterminada porque Webpack et al no tienen forma de saber si es necesario.

Esto se realiza mediante análisis estático de la ruta del código.

Por ejemplo, usando:

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

... le da al paquete una pista que package.anotherPartno es necesaria (si no se importa, no se puede usar, ¿verdad?), por lo que no se molestará en agruparlo.

Para habilitar esto para Webpack 2, debe asegurarse de que su transpiler no esté escupiendo módulos CommonJS. Si está utilizando el es2015complemento con babel, puede deshabilitarlo de la .babelrcmisma manera:

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

El paquete acumulativo y otros pueden funcionar de manera diferente: vea los documentos si está interesado.

Lee Benson
fuente
2
también ideal para sacudir árboles 2ality.com/2015/12/webpack-tree-shaking.html
prosti
25

Cuando se trata de una carga asíncrona o tal vez lenta, import ()es mucho más potente. Vea cuándo requerimos el componente de manera asíncrona, luego lo usamos importde alguna manera asíncrona como en el constuso de variables await.

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

O si quieres usar require()entonces,

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

La cosa es en import()realidad asíncrona en la naturaleza. Como lo menciona neehar venugopal en ReactConf , puede usarlo para cargar dinámicamente componentes de reacción para la arquitectura del lado del cliente.

También es mucho mejor cuando se trata de enrutamiento. Esa es la única cosa especial que hace que el registro de red descargue una parte necesaria cuando el usuario se conecta a un sitio web específico a su componente específico. por ejemplo, la página de inicio de sesión antes del tablero no descargará todos los componentes del tablero. Porque lo que se necesita actual es decir, el componente de inicio de sesión, que solo se descargará.

Lo mismo ocurre con export: ES6 exportson exactamente iguales que para CommonJS module.exports.

NOTA - Si está desarrollando un proyecto node.js, entonces debe usarlo estrictamente require()como nodo arrojará un error de excepción como invalid token 'import'si fuera a usar import. Por lo tanto, el nodo no admite declaraciones de importación.

ACTUALIZACIÓN: según lo sugerido por Dan Dascalescu : desde v8.5.0 (lanzado en septiembre de 2017), le node --experimental-modules index.mjspermite usar importsin Babel. Se puede (y debe) también publicar sus paquetes MNP ESModule nativa, con compatibilidad hacia atrás para la edadrequire manera.

Consulte esto para obtener más autorización sobre dónde usar las importaciones asíncronas: https://www.youtube.com/watch?v=bb6RCrDaxhw

Conoce a Zaveri
fuente
1
Entonces, ¿será necesario sincronizar y esperar?
baklazan
1
Se puede decir de hecho!
Conoce a Zaveri el
15

Lo más importante que debe saber es que los módulos ES6 son, de hecho, un estándar oficial, mientras que los módulos CommonJS (Node.js) no lo son.

En 2019, los módulos ES6 son compatibles con el 84% de los navegadores. Si bien Node.js los coloca detrás de un indicador --experimental-modules , también hay un conveniente paquete de nodos llamado esm , que hace que la integración sea fluida.

Otro problema que es probable que encuentre entre estos sistemas de módulos es la ubicación del código. Node.js supone que la fuente se mantiene en un node_modulesdirectorio, mientras que la mayoría de los módulos ES6 se implementan en una estructura de directorio plana. No es fácil conciliarlos, pero se puede hacer pirateando su package.jsonarchivo con scripts de instalación previos y posteriores. Aquí hay un ejemplo de módulo isomorfo y un artículo que explica cómo funciona.

isysd
fuente
8

Personalmente uso la importación porque, podemos importar los métodos requeridos, miembros, usando la importación.

import {foo, bar} from "dep";

FileName: dep.js

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

El crédito va a Paul Shan. Más información .

chandoo
fuente
1
¡Gran elección! ¿También la publicación de sus paquetes MNP ESModule nativa, la compatibilidad hacia atrás de la vieja requiremanera?
Dan Dascalescu
66
¡Puedes hacer lo mismo con require!
Suisse
44
const {a,b} = require('module.js'); funciona igual ... si exportas ayb
BananaAcid
module.exports = { a: ()={}, b: 22 }- La segunda parte de @BananaAcid responde
Seth McClaine
7

A partir de ahora la importación de ES6, la exportación siempre se compila a CommonJS , por lo que no hay ningún beneficio al usar uno u otro. Aunque se recomienda el uso de ES6, ya que debería ser ventajoso cuando se lanza el soporte nativo de los navegadores. La razón es que puede importar parciales de un archivo, mientras que con CommonJS debe requerir todo el archivo.

ES6 → import, export default, export

CommonJS → require, module.exports, exports.foo

A continuación se muestra el uso común de esos.

Exportación ES6 predeterminada

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

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

ES6 exporta múltiples e importa múltiples

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

Módulo CommonJS exporta múltiples

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

No estoy seguro de por qué (probablemente optimización - ¿carga lenta?) Funciona así, pero he notado que es importposible que no analice el código si no se utilizan módulos importados.
Lo cual puede no ser un comportamiento esperado en algunos casos.

Tome la odiada clase Foo como nuestra dependencia de muestra.

foo.ts

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

Por ejemplo:

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

Por otra parte:

index.ts

import Foo from './foo'
typeof Foo; // any use case
// prints "Foo loaded"
l00k
fuente