I’m struggling with setting up a monorepo using NPM workspaces where I want to share a common package containing types and utilities between my frontend and backend apps.
The main issue I’m facing is with the package structure. When my shared package has just one index.js file in the dist folder, importing works fine from other packages. But as soon as I organize my source code into multiple directories and files, the imports stop working.
Here’s a simplified example of what I’m trying to achieve:
// In backend package
import { UserData, validateUser } from 'shared-types';
const processUser = (user: UserData) => {
if (validateUser(user)) {
// process user logic
}
};
I’m confused about the proper way to structure the shared package and configure the build process so that other packages can import from it reliably. Any guidance on best practices for NPM workspace monorepo setup would be really appreciated.
Your issue is likely related to module resolution and export configuration in your shared package. When you move beyond a single index.js file, it’s essential to properly set up the exports field in your package.json to reflect each subdirectory or module and ensure your build process accommodates the directory structure.
In the package.json for your shared package, map each subdirectory or module in the exports field. If you’re using TypeScript, make sure to include both compiled JavaScript files and declaration files. Your build should maintain the same directory structure in the dist folder as in the source, or alternatively, you can use barrel exports to flatten it.
I’ve had success with barrel exports—essentially, re-exporting everything through index files, which consistently works across different bundlers. Also, ensure your main field points to the correct entry point and that you’re publishing the built files rather than the source. If your workspace is set up correctly, it should automatically detect changes during development.
your build setup’s probably not handling multiple files right. ditch tsc and use rollup or esbuild for your shared package instead. had the same problem - rollup with proper entry points sorted it out. build your shared package first before importing anything. the dist folder goes out of sync a lot. run npm run build in the shared package, then check if your compiled files actually match the source structure.
This is usually a TypeScript config issue with how your shared package builds. You need the right module resolution and declaration files generated properly. I’ve hit this before - set “moduleResolution” to “node” and “declaration” to true in your shared package’s tsconfig.json. That fixes most import problems. Also check your shared package’s package.json has “main” and “types” pointing to the right compiled files in dist. Another gotcha: your consuming packages need the workspace dependency set up right. Use “shared-types”: “workspace:*” in their package.json so they always grab the local version. Build order matters too - compile your shared package first before anything tries importing from it.