Nazwa importu zmiennej ES6 w node.js?

108

czy można zaimportować coś do modułu podając nazwę zmiennej podczas korzystania z importu ES6?

To znaczy chcę zaimportować jakiś moduł w czasie wykonywania w zależności od wartości podanych w konfiguracji:

import something from './utils/' + variableName;
Vytautas Butkus
źródło
1
@Bigood tak, kompilator wyskakuje i Webstorm również pokazuje błąd
Vytautas Butkus

Odpowiedzi:

67

Nie z importoświadczeniem. importi exportsą zdefiniowane w taki sposób, że można je analizować statycznie, więc nie mogą zależeć od informacji o czasie wykonywania.

Szukasz API modułu ładującego (polyfill) , ale jestem trochę niejasny co do statusu specyfikacji:

System.import('./utils/' + variableName).then(function(m) {
  console.log(m);
});
Felix Kling
źródło
3
czy muszę „wymagać” systemu? Nie ma zasięgu globalnego .. Ps Używam babel js
Vytautas Butkus
Nie jest jeszcze nigdzie zaimplementowany AFAIK. Musisz użyć wypełnienia z linku.
Felix Kling
1
Właśnie sprawdziłem, to trochę działa (próbuje załadować plik), ale potem psuje ścieżki dla innych modułów ... Szkoda, że ​​nie jest obsługiwany natywnie ..
Vytautas Butkus
3
Czy to nadal problem? Muszę dynamicznie załadować moduły ES6, ale nie udało mi się.
calbertts
26

Oprócz odpowiedzi Felixa , zauważę wyraźnie, że nie jest to obecnie dozwolone w gramatyce ECMAScript 6 :

Deklaracja importu :

  • import ImportClause FromClause;

  • import ModuleSpecifier;

FromClause :

  • z ModuleSpecifier

ModułSpecifier :

  • StringLiteral

ModuleSpecifier może być tylko literał łańcuchowy , a nie innego rodzaju ekspresji niczym AdditiveExpression .

apsillers
źródło
2
Szkoda, że ​​nie zostało to rozszerzone o const string literals. Można je analizować statycznie, prawda? Umożliwiłoby to ponowne wykorzystanie lokalizacji zależności. (np. zaimportuj szablon i miej dostęp do szablonu i lokalizacji szablonu).
nicodemus13
26

Chociaż w rzeczywistości nie jest to import dynamiczny (np. W moich okolicznościach wszystkie pliki, które importuję poniżej, zostaną zaimportowane i dołączone do pakietu internetowego, a nie wybrane w czasie wykonywania), wzorzec, którego używam, który może pomóc w pewnych okolicznościach to :

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

const templates = {
  Template1,
  Template2
};

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

lub alternatywnie:

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

Nie sądzę, żebym mógł tak łatwo wrócić do wartości domyślnej za pomocą require(), co powoduje błąd, jeśli spróbuję zaimportować skonstruowaną ścieżkę szablonu, która nie istnieje.

Dobre przykłady i porównania między wymaganiami i importem można znaleźć tutaj: http://www.2ality.com/2014/09/es6-modules-final.html

Doskonała dokumentacja dotycząca ponownego eksportu z @iainastacio: http://exploringjs.com/es6/ch_modules.html#sec_all-exporting-styles

Interesuje mnie opinia na temat tego podejścia :)

ptim
źródło
Głosuj za. Użyłem podejścia „lub alternatywnie”. Zadziałało jak urok dla mojego niestandardowego rozwiązania lokalizacyjnego.
groundh0g
1
Powinienem był o tym pomyśleć. Świetne rozwiązanie, bez względu na to, w jaki sposób importujesz rzeczy (i nawet jeśli niczego nie importujesz). Masz listę elementów, które chcesz później uzyskać nazwę lub nazwę? Umieść je w literale obiektu zamiast literału tablicowego i pozwól składni obiektu zająć się nazywaniem ich na podstawie lokalnej nazwy stałej / zmiennej. Jeśli potrzebujesz ich ponownie jako listy, po prostu zrób Object.values(templates).
Andrew Koster
15

Istnieje nowa specyfikacja nazywana dynamicznym importem dla modułów ES. Po prostu dzwonisz import('./path/file.js')i jesteś gotowy. Funkcja zwraca obietnicę, która jest rozpatrywana z modułem, jeśli import się powiódł.

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

Przypadków użycia

Przypadki użycia obejmują importowanie komponentów oparte na trasach dla React, Vue itp. Oraz możliwość leniwego ładowania modułów , gdy są one wymagane w czasie wykonywania.

Dalsza informacja

Oto wyjaśnienie w Google Developers .

Zgodność z przeglądarkami (kwiecień 2020 r.)

Według MDN jest obsługiwany przez wszystkie obecnie popularne przeglądarki (z wyjątkiem IE), a caniuse.com wykazuje 87% poparcia w globalnym udziale w rynku. Znowu brak wsparcia w IE lub non-chromium Edge.

Nicolai Schmid
źródło
czy jesteś pewien swojej zmiany? propozycja pokazuje przykład ze zmienną ścieżką: github.com/tc39/proposal-dynamic-import#example
phil294
@Blauhirn Byłem, ale Twój link wyraźnie pokazuje, że jest taka możliwość. Chociaż nie mam pojęcia, jak na przykład webpack rozwiązałby te importy
Nicolai Schmid
popraw mnie, jeśli się mylę, ale webpack ich nie przetwarza, prawda? Pomyślałem, że chodzi o to, aby import dynamiczny działał „tak jak jest” w przeglądarce.
phil294
Tak, możesz je uruchomić w przeglądarce bez zmian. Jednak pakiet webpack automatycznie wykorzystuje import, aby podzielić aplikację na wiele pakietów dla różnych części aplikacji, na przykład dla tras. Używam ich cały czas i są naprawdę pomocne. A jeśli chodzi o „przetwarzanie”; webpack przekaże import babel, który wypełni funkcję dla starszych przeglądarek.
Nicolai Schmid
Dla jasności: dynamiczny import () będzie działał ze zmiennymi i nie musi być analizowalny statycznie (z pewnością o to właśnie chodzi w „dynamicznym”?). Zobacz moją odpowiedź.
Velojet
6

Rozumiem pytanie zadane konkretnie dla ES6 importw Node.js, ale poniższe mogą pomóc innym szukającym bardziej ogólnego rozwiązania:

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

Uwaga, jeśli importujesz moduł ES6 i chcesz uzyskać dostęp do defaulteksportu, musisz użyć jednej z następujących opcji:

let variableName = "es6.js";

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

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

Możesz również użyć destrukturyzacji z tym podejściem, które może zwiększyć znajomość składni z innymi importami:

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

Niestety, jeśli chcesz uzyskać dostęp, defaulta także zniszczyć, musisz wykonać to w kilku krokach:

// 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
źródło
4

możesz do tego użyć notacji innej niż ES6. to działa dla mnie:

let myModule = null;
if (needsToLoadModule) {
  myModule = require('my-module').default;
}
mlevanon
źródło
3

Mniej podoba mi się ta składnia, ale działa:
zamiast pisać

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

użyj tej składni:

let memberName = require("path" + "fileName");
Gil Epshtain
źródło
1
@UlysseBN Różni się w zły sposób? Albo sposób, który tak naprawdę nie ma znaczenia?
Sam
@Jacob, naprawdę są zupełnie inni, więc tak, to może mieć znaczenie w zależności od tego, co robisz. Pierwsza składnia jest oceniana statycznie, podczas gdy druga jest oceniana dynamicznie. Na przykład, jeśli używasz webpacka, nie będzie poprawnie wykonywać potrząsania drzewem z drugim. Istnieje wiele innych różnic, sugeruję przeczytanie tego dokumentu i sprawdzenie, który z nich jest bardziej odpowiedni dla Ciebie!
Ulysse BN
@Jacob - nie ma znaczenia (w większości przypadków). require()to metoda Node.JS do ładowania plików, która jest wczesną wersją. importInstrukcja jest nowszą wersją, która jest teraz częścią składni języka urzędowego. Jednak w wielu przypadkach przeglądarka użyje poprzedniej (za nauką). Wymagane oświadczenie również spienięży twoje pliki, więc jeśli plik zostanie załadowany po raz drugi, zostanie załadowany z pamięci (lepsza wydajność). Sposób importu ma swoje zalety - jeśli używasz WebPack. wtedy webpack może usunąć martwe odnośniki (te skrypty nie zostaną pobrane do klienta).
Gil Epshtain
1

Import dynamiczny () (dostępny w Chrome 63+) wykona Twoją pracę. Oto jak:

let variableName = 'test.js';
let utilsPath = './utils/' + variableName;
import(utilsPath).then((module) => { module.something(); });
Velojet
źródło
0

Zrobiłbym to w ten sposób

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
kwiecień
źródło
0

./utils/test.js

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

zadzwoń z pliku

const variableName = 'test';
const package = require(`./utils/${variableName}`);
package.default();
Andres Munoz
źródło