I have project which uses lerna ( monorepo, multiple packages ). Few of the packages are standalone apps.
What I want to achieve is having aliases on few of the packages to have something like dependency injection. So for example I have alias @package1/backendProvider/useCheckout
and in webpack in my standalone app I resolve it as ../../API/REST/useCheckout
. So when I change backend provider to something else I would only change it in webpack.
Problem
Problem appears when this alias is used by some other package ( not standalone app ). For example:
Directory structure looks like this:
Project packageA ComponentA packageB API REST useCheckout standalone app
ComponentA is in packageA
useCheckout is in packageB under /API/REST/useCheckout
path
ComponentA uses useCheckout
with alias like import useCheckout from '@packageA/backendProvider/useCheckout
Standalone app uses componentA
The error I get is that Module not found: Can't resolve '@packageA/backendProvider/useCheckout
However when same alias is used in standalone app ( that has webpack with config provided below ) it is working. Problem occurs only for dependencies.
Potential solutions
I know that one solution would be to compile each package with webpack – but that doesn’t really seem friendly. What I think is doable is to tell webpack to resolve those aliases to directory paths and then to recompile it. First part ( resolving aliases ) is done.
Current code
As I’m using NextJS my webpack config looks like this:
webpack: (config, { buildId, dev, isServer, defaultLoaders }) => { // Fixes npm packages that depend on `fs` module config.node = { fs: "empty" }; const aliases = { ... "@package1/backendProvider": "../../API/REST/" }; Object.keys(aliases).forEach(alias => { config.module.rules.push({ test: /.(js|jsx)$/, include: [path.resolve(__dirname, aliases[alias])], use: [defaultLoaders.babel] }); config.resolve.alias[alias] = path.resolve(__dirname, aliases[alias]); }); return config; }
Advertisement
Answer
You don’t need to use aliases. I have a similar setup, just switch to yarn (v1) workspaces which does a pretty smart trick, it adds sym link to all of your packages in the root node_modules.
This way, each package can import other packages without any issue.
In order to apply yarn workspaces with lerna:
// lerna.json { "npmClient": "yarn", "useWorkspaces": true, "packages": [ "packages/**" ], }
// package.json { ... "private": true, "workspaces": [ "packages/*", ] ... }
This will enable yarn workspace with lerna.
The only think that remains to solve is to make consumer package to transpile the required package (since default configs of babel & webpack is to ignore node_module transpilation).
In Next.js project it is easy, use next-transpile-modules.
// next.config.js const withTM = require('next-transpile-modules')(['somemodule', 'and-another']); // pass the modules you would like to see transpiled module.exports = withTM();
In other packages that are using webpack you will need to instruct webpack to transpile your consumed packages (lets assume that they are under npm scope of @somescope/
).
So for example, in order to transpile typescript, you can add additional module loader.
// webpack.config.js { ... module: { rules: [ { test: /.ts$/, loader: 'ts-loader', include: /[\/]node_modules[\/]@somescope[\/]/, // <-- instruct to transpile ts files from this path options: { allowTsInNodeModules: true, // <- this a specific option of ts-loader transpileOnly: isDevelopment, compilerOptions: { module: 'commonjs', noEmit: false, }, }, } ] } ... resolve: { symlinks: false, // <-- important } }
If you have css, you will need add a section for css as well.
Hope this helps.
Bonus advantage, yarn workspaces will reduce your node_modules
size since it will install duplicate packages (with the same semver version) once!