234
votes

I am using create-react-app. I am trying to call an image from my public folder from a file inside my src/components. I am receiving this error message.

./src/components/website_index.js Module not found: You attempted to import ../../public/images/logo/WC-BlackonWhite.jpg which falls outside of the project src/ directory. Relative imports outside of src/ are not supported. You can either move it inside src/, or add a symlink to it from project's node_modules/.

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

I have read many things saying you can do an import to the path but that is still not working for me. Any help would be greatly appreciated. I know there are many questions like this but they are all telling me to import logo or image so clearly I am missing something in the big picture.

23
You need ../public/images/logo_2016.png You went up twice, first out of the components folder, then out of the src folder.Chris G
./src/components/website_index.js Module not found: You attempted to import ../../public/images/logo/WC-BlackonWhite.jpg which falls outside of the project src/ directory. Relative imports outside of src/ are not supported. You can either move it inside src/, or add a symlink to it from project's node_modules/.David Brierton
My comment assumes that your public folder is directly inside your src folder. Your commentless comment features the old path starting with ../.. so not sure what your point is?Chris G
no public is on the same level as srcDavid Brierton
What they mean by "or add a symlink to it from project's node_modules/" ?Julha

23 Answers

181
votes

This is special restriction added by developers of create-react-app. It is implemented in ModuleScopePlugin to ensure files reside in src/. That plugin ensures that relative imports from app's source directory don't reach outside of it.

You can disable this feature (one of the ways) by eject operation of create-react-app project.

Most features and its updates are hidden into the internals of create-react-app system. If you make eject you will no more have some features and its update. So if you are not ready to manage and configure application included to configure webpack and so on - do not do eject operation.

Play by the existing rules (move to src). But now you can know how to remove restriction: do eject and remove ModuleScopePlugin from webpack configuration file.


Instead of eject there are intermediate solutions, like rewire which allows you to programmatically modify the webpack config without eject. But removing the ModuleScopePlugin plugin is not good - this loses some protection and does not adds some features available in src.

The better way is to add fully working additional directories similar to src. This can be done using react-app-rewire-alias


Do not import from public folder - that will be duplicated in the build folder and will be available by two different url (or with different ways to load), which ultimately worsen the package download size.

Importing from the src folder is preferable and has advantages. Everything will be packed by webpack to the bundle with chunks optimal size and for best loading efficiency.

61
votes

The package react-app-rewired can be used to remove the plugin. This way you do not have to eject.

Follow the steps on the npm package page (install the package and flip the calls in the package.json file) and use a config-overrides.js file similar to this one:

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

This will remove the ModuleScopePlugin from the used WebPack plugins, but leave the rest as it was and removes the necessity to eject.

40
votes

If your images are in the public folder then you should use

"/images/logo_2016.png"

in your <img> src instead of importing

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

This will work

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

Remove it using 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;
    }
  }
};
28
votes

To offer a little bit more information to other's answers. You have two options regarding how to deliver the .png file to the user. The file structure should conform to the method you choose. The two options are:

  1. Use the module system (import x from y) provided with react-create-app and bundle it with your JS. Place the image inside the src folder.

  2. Serve it from the public folder and let Node serve the file. create-react-app also apparently comes with an environment variable e.g. <img src={process.env.PUBLIC_URL + '/img/logo.png'} />;. This means you can reference it in your React app but still have it served through Node, with your browser asking for it separately in a normal GET request.

Source: create-react-app

18
votes

There are a few answers that provide solutions with react-app-rewired, but customize-cra includes a removeModuleScopePlugin() API which is a bit more elegant. (It's the same solution, but abstracted away by the customize-cra package.)

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

package.json

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

config-overrides.js

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

module.exports = removeModuleScopePlugin()
12
votes

You need to move WC-BlackonWhite.jpg into your src directory. The public directory is for static files that's going to be linked directly in the HTML (such as the favicon), not stuff that you're going to import directly into your bundle.

7
votes

I think Lukas Bach solution to use react-app-rewired in order to modify webpack config is a good way to go, however, I wouldn't exclude the whole ModuleScopePlugin but instead whitelist the specific file that can be imported outside of 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;
};
6
votes

install these two packages

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

package.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;
};

4
votes

This restriction makes sure all files or modules (exports) are inside src/ directory, the implementation is in ./node_modules/react-dev-utils/ModuleScopePlugin.js, in following lines of code.

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

You can remove this restriction by

  1. either changing this piece of code (not recommended)
  2. or do eject then remove ModuleScopePlugin.js from the directory.
  3. or comment/remove const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); from ./node_modules/react-scripts/config/webpack.config.dev.js

PS: beware of the consequences of eject.

4
votes

Image inside public folder

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

  use image inside  js extension
  <img src={process.env.PUBLIC_URL+"/resumepic.png"}/>
  • use image inside js Extension
3
votes

I have had to overcome this same issue in Truffle. The solution was as follows:

ince Create-React-App's default behavior disallows importing files from outside of the src folder, we need to bring the contracts in our build folder inside src. We can copy and paste them every time we compile our contracts, but a better way is to simply configure Truffle to put the files there.

In the truffle-config.js file, replace the contents with the following:

const path = require("path");

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

I don't know if this helps you, but I know I found your question when I had the same issue in Truffle, and this might help someone else.

2
votes

You don't need to eject, you can modify the react-scripts config with the rescripts library

This would work then:

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

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

  return config;
};
2
votes

Adding to Bartek Maciejiczek's answer, this is how it looks with 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;
        }
      }
    };
1
votes

If you only need to import a single file, such as README.md or package.json, then this can be explicitly added to ModuleScopePlugin()

config/paths.js

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

config/webpack.config.dev.js + config/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()
      ]),
    ]
  }
}
1
votes

the best solution is to fork react-scripts, this is actually mentioned in the official documentation, see: Alternatives to Ejecting

1
votes

If you need multiple modifications, like when using ant design, you can combine multiple functions like this:

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

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

You can try using simlinks, but in reverse.

React won't follow simlinks, but you can move something to the source directory, and create a simlink to it.

In the root of my project, I had a node server directory that had several schema files in it. I wanted to use them on the frontend, so I:

  • moved the files /src
  • in the termal, I cd'ed into where the schema files belonged in server
  • ln -s SRC_PATH_OF_SCHEMA_FILE

This gave react what it was looking for, and node was perfectly happy including files through simlinks.

1
votes

Came to the same issue in my project, and found this in the official create-react-app docs: https://create-react-app.dev/docs/using-the-public-folder/

There is an "escape hatch" to add an asset outside the module system:

If you put a file into the public folder, it will not be processed by webpack. Instead it will be copied into the build folder untouched. To reference assets in the public folder, you need to use an environment variable called PUBLIC_URL.

Here's an example they provide:

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'} />;
}
1
votes

If you want to access CSS files from the public, you might face an error OUTSIDE OF SOURCE DIRECTORY

Alternatively, you can link this file in index.html which also resides in the public directory.

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

Here's an alternative that works well in simple cases (using fs and ncp). While developing, keep a script running that watches for changes to your shared folder(s) outside of /src. When changes are made, the script can automatically copy the shared folder(s) to your project. Here's an example that watches a single directory recursively:


// 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!');
    });
})
1
votes

This is an issue with the relative import, which might have caused because we've used "create-react-app project" command which forms a directory named project with node_modules folder and several other files in public and src folders inside it. The create-react-app command puts a limitation that we can't import anything from outside src.

My Problem:

  1. I had to import react-bootstrap css files which are created in node_modules folder outside the src folder.
  2. I used import "../node_modules/bootstrap/dist/css/bootstrap.min.css"; but I got the error on terminal.
  3. I found out that I can create a new react app and follow solution steps from A to G, in order to fix this issue.

Solution: A) Create a new react app, using create-react-app new

B) cd new

C) run this command: "npm install react-bootstrap [email protected]" (without the "" double quotes )

D) in your react file put this to import bootstrap: D.1) import "../node_modules/bootstrap/dist/css/bootstrap.min.css"; or D.2)import Button from "react-bootstrap/Button";

E) create a bootstrap element like a Button or anything in your react file, for D.1) < button className="btn btn-success" > Bootstrap < /button> or for D.2) < Button variant="primary"> Bootstrap < /Button>

F) in terminal: cd src

G) in terminal: npm start,

this time it will be compiled successfully.

Reasoning: I could see react-bootstrap working finally once I followed steps A to G in order, and this time I didn't get any error. (I thought of this solution because:

  1. I've used npm install "@material-ui/icons" and that got installed in the node_modules folder outside the src.
  2. In my react file I've used import Add from "@material-ui/icons/Add"
  3. and Material-ui icons were working fine for me, but here also we are importing from outside src, from node_modules.. and everything works fine. Why there is no error of importing from outside src this time)
  4. That's why I just created a new react app, and followed solution steps A to G.
1
votes

Copy-Paste Typescript solution

(e.g. this will work for a CRA/TS stack, which requires an additional step compared to CRA/JS. The solution itself is not typed.)

Adds the required paths to the ModuleScopePlugin instead of bluntly removing the plugin.

This code below is using craco, but should be easily usable for react-app-rewired or similar solutions. You just need to find the spot where you have a webpackConfig object (react-app-rewired: module.exports.webpack inside your config-overrides.js), and pass it to the provided functions.

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;

Taken from here and here 🙏