parcel: Parcel 2 doesn't detect changes in linked dependencies.

🐛 bug report

When working on a dependency locally by linking it, Parcel 2 doesn’t detect changes and therefore doesn’t rebuild. You also need to remove the cache manually in order for it to work on re-runs.

🎛 Configuration (.babelrc, package.json, cli command)

Package.json

{
	"name": "parcel-tamplate",
	"version": "1.1.0",
	"description": "My personal Parcel Template",
	"main": "public/index.html",
	"source": "src/index.html",
	"author": "Samuel Elgozi <samu.elgozi@gmail.com>",
	"license": "none",
	"private": true,
	"scripts": {
		"dev": "parcel src/index.html",
		"build": "parcel build src/index.html"
	},
	"targets": {
		"main": {
			"includeNodeModules": true
		}
	},
	"devDependencies": {
		"@babel/plugin-proposal-class-properties": "^7.8.3",
		"babel-eslint": "^10.0.2",
		"eslint": "^6.0.1",
		"less": "^3.9.0",
		"parcel": "^2.0.0-alpha.3.2"
	},
	"dependencies": {
		"firebase-auth-lite": "^0.4.1"
	}
}

The commands I use:

parcel src/index.html

🤔 Expected Behavior

Like in version 1, Parcel should detect changes in linked dependencies, invalidate the cache and rebuild.

😯 Current Behavior

No changes are made, which require manual removal of .parcel-cache and then building again.

🔦 Context

I use Parcel to work on npm modules often. Without detecting the changes in linked packages there is no reason for me to use parcel. In fact, it becomes painful to do so…

🌍 Your Environment

Software Version(s)
Parcel 2.0.0-alpha.3.2
Node v13.10.1
Yarn 1.22.1
Operating System MacOs Catalina 10.15.3

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 42
  • Comments: 78 (21 by maintainers)

Commits related to this issue

Most upvoted comments

This issue keeps me from migrating to v2, too. I wonder how other developers handle this workspaces issue - also considering monorepos are now en vogue. We can’t possibly be alone here @ramirofages

@samuelgozi thanks for creating the repo, I can reproduce it pretty consistently, will try and figure out why it happens and hopefully fix it

This does not seem like a difficult feature to implement, and I think it would help a good number of folks. If those in charge of this project are up for such a change, I’d be happy to try my hand at a PR for this.

My suggested approach is to simply add a CLI flag to explicitly specify other paths to watch outside the project root. If the user does not explicitly specify any folders, the default behavior is the current one: only files within the project root are watched for changes. While not ideal for all use cases, I think this approach provides the most flexibility without changing existing behavior.

Thoughts?

This is really bad… Parcel 1 used to work with symlinked packages as already mentioned in this thread. Not being able to symlink packages basically means one needs to restart the bundler every single time a change is made to external package.

Imagine working on an app that has a thin shell and a number of libraries that deliver parts of the app in the form of components. Unless you go monorepo it is just not possible to do it with Parcel right now.

That’s really a shame. I wanted to base my next set of projects on Parcel 2, after waiting for so long for it to go GA and now it turns out it has this limitation 😦

Any idea about other bundlers that don’t have that limitation?

This is because the file watcher only watches for changes in the project root. It would be somewhat hard to implement support for symlinks outside the project I think. If we implemented this in the watcher we’d need to somehow crawl the whole file system to detect symlinks in the first place which would be very slow. We could potentially do it by detecting files that resolve to symlinks as we build the asset graph and keep nodes in the request graph to track watch roots. Then we’d have to re-watch all of those on startup and invalidate each of them for changes as opposed to just the project root.

(parcel-bundler/watcher#32)

This was actually working in parcel 1. Currently I had to revert back to parcel 1 while keeping its dependencies in parcel 2 to correctly build as module.

FYI, thanks for @jondlm, there is a new --watch-root CLI option to configure the root directory where the watcher listens which will be available in the next release. See #9424 for details.

This issue is a true blocker, and I tend to regret having upgraded from parcel 1 because of this 😦 This is a PITA when working with workspaces now. Having builds that take 3 seconds instead of 4 is cool, but not when you need to stop, delete cache and rebuild.

I’m really hoping this issue (in the short term roadmap) is addressed quickly.

Also, thank you for this great bundler !

A very hacky workaround I found:

  1. Run parcel with --no-cache, and
  2. Whenever the locally linked packaged changes, save package.json to trigger a rebuild

But I definitely agree that this should just work, Create React App does pick up changes in packages that are added with npm link ../../another-project so that could be inspiration?

This is because the file watcher only watches for changes in the project root. It would be somewhat hard to implement support for symlinks outside the project I think. If we implemented this in the watcher we’d need to somehow crawl the whole file system to detect symlinks in the first place which would be very slow. We could potentially do it by detecting files that resolve to symlinks as we build the asset graph and keep nodes in the request graph to track watch roots. Then we’d have to re-watch all of those on startup and invalidate each of them for changes as opposed to just the project root.

(https://github.com/parcel-bundler/watcher/pull/32)

This issue is easy to fix if the path to watch is configurable. Basically, borg this line:

https://github.com/parcel-bundler/parcel/blob/de1569572ab681bb6e90d2b330d002b5df7947ea/packages/core/core/src/Parcel.js#L376

And assimilate it to accept another option from the initial option, which it parsed inside the resolve option:

https://github.com/parcel-bundler/parcel/blob/de1569572ab681bb6e90d2b330d002b5df7947ea/packages/core/core/src/resolveOptions.js#L33

In particularly, this line: https://github.com/parcel-bundler/parcel/blob/de1569572ab681bb6e90d2b330d002b5df7947ea/packages/core/core/src/resolveOptions.js#L68

can be made to handle another optional input from the initialOption that would override the “project root”. Better yet, I think we should just separate the darn watcher’s root from the projectRoot altogether - as in letting user specify a “watchRoot” and just use that in the parcel watcher instead.

@devongovett - Agreed. Portability across machines could be compromised a bit if one could specify arbitrary folders to watch. Although playing devil’s advocate, this at least allows users to do it. I can foresee a situation where users want to watch changes for a dependency that is npm link-ed into a project root. Many “hacks” like these are temporary while dependencies are still under heavy development.

Here’s the most common use case, IMHO. Imagine, for example, a Git repository with backend, frontend, and common folders. The Parcel root project folder might be frontend (i.e. where package.json is located), but there might be isomorphic code shared between the backend and frontend inside of the common folder. In this case, it would be nice if Parcel could listen for changes in the common folder, even though this is a sibling folder outside of the project root.

I agree it would be great to have this fixed. My scenario seems even simpler:

some_root_dir/ │ ├─ client/ │ ├─ index.html │ ├─ package.json │ ├─ package-lock.json │ └─ client.js │ └─ shared/shared.js

where client.js imports shared.js – When I change client.js Parcel does its thing, updates the browser and life is good. When I updated shared.js nothing happens. So to me, the file-watching issue comes down to: Parcel should watch all files referenced starting from index.html as entry point no matter which directories these are in.

The approach @jameslaneconkling outlined applies to those with parcels as their only bundler invoked at the project’s root. It works because Parcel symlinks resolver works just fine.

The key issue here is that Parcel watcher will ignore any resolved symlink path that’s outside of the project root. Bundling/building will work just fine, but watching will not.

FYI while Parcel doesn’t currently support global symlinks created via npm link, it does work fine when building a monorepo (cc @krnlde , @danmarshall , @yume-chan , @theopolisme ). The configuration is hard to get right and isn’t documented super well. Here’s a super minimal example:

.parcel-cache/
node_modules/
packages/
  |- package_a/
    |- index.html
    |- index.js
    |- package.json
  |- package_b/
    |- index.js
    |- package.json
package.json
package-lock.json
// package.json
{
  "name": "root",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "dev": "npm run dev --workspace package_a"
  },
  "workspaces": [
    "packages/package_b",
    "packages/package_a"
  ]
}
// packages/package_a/package.json
{
  "name": "package_a",
  "version": "0.0.1",
  "scripts": {
    "dev": "parcel index.html"
  },
  "dependencies": {
    "package_b": "*"
  },
  "devDependencies": {
    "parcel": "^2.7.0"
  }
}
// packages/package_b/package.json
{
  "name": "package_b",
  "version": "0.0.1",
  "source": "index.js",
  "main": "index.js"
}

Make sure only the workspace root has a package-lock.json file and no packages do. Ditto only project root should have node_modules/ and .parcel-cache/ directories.

Would an option to set the project root explicitly via the CLI to override the inferred root based on the lockfile work for you? Watching outside the project root is much harder, but if you could set the project root manually that might be a reasonable workaround?

It’s on the roadmap, but it will be a bit complex to implement. We somehow need to discover all of the watch roots as parcel builds a project for the first time. We also need to check them all on startup when building from cache.

In addition, we will have to figure out how to handle paths that are outside the project root. Parcel’s cache is meant to be portable between machines, or if you move your project directory, but symlinks outside the project root break this.

Also, symlinks tend to break node_modules resolution. For example, if your project has a dependency on React, and you symlink another module which also has a dependency on React, you will get two copies of React in your build and it most likely won’t work at runtime.

Speaking from personal experience in the past trying to symlink projects together, I would definitely not recommend a setup like this. It breaks many things, not just watching. Using yarn/npm workspaces is much better in basically every way.

Here to provide my project structure that would be helped by this functionality. I have an example directory in my npm library repo which provides a fully ready togo example project that uses the library. The library is included in the example/package.json as file:... The library code is not auto updated due to parcel in example not traversing symlinks.

I am currently using this as a workaround:

$ ls dist/* | entr touch example/package.json

A very hacky workaround I found:

  1. Run parcel with --no-cache, and
  2. Whenever the locally linked packaged changes, save package.json to trigger a rebuild

But I definitely agree that this should just work, Create React App does pick up changes in packages that are added with npm link ../../another-project so that could be inspiration?

Good workaround, attach my code for example:

chokidar.watch(LINKED_DEPENDENCIES, {/* OPTIONS */}).on('all', () => {
    const projectPackagePath = path.resolve(PROJECT, 'package.json');
    const now = new Date();
    fs.utimes(projectPackagePath, now, now);
});

But there’s a minor issue with the workaround, LINKED_DEPENDENCIES changed will trigger the whole page reload, not HMR.

@bminer I don’t see why at least giving developers an option through a flag as you suggested would be an issue, if respecting symlinks outside the project root as before is out of the question. In our case, the package I’m working on is a dependency on more than one in-house project, and is not an integral part of any of the parent projects. It doesn’t make sense to build all as “workspaces” as the parent projects have no relation between them.

@Systemcluster - As discussed above, allowing Parcel to watch symlinks outside the project root is trickier to implement. Allowing users to specify additional paths to watch outside of Parcel’s project root seems like a fair compromise - and it sounds rather easy to do. As many have mentioned, the “project root” means the folder containing the package lock file.

Hello, @devongovett I made a repository to reproduce the issue I had : https://github.com/am-creations/parcel-issue-4332-symlink-watch

The project structure in this example is similar to our projects : /src/package.json and /vendor/whatever/package.json, so the “main” package.json file is not in the project root.

  • I do work with a team
  • The project uses internal libraries added via subtree or submodule
  • The issue happens when applying changes to thes libraries
  • All devs use NPM 7 / node 14 or 8 / node 16, the issue is the same
  • We work on Windows

If you are having problems, could you please explain your setup in more detail? Parcel 2 works fine with yarn workspaces for example. As long as the symlinks are within your project root (i.e. git repo), it should already work fine.

If you are symlinking outside, how do you set this up? Manual npm link or similar? Is this only temporary? Do you work with a team? How do you ensure all devs have the same setup? I feel that monorepos i.e. yarn/npm workspaces are the true solution here. Happy to eventually add support for watching outside the project root but there is complexity there and I want to understand why this issue gets so much attention. So, please help me understand your setups.

Afaik parcel can’t look outside its start context so if you want to watch all workspaces in a workspace root you’ll need to start parcel in that root. Did you do that? Be extra sure to not have any lock files inside any of the workspaces. Parcel unfortunately has no warning for that and will just silently fall back to the workspace scope instead of root scope, which took a lot of lifetime from me in the past the point where I wanted to quit my job and start a lumber business 😄

I run into this issue recently, the fact that it is standing for 3 years is amazing. unfortuntly, such limitition in parcel 2 makes it less productive when working with linked dependencies. Possible workaround I found is to refresh package.json for triggering new build. any convenient solution proposed so far?

I’m getting the same issue. Building a package and I have a demo app using Parcel 2 in an folder example, I don’t want to mix my .lock files and I would like to be able to get the app refreshing when I apply changes on the package components.

Wow it really has been 3 years! Like I’ve said, I’d be happy to open a PR to resolve this one if I can get some feedback from the devs that it’s likely to be merged. If the devs explicitly don’t want this feature, I don’t want to waste my time.

Thoughts? @devongovett

I am having a very similar issue. We are using a workspace for convenience. The packages are standalone and have their own .lock files. The workspace is optional. Being able to specify the root would solve what seems to be a common problem.

Edit: We are “solving” the issue for now by temporarily removing the lock file and checking it back out before committing changes. This is a horrible workflow, but the one we’ve chosen so we can still use Parcel.

We have a similar setup too.

I may not know all the aspects of the watcher, and I’m certainly ignoring a lot, but I don’t think the entire file system needs to be traversed, only the paths we need to resolve.

After having replaced all aliases, and resolved any “…” in paths ans such, when a “real path” is not the same as the path to resolve, we may consider it to have at least one symlink, and the common root between these two paths could be taken as a new directroy to watch.

That was an option I tried, but got issues as NPM has a inheritance mechanism that is a PITA because we use preinstall scripts : all modules in a directory inside the root execute the scripts, for example. And there are maybe issues I could not identify because I was blocked by the first issue. And as that it worked fine with parcel 1, we structured all our projects like this.

Does someone have an example with a working setup with npm workspaces? I tried setting up my new project with them, but still need to (re)start manually when referring to packages by name like:

import JSGDHost from '@kronbergerspiele/jsgdbridge'

Using a relative path works though

import JSGDHost from '../bridge'

@JanMisker thanks for the workaround idea.

  1. Whenever the locally linked packaged changes, save package.json to trigger a rebuild

In my case, I have more than 2 levels of dependencies in a monorepo, and this technique only seems to work one level deep.

Parcel 1 “just worked” quite magically. Without this ability, Parcel 2 is a step back in productivity. I would not have updated if it weren’t for the many security issues of Parcel1 's dependencies.

How about symlinks then?

“only files inside of that are watched” essentially ignores symlinks. This is mostly because the file watching APIs from the various OSs only fire change events for the actual parent directories and don’t traverse through every symlink

So node_modules is watched, but node_modules/symlinked-package/* isn’t

So this is more of a technical limitation at the moment, as explained in https://github.com/parcel-bundler/parcel/issues/4332#issuecomment-706837311 and not a design decision

a legitimate bug and not intended behavior, then. Should node_modules possibly be excluded

What do you mean? It intentionally watches node_modules so that updating dependencies and rerunning Yarn triggers a Parcel rebuild for changed files.

Okay yes it is the “Edit” there that is my problem then. The confusing thing is that this did work with parcel v1. When I would npm link package-b, parcel would rebuild package-a whenever it changed.

As mentioned, project-a does contain a lockfile and a .git directory, but node_modules are within that folder so I expected them to be watched, or at least not permanently cached when I restart with npm run example. I currently have to delete .parcel-cache in order for it to include the updated node_modules.

As long as the node_modules is inside the parcel start context it works. Again parcel picks its start context by probing all folders for .lock files starting with the most-inner one from your start path: parcel serve workspaces/workspace-a/index.html probes:

  1. workspaces/workspace-a/*.lock
  2. workspaces/*.lock
  3. *.lock

wherever a lock file is found it will not recognize changes beyond that folder. Ever. And they are not planning to change that.

That linked issue doesn’t resolve this for me. I don’t have a dangling yarn.lock file, just the one in my primary repository I’m working on. Linked dependency changes don’t trigger a recompile, and it seems even restarting parcel and running it with --no-cache don’t trigger a full rebuild. I have the delete .parcel-cache in order to have the changes picked up : /

Ok, here you go: https://github.com/samuelgozi/Parcel2-issue-4332 Further instructions can be found in the repo’s README file.