如何有条件地导入 ES6 模块?

Posted

技术标签:

【中文标题】如何有条件地导入 ES6 模块?【英文标题】:How can I conditionally import an ES6 module? 【发布时间】:2016-07-21 22:07:01 【问题描述】:

我需要做类似的事情:

if (condition) 
    import something from 'something';

// ...
if (something) 
    something.doStuff();

以上代码无法编译;它抛出SyntaxError: ... 'import' and 'export' may only appear at the top level

我尝试使用System.import,如图所示here,但我不知道System 来自哪里。这是一个没有被接受的 ES6 提案吗?那篇文章中指向“程序化 API”的链接将我转储到 deprecated docs page。

【问题讨论】:

我的用例:我想让一个可选的依赖项变得容易。如果不需要该dep,则用户将其从package.json 中删除;我的gulpfile 然后在执行一些构建步骤之前检查该依赖项是否存在。 另一个用例:用于测试目的。我正在使用 webpackbabel 将 es6 转换为 es5。 webpack-rewire 和类似的项目在这里没有帮助 - github.com/jhnns/rewire-webpack/issues/12 。设置测试替身或删除有问题的依赖项的一种方法可能是条件导入。 +1。能够在依赖项可能起作用或可能不起作用的多个环境中使用模块至关重要,特别是当模块可能引用仅在浏览器中起作用的依赖项时(例如,webpack 用于将样式表转换为插入相关的模块样式导入 DOM 时),但模块还需要在浏览器之外运行(例如,用于单元测试)。 如果此(condition) 可以在构建时解决,则可以准备产品的不同预处理版本并删除条件。例如,(condition) 用于区分前端(浏览器)和后端(普通 js)。那么条件语句就变得不必要了。 【参考方案1】:

我们现在确实有 ECMA 的动态导入提案。这是在第 3 阶段。这也可以通过 babel-preset 获得。

以下是根据您的情况进行条件渲染的方法。

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

这基本上返回了一个承诺。承诺的解决方案有望具有该模块。该提案还具有其他功能,例如多个动态导入,默认导入,js文件导入等。您可以找到有关dynamic imports here的更多信息。

【讨论】:

终于,一个真正的 ES6 答案!谢谢@thecodejack。根据那篇文章,实际上在撰写本文时处于第 3 阶段。 或者如果你刚刚命名了exports,你可以解构:if (condition) import('something') .then(( somethingExported ) => console.log(somethingExported); ); 在 Firefox 上运行 npm run build 我仍然收到错误:SyntaxError: ... 'import' and 'export' may only appear at the top level @stackjlei:这个功能还不是 javascript 标准的一部分,它只是第 3 阶段的提议!但是它已经在许多较新的浏览器中实现,请参阅caniuse.com/#feat=es6-module-dynamic-import。 条件动态导入函数不具备仅导入“从 Y 导入 X”所具有的特定元素的细粒度能力。事实上,细粒度能力在动态加载中可能更为重要(与预处理捆绑相反)【参考方案2】:

如果您愿意,可以使用 require。这是一种有条件的 require 语句的方法。

let something = null;
let other = null;

if (condition) 
    something = require('something');
    other = require('something').other;

if (something && other) 
    something.doStuff();
    other.doOtherStuff();

【讨论】:

我认为某些东西和其他变量是使用块作用域的 const 来定义的,所以第二个 if 条件会抛出未定义的东西 最好使用 let 并在块外声明两个变量,而不是使用 'var' 并完全避开块范围。 在这种情况下吊装会影响什么吗?我遇到了一些问题,其中提升意味着我在遵循接近此的模式时意外地导入了一个库,如果有记忆的话。 需要指出 require() 不是标准 JavaScript 的一部分 - 它是 Node.js 中的内置函数,因此仅在该环境中有用。 OP 没有给出使用 Node.js 的指示。 2020 年编辑:静态和动态导入现在都是标准 JS 产品的一部分。【参考方案3】:

你不能有条件地导入,但你可以做相反的事情:有条件地导出一些东西。这取决于您的用例,因此此解决方法可能不适合您。

你可以这样做:

api.js

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

const exportedAPI = shouldUseMock ? mockAPI : realAPI
export default exportedAPI

apiConsumer.js

import API from './api'
...

我用它来模拟 mixpanel 等分析库......因为我目前不能有多个构建或我们的前端。不是最优雅的,但有效。根据环境的不同,我只是在这里和那里有一些“if”,因为在 mixpanel 的情况下,它需要初始化。

【讨论】:

此解决方案会导致加载不需要的模块,因此我认为不是最佳解决方案。 如答案中所述,这是一种解决方法。那个时候,根本就没有办法解决。 ES6 导入不是动态的,这是设计使然。目前接受的答案中描述的ES6动态导入功能提案可以做到。 JS 正在发展 :) 我觉得真的很好,因为我想在不同的地方导入。之后您可以删除/评论 mockAPI【参考方案4】:

2020 年更新

您现在可以将import 关键字作为函数调用(即import())以在运行时加载模块。

例子:

const mymodule = await import(modulename);

或:

import(modulename)
    .then(mymodule => /* ... */);

见https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports

【讨论】:

但是再出口怎么样?【参考方案5】:

看起来答案是,到目前为止,你不能。

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

我认为其目的是尽可能地启用静态分析,而有条件导入的模块会破坏这一点。另外值得一提的是——我使用的是Babel,我猜Babel 不支持System,因为模块加载器API 没有成为ES6 标准。

【讨论】:

2020 年编辑:静态和动态导入现在都是标准 JS 产品的一部分。【参考方案6】:

如果使用动态导入Webpack模式的重要区别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(...);

【讨论】:

但是 import 返回一个承诺。 @newguy Webpack 在构建时替换了类似节点的环境变量(即process.env.SOMETHING)。这意味着如果上面示例中的环境变量不是“true”,webpack 将远程调用 if 语句,因为它基本上变成了死代码。这种 webpack 行为与导入完全没有关系。【参考方案7】:

require() 是一种在运行时导入某些模块的方法,如果与字符串文字路径一起使用,它同样适用于像 import 这样的静态分析。 bundler 需要它来选择 bundle 的依赖项。

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

对于具有完整静态分析支持的动态模块解析,首先在索引器(index.js)中索引模块并在主机模块中导入索引器。

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

【讨论】:

需要指出,require() 不是标准 JavaScript 的一部分 - 它是 Node.js 中的内置函数,因此仅在该环境中有用。 OP 没有给出使用 Node.js 的指示。【参考方案8】:

在 eval 中隐藏它对我有用,从静态分析器中隐藏它......

if (typeof __CLI__ !== 'undefined') 
  eval("require('fs');")

【讨论】:

有人可以解释为什么这个答案被否决了吗?是否有任何真正的缺点,或者它只是对邪恶关键字“eval”的自动负面反应? 自动拒绝使用可怕的 eval 关键字。远离。 你能解释一下@TormodHaugene 在这里使用eval 有什么问题吗? MDN 总结了很多eval should not be used 的原因。一般来说:如果你发现需要使用eval,你可能做错了,应该退后一步考虑你的替代方案。在某些情况下使用eval 可能是正确的,但您很可能没有遇到过这些情况。 需要指出 require() 不是标准 JavaScript 的一部分 - 它是 Node.js 中的内置函数,因此仅在该环境中有用。 OP 没有给出使用 Node.js 的指示。【参考方案9】:

条件导入也可以通过三元和require()s 实现:

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

此示例取自 ES Lint global-require 文档:https://eslint.org/docs/rules/global-require

【讨论】:

需要指出 require() 不是标准 JavaScript 的一部分 - 它是 Node.js 中的内置函数,因此仅在该环境中有用。 OP 没有给出使用 Node.js 的指示。【参考方案10】:

我能够使用立即调用的函数和 require 语句来实现这一点。

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

if(something) 
  something.doStuff();

【讨论】:

需要指出 require() 不是标准 JavaScript 的一部分 - 它是 Node.js 中的内置函数,因此仅在该环境中有用。 OP 没有给出使用 Node.js 的指示。【参考方案11】:

查看此示例以清楚了解动态导入的工作原理。

Dynamic Module Imports Example

对导入和导出模块有基本的了解。

JavaScript modules Github

Javascript Modules MDN

【讨论】:

【参考方案12】:

不,你不能!

但是,遇到这个问题应该会让您重新考虑如何组织代码。

在 ES6 模块之前,我们有使用 require() 语法的 CommonJS 模块。这些模块是“动态的”,这意味着我们可以根据代码中的条件导入新模块。 - 来源:https://bitsofco.de/what-is-tree-shaking/

我想他们放弃对 ES6 的支持的原因之一是编译它会非常困难或不可能。

【讨论】:

【参考方案13】:

JS 有条件的导入导出

const value = (
    await import(`$condtion ? `./file1.js` : `./file2.js``)
).default

export default value

【讨论】:

【参考方案14】:

可以通过以下链接了解有关动态导入的更多信息

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports

【讨论】:

以上是关于如何有条件地导入 ES6 模块?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ES6 模块导入来导入路径

如何使用 ES6 模块导入文件,并将其内容作为字符串读取?

如何模拟 ES6 模块的导入?

如何模拟ES6模块的导入?

如何从 ES6 模块导入默认和命名

如何使用 es6 导入加载 emscripten 生成的模块?