Reputation: 470
I am trying to share enums and dtos between my backend and frontend (NestJS at the back, React at the front. Both use typescript).
To do so, I tried to add my "shared" files to the client's tsconfig.json
> compilerOptions
> paths
like so:
"paths": {
"@project/shared/*": [
"../server/src/common/*"
]
}
Unfortunately, this doesn't seem to work. When I run the react client, it throws "webpack Module not found."
This is my file structure:
App.tsx
consumes the TestEnum
from the server's common directory.
Upvotes: 2
Views: 4930
Reputation: 470
I want to share what I had to do in my projects and file structure to share models, types, enums, entities, etc., between the server (NestJS) and the client (React).
First of all, I started setting up npm workspaces to achieve a monorepo, as @sungryeol described in his detailed answer (thanks for it).
Unfortunately, it was only half of the way to making it work. So here is a solution I came up with (remarks are welcome) in addition to workspaces:
create react app
is designed to compile code only from the src
directory. Because I am using typescript in my shared code, I need to compile it to make it work. To make CRA compile my shared code, I used react-app-rewired
to modify the webpack config with the following config-overrides.js
file:
const path = require('path');
module.exports = function override(config, env) {
const babelLoaderModule = require.resolve('babel-loader');
const rule = config.module.rules[1].oneOf.find(rule => rule.loader === babelLoaderModule);
rule.include = [
rule.include,
path.resolve(__dirname, '../shared')
];
return config;
}
React finally started accepting my shared code without any issues.
Since NestJS uses tsc
as the compiler, I had to specify the directory of the shared code in the server's tsconfig.json
as a path.
"paths": {
"@managed-businesses/shared/*": [
"../shared/*"
]
}
This made my shared code compile when I started the nest app, but it couldn't find the entry point, as it compiled the code in the following structure
dist
├── server
│ ├── src
│ ├── main.d.ts
│ └── main.js
├── shared
│ ├── dtos
│ └── enums
└── tsconfig.build.tsBuildInfo
while Nest lookup for the src/main.js
file to be at the root of the dist
directory.
To fix that, I had to tweak the nest-cli.json
file to lookup the file in the dist/server/src
directory. Here is my new config for nest-cli.
{
"collection": "@nestjs/schematics",
"sourceRoot": "server/src"
}
After all, the server finally works with the shared code, and I don't need to duplicate it between the projects. I am probably missing something or did something incorrectly, and I'll be happy to know if there is a better way, but at the bottom line, it works.
Upvotes: 1
Reputation: 4005
What you are trying to achieve is essentially a monorepo. The best way to solve the dependency issue is of monorepo is to refer each package as a separate package; it's clear in the comment that you do not want to do so but it's de facto standard already.
It's because importing what's outside the workspace(where the package.json
lies) is really really bad idea. I've tried this but not recommend it; the package managers, bundlers and IDEs(let's say "the tools") will work strangely due to conflict issues:
the file's root has it's own config such as tsconfig.json, package.json
which can conflict with project's setting. it is not clear which one they should follow.
the tools usually have their boundary due to security issues; if the tool can leave the project root, it can do something sneaky, such as compromising internal system files.
when deploying the apps in the clouds, they(client/server) have to stick together all the time. it becomes devOps issue.
the best way to deal with this, is to use workspace feature of package managers. here the term workspace means separate packages inside monorepo. npm's explanation of workspace clearly fits to your settings.
Workspaces is a generic term that refers to the set of features in the npm cli that provides support to managing multiple packages from your local file system from within a singular top-level, root package.
npm and yarn both support workspace features.
from now on, the project structure should be perceived as this.
project root --> place workspace setting here
|-package root(client) --> workspace
|-package root(server) -> workspace too
most workspace features support 'hoist' feature. without publishing the actual packages, your packages in different folders can be imported and referred as if they are published, via softlink(symlink, something like shortcut in windows). it's same as typescript config's path alias but it actually creates a real file so that the tool won't have any side effects.
to do so, setup workspace config at your project root so that the tool can perceive this packages as workspaces.
# create shared package directory first
$(projectRoot) mkdir shared
// projectRoot/package.json
{
"name": "project",
"workspaces": [
"client",
"server",
"shared"
]
}
// projectRoot/client/package.json
{
"name": "@project/client",
"private": true, // set this to private so that this won't get published by mistake
"packages": {
// your default packages
"@project/shared": "1.0.0" // <-- version has to match
}
// ...
}
// projectRoot/server/package.json
{
"name": "@project/server",
"private": true
// ...
}
// projectRoot/shared/package.json
{
"name": "@project/shared",
"private": false, // make it public so that you can publish it later
"version": "1.0.0"
// ...
}
# remove all `node_modules` folder.
$(projectRoot) rm -rf client/node_modules
$(projectRoot) rm -rf server/node_modules
# reinstall node_modules so that the projects can be symlinked as packages.
$(projectRoot) npm i
then it'll work as workspace from now on and the packages will be linked. it's that simple!
as you can see, cleaning up and bootstrapping the package is bit of a work. that's where monorepo tool(i.g. lerna) comes handy; try expand your monorepo knowledge from here.
if it's deployed to AWS, it'll probably work. however, it's best to publish the @project/shared
package to npm in the end. many cloud instances don't support symlinks(vercel, netlify...) still, symlink is useful in local/dev environment.
here's working demo:https://github.com/rabelais88/monorepo-workspace-npm
check readme before proceed. after typing npm install
, symlinked hollow modules are seen. as far as they are there, scripts will run fine
edit 2: after setting up the workspace, some dependencies or all the dependencies will be placed at project root instead of each workspace. it is intentional by design, to avoid having duplicated packages in each workspace. this might surprise some but actually very useful in maintaining bigger systems.
Upvotes: 4