¿Cómo puedo importar condicionalmente un módulo ES6?

194

Necesito hacer algo como:

if (condition) {
    import something from 'something';
}
// ...
if (something) {
    something.doStuff();
}

El código anterior no se compila; lanza SyntaxError: ... 'import' and 'export' may only appear at the top level.

Intenté usar System.importcomo se muestra aquí , pero no sé de dónde Systemviene. ¿Es una propuesta de ES6 que no terminó siendo aceptada? El enlace a la "API programática" de ese artículo me devuelve a una página de documentos en desuso .

ericsoco
fuente
Solo impórtelo normalmente. Su módulo lo necesita independientemente.
Andy
Realmente no veo ninguna razón por la que no importarías sin importar la condición. No es que haya algún tipo de sobrecarga. En algunos casos, necesita el archivo, por lo que no es como si alguna vez se hubiera omitido por completo. En ese caso, simplemente impórtelo incondicionalmente.
Gracias
8
Mi caso de uso: quiero que sea fácil tener una dependencia opcional. Si el dep no es necesario, el usuario lo elimina package.json; my gulpfileluego comprueba si esa dependencia existe antes de realizar algunos pasos de compilación.
ericsoco
1
Otro caso de uso: para fines de prueba. Estoy usando webpacky babelpara transpilar es6 a es5. Proyectos como webpack-rewirey similares no son de ayuda aquí: github.com/jhnns/rewire-webpack/issues/12 . Una forma de establecer que la prueba se duplique O para eliminar dependencias problemáticas podría ser la importación condicional.
Amio.io
3
+1. Poder usar un módulo en múltiples entornos donde las dependencias pueden o no funcionar es crítico, particularmente cuando los módulos pueden referirse a dependencias que solo funcionarían en el navegador (por ejemplo, dónde webpackse usa para convertir hojas de estilo en módulos que insertan los estilos relevantes en el DOM cuando se importan), pero el módulo también debe ejecutarse fuera del navegador (por ejemplo, para pruebas unitarias).
Julio

Respuestas:

146

Tenemos una propuesta dinámica de importaciones ahora con ECMA. Esto está en la etapa 3. Esto también está disponible como preajuste de babel .

A continuación se muestra una forma de hacer una representación condicional según su caso.

if (condition) {
    import('something')
    .then((something) => {
       console.log(something.something);
    });
}

Esto básicamente devuelve una promesa. Se espera que la resolución de promesa tenga el módulo. La propuesta también tiene otras características como importaciones dinámicas múltiples, importaciones predeterminadas, importación de archivos js, etc. Puede encontrar más información sobre importaciones dinámicas aquí .

thecodejack
fuente
13
Finalmente, una respuesta real, ES6! Gracias @thecodejack. En realidad, en la etapa 3 a partir de este escrito, de acuerdo con ese artículo ahora.
ericsoco
55
o si acaba de nombrar exportaciones puede desestructurar:if (condition) { import('something') .then(({ somethingExported }) => { console.log(somethingExported); }); }
ivn
44
en Firefox y mientras se ejecuta npm run buildsigo recibiendo el error:SyntaxError: ... 'import' and 'export' may only appear at the top level
ste
1
@stackjlei: Esta característica aún no forma parte del estándar de JavaScript, ¡es solo una propuesta de etapa 3! Sin embargo, ya está implementado en muchos navegadores más nuevos, consulte caniuse.com/#feat=es6-module-dynamic-import .
Konrad Höffner
1
Esa función de importación dinámica condicional no tiene la capacidad específica para importar solo elementos particulares que tiene "importar X de Y". De hecho, esa habilidad de grano fino podría ser aún más importante en la carga dinámica (en oposición a la agrupación previa al proceso)
Craig Hicks
102

Si lo desea, puede usar require. Esta es una manera de tener una declaración de requerimiento condicional.

let something = null;
let other = null;

if (condition) {
    something = require('something');
    other = require('something').other;
}
if (something && other) {
    something.doStuff();
    other.doOtherStuff();
}
BaptWaels
fuente
1
Creo que algo y otras variables se declsed usando const, que tiene un alcance de bloque, por lo que el segundo si la condición arrojará que algo no está definido
Mohammed Essehemy
Sería mejor usar let y declarar las dos variables fuera del bloque en lugar de usar 'var' y evitar el alcance del bloque por completo.
Vorcan
¿La elevación afecta algo en este caso? Me he encontrado con algunos problemas en los que la elevación significaba que había importado inesperadamente una biblioteca al seguir un patrón cercano a este si la memoria funciona.
Thomas
11
Debe señalarse que require()no es parte del JavaScript estándar: es una función incorporada en Node.js, por lo que solo es útil en ese entorno. El OP no indica que trabaje con Node.js.
Velojet
56

No puede importar condicionalmente, pero puede hacer lo contrario: exportar algo condicionalmente. Depende de su caso de uso, por lo que esta solución podría no ser adecuada para usted.

Tu puedes hacer:

api.js

import mockAPI from './mockAPI'
import realAPI from './realAPI'

const exportedAPI = shouldUseMock ? mockAPI : realAPI
export default exportedAPI

apiConsumer.js

import API from './api'
...

Lo uso para burlarme de bibliotecas analíticas como mixpanel, etc., porque no puedo tener varias compilaciones o nuestra interfaz actualmente. No es el más elegante, pero funciona. Solo tengo algunos 'si' aquí y allá dependiendo del entorno porque en el caso de mixpanel, necesita inicialización.

Kev
fuente
40
Esta solución hace que se carguen módulos no deseados, así que creo que no es una solución óptima.
ismailarilik
55
Como se indica en la respuesta, esta es una solución alternativa. En ese momento, simplemente no había solución. Las importaciones de ES6 no son dinámicas, esto es por diseño. La propuesta de función de importación dinámica ES6, que se describe en la respuesta actualmente aceptada, puede hacerlo. JS está evolucionando :)
Kev
9

Parece que la respuesta es que, a partir de ahora, no puedes.

http://exploringjs.com/es6/ch_modules.html#sec_module-loader-api

Creo que la intención es permitir el análisis estático tanto como sea posible, y los módulos importados condicionalmente lo rompen. También vale la pena mencionar: estoy usando Babel , y supongo que SystemBabel no es compatible porque la API del cargador de módulos no se convirtió en un estándar ES6.

ericsoco
fuente
@FelixKling hace que esta sea su propia respuesta y lo aceptaré con mucho gusto.
ericsoco
4

require()es una forma de importar algún módulo en el tiempo de ejecución y califica igualmente para el análisis estático como importsi se usara con rutas literales de cadena. El paquete lo requiere para seleccionar dependencias para el paquete.

const defaultOne = require('path/to/component').default;
const NamedOne = require('path/to/component').theName;

Para la resolución de módulos dinámicos con soporte completo de análisis estático, primeros módulos de índice en un indexador (index.js) e importar el indexador en el módulo host.

// index.js
export { default as ModuleOne } from 'path/to/module/one';
export { default as ModuleTwo } from 'path/to/module/two';
export { SomeNamedModule } from 'path/to/named/module';

// host.js
import * as indexer from 'index';
const moduleName = 'ModuleOne';
const Module = require(indexer[moduleName]);
Shoaib Nawaz
fuente
77
Debe señalarse que require()no es parte del JavaScript estándar: es una función incorporada en Node.js, por lo que solo es útil en ese entorno. El OP no indica que trabaje con Node.js.
Velojet
2

Diferencia importante si usa el modo dinámico de Webpack de importación eager:

if (normalCondition) {
  // this will be included to bundle, whether you use it or not
  import(...);
}

if (process.env.SOMETHING === 'true') {
  // this will not be included to bundle, if SOMETHING is not 'true'
  import(...);
}
Solo
fuente
Pero importdevuelve una promesa.
Newguy
0

oscurecerlo en una evaluación funcionó para mí, ocultándolo del analizador estático ...

if (typeof __CLI__ !== 'undefined') {
  eval("require('fs');")
}
Chris Marstall
fuente
3
¿Alguien puede explicar por qué esta respuesta fue rechazada? ¿Hay algún inconveniente real o fue solo una reacción negativa automática a la palabra clave malvada 'eval'?
Yuri Gor
3
Voto negativo automático para usar la horrible palabra clave eval. Mantente alejado.
Tormod Haugene
1
¿Puedes explicar qué está mal con el uso de evalaquí, @TormodHaugene?
Adam Barnes el
MDN resume bastantes razones por las cuales eval no deben usarse . En general: si encuentra la necesidad de usar eval, probablemente lo esté haciendo mal y debería dar un paso atrás para considerar sus alternativas. Probablemente hay algunos escenarios donde el uso evales correcto, pero lo más probable es que no haya encontrado una de esas situaciones.
Tormod Haugene
55
Debe señalarse que require()no es parte del JavaScript estándar: es una función incorporada en Node.js, por lo que solo es útil en ese entorno. El OP no indica que trabaje con Node.js.
Velojet
0

Pude lograr esto usando una función invocada de inmediato y requiero una declaración.

const something = (() => (
  condition ? require('something') : null
))();

if(something) {
  something.doStuff();
}
bradley2w1dl
fuente
55
Debe señalarse que require()no es parte del JavaScript estándar: es una función incorporada en Node.js, por lo que solo es útil en ese entorno. El OP no indica que trabaje con Node.js.
Velojet
0

Las importaciones condicionales también podrían lograrse con un ternario y require()s:

const logger = DEBUG ? require('dev-logger') : require('logger');

Este ejemplo fue tomado de ES Lint global-require docs: https://eslint.org/docs/rules/global-require

Elliot Ledger
fuente
55
Debe señalarse que require()no es parte del JavaScript estándar: es una función incorporada en Node.js, por lo que solo es útil en ese entorno. El OP no indica que trabaje con Node.js.
Velojet
0

No, no puedes!

Sin embargo, haber topado con ese problema debería hacerte repensar cómo organizas tu código.

Antes de los módulos ES6, teníamos módulos CommonJS que usaban la sintaxis require (). Estos módulos eran "dinámicos", lo que significa que podíamos importar nuevos módulos según las condiciones de nuestro código. - fuente: https://bitsofco.de/what-is-tree-shaking/

Supongo que una de las razones por las que dejaron de admitir ES6 en adelante es el hecho de que compilarlo sería muy difícil o imposible.

Aldee
fuente