如何有条件地导入 ES6 模块

How can I conditionally import an ES6 module?

本文关键字:ES6 模块 导入 有条件      更新时间:2023-11-01

我需要做这样的事情:

if (condition) {
    import something from 'something';
}
// ...
if (something) {
    something.doStuff();
}

上面的代码不编译;它抛出SyntaxError: ... 'import' and 'export' may only appear at the top level

我尝试使用此处所示的System.import,但我不知道System来自哪里。这是一个最终没有被接受的ES6提案吗?该文章中指向"编程 API"的链接将我转储到一个已弃用的文档页面。

我们现在确实有ECMA的动态导入提案。这是在第 3 阶段。这也可以作为 babel 预设使用。

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

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

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

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

let something = null;
let other = null;
if (condition) {
    something = require('something');
    other = require('something').other;
}
if (something && other) {
    something.doStuff();
    other.doOtherStuff();
}

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

你可以做:

接口.js

import mockAPI from './mockAPI'
import realAPI from './realAPI'
const exportedAPI = shouldUseMock ? mockAPI : realAPI
export default exportedAPI

apiConsumer.js

import API from './api'
...

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

2020 年更新

您现在可以将 import 关键字作为函数调用(即 import() ( 在运行时加载模块。它返回一个 Promise,该承诺解析为具有模块导出的对象。

例:

const mymodule = await import('modulename');
const foo = mymodule.default; // Default export
const bar = mymodule.bar; // Named export

或:

import('modulename')
    .then(mymodule => {
        const foo = mymodule.default; // Default export
        const bar = mymodule.bar; // Named export
    });

请参阅 MDN 上的动态导入

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

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

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

如果您使用动态导入 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(...);
}

在 JS 中有条件地导入和导出

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

require() 是一种在运行时导入某些模块的方法,如果与字符串文字路径一起使用,它同样有资格进行静态分析,例如import。这是捆绑程序为捆绑包选择依赖项所必需的。

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() s来实现:

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

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

评估中掩盖它对我有用,将其隐藏在静态分析器中......

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

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

const something = (() => (
  condition ? require('something') : null
))();
if(something) {
  something.doStuff();
}

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

动态模块导入示例

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

JavaScript modules Github

Javascript Modules MDN

不,你不能!

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

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

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

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

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

我知道这不是

问题所要求的,但这是我在使用 vite 时使用模拟的方法。我相信我们可以对 webpack 和其他做同样的事情。

假设我们有两个具有相同接口的库:link.jslink-mock.js ,那么:

在我的vite.config.js

export default defineConfig(({ command, mode }) => {
    
    const cfg = {/* ... */}
    if (process.env.VITE_MOCK == 1) {
        cfg.resolve.alias["./link"] = "./link-mock"; // magic is here!
    }
    return cfg;
}

法典:

import { link } from "./link";

在控制台中,我们调用:

# to use the real link.js
npm run vite
# to use the mock link-mock.js
VITE_MOCK=1 npm run vite

包.json 脚本

{
    ....
    "scripts": {
        "dev": "vite",        
        "dev-mock": "VITE_MOCK=1 vite"
    }
}

正如 ericsoco 所说,而不是:

import {ExampleClass} from "./ExampleClass.js";
new ExampleClass();

你可以写

if(condition) {
  import("./ExampleClass.js").then((module) => {
    new module.ExampleClass();
  });
}

我也有类似的情况。我的项目结构是这样的:

  • 库/
    • 模拟Api.js
    • realApi.js
  • 索引.js

对我来说,在生产模式下模拟没有进入捆绑包是必要的。对我来说,不要在每个使用的地方都写条件,不要使用Promises,这一点也很重要。

对我来说,解决方案是创建一个统一的 api.js 文件,其中包含代码:

// solution 1
export const api = (
    await import(`${process.env.NODE_ENV === 'development' ? './mockAPI.js' : './realAPI.js'}`)
).default
export default api;

在生产模式下使用这种方法,只有经过处理的 realAPI .js 才能进入捆绑包,并且解决方案的使用不需要单独的条件或使用 Promises,例如:

import api from './libs/api';
api.getUser();

也可以使用类似的解决方案:

// solution 2
let api = (await import('./realAPI')).default;
if (process.env.NODE_ENV === 'development') {
    api = (await import('./mockAPI')).default;
}
export default api;

这两种解决方案都允许在生产模式下的捆绑包中不包含"模拟"。这是通过在构建过程中删除无法访问的代码来完成的,重要的是不要将process.env.NODE_ENV ==="开发">条件移动到变量中。