¿Es posible importar módulos de todos los archivos en un directorio, usando un comodín?

256

Con ES6, puedo importar varias exportaciones de un archivo como este:

import {ThingA, ThingB, ThingC} from 'lib/things';

Sin embargo, me gusta la organización de tener un módulo por archivo. Termino con importaciones como esta:

import ThingA from 'lib/things/ThingA';
import ThingB from 'lib/things/ThingB';
import ThingC from 'lib/things/ThingC';

Me encantaría poder hacer esto:

import {ThingA, ThingB, ThingC} from 'lib/things/*';

o algo similar, con la convención entendida de que cada archivo contiene una exportación predeterminada, y que cada módulo recibe el mismo nombre que su archivo.

es posible?

Frambot
fuente
Esto es posible. Consulte la documentación del módulo para babel babeljs.io/docs/learn-es2015 ... guched "import {sum, pi} from" lib / math ";". La respuesta aceptada ya no es válida. Por favor actualízalo.
Eduard Jacko
66
@kresli No creo que entiendas la pregunta. En los documentos, lib/mathes un archivo que contiene múltiples exportaciones. En mi pregunta, lib/math/es un directorio que contiene varios archivos, cada uno con una exportación.
Frambot
2
OK veo. En ese caso, Bergi tiene razón. Lo siento
Eduard Jacko

Respuestas:

231

No creo que esto sea posible, pero afaik la resolución de los nombres de los módulos depende de los cargadores de módulos, por lo que podría haber una implementación de cargador que sí lo soporte.

Hasta entonces, podría usar un "archivo de módulo" intermedio lib/things/index.jsque solo contiene

export * from 'ThingA';
export * from 'ThingB';
export * from 'ThingC';

y te permitiría hacer

import {ThingA, ThingB, ThingC} from 'lib/things';
Bergi
fuente
66
Gracias por la ayuda. Yo era capaz de conseguir este trabajo con index.jsel aspecto de: import ThingA from 'things/ThingA'; export {ThingA as ThingA}; import ThingB from 'things/ThingB'; export {ThingB as ThingB};. Otros encantamientos index.jsno se moverían.
Frambot
2
Hm, export * fromdebería funcionar. ¿Has probado …from './ThingA'o export ThingA from …? ¿Qué cargador de módulo estás usando?
Bergi
77
Sí, su respuesta original funcionó si cada ThingA.js, ThingB.js, cada uno exportaba exportaciones con nombre. Correcto.
Frambot
1
¿Tiene que especificar el archivo de índice o puede especificar solo la carpeta y se cargará index.js en su lugar?
Zorgatone
1
@ Zorgatone: Eso depende del cargador de módulos que esté utilizando, pero sí, por lo general, la ruta de la carpeta será suficiente.
Bergi
128

Solo una variación del tema ya proporcionado en la respuesta, pero ¿qué tal esto:

En una Thing,

export default function ThingA () {}

en things/index.js,

export {default as ThingA} from './ThingA'
export {default as ThingB} from './ThingB'
export {default as ThingC} from './ThingC'

Luego de consumir todas las cosas en otra parte,

import * as things from './things'
things.ThingA()

O para consumir solo algunas cosas,

import {ThingA,ThingB} from './things'
Jed Richards
fuente
¿Quieres echar un vistazo a la respuesta de @ wolfbiter? No estoy seguro de por qué dice que los paréntesis no funcionan.
Bergi
@Bergi Sí, de acuerdo, no creo que Wolfbiter's sea ES6 válido. ¿Tal vez está usando una versión antigua de Babel, o algún otro transpilador?
Jed Richards
¿Cómo se transpira esto? Importar un directorio no se resuelve index.jspara mí. Estoy usando SystemJs + Babel
jasonszhao
2
¿No puedes simplemente escribir en export ThingA from './ThingA'lugar deexport {default as ThingA} from './ThingA'
Petr Peller
1
¿Se aprovecha esto de tres temblores? si importo {ThingA} desde './things', ¿también se agregarán ThingB y ThingC al paquete?
Giorgio
75

Las respuestas actuales sugieren una solución alternativa, pero me molesta por qué esto no existe, así que he creado un babelcomplemento que hace esto.

Instálelo usando:

npm i --save-dev babel-plugin-wildcard

luego agréguelo a su .babelrccon:

{
    "plugins": ["wildcard"]
}

vea el repositorio para obtener información detallada sobre la instalación


Esto le permite hacer esto:

import * as Things from './lib/things';

// Do whatever you want with these :D
Things.ThingA;
Things.ThingB;
Things.ThingC;

Una vez más, el repositorio contiene más información sobre lo que hace exactamente, pero hacerlo de esta manera evita crear index.jsarchivos y también sucede en tiempo de compilación para evitar hacer readdirs en tiempo de ejecución.

También con una versión más nueva puede hacer exactamente como su ejemplo:

 import { ThingsA, ThingsB, ThingsC } from './lib/things/*';

funciona igual que el anterior.

Downgoat
fuente
3
Advertencia, tengo problemas graves con este complemento. Probablemente los problemas provengan de su almacenamiento en caché interno, se estará sacando el pelo, cuando su código será perfecto, pero su script no funcionará correctamente porque agregó un archivo ./lib/things;y no se está recogiendo. Los problemas que causa son ridículos. Acabo de presenciar la situación, cuando cambiar el archivo con import *Babel hace que recoja el archivo agregado, pero cambiarlo de nuevo, devuelve el problema, como si reutilizara el caché antes del cambio. Usar con precaución.
Łukasz Zaroda
@ ŁukaszZaroda babel tiene un caché interno en el ~/.babel.jsonque causa este comportamiento extraño. Además, si está utilizando como un observador o un recargador en caliente, debe guardar el nuevo archivo para que se vuelva a compilar con la nueva lista del directorio
Downgoat
@Downgoat, ¿cómo superar esto, excepto eliminar el caché de Babel? Y por cierto. No creo que tu comentario sea correcto. Tengo el almacenamiento en caché de Babel deshabilitado y tuve un problema tan grande con ese complemento. No lo recomiendo totalmente
SOReader
1
Por cierto, para cualquiera que tenga más problemas, agregue bpwc clear-cacheporque el paquete web y otros procesos de compilación seguirán almacenando en caché en silencio
Downgoat
Esta es una gran idea, pero tampoco pude hacerlo funcionar. Posiblemente un conflicto con mi código de tipo de flujo, no estoy seguro, pero estaba obteniendo 'ReferenceError: Foo no está definido' sin importar cómo estructuré las importaciones.
jlewkovich
13

Grandes muglys gugly! Esto fue más difícil de lo necesario.

Exportar un valor predeterminado plano

Esta es una gran oportunidad para usar spread ( ...a { ...Matters, ...Contacts }continuación:

// imports/collections/Matters.js
export default {           // default export
  hello: 'World',
  something: 'important',
};
// imports/collections/Contacts.js
export default {           // default export
  hello: 'Moon',
  email: '[email protected]',
};
// imports/collections/index.js
import Matters from './Matters';      // import default export as var 'Matters'
import Contacts from './Contacts';

export default {  // default export
  ...Matters,     // spread Matters, overwriting previous properties
  ...Contacts,    // spread Contacts, overwriting previosu properties
};
// imports/test.js
import collections from './collections';  // import default export as 'collections'

console.log(collections);

Luego, para ejecutar el código compilado de babel desde la línea de comandos (desde la raíz del proyecto /):

$ npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node 
(trimmed)

$ npx babel-node --presets @babel/preset-env imports/test.js 
{ hello: 'Moon',
  something: 'important',
  email: '[email protected]' }

Exportar un valor predeterminado similar a un árbol

Si prefiere no sobrescribir las propiedades, cambie:

// imports/collections/index.js
import Matters from './Matters';     // import default as 'Matters'
import Contacts from './Contacts';

export default {   // export default
  Matters,
  Contacts,
};

Y la salida será:

$ npx babel-node --presets @babel/preset-env imports/test.js
{ Matters: { hello: 'World', something: 'important' },
  Contacts: { hello: 'Moon', email: '[email protected]' } }

Exportar múltiples exportaciones con nombre sin valor predeterminado

Si está dedicado a DRY , la sintaxis de las importaciones también cambia:

// imports/collections/index.js

// export default as named export 'Matters'
export { default as Matters } from './Matters';  
export { default as Contacts } from './Contacts'; 

Esto crea 2 exportaciones con nombre sin exportación predeterminada. Luego cambia:

// imports/test.js
import { Matters, Contacts } from './collections';

console.log(Matters, Contacts);

Y la salida:

$ npx babel-node --presets @babel/preset-env imports/test.js
{ hello: 'World', something: 'important' } { hello: 'Moon', email: '[email protected]' }

Importar todas las exportaciones nombradas

// imports/collections/index.js

// export default as named export 'Matters'
export { default as Matters } from './Matters';
export { default as Contacts } from './Contacts';
// imports/test.js

// Import all named exports as 'collections'
import * as collections from './collections';

console.log(collections);  // interesting output
console.log(collections.Matters, collections.Contacts);

Observe la desestructuración import { Matters, Contacts } from './collections'; en el ejemplo anterior.

$ npx babel-node --presets @babel/preset-env imports/test.js
{ Matters: [Getter], Contacts: [Getter] }
{ hello: 'World', something: 'important' } { hello: 'Moon', email: '[email protected]' }

En la práctica

Dados estos archivos fuente:

/myLib/thingA.js
/myLib/thingB.js
/myLib/thingC.js

La creación de un /myLib/index.jspaquete para agrupar todos los archivos anula el propósito de importar / exportar. Sería más fácil hacer que todo sea global en primer lugar, que hacerlo todo global mediante la importación / exportación a través de index.js "wrapper files".

Si desea un archivo import thingA from './myLib/thingA';en particular, en sus propios proyectos.

Crear un "archivo contenedor" con exportaciones para el módulo solo tiene sentido si está empaquetando para npm o en un proyecto multianual de varios equipos.

¿Llegó tan lejos? Vea los documentos para más detalles.

Además, sí, Stackoverflow finalmente admite tres s como marcado de valla de código.

Michael Cole
fuente
10

Puede usar la importación asincrónica ():

import fs = require('fs');

y entonces:

fs.readdir('./someDir', (err, files) => {
 files.forEach(file => {
  const module = import('./' + file).then(m =>
    m.callSomeMethod();
  );
  // or const module = await import('file')
  });
});
mr_squall
fuente
2
Las importaciones dinámicas son buenas así. Seguro que no existían cuando se hizo la pregunta. Gracias por la respuesta.
Frambot
6

Similar a la pregunta aceptada, pero le permite escalar sin la necesidad de agregar un nuevo módulo al archivo de índice cada vez que crea uno:

./modules/moduleA.js

export const example = 'example';
export const anotherExample = 'anotherExample';

./modules/index.js

// require all modules on the path and with the pattern defined
const req = require.context('./', true, /.js$/);

const modules = req.keys().map(req);

// export all modules
module.exports = modules;

./example.js

import { example, anotherExample } from './modules'
Nicolas
fuente
Esto no funciona para mí cuando intento importar como un alias en./example.js
tsujp
doens't funciona para mí ni (4,41 webpack, Babel 7.7)
Edwin Joassart
3

Los he usado varias veces (en particular para construir objetos masivos que dividen los datos en muchos archivos (por ejemplo, nodos AST)), para construirlos hice un pequeño script (que acabo de agregar a npm para que todos los demás puede usarlo).

Uso (actualmente necesitará usar babel para usar el archivo de exportación):

$ npm install -g folder-module
$ folder-module my-cool-module/

Genera un archivo que contiene:

export {default as foo} from "./module/foo.js"
export {default as default} from "./module/default.js"
export {default as bar} from "./module/bar.js"
...etc

Entonces puedes consumir el archivo:

import * as myCoolModule from "my-cool-module.js"
myCoolModule.foo()
Jamesernator
fuente
No funciona correctamente en Windows, genera la ruta como una ruta de Windows ( \` instead of / ) also as an improvment you may want to allow two options like --filename` && --destpara permitir la personalización de dónde se debe almacenar el archivo creado y bajo qué nombre. Tampoco funciona con nombres de archivo que contienen .(como user.model.js)
Yuri Scarbaci
2

Solo otro enfoque para la respuesta de @ Bergi

// lib/things/index.js
import ThingA from './ThingA';
import ThingB from './ThingB';
import ThingC from './ThingC';

export default {
 ThingA,
 ThingB,
 ThingC
}

Usos

import {ThingA, ThingB, ThingC} from './lib/things';
Ashok Vishwakarma
fuente
No va a funcionar Acabo de probarlo en una aplicación de reacción y volvió export '...' was not found in '.....
Hamid Mayeli
1

Puede usar require también:

const moduleHolder = []

function loadModules(path) {
  let stat = fs.lstatSync(path)
  if (stat.isDirectory()) {
    // we have a directory: do a tree walk
    const files = fs.readdirSync(path)
    let f,
      l = files.length
    for (var i = 0; i < l; i++) {
      f = pathModule.join(path, files[i])
      loadModules(f)
    }
  } else {
    // we have a file: load it
    var controller = require(path)
    moduleHolder.push(controller)
  }
}

Luego use su moduleHolder con controladores cargados dinámicamente:

  loadModules(DIR) 
  for (const controller of moduleHolder) {
    controller(app, db)
  }
mr_squall
fuente
0

Esto no es exactamente lo que pediste, pero con este método puedo iterar componentsListen mis otros archivos y usar funciones como las componentsList.map(...)que encuentro bastante útiles.

import StepOne from './StepOne';
import StepTwo from './StepTwo';
import StepThree from './StepThree';
import StepFour from './StepFour';
import StepFive from './StepFive';
import StepSix from './StepSix';
import StepSeven from './StepSeven';
import StepEight from './StepEight';

const componentsList= () => [
  { component: StepOne(), key: 'step1' },
  { component: StepTwo(), key: 'step2' },
  { component: StepThree(), key: 'step3' },
  { component: StepFour(), key: 'step4' },
  { component: StepFive(), key: 'step5' },
  { component: StepSix(), key: 'step6' },
  { component: StepSeven(), key: 'step7' },
  { component: StepEight(), key: 'step8' }
];

export default componentsList;
FlyingZipper
fuente
0

Si estás usando webpack. Esto importa archivos automáticamente y exporta como espacio de nombres api .

Por lo tanto, no es necesario actualizar cada vez que se agrega un archivo.

import camelCase from "lodash-es";
const requireModule = require.context("./", false, /\.js$/); // 
const api = {};

requireModule.keys().forEach(fileName => {
  if (fileName === "./index.js") return;
  const moduleName = camelCase(fileName.replace(/(\.\/|\.js)/g, ""));
  api[moduleName] = {
    ...requireModule(fileName).default
  };
});

export default api;

Para usuarios de Typecript;

import { camelCase } from "lodash-es"
const requireModule = require.context("./folderName", false, /\.ts$/)

interface LooseObject {
  [key: string]: any
}

const api: LooseObject = {}

requireModule.keys().forEach(fileName => {
  if (fileName === "./index.ts") return
  const moduleName = camelCase(fileName.replace(/(\.\/|\.ts)/g, ""))
  api[moduleName] = {
    ...requireModule(fileName).default,
  }
})

export default api
atilkan
fuente
0

Pude tomar el enfoque del usuario atilkan y modificarlo un poco:

Para usuarios de Typecript;

require.context('@/folder/with/modules', false, /\.ts$/).keys().forEach((fileName => {
    import('@/folder/with/modules' + fileName).then((mod) => {
            (window as any)[fileName] = mod[fileName];
            const module = new (window as any)[fileName]();

            // use module
});

}));
Justin Icenhour
fuente
-9

si no exporta valores predeterminados en A, B, C pero solo exporta {}, entonces es posible hacerlo

// things/A.js
export function A() {}

// things/B.js
export function B() {}

// things/C.js
export function C() {}

// foo.js
import * as Foo from ./thing
Foo.A()
Foo.B()
Foo.C()
hjl
fuente
1
Esto no es javascript válido (no hay comillas ./thing) e incluso si lo hubiera, no funcionaría. (Lo probé y no funcionó).
John