Friday, 3 November 2023

How do I properly structure, configure, and bundle npm references in a Typescript monorepo using npm workspaces?

I'm having trouble getting my head around how to configure this monorepo so it bundles, builds, and executes without problems.

It's a fullstack serverless (AWS) monorepo using npm workspaces, built entirely in TS.

It's It's structured like so:

my-monorepo/
├── node_modules
├── packages/
│   ├── lib/
│   │   ├── index.ts
│   │   ├── random.ts
│   │   ├── package.json
│   │   └── tsconfig.json
│   ├── lambda/
│   │   ├── test.ts
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── web/
│       └── react-vite/
│           ├── App.tsx
│           ├── package.json
│           └── tsconfig.json
├── package.json
├── tsconfig.base.json
└── tsconfig.json

I'm simply trying to make it so all external and internal npm package references work, so I can share code between all projects out of the /lib folder.

Here's lambda/test.ts:

import { ulid } from "ulid";
import { random } from "@my-monorepo/lib";

export async function routesHandler(event: any) {
    try {
        return {
            statusCode: 200,
            // body: JSON.stringify(`${random.randomPhrase()} Lambda!`)
            body: JSON.stringify(`Hello Lambda! How about a ULID? -> ${ulid()}`)
        };
    } catch (e) {
        console.log("It's dead:", e);
    }
}

This executes just fine, and prints:

Compiling with Typescript...
Using local tsconfig.json - ./packages/lambda/tsconfig.json
Typescript compiled.
{
    "statusCode": 200,
    "body": "\"Hello Lambda! How about a ULID? -> 01HE33FGF9DE4Q6H8K1KP42KEA\""
}

When I try to call from the random module by swapping the body lines in the lambda above, like this:

return {
    statusCode: 200,
    body: JSON.stringify(`${random.randomPhrase()} Lambda!`)
    //body: JSON.stringify(`Hello Lambda! How about a ULID? -> ${ulid()}`)
};

I get this:

import * as random from "./random"; ^^^^^^

SyntaxError: Cannot use import statement outside a module

/lib/index.ts:

import * as random from "./random";
import * as model from "./model/model";

export {
    random,
    model
};

/lib/random.ts:

const randomPhrase = (): string => {
    const phrases = [
        "Gadzooks!",
        "Eureka!",
        "D'oh!",
        "Egads!",
        "Bazinga!"
    ];
    return phrases[Math.round(Math.random() * phrases.length)];
};

export {
    randomPhrase
};

It has to be a combination of tsconfig props that I'm getting wrong, because I can execute code from the /lib within App.tsx in the web/react-vite project, it renders and works as expected. Here's that snippet:

import { random } from "@my-monorepo/lib";

function App() {
    return (
        <div>Welcome to React...{random.randomPhrase()}</div>
    )
}

export default App

I see what I should:

enter image description here

Here's the root config stuff:

./package.json

{
    "name": "my-monorepo",
    "type": "module",
    "private": true,
    "workspaces": [
        "packages/lambda",
        "packages/web/react-vite",
        "packages/lib"
    ],
    "devDependencies": {
        ...  
    },
    "dependencies": {
        ...
    }
}

./tsconfig.base.json

{
    "compilerOptions": {
        "target": "ES2022",
        "module": "CommonJS",
        "declaration": true,
        "sourceMap": true,
        "strict": true,
        "moduleResolution": "node",
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,
        "composite": true
    },
    "exclude": ["node_modules"],
    "include": [
        "packages/lib/*",
        "packages/lib/**/*"
    ]
}

./tsconfig.json

{
    "references": [
        {
            "path": "packages/lambda"
        },
        {
            "path": "packages/lib"
        }
    ]
}

/lib config files:

lib/package.json:

{
    "name": "@my-monorepo/lib",
    "version": "1.0.0",
    "main": "index.ts",
    "type": "module"
}

lib/tsconfig.json:

{
    "extends": "../../tsconfig.base.json"
}

/lambda config files:

lambda/package.json

{
    "name": "@my-monorepo/lambda",
    "version": "1.0.0",
    "main": "test.ts",
    "type": "module",
    "dependencies": {
        "@my-monorepo/lib": "^1.0.0",
        "ulid": "^2.3.0"
    }
}

lambda/tsconfig.json

{
    "extends": "../../tsconfig.base.json"
}

UPDATE 1:

I was able to manually compile the /lib project and change the main reference in package.json to "main": "build/index.js", and get it working locally. However...was hoping to avoid that manual build step, if possible.

It also didn't help me at all once the Lambda was deployed to AWS. I still get this error on execution:

{
  "errorType": "Runtime.ImportModuleError",
  "errorMessage": "Error: Cannot find module '@my-monorepo/lib'\nRequire stack:\n- /var/task/index.js\n- /var/runtime/index.mjs",
  "trace": [
    "Runtime.ImportModuleError: Error: Cannot find module '@my-monorepo/lib'",
    "Require stack:",
    "- /var/task/index.js",
    "- /var/runtime/index.mjs",
    "    at _loadUserApp (file:///var/runtime/index.mjs:1087:17)",
    "    at async UserFunction.js.module.exports.load (file:///var/runtime/index.mjs:1119:21)",
    "    at async start (file:///var/runtime/index.mjs:1282:23)",
    "    at async file:///var/runtime/index.mjs:1288:1"
  ]
}

It ALSO breaks the reference in the React app - so I suppose it's a matter of figuring out how to get the Lambda to bundle/build with the source TS files like the Vite build does.



from How do I properly structure, configure, and bundle npm references in a Typescript monorepo using npm workspaces?

No comments:

Post a Comment