繁体   English   中英

src 目录之外的 create-react-app 导入限制

[英]The create-react-app imports restriction outside of src directory

我正在使用 create-react-app。 我正在尝试从我的src/components内的文件中调用我的公用文件夹中的图像。 我收到此错误消息。

./src/components/website_index.js 未找到模块:您尝试导入项目 src/ 目录之外的 ../../public/images/logo/WC-BlackonWhite.jpg。 不支持 src/ 之外的相对导入。 你可以将它移到 src/ 中,或者从项目的 node_modules/ 中添加一个符号链接。

import logo from '../../public/images/logo_2016.png'; <img className="Header-logo" src={logo} alt="Logo" />

我读过很多东西说你可以对路径进行导入,但这仍然对我不起作用。 任何帮助将不胜感激。 我知道有很多这样的问题,但他们都告诉我要导入徽标或图像,所以很明显我在大局中遗漏了一些东西。

这是 create-react-app 的开发者添加的特殊限制。 它在ModuleScopePlugin实现以确保文件驻留在src/ 该插件确保来自应用程序源目录的相对导入不会到达它之外。

您可以通过 create-react-app 项目的eject操作禁用此功能(其中一种方法)。

大多数功能及其更新都隐藏在 create-react-app 系统的内部。 如果您进行eject您将不再拥有某些功能及其更新。 因此,如果您还没有准备好管理和配置包含的应用程序以配置 webpack 等 - 请勿执行eject操作。

按照现有规则进行操作(移至 src)。 但是现在你可以知道如何移除限制:从 webpack 配置文件中eject和移除ModuleScopePlugin


而不是eject有中间解决方案,如再布线,让您以编程方式修改的WebPack配置没有eject 但是删除ModuleScopePlugin插件并不好- 这会失去一些保护并且不会添加src可用的一些功能。

更好的方法是添加类似于src完全可用的附加目录。 这可以使用react-app-rewire-alias来完成


不要从public文件夹导入 - 这将在build文件夹中复制,并且可以通过两个不同的 url(或使用不同的加载方式)访问,这最终会恶化包下载的大小。

src文件夹导入是可取的并且具有优势。 一切都将被 webpack 打包到具有最佳大小最佳加载效率的块的包中。

react-app-rewired可用于删除插件。 这样您就不必弹出。

按照 npm 包页面上的步骤(安装包并翻转 package.json 文件中的调用)并使用与此类似的config-overrides.js文件:

const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');

module.exports = function override(config, env) {
    config.resolve.plugins = config.resolve.plugins.filter(plugin => !(plugin instanceof ModuleScopePlugin));

    return config;
};

这将从使用的 WebPack 插件中删除 ModuleScopePlugin,但保留其余部分并消除弹出的必要性。

如果您的图像在公共文件夹中,那么您应该使用

"/images/logo_2016.png"

在你的<img> src而不是导入

'../../public/images/logo_2016.png'; 

这将工作

<img className="Header-logo" src="/images/logo_2016.png" alt="Logo" />

使用 Craco 删除它:

module.exports = {
  webpack: {
    configure: webpackConfig => {
      const scopePluginIndex = webpackConfig.resolve.plugins.findIndex(
        ({ constructor }) => constructor && constructor.name === 'ModuleScopePlugin'
      );

      webpackConfig.resolve.plugins.splice(scopePluginIndex, 1);
      return webpackConfig;
    }
  }
};

为其他人的答案提供更多信息。 关于如何将 .png 文件交付给用户,您有两种选择。 文件结构应符合您选择的方法。 这两个选项是:

  1. 使用 react-create-app 提供的模块系统( import x from y )并将其与您的 JS 捆绑在一起。 将图像放在src文件夹中。

  2. public文件夹提供它,让 Node 提供文件。 create-react-app 显然也带有一个环境变量,例如<img src={process.env.PUBLIC_URL + '/img/logo.png'} />; . 这意味着你可以在你的 React 应用程序中引用它,但仍然通过 Node 提供它,你的浏览器在普通的 GET 请求中单独请求它。

来源: 创建反应应用程序

有一些答案提供了react-app-rewired解决方案,但customize-cra包含一个更优雅的removeModuleScopePlugin() API。 (这是相同的解决方案,但被customize-cra包抽象掉了。)

npm i --save-dev react-app-rewired customize-cra

包.json

"scripts": {
    - "start": "react-scripts start"
    + "start": "react-app-rewired start",
    ...
},

config-overrides.js

const { removeModuleScopePlugin } = require('customize-cra')

module.exports = removeModuleScopePlugin()

您需要将WC-BlackonWhite.jpg移动到您的src目录中。 public目录用于将直接链接到 HTML 中的静态文件(例如收藏夹图标),而不是您将直接导入到包中的内容。

我认为Lukas Bach 解决方案使用react-app- rewired 来修改 webpack 配置是一个很好的方法,但是,我不会排除整个ModuleScopePlugin而是将可以在 src 之外导入的特定文件列入白名单:

config-overrides.js

const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
const path = require("path");

module.exports = function override(config) {
  config.resolve.plugins.forEach(plugin => {
    if (plugin instanceof ModuleScopePlugin) {
      plugin.allowedFiles.add(path.resolve("./config.json"));
    }
  });

  return config;
};

安装这两个包

npm i --save-dev react-app-rewired customize-cra

包.json

"scripts": {
    - "start": "react-scripts start"
    + "start": "react-app-rewired start"
},

config-overrides.js

const { removeModuleScopePlugin } = require('customize-cra');

module.exports = function override(config, env) {
    if (!config.plugins) {
        config.plugins = [];
    }
    removeModuleScopePlugin()(config);

    return config;
};

此限制确保所有文件或模块(导出)都在src/目录中,实现在./node_modules/react-dev-utils/ModuleScopePlugin.js ,在以下代码行中。

// Resolve the issuer from our appSrc and make sure it's one of our files
// Maybe an indexOf === 0 would be better?
     const relative = path.relative(appSrc, request.context.issuer);
// If it's not in src/ or a subdirectory, not our request!
     if (relative.startsWith('../') || relative.startsWith('..\\')) {
        return callback();
      }

您可以通过以下方式取消此限制

  1. 要么改变这段代码(不推荐)
  2. 或者eject然后从目录中删除ModuleScopePlugin.js
  3. 或注释/删除const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); 来自./node_modules/react-scripts/config/webpack.config.dev.js

PS:注意eject的后果。

公用文件夹内的图像

  use image inside html extension
  <img src="%PUBLIC_URL%/resumepic.png"/>

  use image inside  js extension
  <img src={process.env.PUBLIC_URL+"/resumepic.png"}/>
  • 在 js 扩展中使用图像

我不得不在 Truffle 中克服同样的问题。 解决方法如下:

由于 Create-React-App 的默认行为不允许从 src 文件夹外部导入文件,我们需要将我们的 build 文件夹中的合约放在 src 中。 我们可以在每次编译合约时复制和粘贴它们,但更好的方法是简单地配置 Truffle 以将文件放在那里。

在 truffle-config.js 文件中,将内容替换为以下内容:

const path = require("path");

module.exports = {
  contracts_build_directory: path.join(__dirname, "client/src/contracts")
};

我不知道这是否对您有帮助,但我知道我在 Truffle 遇到同样问题时发现了您的问题,这可能对其他人有所帮助。

复制粘贴打字稿解决方案

(例如,这将适用于 CRA/TS 堆栈,与 CRA/JS 相比,这需要一个额外的步骤。解决方案本身没有类型化。)

将所需的路径添加到ModuleScopePlugin而不是直接删除插件。

下面的代码使用craco ,但应该很容易用于react-app-rewired或类似的解决方案。 您只需要找到您拥有webpackConfig对象的位置(react-app- module.exports.webpack : module.exports.webpack在您的config-overrides.js ),并将其传递给提供的函数。

craco.config.js

const path = require("path");
const enableImportsFromExternalPaths = require("./src/helpers/craco/enableImportsFromExternalPaths");

// Paths to the code you want to use
const sharedLibOne = path.resolve(__dirname, "../shared-lib-1/src");
const sharedLibTwo = path.resolve(__dirname, "../shared-lib-2/src");

module.exports = {
    plugins: [
        {
            plugin: {
                overrideWebpackConfig: ({ webpackConfig }) => {
                    enableImportsFromExternalPaths(webpackConfig, [
                        // Add the paths here
                        sharedLibOne,
                        sharedLibTwo,
                    ]);
                    return webpackConfig;
                },
            },
        },
    ],
};

helpers/craco/enableImportsFromExternalPaths.js

const findWebpackPlugin = (webpackConfig, pluginName) =>
    webpackConfig.resolve.plugins.find(
        ({ constructor }) => constructor && constructor.name === pluginName
    );

const enableTypescriptImportsFromExternalPaths = (
    webpackConfig,
    newIncludePaths
) => {
    const oneOfRule = webpackConfig.module.rules.find((rule) => rule.oneOf);
    if (oneOfRule) {
        const tsxRule = oneOfRule.oneOf.find(
            (rule) => rule.test && rule.test.toString().includes("tsx")
        );

        if (tsxRule) {
            tsxRule.include = Array.isArray(tsxRule.include)
                ? [...tsxRule.include, ...newIncludePaths]
                : [tsxRule.include, ...newIncludePaths];
        }
    }
};

const addPathsToModuleScopePlugin = (webpackConfig, paths) => {
    const moduleScopePlugin = findWebpackPlugin(
        webpackConfig,
        "ModuleScopePlugin"
    );
    if (!moduleScopePlugin) {
        throw new Error(
            `Expected to find plugin "ModuleScopePlugin", but didn't.`
        );
    }
    moduleScopePlugin.appSrcs = [...moduleScopePlugin.appSrcs, ...paths];
};

const enableImportsFromExternalPaths = (webpackConfig, paths) => {
    enableTypescriptImportsFromExternalPaths(webpackConfig, paths);
    addPathsToModuleScopePlugin(webpackConfig, paths);
};

module.exports = enableImportsFromExternalPaths;

取自这里这里🙏

不需要eject,可以用rescripts库修改react-scripts配置

这将起作用:

module.exports = config => {
  const scopePluginIndex = config.resolve.plugins.findIndex(
    ({ constructor }) => constructor && constructor.name === "ModuleScopePlugin"
  );

  config.resolve.plugins.splice(scopePluginIndex, 1);

  return config;
};

添加到 Bartek Maciejiczek 的回答中,这就是 Craco 的样子:

    const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
    const path = require("path");

    module.exports = {
      webpack: {
        configure: webpackConfig => {
          webpackConfig.resolve.plugins.forEach(plugin => {
            if (plugin instanceof ModuleScopePlugin) {
              plugin.allowedFiles.add(path.resolve("./config.json"));
            }
          });
          return webpackConfig;
        }
      }
    };

如果你只需要导入单个文件,比如 README.md 或 package.json,那么这可以显式添加到 ModuleScopePlugin()

配置/paths.js

const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
module.exports = {
  appPackageJson: resolveApp('package.json'),
  appReadmeMD:    resolveApp('README.md'),
};

配置/webpack.config.dev.js + 配置/webpack.config.prod.js

module.exports = {
  resolve: {
    plugins: [
      // Prevents users from importing files from outside of src/ (or node_modules/).
      // This often causes confusion because we only process files within src/ with babel.
      // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
      // please link the files into your node_modules/ and let module-resolution kick in.
      // Make sure your source files are compiled, as they will not be processed in any way.
      new ModuleScopePlugin(paths.appSrc, [
          paths.appPackageJson,
          paths.appReadmeMD         // README.md lives outside of ./src/ so needs to be explicitly included in ModuleScopePlugin()
      ]),
    ]
  }
}

最好的解决方案是 fork react-scripts ,这实际上在官方文档中有提到,请参阅: Alternatives to Ejecting

如果您需要多次修改,例如使用 ant design 时,您可以像这样组合多个功能:

const {
  override,
  removeModuleScopePlugin,
  fixBabelImports,
} = require('customize-cra');

module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: 'css',
  }),
  removeModuleScopePlugin(),
);

您可以尝试使用 simlinks,但反过来。

React 不会遵循 simlinks,但您可以将某些内容移动到源目录,并创建一个指向它的 simlink。

在我的项目的根目录中,我有一个节点服务器目录,其中包含多个架构文件。 我想在前端使用它们,所以我:

  • 移动文件 /src
  • 在终端中,我进入了模式文件在服务器中的位置
  • ln -s SRC_PATH_OF_SCHEMA_FILE

这给了反应它正在寻找的东西,节点非常高兴通过 simlinks 包含文件。

在我的项目中遇到了同样的问题,并在官方的 create-react-app 文档中找到了这个: https : //create-react-app.dev/docs/using-the-public-folder/

有一个“逃生舱口”可以在模块系统之外添加资产:

如果将文件放入 public 文件夹,则 webpack 不会对其进行处理。 相反,它将被原封不动地复制到构建文件夹中。 要引用公共文件夹中的资产,您需要使用名为 PUBLIC_URL 的环境变量。

这是他们提供的示例:

 render() { // Note: this is an escape hatch and should be used sparingly! // Normally we recommend using `import` for getting asset URLs // as described in “Adding Images and Fonts” above this section. return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />; }

如果你想从公共访问 CSS 文件,你可能会遇到一个错误OUTSIDE OF SOURCE DIRECTORY

或者,您可以在index.html 中链接此文件,该文件也位于公共目录中。

<link rel="stylesheet" href="App.css">

这是在简单情况下运行良好的替代方案(使用fsncp )。 在开发过程中,保持运行脚本以监视 /src 之外的共享文件夹的更改。 进行更改时,脚本可以自动将共享文件夹复制到您的项目中。 这是一个递归监视单个目录的示例:


// This should be run from the root of your project

const fs = require('fs')
const ncp = require('ncp').ncp;

ncp.limit = 16

// Watch for file changes to your shared directory outside of /src
fs.watch('../shared', { recursive: true }, (eventType, filename) => {
    console.log(`${eventType}: ${filename}`)

    // Copy the shared folder straight to your project /src
    // You could be smarter here and only copy the changed file
    ncp('../shared', './src/shared', function(err) {
        if (err) {
          return console.error(err);
        }

        console.log('finished syncing!');
    });
})

这是相对导入的一个问题,这可能是因为我们使用了“create-react-app project”命令,该命令形成了一个名为 project 的目录,其中包含 node_modules 文件夹以及其中 public 和 src 文件夹中的几个其他文件。 create-react-app 命令有一个限制,即我们不能从 src 外部导入任何东西。

我的问题:

  1. 我必须导入在 src 文件夹外的 node_modules 文件夹中创建的 react-bootstrap css 文件。
  2. 我使用了 import "../node_modules/bootstrap/dist/css/bootstrap.min.css"; 但我在终端上遇到了错误。
  3. 我发现我可以创建一个新的 React 应用程序并按照从 A 到 G 的解决方案步骤来解决这个问题。

解决方案:A)创建一个新的反应应用程序,使用 create-react-app new

B) 新光盘

C)运行这个命令:“npm install react-bootstrap bootstrap@4.6.0”(没有“”双引号)

D) 在你的反应文件中把它导入 bootstrap: D.1) import "../node_modules/bootstrap/dist/css/bootstrap.min.css"; 或 D.2) 从“react-bootstrap/Button”导入按钮;

E) 为 D.1) < button className="btn btn-success"> Bootstrap </button> 或 D.2) < Button variant="primary" 创建一个引导程序元素,例如 Button 或您的反应文件中的任何内容> 引导程序</Button>

F) 在终端:cd src

G)在终端:npm start,

这次就编译成功了。

推理:一旦我按顺序执行步骤 A 到 G,我就可以看到 react-bootstrap 终于工作了,这次我没有收到任何错误。 (我想到了这个解决方案,因为:

  1. 我使用了 npm install "@material-ui/icons" 并将其安装在 src 之外的 node_modules 文件夹中。
  2. 在我的反应文件中,我使用了 import Add from "@material-ui/icons/Add"
  3. 和 Material-ui 图标对我来说很好用,但在这里我们也从外部 src 导入,从 node_modules .. 一切正常。 为什么这次从外部src导入没有错误)
  4. 这就是为什么我刚刚创建了一个新的 React 应用程序,并遵循解决方案步骤 A 到 G。

这可以直接完成,而无需使用公用文件夹的路径。 你可以这样做

<img src="/images/image-name" alt=""/>

发生这种情况是因为我们在浏览器中没有使用 App.js,在浏览器中只执行了 index.html,并且图片的路径已经在包含 index.html 文件的公共文件夹中

如果要使用 CSS 设置背景图像。 因此,您必须使用本地主机的 URL 设置图像并添加图像的路径。 请看下面的例子。

.banner {
  width: 100%;
  height: 100vh;
  background-image: url("http://localhost:3000/img/bg.jpg");
  background-size: cover;
  background-repeat: no-repeat;
}

显然,使用Create-react-app将限制您访问项目根目录之外的图像 - 通常是src

对此的快速解决方法是将您的图像从public文件夹移动到src文件夹,您就可以开始了。
所以你的代码会是这样的,例如:

background-image: URL('/src/images/img-2.jpg');

在这里发布@Flaom 在标记为回复答案中作为评论写的内容,这实际上可以挽救生命:

“这是如何被接受的答案?只需在 .env 文件中设置 NODE_PATH=./src/.. 就可以轻松消除这种虚假限制。通过这样做,您可以从 src 文件夹外部导入,而无需经历相关的痛苦弹出你的应用程序。”

  • 火焰

编辑根据@cigien 的要求添加了更多信息。

上面的所有答案都很好地描述了为什么当我们使用 create-react-app 创建我们的 React 应用程序时我们不能使用来自公共文件夹的图像。 我自己遇到了这个问题并阅读了所有这些答案,我意识到,答案是“破解”应用程序以删除限制我们的模块。 有些答案甚至没有撤消选项。 对于可以的“培训”应用程序。

就我个人而言,我不想在我自己的项目中添加一个改变应用程序概念的解决方案,特别是在商业项目中。 @Flaom 解决方案是最简单的,如果将来有任何变化,可以用另一种解决方案替换。 它没有风险,可以随时删除,而且是最简单的。

我可以通过使用file:作为本地依赖项“复制”外部文件来导入src/之外的文件。

"dependencies": {
    "@my-project/outside-dist": "file:./../../../../dist".
}

然后

import {FooComponent} from "@my-project/outside-dist/components";

不需要ejectreact-app-rewired或其他 3rd 方解决方案。

这是我的代码:

import React from 'react';

import './Navbar.scss';
import {images} from '../../constants';
const Navbar = () => {
    return (
        <nav>
            <div>
                < img src = {images.logo} alt = "logo" />

            </div>

        </nav>
    );
}

export default Navbar;

也改了:

import React from 'react';

import './Navbar.scss';
import {images} from '././constants';
const Navbar = () => {
    return (
        <nav>
            <div>
                < img src = {images.logo} alt = "logo" />

            </div>

        </nav>
    );
}

export default Navbar;

它奏效了! 我越来越擅长修复错误哈哈。

如果您的文件位于公共文件夹中,并且如果您想在不弹出或不使用 react-app-rewired 的情况下导入它,那么在这种情况下,您可以通过域名和文件路径并使用 axios 访问文件。 示例:在 public 文件夹中有一个名为 favico.ico 的字体文件。 您想将其导入位于 src.xml 的文件中。 您可以使用以下逻辑访问字体。

axios.get('example.com/favico.ico').then(() => {
  // here you can access this file.
})

在上面的示例中 example.com 是域。 如果您有不同的环境,如本地主机、登台、生产,那么在这种情况下,域名是不同的。 因此,要获取 favico.ico,您可以使用以下逻辑。

axios.get(`${window.location.origin}/favico.ico`).then(() => {
  // here you can access this file.
})

在上面的示例中,如果您在本地运行代码,window.location.origin 会为您提供当前域含义,它将为您提供 http://localhost:{portnumber},如果您的代码在生产和生产域上运行,那么 example.com ,它会给你“example.com”。 因此,使用此模式,您可以访问位于公共文件夹中的资产。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM