1

I created a project using create-react-app. The front-end is described inside the ./src folder. I have a file named server.js on the root of the project. This file is describing the API using express.

I would like to have a folder with models that would be shared between the front-end (under ./src) and the back-end (under ./server.js).

An example of a one of the classes that I would like to share:

export default class DataModel {
    constructor(name) {
        this.name = name;
    }
}

If I put this class under ./src/models/DataModel.js, I can use it inside ./src by using import DataModel from '../models/DataModel'; but I can't use it inside ./server.js as it gives me the following error:

Unexpected token export

And I can't put the class directly under to the root of my project as create-react-app do not accept import from outside the ./src folder and will give me the following error:

Module not found: You attempted to import ../../DataModel which falls outside of the project src/ directory. Relative imports outside of src/ are not supported.


Update

I tried using react-app-rewired but without success.

My package.json has been updated:

{
    //...
    "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  },
    "devDependencies": {
    "react-app-rewired": "^2.1.0"
  }
}

And I added the file config-overrides.js on the root of my project (same level as package.json and my DataModel class).

/* config-overrides.js */

module.exports = function override(config, env) {
  delete config.resolve.plugins.ModuleScopePlugin;
  return config;
}

But I still have the same issue:

Module not found: You attempted to import ../../DataModel which falls outside of the project src/ directory. Relative imports outside of src/ are not supported.

Update #2

I logged the config that is passed through the override function and here is what I got:

{
  "mode": "development",
  "devtool": "cheap-module-source-map",
  "entry": [
    "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\react-dev-utils\\webpackHotDevClient.js",
    "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\src\\index.js"
  ],
  "output": {
    "pathinfo": true,
    "filename": "static/js/bundle.js",
    "chunkFilename": "static/js/[name].chunk.js",
    "publicPath": "/"
  },
  "optimization": {
    "splitChunks": {
      "chunks": "all",
      "name": false
    },
    "runtimeChunk": true
  },
  "resolve": {
    "modules": [
      "node_modules"
    ],
    "extensions": [
      ".web.mjs",
      ".mjs",
      ".web.js",
      ".js",
      ".json",
      ".web.jsx",
      ".jsx"
    ],
    "alias": {
      "react-native": "react-native-web"
    },
    "plugins": [
      {
        "topLevelLoader": {}
      },
      {
        "appSrcs": [
          "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\src"
        ],
        "allowedFiles": {}
      }
    ]
  },
  "resolveLoader": {
    "plugins": [
      {}
    ]
  },
  "module": {
    "strictExportPresence": true,
    "rules": [
      {
        "parser": {
          "requireEnsure": false
        }
      },
      {
        "test": {},
        "enforce": "pre",
        "use": [
          {
            "options": {
              "formatter": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\react-dev-utils\\eslintFormatter.js",
              "eslintPath": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\eslint\\lib\\api.js",
              "baseConfig": {
                "extends": [
                  "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\eslint-config-react-app\\index.js"
                ],
                "settings": {
                  "react": {
                    "version": "999.999.999"
                  }
                }
              },
              "ignore": false,
              "useEslintrc": false
            },
            "loader": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\eslint-loader\\index.js"
          }
        ],
        "include": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\src"
      },
      {
        "oneOf": [
          {
            "test": [
              {},
              {},
              {},
              {}
            ],
            "loader": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\url-loader\\dist\\cjs.js",
            "options": {
              "limit": 10000,
              "name": "static/media/[name].[hash:8].[ext]"
            }
          },
          {
            "test": {},
            "include": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\src",
            "loader": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\babel-loader\\lib\\index.js",
            "options": {
              "customize": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\babel-preset-react-app\\webpack-overrides.js",
              "babelrc": false,
              "configFile": false,
              "presets": [
                "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\babel-preset-react-app\\index.js"
              ],
              "cacheIdentifier": "development:babel-plugin-named-asset-import@0.2.3:babel-preset-react-app@6.1.0:react-dev-utils@6.1.1:react-scripts@2.1.1",
              "plugins": [
                [
                  "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\babel-plugin-named-asset-import\\index.js",
                  {
                    "loaderMap": {
                      "svg": {
                        "ReactComponent": "@svgr/webpack?-prettier,-svgo![path]"
                      }
                    }
                  }
                ]
              ],
              "cacheDirectory": true,
              "cacheCompression": false
            }
          },
          {
            "test": {},
            "exclude": {},
            "loader": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\babel-loader\\lib\\index.js",
            "options": {
              "babelrc": false,
              "configFile": false,
              "compact": false,
              "presets": [
                [
                  "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\babel-preset-react-app\\dependencies.js",
                  {
                    "helpers": true
                  }
                ]
              ],
              "cacheDirectory": true,
              "cacheCompression": false,
              "cacheIdentifier": "development:babel-plugin-named-asset-import@0.2.3:babel-preset-react-app@6.1.0:react-dev-utils@6.1.1:react-scripts@2.1.1",
              "sourceMaps": false
            }
          },
          {
            "test": {},
            "exclude": {},
            "use": [
              "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\style-loader\\index.js",
              {
                "loader": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\css-loader\\index.js",
                "options": {
                  "importLoaders": 1
                }
              },
              {
                "loader": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\postcss-loader\\src\\index.js",
                "options": {
                  "ident": "postcss"
                }
              }
            ]
          },
          {
            "test": {},
            "use": [
              "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\style-loader\\index.js",
              {
                "loader": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\css-loader\\index.js",
                "options": {
                  "importLoaders": 1,
                  "modules": true
                }
              },
              {
                "loader": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\postcss-loader\\src\\index.js",
                "options": {
                  "ident": "postcss"
                }
              }
            ]
          },
          {
            "test": {},
            "exclude": {},
            "use": [
              "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\style-loader\\index.js",
              {
                "loader": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\css-loader\\index.js",
                "options": {
                  "importLoaders": 2
                }
              },
              {
                "loader": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\postcss-loader\\src\\index.js",
                "options": {
                  "ident": "postcss"
                }
              },
              "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\sass-loader\\lib\\loader.js"
            ]
          },
          {
            "test": {},
            "use": [
              "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\style-loader\\index.js",
              {
                "loader": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\css-loader\\index.js",
                "options": {
                  "importLoaders": 2,
                  "modules": true
                }
              },
              {
                "loader": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\postcss-loader\\src\\index.js",
                "options": {
                  "ident": "postcss"
                }
              },
              "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\sass-loader\\lib\\loader.js"
            ]
          },
          {
            "exclude": [
              {},
              {},
              {}
            ],
            "loader": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules\\file-loader\\dist\\cjs.js",
            "options": {
              "name": "static/media/[name].[hash:8].[ext]"
            }
          }
        ]
      }
    ]
  },
  "plugins": [
    {
      "options": {
        "template": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\public\\index.html",
        "templateContent": false,
        "filename": "index.html",
        "hash": false,
        "inject": true,
        "compile": true,
        "favicon": false,
        "minify": false,
        "cache": true,
        "showErrors": true,
        "chunks": "all",
        "excludeChunks": [],
        "chunksSortMode": "auto",
        "meta": {},
        "title": "Webpack App",
        "xhtml": false
      },
      "version": 4
    },
    {
      "replacements": {
        "NODE_ENV": "development",
        "PUBLIC_URL": "",
        "REACT_APP_DEFAULT_LANGUAGE": "fr"
      }
    },
    {
      "appPath": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT"
    },
    {
      "definitions": {
        "process.env": {
          "NODE_ENV": "\"development\"",
          "PUBLIC_URL": "\"\"",
          "REACT_APP_DEFAULT_LANGUAGE": "\"fr\""
        }
      }
    },
    {
      "options": {},
      "fullBuildTimeout": 200,
      "requestTimeout": 10000
    },
    {
      "options": {},
      "pathCache": {},
      "fsOperations": 0,
      "primed": false
    },
    {
      "nodeModulesPath": "C:\\Users\\me\\Documents\\nodejs\\MY_PROJECT\\node_modules"
    },
    {
      "options": {
        "resourceRegExp": {},
        "contextRegExp": {}
      }
    },
    {
      "opts": {
        "publicPath": "/",
        "basePath": "",
        "fileName": "asset-manifest.json",
        "transformExtensions": {},
        "writeToFileEmit": false,
        "seed": null,
        "filter": null,
        "map": null,
        "generate": null,
        "sort": null
      }
    }
  ],
  "node": {
    "dgram": "empty",
    "fs": "empty",
    "net": "empty",
    "tls": "empty",
    "child_process": "empty"
  },
  "performance": false
}

I log it before calling the delete operation and as we can see their is no ModuleScopePlugin. So why does it still failing?

Any help is welcome.

Thanks.

MHogge
  • 5,408
  • 15
  • 61
  • 104
  • https://stackoverflow.com/questions/44114436/the-create-react-app-imports-restriction-outside-of-src-directory . And use CommonJS exports instead of ES modules. – Estus Flask Feb 27 '19 at 13:07
  • But it require to do an `eject`. What if I do not want to eject my app? – MHogge Feb 27 '19 at 13:35
  • 1
    You may use react-app-rewired. It's either one or another. A more generalized way is to make private `common` package and use it in both. – Estus Flask Feb 27 '19 at 13:44
  • I didn't know about react-app-rewired (I'm new to react). I'll try it soon, thanks. – MHogge Feb 27 '19 at 13:47
  • I tried `react-app-rewired` but it doesn't seem to be working. Any idea of what I'm doing wrong? – MHogge Mar 08 '19 at 14:47

1 Answers1

1

Sharing code is what modules are for. Ideally you would package up you models into a module and install/import into each codebase. To keep development simple you would probably want to utilise a monorepo format using Lerna and Yarn (or similar alternatives).

If that's beyond the scope of your project then I think you should be able to get away with using CommonJS exports in your model code (as previously suggested) which you keep in the React app src and require from your server code.

./src/models/DataModel.js:

class DataModel {
  constructor(name) {
    this.name = name;
  }
}
module.exports = { DataModel };

from React app

import { DataModel } from "./models/DataModel";

from server

const { DataModel } = require("./src/models/DataModel");

=========================================================================

Update

So as far as I can tell we're hitting some deep js-module voodoo here.

The code I suggested above works in codesandbox, but not locally.

I have tried matching the react-scripts and react versions with those in codesandbox with no success. I can only assume that codesandbox has it's own coping mechanisms which alleviate the problem.

As soon as the class is defined in the module using the commonjs export, things get weird with

module.exports = { DataModel }; and import {DataModel} give Attempted import error: 'DataModel' is not exported from '../models/DataModel'.

module.exports = { DataModel }; and require gives TypeError: Cannot assign to read only property 'exports' of object '#<Object>'

and

exports.DataModel = DataModel; and require gives ReferenceError: exports is not defined

Swap out the class for an old-school "class" though, and require the module from our app, and everything works as expected.

function DataModel(name) {
  this.name = name;
}

DataModel.prototype.logName = function () {
  console.log(this.name);
}

module.exports = { DataModel };

It seems it's to do with all the magic being performed by webpack/babel/et al to deal with the different module formats floating around, but I really couldn't nail it down.

For a second I thought this was going to explain it, but.. it didn't.. https://medium.com/webpack/webpack-4-import-and-commonjs-d619d626b655

lecstor
  • 5,619
  • 21
  • 27
  • In fact, sharing the code is probably the best solution in most cases but is not what we're going to do for internal reasons (+ the project is small and do not need to be split). The solution with mixing `import` and `require` is ok but I still have an issue about this: I can't use my constructor inside the React app. If I try `new DataModel('my model');` I got the following error: *Attempted import error: 'DataModel' is not exported from './models/DataModel'.* – MHogge Mar 11 '19 at 14:11
  • did you use the named import format, ie `import { DataModel }` rather than the default import format, `import DataModel`? – lecstor Mar 11 '19 at 21:19
  • I used the named import format (like you did in the example) – MHogge Mar 11 '19 at 22:40
  • I'm not sure what's going wrong then. Maybe something in your webpack config? I don't see why that wouldn't work, and it works for me here.. https://codesandbox.io/s/8l17wn84x0 – lecstor Mar 11 '19 at 22:52
  • My webpack config is the default one given by create-react-app. But anyway, thank you for your time, I will continue to search a solution for this. – MHogge Mar 12 '19 at 08:18
  • And if I do not use the named import format I got an error saying their is not default export for DataModel. – MHogge Mar 12 '19 at 08:20
  • no worries. Are you able to replicate it in codesandbox at all? maybe that would help. – lecstor Mar 12 '19 at 08:36
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/189865/discussion-between-mhogge-and-lecstor). – MHogge Mar 12 '19 at 08:49