yarnpkg / yarn

The 1.x line is frozen - features and bugfixes now happen on https://github.com/yarnpkg/berry
https://classic.yarnpkg.com
Other
41.39k stars 2.72k forks source link

Yarn workspaces: a lot of `node_modules` are missing in workspace #4503

Open ghost opened 6 years ago

ghost commented 6 years ago

Do you want to request a feature or report a bug? Bug

What is the current behavior? I have 2 workspaces. In the root dir I created package.json:

{
    "private": true,
    "name": "project-name",
    "workspaces": ["api", "frontend"]
}

When I use yarn install and I go to the frontend/node_modules I see just several modules, but not all needed which are in the root dir. As far as I understand there's no symlink created for these packages? For e.g. I need ember-pikaday, but it's not in the frontend/node_modules, but it's in the root node_modules.

In short: in frontend/package.json I have ember-pikaday, but it's not in the frontend/node_modules after yarn install. Because of this I can't start my project (Node.js can't find ember-pikaday and thinks it's not installed).

What is the expected behavior? To get all needed node_modules inside workspaces.

Please mention your node.js, yarn and operating system version. Node: 7.10.1 Yarn: 1.0.2 macOS

P.S. This feature is AMAZING!

BYK commented 6 years ago

Hey! This is by design, Yarn hoists all dependencies it can to the workspaces root. Why do you need them inside?

ghost commented 6 years ago

@BYK Hi, but how to fix this issue? I can't start project, because it can't find for e.g. ember-pikaday it thinks it's not installed...

BYK commented 6 years ago

@bsvipas this is not really "fixable" I think. The tool itself would need to check if it is available on the higher level. Can you run things from the workspace root?

ghost commented 6 years ago

@BYK I can't do that, because I'm using ember build and in such case there's no point to use workspaces if they are not "smart" enough. I think better is maybe to create symlinks in workspace node_modules which will fix such case issues, because they will know where is for e.g. ember-pikaday package, etc.

EDIT: But weird thing is my API workspace is smart enough to search node_modules in root, but frontend can't find it... Maybe it's, because of Ember fault?

ghost commented 6 years ago

Basically this happens to me https://github.com/yarnpkg/yarn/issues/4081 and I think this is only with Ember...

ghost commented 6 years ago

@BYK This issue is related with ember-cli-dependency-checker as you said. ~I fixed it and right now I need to polish it and I will try to make PR.~ Seems this is harder than I thought.

rulonder commented 6 years ago

Similar issue in create-react-app, what is the problem with the creation of symbolic links in the local packages? see https://github.com/facebookincubator/create-react-app/issues/3031

arcanis commented 6 years ago

Symlinks have problems.

ghost commented 6 years ago

I think this is not Yarn fault, it's because of tools which search for node_modules by themself to show you a error, some info or other stuff and it doesn't go in upper level, that's why. Because Yarn with workspaces install almost all node_modules in project dir, not the workspace dir. I tried to use it with simple project (with express) and it works :)

rulonder commented 6 years ago

@arcanis thanks for the link. @bsvipas yep, it seems that is the create-react-app is guessing the package absolute path.

lostpebble commented 6 years ago

This is an issue I'm running into with various tooling. It's not really yarn workspace's fault - it makes sense to push the modules to the parent directory. But it can be frustrating when working with and mixing your other workspace modules as regular node modules within each other.

The biggest issue I have is that using auto-import in the IDE's I've been trying (WebStorm and VS Code), trying to auto-complete a class / function from a different workspace package - as in the OP example, something from api inside of frontend - the tooling will decide to create a relative path to the other workspace module, like: import { getUser } from "../../api" - instead of using the node module resolution of just import { getUser } from "api".

If there was instead a symlink to the module in the actual workspace folder (only needed for workspace modules) perhaps this could be avoided. As you can open up a new project window in only the folder of each workspace module, which should scope it only to that directory, and check in that node_modules folder instead of going further up and creating a relative path.

ghost commented 6 years ago

@lostpebble I can just agree with you. If symlinks was created in all workspaces from the root (project node_modules) all issues will be gone and we can all easily use Yarn workspaces with any tool. Maybe we need to open new issue to suggest by adding symlinks?

gustaff-weldon commented 6 years ago

@bsvipas we also use monorepo with interconnected Ember addons and we solved the issue by automatically symlinking whatever package specifically requires from a hoisted node_modules folder into the package/node_modules folder. Happens after bootstrap. Works so far.

ghost commented 6 years ago

FYI I'm running into this same issue with babel plugins. I'm getting:

ReferenceError: Unknown plugin "...../node_modules/babel-plugin-transform-flow-comments" specified in "base" at 1, attempted to resolve relative to "<child path>"

Since babel won't look outside of the project folder but the yarn workspace is installing everything in <parent path>/node_modules. Symlinking in the child module resolves it which is what I'm doing for now. I believe webpack will have the same issue but I've just started looking at workspaces.

To the various comments in this thread arguing that it's not yarn's fault, I would argue that it is. Yarn workspaces change the expected directory structure that has been consistent since whatever npm version was with node v0.x (for at least top-level modules)

joefiorini commented 6 years ago

I'm having this issue as well, in my case with babel. We have a monorepo with a couple rails apps, each of which use webpack to bundle react code. What I don't understand is that even prior to workspaces we had babel specified at the root of the project, and it would get loaded fine because the standard node resolution rules would look up from the rails app's folder to the root and find babel.

I'm quite confused as to how this could change when using workspaces, but it seems like every case mentioned here should work just fine thanks to the default resolution rules. No?

francois-codes commented 6 years ago

I've had this issue too, but not consistently, so I couldn't really pinpoint the issue... My best guess is that if one of the packages shares all its dependencies with the other packages, they're all hoisted in the root, and that package doesn't even have a node_modules folder.

it's mostly a problem for binaries that are run form the package with a script pointing to the package's node_modules folder, so I added this script as a prepare hook in the main package.json file. Basically, if one of the package is missing a node_modules folder, I create it and then symlink the node_modules/.bin folder from the workspace root.

// scripts/link-binaries.js
const { readdir, access } = require('fs');
const { promisify } = require('util');
const { resolve } = require('path');
const shell = require('shelljs');
const { map } = require('ramda');

const asyncReaddir = promisify(readdir);
const asyncAccess = promisify(access);

const packagesDir = resolve(__dirname, '../packages');
const rootDir = resolve(__dirname, '../');

function linkBinaries(_package, nodeModulePath) {
  console.log('linking binaries in ', _package);
  shell.mkdir(nodeModulePath);
  shell.exec(`ln -s ${rootDir}/node_modules/.bin ${nodeModulePath}/.bin`);
}

async function perform() {
  const packages = await asyncReaddir(packagesDir);

  const result = map(async _package => {
    const nodeModulePath = resolve(
      packagesDir,
      `./${_package}`,
      './node_modules'
    );
    try {
      return await asyncAccess(nodeModulePath);
    } catch (e) {
      if (e.code === 'ENOENT') {
        linkBinaries(_package, nodeModulePath);
        return;
      }
      throw e;
    }
  }, packages);
  return Promise.all(result);
}

const successHandler = () => {
  console.log('Binaries linked !');
  process.exit(0);
};

const errorHandler = err => {
  console.error(err);
  process.exit(1);
};

perform().then(successHandler, errorHandler);
// in root package.json
"scripts": {
  "prepare": "node scripts/link-binaries.js"
}

the script could probably be improved, but it fixed the issue for me

Zerim commented 6 years ago

Also having issues with this where hoisting is leading to a number of difficult to resolve bugs in our Typescript setup. In some cases, it's causing duplicated conflicting module declarations and in other cases it's causing it to not find types that would be declared by a peer dependency. The inconsistency of the behavior makes it difficult to address by adjusting the global compiler settings in tsconfig.json.

Zerim commented 6 years ago

In case it helps anyone else, a hack that seems to work to keep your modules from being hoisted is to install a conflicting version of the same module in the root directory of your monorepo. That way the specific version used by your package will be kept in your respective workspace.

For my issue mentioned about the fix was a bit different, I used the resolutions field to force a single version of my dependency to be installed and in that way I avoided conflicts between the hoisted version of the dependency and the local versions of the dependency in the workspace.

blackxored commented 6 years ago

Tools like react-native link and react-native-git-upgrade seem to have this problem as well.

darthtrevino commented 6 years ago

react-vr is having problems with this setup as well. It would be nice to be able to opt-out of hoisting on a per-package level or mark dependencies as being required-local.

mjsisley commented 6 years ago

Perhaps adding a shell node_modules directory to each workspace that mirrors the directory structure of the root node_modules directory and has 1 file that re-exports the entry in the parent would solve this issue?

This gives the advantage of the root node_modules, but allows any library that is expecting to find a node_modules folder in its directory to keep working.

While ideally babel and other tools would each be useable with yarn workspaces without this need, it seems like it may be easier for yarn workspaces to provide a way to conform to the many tools that have the expectation of having a node_modules folder.

This could be opt in via a nodeModulesInWorkspace: boolean|array. (true to turn it on for all workspaces, or a whitelist of workspaces that need it).

tilwinjoy commented 6 years ago

Maybe the external tools (ember-cli, webpack etc) should travers upwards until it finds a node_modules before throwing errors?

PaulRBerg commented 5 years ago

I used to have a similar problem when ussing yarn 1.9.4 and trying to install eslint-config-airbnb-base. I was getting the following error when running eslint:

Cannot find module doctrine

I initially solved it by adding doctrine to {"resolutions"} in package.json, but I started to get other quirky warnings and I generally felt tihis was too hackish for what I wanted to achieve (get a linter to work).

But then I upgrade from yarn 1.9.4 to 1.15.2 and that solved the problem. I'm on macOS Mojave 10.14.2.

traviswimer commented 4 years ago

This is an old issue, but I have found a solution if anyone runs into this. Yarn has a somewhat poorly documented nohoist feature: https://yarnpkg.com/blog/2018/02/15/nohoist/

It lets you specify packages that you don't want to have hoisted to the top. For example, if you want to completely eliminate the node_modules hoisting, you could use something like this in your package.json:

{
    "workspaces": {
        "packages": [
            "projects/*",
        ],
        "nohoist": [
            "**"
        ]
    },
}

To get this to work, I had to delete my problematic node_modules directory and then run yarn install.

leolcao commented 4 years ago

I also meet this problem. If not set nohoist for yarn workspaces, all packages are stored in the root node_modules folder, when I run yarn workspace app-a build, it even cannot resolve native node.js modules, like:

Cannot find name 'console'

But, If I enter the workspace package folder(app-a), and run npm run build it works. After set the nohoist from @traviswimer comment, I can run yarn workspace app-a build now, but the drawback is also very big: there are duplicated node_modules in each workspace package folder.

At the end, I give up, and will try yarn v2 workspace.

kasperisager commented 3 years ago

For others looking for answers for Berry, the hoisting behaviour can be adjusted with the https://yarnpkg.com/configuration/yarnrc#nmHoistingLimits option. I was pulling in a third-party package as a workspace and it needed to run a script in one of its dependencies as part of its own postinstall script. Setting nmHoistingLimits: workspaces ensured that the dependency was actually linked in the workspace.

vbansal2 commented 1 year ago

is there any update on this issue?

We are facing a similar situation where the yarn install is not creating node_modules with symlinks in workspaces.

Please help

abdokouta commented 11 months ago

@vbansal2 use nohoist this is the only available solution for now :(

"workspaces": { "packages": [ "apps/*", ], "nohoist": [ "**/@pixiedia", "**/@pixiedia/**", "**pixiedia**" ] },