I am authoring a server in Node.js. I am using three things:
- 100% ESM-Only with
type: module
and*.js
extension for compiled files. - TypeScript with project references.
- The subpath imports.
My project directory structure is:
REPO
|- api
| |-- index.ts
| |-- tsconfig.json
|- server
| |-- index.ts
| |-- tsconfig.json
|- tsconfig.json
|- package.json
My package.json has following subpath imports:
{
name: "my-app",
imports: {
"#api": "./api/index.js",
}
}
The top-level tsconfig.json
has references:
{
"compilerOptions": {
"noEmit": true,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"verbatimModuleSyntax": true
},
"references": [
{ "path": "./api/tsconfig.json" },
{ "path": "./server/tsconfig.json" },
],
"include": [],
"files": []
}
I am using subpath imports in my server/index.js
like following:
import { graphql } from '#api';
async function main() {
const response = await graphql('query-string');
}
main();
When I run the server in development mode using npx tsx --watch ./server/index.ts
, it just works and tsx is able to properly resolve the ./api/index.ts
file accordingly.
The problem happens when I compile this project for production. The project gets compiled to dist
folder. In the docker image, only following files are present:
WORKSPACE
|- dist
| |-- api
| | |-- index.js
| |-- server
| | |-- index.js
|- package.json
When I try to run node ./dist/server/index.js
it gives as error as it cannot fild the api/index.js
file in the root of the folder.
I have two questions:
- What is the correct mechanism to handle subpath imports differently for development and production environments in ESM-only mode?
- Are they any recommended best practices around this pattern?