2

In my React JS project I have configured a jsconfig.json such that I can recursively export nested directories and import a specific export from the base directory as follows:

jsconfig.json:

{
  "compilerOptions": {
    "jsx": "react",
    "baseUrl": "src"
  }
}

Project folder structure:

react-app
  src
    common
    index.js
      services
        ServiceA.js
        ServiceB.js
        index.js
      components
        ComponentA.jsx
        index.js
    pages
      pageA
        PageA.jsx
        index.js
    App.jsx
    index.js
    

Now in each index.js I would export all from each file/folder. So for example in common/services/index.js:

export * from 'common/services/ServiceA.js';
export * from 'common/services/ServiceB.js';

And in common/index.js:

export * from 'common/services';
export * from 'common/components';

Now if I require ServiceA exported from ServiceA.js in the PageA.jsx file I could import it as follows:

// PageA.jsx
import {
  ServiceA
} from 'common';
// ServiceA.js
export class ServiceA {
  doStuff () {
    // do stuff
  }
}

How can I setup my NodeJS server project to allow for similar exports and imports?

I'd like to do this for consistency between the FE and BE such that I can easily port over any FE code to my BE project without having to make any significant changes to exports and imports.

Edit: I managed to get it working using the answer by Besworks to which I awarded the bounty, however VS Code Intellisense wouldn't navigate to the export definition from the import statement until I added a jsconfig.json in the project root:

{
    "compilerOptions": {
        "baseUrl": "./src",
        "paths": {
            "#common" : ["./common/index.js"]
        }
    }
}
MShakeG
  • 391
  • 7
  • 45

3 Answers3

1

You can map exports in your package.json :

{
  "name": "@your-namespace/your-package",
  ...
  "exports": {
    ".": "./index.js",
    "./common": "./common/index.js"
  }
}

Then you can refer to the exports by name :

import { YourClass } from '@your-namespace/your-package';
import { AnotherClass } from '@your-namespace/your-package/common';

Alternatively, if your submodules only need to be accessible from within your package, you could map imports instead of exports which would not need your package name prepended but must start with a # character. You don't need to explicitly specify each submodule either, you can map everything from within a folder with wildcard substitution :

{
  "name": "your-package",
  ...
  "imports": {
    "#common": "./common/index.js",
    "#common/services": "./common/services/index.js",
    "#common/services/*": "./common/services/*.js",
    "#shortcut": "./deeply/nested/path/to/module.js"
  }
}

And use them :

import { YourClass } from '#common';
import * from '#common/services';
import { AnotherClass } from '#common/services/ServiceA';
import something from '#shortcut';

In the above example, #common would be a reference to ./common/index.js and #common/services/ServiceA would point to ./common/services/ServiceA.js.

Besworks
  • 4,123
  • 1
  • 18
  • 34
  • Appreciate the answer, but is this the only way, also if I have multiple levels of nested folders, e.g. /services within common I would also have to add ./common/services/index.js to exports and similarly for any other folders, also I have to prepend all imports with @{namespace}. Is it not possible to work with exports&imports like I do in my ReactJS projects? – MShakeG May 28 '22 at 17:21
  • I updated my answer with information about mapping `imports` rather than `exports`, this might be closer to what you're looking for. – Besworks May 28 '22 at 17:55
  • Thanks, I'll give it a try, presumably I'll have to change type to module to work with import and export – MShakeG May 28 '22 at 18:10
  • Also I don't want to necessarily import from nested folders(e.g. ./common/services) explicitly but simply import from ./common which exports from all nested folders/files recursively as I described in my question. – MShakeG May 28 '22 at 18:17
  • So just add `"#common/*": "./common/*/index.js"` to `imports`. Then you can access any path below ./common/ like `#common/deeply/nested/module` which will point to `./common/deeply/nested/module/index.js` – Besworks May 28 '22 at 19:08
  • I pushed a full [working example](https://github.com/besworks/submodule-mapping) to github. – Besworks May 28 '22 at 19:45
  • Much appreciated, is there a reason you didn't `export * from ./deep` in /services/index.js ? – MShakeG May 28 '22 at 20:02
  • I did it that way to show that you can access deeply nested paths directly without needing to export them at the intermediate layer. – Besworks May 28 '22 at 20:06
  • Thanks, I've got it working, I'll wait a few days to see if anyone has a better way of doing it before awarding the bounty – MShakeG May 29 '22 at 11:10
  • I think the biggest issue I have atm is that I can't easily navigate to the definition of whatever was imported in vs code. – MShakeG May 29 '22 at 13:46
  • That sounds like the topic for another question. – Besworks May 29 '22 at 13:54
  • True, it's a non-issue in my react projects as I don't have to prepend import paths with # – MShakeG May 29 '22 at 13:57
  • Another issue is that VS Code doesn't auto suggest/complete class methods – MShakeG May 29 '22 at 14:12
  • I cannot help you with any vs code related issues. This method of import/export mapping has been around since Node v12, if Microsoft has not implemented support by now that is a failure on their part and has no bearing on the validity of my answer. – Besworks May 29 '22 at 14:16
  • @MShakeG If I’ll recall, VS code uses typescript to support its JavaScript navigation. Typescript implemented the package exports support around 4.7. Looks like the next release of VS code will have it in, else you can install a newer version of typescript and point the intellisense to use that (either globally or locally). – Niall May 31 '22 at 08:21
  • I see, thanks, does it also support package imports? or just exports? – MShakeG May 31 '22 at 10:16
1

If your node project is using "type": "module" in package.json, then the filestructure is exactly the same. However, importing from index files in ES modules is still behind an experimental flag:

https://nodejs.org/api/esm.html#customizing-esm-specifier-resolution-algorithm

So you would have to launch it with e.g.:

node --experimental-specifier-resolution=node src/index.js

$ tree src/
src/
├── common
│   ├── index.js
│   ├── ServiceA.js
│   └── ServiceB.js
├── index.js
└── other
    ├── index.js
    ├── OtherA.js
    └── OtherB.js

src/index.js

import { serviceA, serviceB } from './common';
import { otherA, otherB } from './other';

serviceA();
serviceB();
otherA();
otherB();

src/common/index.js

export * from './ServiceA';
export * from './ServiceB';

`src/common/ServiceA.js'

export const serviceA = () => console.log('***** ServiceA');
Eric Haynes
  • 5,126
  • 2
  • 29
  • 36
  • Note that you'll still need the relative path between the modules. In your react projects, babel and/or webpack are taking all of your files and cramming them into (conceptually) one big file, so the relationship between paths on disk is wiped out completely. This is fundamentally different with node, as the file structure is maintained on the production server. You **can** use webpack for node projects too, if you like, but personally I wouldn't if the only reason is to avoid a few `../..`. – Eric Haynes May 28 '22 at 23:31
0

You should never export all packages.

The nodejs module/package import/require takes care of all dependencies and module caching.

Very similar to - https://www.youtube.com/watch?v=-5wpm-gesOY ;)

EvgenyKolyakov
  • 3,310
  • 2
  • 21
  • 31
  • I didn't notice any issue in my ReactJS project with having an index.js in every folder that exports all from all sibling files/folders. – MShakeG May 28 '22 at 10:23
  • @MShakeG, ok, now try packing it into a production suite and compare the difference of the final sizes when you're doing it your way or the right way. – EvgenyKolyakov May 28 '22 at 10:27
  • 1
    @MShakeG - from the zen of python (but it applies everywhere) - "Explicit is better than implicit." (see, https://peps.python.org/pep-0020/) – EvgenyKolyakov May 28 '22 at 10:28
  • Hmm, I've worked on quite a few large production React applications that managed exports and imports as I've described with no noticeable bloat. – MShakeG May 28 '22 at 10:30
  • @MShakeG, mate, do whatever you want, just keep in mind that these import/require mechanisms are there for a reason and I bet they do the task 100x faster and better than you are, no offense. – EvgenyKolyakov May 28 '22 at 10:34
  • I didn't come up with this style of folder exports&imports, I've seen it used both online and at my day job. Take this answer for example that's basically identical to how I manage exports&imports. https://stackoverflow.com/a/29722646/10261711 – MShakeG May 28 '22 at 10:45
  • @MShakeG You'll have major logic problems when your function names will start colliding. – EvgenyKolyakov May 28 '22 at 10:50
  • That'll result in a compilation error, which can be easily correct. And if you're wrapping your functions in classes(i.e. as class methods) that are exported that's extremely unlikely to occur in any case. – MShakeG May 28 '22 at 10:56
  • @MShakeG, it really depends on your settings because method overwriting is valid. And if you have two methods which take the same input and return different things, even typescript won't see an issue there. – EvgenyKolyakov May 28 '22 at 10:59
  • Exactly, but I think we're digressing from my original question. – MShakeG May 28 '22 at 11:01
  • @MShakeG my advice - don't. I know it seems like a mess importing everything in particular, but it provides much better code readability where you understand which method came from which module and saves tons of head-aces in the long run ;) – EvgenyKolyakov May 28 '22 at 11:05
  • Tbh I don't have an issue knowing what import came from where as VS Code allows you to easily navigate to the definition of an import. – MShakeG May 28 '22 at 11:07
  • @MShakeG as a rule of thumb I recommend you to never heavily rely on your IDE. I personally usually use Sublime or vim... I use VSCode only for types languages (like Golang, C++). – EvgenyKolyakov May 28 '22 at 11:10
  • Appreciate the advice, but I still think we're digressing from the original question. I'll consider your advice, but I still don't think you've provided an answer to the question, only why you think I shouldn't attempt to do what I'm looking for :) – MShakeG May 28 '22 at 11:13
  • @MShakeG sometimes the best thing to do is not to do anything at all – EvgenyKolyakov May 28 '22 at 11:15
  • Another point from the Zen - "If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea." – EvgenyKolyakov May 28 '22 at 11:16