Como posso importar condicionalmente um módulo ES6?

192

Eu preciso fazer algo como:

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

O código acima não compila; joga SyntaxError: ... 'import' and 'export' may only appear at the top level.

Tentei usar System.importcomo mostrado aqui , mas não sei de onde Systemvem. É uma proposta do ES6 que não acabou sendo aceita? O link para "API programática" desse artigo me leva a uma página de documentos obsoleta .

ericsoco
fonte
Apenas importe-o normalmente. Seu módulo precisa dele independentemente.
Andy
Realmente não vejo nenhuma razão para você não importar apenas independentemente da condição. Não é como se houvesse algum tipo de sobrecarga. Em alguns cenários, você precisa do arquivo, por isso não é como se houvesse um caso em que ele pudesse ser totalmente ignorado. Nesse caso, apenas importe-o incondicionalmente.
Obrigado
8
Meu caso de uso: desejo facilitar a dependência opcional. Se o dep não for necessário, o usuário o remove package.json; o meu gulpfileverifica se essa dependência existe antes de executar algumas etapas de compilação.
Ericsoco 04/04
1
Outro caso de uso: para fins de teste. Estou usando webpacke babelpara transpilar es6 para es5. Projetos como webpack-rewiree similares não devem ajudar aqui - github.com/jhnns/rewire-webpack/issues/12 . Uma maneira de definir o dobro de teste OU para remover dependências problemáticas pode ser a importação condicional.
Amio.io 5/09/16
3
+1. Ser capaz de usar um módulo em vários ambientes onde dependências podem ou não funcionar é fundamental, principalmente quando os módulos podem se referir a dependências que funcionariam apenas no navegador (por exemplo, onde webpacké usado para converter folhas de estilo em módulos que inserem os estilos relevantes no DOM quando são importados), mas o módulo também precisa ser executado fora do navegador (por exemplo, para teste de unidade).
Jules

Respostas:

142

Agora temos uma proposta de importação dinâmica com a ECMA. Isso está no estágio 3. Também está disponível como predefinição de babel .

A seguir, é apresentado um modo de renderização condicional conforme seu caso.

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

Isso basicamente retorna uma promessa. Espera-se que a resolução da promessa tenha o módulo. A proposta também possui outros recursos, como várias importações dinâmicas, importações padrão, importação de arquivos js etc. Você pode encontrar mais informações sobre importações dinâmicas aqui .

thecodejack
fonte
13
Finalmente, uma resposta ES6 real! Obrigado @thecodejack. Na verdade, no estágio 3 até o momento da redação deste artigo, de acordo com esse artigo agora.
Ericsoco 9/10/19
5
ou se você acabou de nomear exportações, pode desestruturar:if (condition) { import('something') .then(({ somethingExported }) => { console.log(somethingExported); }); }
ivn 23/03/18
4
no Firefox e durante a execução npm run build, ainda recebo o erro:SyntaxError: ... 'import' and 'export' may only appear at the top level
ste
isso falha no teste tho, alguém tem idéias?
stackjlei
1
Essa função de importação dinâmica condicional não possui a capacidade refinada de importar apenas elementos específicos que "importam X de Y". Na verdade que a capacidade de granulação fina poderia ser ainda mais importante em carga dinâmica (em oposição a um agrupamento de pré-processamento)
Craig Hicks
101

Se desejar, você pode usar o exigir. Esta é uma maneira de ter uma instrução de requisição condicional.

let something = null;
let other = null;

if (condition) {
    something = require('something');
    other = require('something').other;
}
if (something && other) {
    something.doStuff();
    other.doOtherStuff();
}
BaptWaels
fonte
1
Acho que alguma coisa e outras variáveis são declsred usando const que está bloco de escopo, de modo que o segundo se a condição vai jogar que algo não está definido
Mohammed Essehemy
Seria melhor usar let e declarar as duas variáveis ​​fora do bloco em vez de usar 'var' e evitar completamente o escopo do bloco.
Vorcan
A elevação afeta alguma coisa neste caso? Encontrei alguns problemas em que a elevação significou que eu imprevisivelmente importei uma biblioteca ao seguir um padrão próximo a isso se a memória servir.
Thomas
11
É preciso salientar que require() não faz parte do JavaScript padrão - é uma função interna do Node.js. Portanto, é útil apenas nesse ambiente. O OP não dá indicação de trabalho com Node.js.
Velojet 16/05/19
54

Você não pode importar condicionalmente, mas pode fazer o oposto: exportar algo condicionalmente. Depende do seu caso de uso, portanto, essa solução alternativa pode não ser para você.

Você pode fazer:

api.js

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

const exportedAPI = shouldUseMock ? mockAPI : realAPI
export default exportedAPI

apiConsumer.js

import API from './api'
...

Eu uso isso para simular bibliotecas de análise como mixpanel, etc ... porque não posso ter várias compilações ou nosso front-end atualmente. Não é o mais elegante, mas funciona. Eu só tenho alguns 'se' aqui e ali, dependendo do ambiente, porque no caso do mixpanel, ele precisa de inicialização.

Kev
fonte
39
Essa solução faz com que módulos indesejados sejam carregados, portanto, não é uma solução ideal, eu acho.
Ismailarilik 11/11/19
5
Como indicado na resposta, esta é uma solução alternativa. Naquela época, simplesmente não havia solução. As importações do ES6 não são dinâmicas, isso ocorre por design. A proposta da função de importação dinâmica ES6, descrita na resposta atualmente aceita, pode fazer isso. JS está evoluindo :)
Kev
9

Parece que a resposta é que, a partir de agora, você não pode.

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

Penso que a intenção é permitir a análise estática, tanto quanto possível, e os módulos importados condicionalmente quebram isso. Também vale a pena mencionar - estou usando o Babel e acho que isso Systemnão é suportado pelo Babel porque a API do carregador de módulos não se tornou um padrão ES6.

ericsoco
fonte
O @FelixKling faz desta a sua própria resposta e eu aceito com prazer!
Ericsoco 04/04
4

require()é uma maneira de importar algum módulo no tempo de execução e se qualifica igualmente para análise estática, como importse usado com caminhos literais de cadeia de caracteres. Isso é requerido pelo bundler para escolher dependências para o bundle.

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

Para resolução dinâmica de módulos com suporte completo à análise estática, primeiro indexe os módulos em um indexador (index.js) e importe o indexador no 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
fonte
7
É preciso ressaltar que require()não faz parte do JavaScript padrão - é uma função interna do Node.js. Portanto, é útil apenas nesse ambiente. O OP não dá indicação de trabalho com Node.js.
Velojet 16/05/19
2

Diferença importante se você usar o modo Webpack de importação dinâmica 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(...);
}

fonte
Mas importretorna uma promessa.
newguy
0

obscurecê-lo em uma avaliação funcionou para mim, escondendo-o do analisador estático ...

if (typeof __CLI__ !== 'undefined') {
  eval("require('fs');")
}
Chris Marstall
fonte
3
Alguém pode explicar por que essa resposta foi rebaixada? Existe alguma desvantagem real ou foi apenas uma reação negativa automática à palavra-chave maligna 'eval'?
Yuri Gor
3
Downvote automático para usar a palavra-chave hedionda eval. Ficar longe.
Tormod Haugene 25/02/19
1
Você pode explicar o que realmente está errado com o uso evalaqui, @TormodHaugene?
Adam Barnes
O MDN resume algumas razões pelas quais evalnão deve ser usado . Em geral: se você achar necessário usar eval, provavelmente está fazendo algo errado e deve dar um passo atrás para considerar suas alternativas. Provavelmente, existem alguns cenários em que o uso evalestá correto, mas você provavelmente não encontrou uma dessas situações.
Tormod Haugene
5
É preciso ressaltar que require()não faz parte do JavaScript padrão - é uma função interna do Node.js. Portanto, é útil apenas nesse ambiente. O OP não dá indicação de trabalho com Node.js.
Velojet 16/05/19
0

Consegui fazer isso usando uma função imediatamente invocada e exigir instrução.

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

if(something) {
  something.doStuff();
}
bradley2w1dl
fonte
5
É preciso ressaltar que require()não faz parte do JavaScript padrão - é uma função interna do Node.js. Portanto, é útil apenas nesse ambiente. O OP não dá indicação de trabalho com Node.js.
Velojet 16/05/19
0

As importações condicionais também poderiam ser alcançadas com um ternário require()es:

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

Este exemplo foi retirado dos documentos de solicitação global do ES Lint: https://eslint.org/docs/rules/global-require

Elliot Ledger
fonte
5
É preciso ressaltar que require()não faz parte do JavaScript padrão - é uma função interna do Node.js. Portanto, é útil apenas nesse ambiente. O OP não dá indicação de trabalho com Node.js.
Velojet 16/05/19
0

Não, você não pode!

No entanto, ter esbarrado nessa questão deve fazer você repensar sobre como você organiza seu código.

Antes dos módulos ES6, tínhamos módulos CommonJS que usavam a sintaxe require (). Esses módulos eram "dinâmicos", o que significa que poderíamos importar novos módulos com base nas condições do nosso código. - fonte: https://bitsofco.de/what-is-tree-shaking/

Eu acho que uma das razões pelas quais eles abandonaram o suporte no ES6 em diante é o fato de que compilá-lo seria muito difícil ou impossível.

Aldee
fonte