Nome de importação da variável ES6 em node.js?

108

é possível importar algo para o módulo fornecendo o nome da variável ao usar a importação ES6?

Ou seja, quero importar algum módulo em um tempo de execução, dependendo dos valores fornecidos em uma configuração:

import something from './utils/' + variableName;
Vytautas Butkus
fonte
1
@Bigood sim, o compilador aparece e o Webstorm também mostra um erro
Vytautas Butkus

Respostas:

67

Não com a importdeclaração. importe exportsão definidos de forma que sejam estaticamente analisáveis, de forma que não possam depender de informações de tempo de execução.

Você está procurando a API do carregador (polyfill) , mas não estou certo sobre o status da especificação:

System.import('./utils/' + variableName).then(function(m) {
  console.log(m);
});
Felix Kling
fonte
3
eu preciso "exigir" o sistema? Não é em um escopo global .. Ps estou usando babel js
Vytautas Butkus
Não foi implementado em nenhum lugar ainda AFAIK. Você tem que usar o polyfill do link.
Felix Kling de
1
Apenas checado, meio que funciona (tenta carregar o arquivo) mas depois bagunça caminhos para outros módulos ... Pena que não é suportado nativamente ..
Vytautas Butkus
3
Isso ainda é um problema? Eu preciso carregar módulos ES6 dinamicamente, mas não tive sucesso ..
calbertts
26

Além da resposta de Felix , observarei explicitamente que isso não é permitido atualmente pela gramática ECMAScript 6 :

ImportDeclaration :

  • import ImportClause FromClause;

  • import ModuleSpecifier;

FromClause :

  • de ModuleSpecifier

ModuleSpecifier :

  • StringLiteral

Um ModuleSpecifier pode ser apenas um StringLiteral , não qualquer outro tipo de expressão como uma AdditiveExpression .

Apsillers
fonte
2
É uma pena que não foi estendido para incluir const string literals. Eles são estaticamente analisáveis, não são? Isso tornaria possível reutilizar a localização de uma dependência. (por exemplo, importe um modelo e tenha o modelo e a localização do modelo disponíveis).
nicodemus13
26

Embora esta não seja realmente uma importação dinâmica (por exemplo, no meu caso, todos os arquivos que estou importando abaixo serão importados e agrupados pelo webpack, não selecionados em tempo de execução), um padrão que tenho usado que pode ajudar em algumas circunstâncias é :

import Template1 from './Template1.js';
import Template2 from './Template2.js';

const templates = {
  Template1,
  Template2
};

export function getTemplate (name) {
  return templates[name];
}

ou alternativamente:

// index.js
export { default as Template1 } from './Template1';
export { default as Template2 } from './Template2';


// OtherComponent.js
import * as templates from './index.js'
...
// handy to be able to fall back to a default!
return templates[name] || templates.Template1;

Não acho que posso voltar a um padrão tão facilmente com o require(), o que gera um erro se eu tentar importar um caminho de modelo construído que não existe.

Bons exemplos e comparações entre exigir e importar podem ser encontrados aqui: http://www.2ality.com/2014/09/es6-modules-final.html

Excelente documentação sobre reexportação de @iainastacio: http://exploringjs.com/es6/ch_modules.html#sec_all-exporting-styles

Estou interessado em ouvir feedback sobre essa abordagem :)

ptim
fonte
Voto positivo. Usei a abordagem "ou alternativamente". Funcionou como um encanto para minha solução de localização personalizada.
groundh0g
1
Eu deveria ter pensado nisso. Ótima solução, não importa como você está importando as coisas (e mesmo se você não estiver importando nada). Tem uma lista de itens cujos nomes deseja obter ou obter por nome posteriormente? Coloque-os em um literal de objeto em vez de um literal de array e deixe a sintaxe do objeto cuidar de nomeá-los com base em seu nome de constante / variável local. Se você precisar deles como uma lista novamente, basta fazer Object.values(templates).
Andrew Koster
15

Há uma nova especificação chamada importação dinâmica para módulos ES. Basicamente, você apenas liga import('./path/file.js')e está pronto para continuar. A função retorna uma promessa, que resolve com o módulo se a importação foi bem-sucedida.

async function importModule() {
   try {
      const module = await import('./path/module.js');
   } catch (error) {
      console.error('import failed');
   }
}

Casos de uso

Os casos de uso incluem a importação de componentes com base em rota para React, Vue etc. e a capacidade de carregar módulos lentamente , uma vez que são necessários durante o tempo de execução.

Outras informações

Aqui está uma explicação sobre o Google Developers .

Compatibilidade do navegador (abril de 2020)

De acordo com o MDN, ele é compatível com todos os principais navegadores atuais (exceto o IE) e caniuse.com mostra 87% de suporte em toda a participação de mercado global. Novamente sem suporte no IE ou Edge sem cromo.

Nicolai Schmid
fonte
tem certeza sobre sua edição? a proposta mostra um exemplo com um caminho variável: github.com/tc39/proposal-dynamic-import#example
phil294
@Blauhirn eu estava, mas seu link mostra claramente que é uma possibilidade. Embora eu não tenha ideia de como o webpack, por exemplo, resolveria essas importações
Nicolai Schmid
corrija-me se eu estiver errado, mas o webpack não processa isso, certo? Achei que era o objetivo das importações dinâmicas serem executadas "como estão" no navegador.
phil294
Sim, você pode executá-los no navegador como estão. Mas o webpack usa automaticamente as importações para dividir seu aplicativo em vários pacotes para diferentes partes de seu aplicativo, por exemplo, para rotas. Eu os uso o tempo todo e eles são muito úteis. E no que diz respeito ao "processamento"; webpack irá passar as importações para o babel, que irá preencher o recurso para navegadores mais antigos.
Nicolai Schmid
Para ser claro: import dinâmica () irá trabalhar com variáveis e não é necessário para ser estaticamente analisável (que é o ponto inteiro de 'dinâmica', não é?). Veja minha resposta.
Velojet
6

Eu entendo a pergunta feita especificamente para ES6 importem Node.js, mas o seguinte pode ajudar outras pessoas que procuram uma solução mais genérica:

let variableName = "es5.js";
const something = require(`./utils/${variableName}`);

Observe que se você estiver importando um módulo ES6 e precisar acessar a defaultexportação, precisará usar um dos seguintes:

let variableName = "es6.js";

// Assigning
const defaultMethod = require(`./utils/${variableName}`).default;

// Accessing
const something = require(`./utils/${variableName}`);
something.default();

Você também pode usar a desestruturação com essa abordagem, o que pode adicionar mais familiaridade de sintaxe com suas outras importações:

// Destructuring 
const { someMethod } = require(`./utils/${variableName}`);    
someMethod();

Infelizmente, se você deseja acessar defaulte também desestruturar, precisará realizar isso em várias etapas:

// ES6 Syntax
Import defaultMethod, { someMethod } from "const-path.js";

// Destructuring + default assignment
const something = require(`./utils/${variableName}`);

const defaultMethod = something.default;    
const { someMethod, someOtherMethod } = something;
MCTaylor17
fonte
4

você pode usar a notação não ES6 para fazer isso. Isto é o que funcionou para mim:

let myModule = null;
if (needsToLoadModule) {
  myModule = require('my-module').default;
}
Mlevanon
fonte
3

Gosto menos dessa sintaxe, mas funciona: em
vez de escrever

import memberName from "path" + "fileName"; 
// this will not work!, since "path" + "fileName" need to be string literal

use esta sintaxe:

let memberName = require("path" + "fileName");
Gil Epshtain
fonte
1
@UlysseBN Diferente de um jeito ruim? Ou uma maneira que realmente não importa?
Sam
@Jacob eles realmente são completamente diferentes, então sim, pode importar dependendo do que você está fazendo. A primeira sintaxe é avaliada estaticamente, enquanto a segunda é avaliada dinamicamente. Então, por exemplo, se você estiver usando o webpack, não será possível realizar o trepidação da árvore corretamente com o segundo. Existem muitas outras diferenças, sugiro que leia o documento e veja qual é mais adequado para você!
Ulysse BN
@Jacob - Não importa (na maioria dos casos). require()é um método Node.JS para carregar arquivos, que é a versão anterior. importdeclaração é a versão mais recente, que agora faz parte da sintaxe do idioma oficial. Porém, em muitos casos, o navegador usará o anterior (por trás da ciência). A declaração de requerimento também descontará seus arquivos, portanto, se um arquivo for carregado pela segunda vez, ele será carregado da memória (melhor desempenho). A forma de importação tem seus próprios benefícios - se você estiver usando o WebPack. então o webpack pode remover referências mortas (esses scripts não serão baixados para o cliente).
Gil Epshtain
1

A importação dinâmica () (disponível no Chrome 63+) fará o seu trabalho. Veja como:

let variableName = 'test.js';
let utilsPath = './utils/' + variableName;
import(utilsPath).then((module) => { module.something(); });
Velojet
fonte
0

Eu faria assim

function load(filePath) {
     return () => System.import(`${filePath}.js`); 
     // Note: Change .js to your file extension
}

let A = load('./utils/' + variableName)

// Now you can use A in your module
abril
fonte
0

./utils/test.js

export default () => {
  doSomething...
}

chamada do arquivo

const variableName = 'test';
const package = require(`./utils/${variableName}`);
package.default();
Andres Munoz
fonte