Short: For the project described below, how can I best configure things so that the two applications (client+server) run?
I am working on a longer running project that over time evolved into a mono repo. I cannot share the full code as the project is private, but the project has roughly the following structure, labelled with numbers so I can reference specific files easily in this example:
<repo-root>/ 1 package.json 2 tsconfig.json client/ 3 package.json 4 tsconfig.json pages/ index.tsx global/ 5 package.json 6 tsconfig.json src/ 9 index.ts build/ index.js index.d.ts server/ 7 package.json 8 tsconfig.json src/ index.ts bin/ index.js
Specifically, the client contains a NextJS project, the server contains a NodeJS Express server, and the global directory contains some shared functions and interfaces that both the client and the server utilize.
The client and server are able to access global via typescript project references, as well as using a typescript alias to map @global/
to the global/src directory on the client, and to the global/build directory on the server, as seen in the tsconfig files below. The reason for there being a build directory in global at all is because we have a compiled environment in the server; the server application builds with tsc
and runs with node server.js
(roughly). Here are the numbered files, put in pastebin so the post isn't huge:
- /package.json
- /tsconfig.json
- /client/package.json
- /client/tsconfig.json
- /global/package.json
- /global/tsconfig.json
- /server/package.json
8 is a code snippet because I ran out of guest pastes for the day:
{
"compilerOptions": {
"module": "commonjs",
"removeComments": true,
"lib": ["es2015", "dom"],
"sourceMap": true,
"esModuleInterop": true,
"target": "es5",
"declaration": false,
"forceConsistentCasingInFileNames": true,
"isolatedModules": false,
"strict": false,
"noImplicitAny": false,
"noImplicitThis": false,
"baseUrl": "./src",
"rootDir": "./src",
"types": ["node", "jest"],
"paths": {
"global/*": ["../../global/build/*"]
},
"outDir": "bin",
"plugins": [
{ "transform": "typescript-transform-paths" },
{ "transform": "typescript-transform-paths", "afterDeclarations": true }
]
},
"include": ["./src/**/*.ts"],
"exclude": ["*.d.ts", "**/*.spec.ts", "../global/**/*.ts"],
"references": [{ "path": "../global" }]
}
This is the best we could do for setup. However, there are several issues remaining before our code could work again.
We decided to migrate our mono repo to yarn pnp, because we ran into this issue at run time, after I included a shared dependency between client and global (draft-js
). The linked medium article in that github comment noted several possible solutions, but we were not using lerna, nor was our whole app using webpack (only client, as NextJS internally uses it). So our solution seemed to be in the footnote, yarn pnp.
I could not find any documentation however on how to properly convert project references from ts into yarn PnP dependencies, or at least how to make everything work again in the yarn pnp environment.
With this set up, we were first running into the issue that global doesn't build. It prints the error, "Cannot find module 'firebase' or it's corresponding type declarations", even though firebase is clearly listed as a dependency in file 5, and I can see the zip file in the .yarn/cache directory for the right version of firebase. I've also verified that it is using the .pnp.cjs file, and I am running yarn build
there (yarn tsc --build
), and yarn -v
prints that I am using the v3 canary version as expected; I cannot understand why somehow it can still not find the firebase module.
After that issue though, the other problem is that the typescript aliases seem that they should now be replaced with portal:
or link:
dependencies as described in their docs here. This did not work for us, it could not resolve the dependencies when trying to build the server specifically. Next, we tried using workspaces instead as that also seemed to promise to allow the client and server to access each-other. This is the configuration you can see in the pastes now. However, now when trying to compile global with this workspace configuration, I get an error similar to the second snippet here, about "Unable to locate pnpapi", saying that global/.pnp.cjs is being controlled by the root .pnp.cjs
Can anyone please advise, how could I best go about setting up this kind of project? What sort of refactoring is required, so that I can have one repository containing a NextJS TS client, a NodeJS server, and a shared global directory for shared TS interfaces and some functions (thus also some shared dependencies to get types / functions from, such as draft-js and firebase)?
If I can provide more specific details or format this question better, please also comment about that.
Thanks!