是否可以使用通配符从目录中的所有文件导入模块?


256

使用ES6,我可以从这样的文件导入多个导出:

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

但是,我喜欢每个文件有一个模块的组织。我最终得到这样的进口:

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

我希望能够做到这一点:

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

或类似的名称,并且每个文件都包含一个默认导出,并且每个模块的名称与其文件相同。

这可能吗?


这个有可能。请参阅babel的模块文档babeljs.io/docs/learn-es2015 ... 引用为“从“ lib / math”导入{sum,pi};”。接受的答案不再有效。请更新。
爱德华·杰克罗

6
@kresli我认为您不明白这个问题。在文档中,lib/math是一个包含多个导出的文件。在我的问题中,lib/math/是一个包含几个文件的目录,每个文件包含一个导出。
Frambot

2
好的我明白了。在那种情况下,贝尔吉是正确的。抱歉
Eduard Jacko

Answers:


230

我不认为这是可行的,但是模块名称的解析取决于模块加载程序,因此可能会有支持此功能的加载程序实现。

在此之前,您可以使用一个中间的“模块文件”,lib/things/index.js其中仅包含

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

它会让你做

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

6
谢谢您的帮助。我能得到这个工作与index.js看起来像:import ThingA from 'things/ThingA'; export {ThingA as ThingA}; import ThingB from 'things/ThingB'; export {ThingB as ThingB};。其他咒语index.js不会让步。
Frambot

2
嗯,export * from应该工作。您尝试过…from './ThingA'还是export ThingA from …?您正在使用什么模块加载器?
Bergi 2015年

7
是的,如果每个ThingA.js,ThingB.js每个导出的都命名导出,那么您的原始答案确实有用。发现。
Frambot 2015年

1
您是否必须指定索引文件,或者可以仅指定文件夹,而是将加载index.js?
Zorgatone

1
@Zorgatone:这取决于您使用的模块加载器,但是通常,文件夹路径就足够了。
Bergi

128

答案中已经提供了主题的一种变体,但是如何做到这一点:

Thing

export default function ThingA () {}

things/index.js

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

然后要消耗其他地方的所有东西,

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

或只消费一些东西,

import {ThingA,ThingB} from './things'

想看看@wolfbiter的答案吗?不知道为什么他声称括号不起作用。
Bergi

@Bergi是的,我认为Wolfbiter的不是有效的ES6。也许他使用的是Babel的旧版本或其他翻译器?
杰德·理查兹

如何转译?导入目录无法解决index.js我的问题。我正在使用SystemJs + Babel
jasonszhao 2015年

2
您不能简单地键入export ThingA from './ThingA'而不是export {default as ThingA} from './ThingA'
Petr Peller

1
这会利用三次震动吗?如果我确实从'./things'导入{ThingA},也会将ThingB和ThingC添加到捆绑包中吗?
乔治

75

当前的答案提出了一种解决方法,但是它使我感到困惑,为什么它不存在,所以我创建了一个babel可以执行此操作的插件。

使用以下方法安装:

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

然后将其添加到您.babelrc的:

{
    "plugins": ["wildcard"]
}

请参阅仓库以获取详细的安装信息


这使您可以执行以下操作:

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

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

同样,存储包含有关其确切功能的更多信息,但是这样做可以避免创建index.js文件,并且还可以在编译时进行,从而避免readdir在运行时执行。

另外,对于较新的版本,您可以完全按照您的示例进行操作:

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

与上述相同。


3
警告,该插件存在严重问题。问题可能来自其内部缓存,当您的代码完美时,您会费劲,但是您的脚本将无法正常工作,因为您已向其中添加了文件./lib/things;且未对其进行提取。它引起的问题是荒谬的。我只是亲眼目睹了这样的情况,当使用import *babel 更改文件时,它会拾取已添加的文件,但是将其更改回来会带来问题,就像它重新使用更改前的缓存一样。请谨慎使用。
卢卡斯Zaroda

@ŁukaszZarodababel有一个内部缓存~/.babel.json,导致此奇怪的行为。另外,如果您使用的是观察者或热加载器,则必须保存新文件,以便使用新目录列表重新编译该文件
Downgoat

@Downgoat那么除了删除babel的缓存之外,如何克服这个问题?顺便说一句。我认为您的评论不正确。我禁用了Babel的缓存,并且该插件有这么大的问题。完全不推荐它
SOReader

1
顺便说一句,还有其他问题的人,请补充bpwc clear-cache一下,因为webpack和其他构建过程仍将默默地缓存
Downgoat

这是一个好主意,但我也无法使其正常工作。我不确定是否可能与流类型化的代码发生冲突,但是无论我如何构造导入,我都会收到“ ReferenceError:Foo未定义”。
jlewkovich

13

伟大的丑陋的笨蛋!这比需要的要难。

导出一个单位默认值

这是使用一个很好的机会传播...{ ...Matters, ...Contacts }下面:

// imports/collections/Matters.js
export default {           // default export
  hello: 'World',
  something: 'important',
};
// imports/collections/Contacts.js
export default {           // default export
  hello: 'Moon',
  email: 'hello@example.com',
};
// 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);

然后,从命令行(从项目根目录/)运行babel编译的代码

$ 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: 'hello@example.com' }

导出一棵树状默认值

如果您不想覆盖属性,请更改:

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

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

输出将是:

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

导出多个已命名的导出,没有默认设置

如果您专用于DRY,则导入的语法也会更改:

// imports/collections/index.js

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

这将创建2个没有默认导出的命名导出。然后更改:

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

console.log(Matters, Contacts);

并输出:

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

导入所有命名的出口

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

注意上一个示例中的解构 import { Matters, Contacts } from './collections';

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

在实践中

给定这些源文件:

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

创建一个/myLib/index.js将所有文件捆绑在一起的方法无法达到导入/导出的目的。首先,将所有内容全局化,而不是通过index.js“包装文件”通过导入/导出,将所有内容全局化。

如果需要特定文件,请import thingA from './myLib/thingA';在自己的项目中。

仅在为npm打包或在多年的多团队项目中打包时,才为模块创建带有导出功能的“包装文件”。

到此为止了吗?有关更多详细信息,请参阅文档

同样,对于Stackoverflow来说,最终支持三个`s作为代码围栏标记。


10

您可以使用异步import():

import fs = require('fs');

然后:

fs.readdir('./someDir', (err, files) => {
 files.forEach(file => {
  const module = import('./' + file).then(m =>
    m.callSomeMethod();
  );
  // or const module = await import('file')
  });
});

2
像这样动态导入很不错。提出问题时,他们肯定不存在。感谢你的回答。
Frambot

6

与已接受的问题类似,但是它允许您扩展,而无需在每次创建一个模块时都向索引文件中添加新模块:

./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'

当我尝试以别名方式导入./example.js
tsujp

也不对我有用(webpack 4.41,babel 7.7)
Edwin Joassart '19

3

我已经使用过几次(特别是用于构建将大量数据拆分为多个文件(例如AST节点)的大型对象),为了构建它们,我制作了一个小脚本(我刚刚将其添加到npm中,所以其他所有人可以使用它)。

用法(当前,您需要使用babel来使用导出文件):

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

生成包含以下内容的文件:

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

然后,您可以使用文件:

import * as myCoolModule from "my-cool-module.js"
myCoolModule.foo()

在Windows 中无法正常工作,将路径生成为Windows路径(\` instead of / ) also as an improvment you may want to allow two options like --filename` &&,--dest以允许自定义创建的文件的存储位置和别名。也不适用于包含.(如user.model.js)的文件名
Yuri Scarbaci

2

@Bergi答案的另一种方法

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

export default {
 ThingA,
 ThingB,
 ThingC
}

用途

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

它不会工作。我只是在react应用程序中尝试过,然后返回了export '...' was not found in '....
Hamid Mayeli

1

您也可以使用require:

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)
  }
}

然后将您的moduleHolder与动态加载的控制器一起使用:

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

0

这不完全是您要的,但是,通过这种方法,我可以遍历componentsList其他文件并使用诸如componentsList.map(...)我认为非常有用的功能!

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;

0

如果您使用的是webpack。这将自动导入文件并导出为api名称空间。

因此,无需更新每个文件。

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;

对于打字稿用户;

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

0

我可以借鉴用户atilkan的方法并对其进行一些修改:

对于打字稿用户;

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
});

}));

-9

如果您没有在A,B,C中导出默认值,而只是导出{},则可以这样做

// 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()

1
这不是有效的javascript(周围没有引号./thing),即使有,也无法正常工作。(我尝试过,但没有用。)
约翰(John
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.