问题
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.
回答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
来源:https://stackoverflow.com/questions/54905481/share-model-between-backend-and-frontend-when-using-create-react-app