Discover local dependencies outside of immediate node_modules
nolanlawson opened this issue · 2 comments
I really hope that I'm just missing something obvious, so please close this issue if I just misread the documentation or something.
Let's say I have a very simple module with a single index.js
that I want to test. If I have this in my tachometer.json
:
{
"benchmarks": [
{
"url": "./benchmark.html",
"expand": [
{
"name": "this-change"
},
{
"name": "tip-of-tree",
"packageVersions": {
"label": "tip-of-tree",
"dependencies": {
"my-package-name": {
"kind": "git",
"repo": "https://github.com/org/my-package-name.git",
"ref": "master"
}
}
}
}
]
}
]
}
And then I use this in my benchmark.html
:
import myPackage from 'my-package-name'
This actually works for the remote dependency, but it doesn't work for the local dependency. Instead, Tachometer doesn't transform the import
statement, so the browser can't find the module.
Here is a small repro.
The only solution I've found is to manually add symlinks so that node_modules/my-package-name
is linked locally:
ln -s .. node_modules/repro-tach-dependency
Then Tachometer works as expected - the local dependency is resolved locally, and the remote one is resolved by fetching it from npm.
I've also observed this issue in a monorepo, where the project structure is like this:
packages/my-package-name
packages/my-package-name/index.js
packages/benchmark
packages/benchmark/benchmark.html
In this case, if benchmark.html
tries to do import 'my-package-name'
, it won't resolve locally.
This might have something to do with subtleties of dependency hoisting. In a monorepo project without dependency hoisting, packages/benchmark/node_modules
would indeed contain the my-package-name
package. But in the case I ran into, dependency hoisting moves all the dependencies to the top-level node_modules
, so Tachometer can't find it, because it seems to check only the immediate node_modules
directory.
The solution I've found is again to do the node_modules
symlinking, e.g.:
ln -s ../../my-package-name ./packages/benchmark/node_modules/my-package-name
To solve both these issues (if this is indeed an issue and I didn't just misconfigure something), it seems to me that the Tachometer module resolver should:
- search for packages at the root level via
package.json
(for the simple single-package case) - search recursively up the tree for
node_modules
(for the hoisted monorepo case)
Thanks for reading this far, and thanks for creating Tachometer! It's a great tool, and I can use it just fine with the symlinking workaround, but I thought I'd report the issue in case others ran into it or I missed something. 🙂
After thinking about this more deeply, I think this can be solved with:
root
(as described here: #244 (comment))- not referring to your own package as a bare
'my-package-name'
but instead as e.g.'./index.js'
I'm not sure why the resolution works differently for remote vs local tests, but in any case, Tachometer is probably right that it would be weird to resolve 'my-package-name'
to the current directory when running inside the my-package-name
directory itself. For example, this doesn't work in Node:
mkdir foo
cd foo
npm init --yes
node -e "require('foo')" # Error: Cannot find module 'foo'
So since the resolution doesn't work in plain Node, you wouldn't really expect it to work in Tachometer either. So referring to your own local package as e.g. './index.js'
makes a lot more sense.
As for the monorepo issue, I think this is the same as #244 and can be resolved with root
. Closing this issue. Sorry for the noise!
For the record, this issue was actually trickier to resolve than I thought. I'll put a summary here in case it helps anyone else.
Situation: you have a package, e.g. my-package
, and you want to test the local branch versus a remote (e.g. main
). There is no monorepo; you want to test a single package and have Tachometer swap it out.
Resolution: use a placeholder package in your package.json
:
"devDependencies": {
"@scope/placeholder": "file:."
}
Then import from that placeholder in your benchmark scripts:
import foo from '@scope/placeholder'
Then tell Tachometer to swap out the placeholder package when resolving the main
branch dependency:
"expand": [
{
"name": "this-change"
},
{
"name": "tip-of-tree",
"packageVersions": {
"label": "tip-of-tree",
"dependencies": {
"@scope/placeholder": {
"kind": "git",
"repo": "https://github.com/scope/package.git",
"ref": "main"
}
}
}
}
]
It's a little gnarly, but it works! Note the @scope
to avoid accidental dependency confusion attacks.