Skip to content

Commit 1f8f6a8

Browse files
author
Tim Koopman
committed
Add 'project:' dependencies
Add a `project:` dependency option, which will resolve tarball packages against the project root instead of the current directory. Unlike `file:`, the tarball will not be cached.
1 parent 58a403f commit 1f8f6a8

File tree

1 file changed

+150
-0
lines changed

1 file changed

+150
-0
lines changed

text/0000-project-dependencies.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
- Start Date: 2017-04-29
2+
- RFC PR:
3+
- Yarn Issue:
4+
5+
# Summary
6+
7+
Add a `project:` dependency option, which will resolve tarball packages against
8+
the project root instead of the current directory. Unlike `file:`, the tarball
9+
will not be cached.
10+
11+
# Motivation
12+
13+
Currently, when you want to add a dependency to a 'local' package you have to
14+
use a `file:` dependency. To have some form of a reusable dev setup you will
15+
likely use a relative path. Problem is that this might not resolve correctly
16+
on transitive dependencies:
17+
18+
```
19+
/shared/foo
20+
21+
/shared/bar
22+
dependencies: foo "file:../foo/build/foo.tgz"
23+
24+
/module/qux
25+
dependencies: bar "file:../shared/bar/build/bar.tgz"
26+
```
27+
28+
In this case package `qux` will look for `foo` at `/module/foo/build/foo.tgz`.
29+
This particular layout could use `../../shared/foo/build/foo.tgz` instead but
30+
this will still break if not all packages are at the same level in the directory
31+
tree.
32+
33+
The proposed solution is to use instead
34+
35+
```
36+
/shared/foo
37+
38+
/shared/bar
39+
dependencies: foo "project:shared/foo/build/foo.tgz"
40+
41+
/module/qux
42+
dependencies: bar "project:shared/bar/build/bar.tgz"
43+
```
44+
45+
Now transitive dependencies can always be resolved correctly. A file like
46+
`.yarn.project` could mark the project root against which paths will be
47+
resolved.
48+
49+
# Detailed design
50+
51+
An initial implementation is available.
52+
53+
A 'project' is a directory containing multiple packages somewhere below it. The
54+
root is marked by the existence of a file `.yarn.project`. Something similar is
55+
used by modern build systems:
56+
57+
- [Bazel](bazel.build): 'Workspace' contains 'packages'. Labels are of the form
58+
'//module/qux:build_name'. Root is marked with a `WORKSPACE` file.
59+
- [Buck](buck.build): 'Project' contains 'build rules'. Build targets are of the
60+
form '//module/qux:rule_name'. Root is marked with a `.buckconfig` file.
61+
- [Gradle](gradle.org): 'Root project' contains 'sub projects'.
62+
Tasks use ':module:qux:taskName'. Root is marked with a `settings.gradle`
63+
file.
64+
65+
I don't know how they handle accidentally nested projects. This might be worth
66+
looking into.
67+
68+
Unlike any other resolvers, the `project:` resolver should check the hash of the
69+
file during integrity checking. If the hash does not match the resolved version
70+
in the lockfile Yarn should not bail out. This is so we can be sure we are
71+
always using the current version of the local tarball. This should throw an
72+
error if you run with `--frozen-lockfile`.
73+
74+
Resolving could use the already existing `LocalTarballFetcher`.
75+
76+
# How We Teach This
77+
78+
I think the easiest way to explain this functionality would be as a comparison
79+
to the `file:` type.
80+
81+
This functionality is completely opt-in, so I foresee no large problems with
82+
use acceptance, given that documentation is available.
83+
84+
# Drawbacks
85+
86+
Basically ignoring version numbers is somewhat orthogonal to the design of NPM.
87+
I think this is exactly what you want when using a monorepo though and think it
88+
is worth it.
89+
90+
This will obviously not work on published packages.
91+
92+
Supporting only tarballs still requires the user to build the actual tarball
93+
from the current sources. You will still need some external application to
94+
handle building in the correct order otherwise you might still use an outdated
95+
version.
96+
97+
# Alternatives
98+
99+
There are a couple of alternatives:
100+
101+
##### Symlinks
102+
These can be created using `yarn link`, the proposed `link:` syntax in
103+
[#34](https://github.com/yarnpkg/rfcs/pull/34) or in newer NPM versions using
104+
`file:`.
105+
106+
The problem with all of these is that `require` will resolve against the real
107+
file location, likely giving completely different results than using an actual
108+
package. This basically splits dependency resolving into multiple completely
109+
independent parts. In most cases this will be unwanted.
110+
111+
##### Using `file:` with tarball
112+
The current `file:` implementation is incredibly broken. It could (should) be
113+
fixed to not cache tarballs using the filename only. This will still not work
114+
for transient dependencies. If you don't have transient local dependencies this
115+
should work fine though.
116+
117+
##### Using `file:` with a directory
118+
Will also not work with transient dependencies, but now you also have the
119+
problem of detecting changes. If you rely on the version in `package.json` you
120+
might get unexpected results if you make changes and forget to update the
121+
version number (new installs will use a different version than updated installs)
122+
123+
##### Linking to a package instead of a tarball
124+
This would be nice, but requires Yarn know how to build a package. There is
125+
already `pack` for packaging, but there is some kind of `prepack` script hook
126+
missing to actually run some build steps. It would be a lot more work to get
127+
working. Detecting if a package needs to be rebuild will be challenging.
128+
129+
##### Transparently use a local version if found in the project
130+
131+
If you declare any dependency on `foo` it will automatically use the local
132+
version if there is any package in the project called `foo`. I guess this will
133+
be pretty unexpected behaviour. You would need to know all local packages in
134+
advance and how to build them if necessary.
135+
136+
# Unresolved questions
137+
138+
The current implementation stores the tarball hash in the lockfile. This makes
139+
sure that everybody is using the same version at all times (if they differ you
140+
will get a different lockfile or a build error if using `--frozen-lockfile`).
141+
It also requires that your build system produces bit-for-bit equal artefacts in
142+
every environment.
143+
144+
This makes it a little harder to get working correctly, but it also opens some
145+
of the advanced caching functionality exposed by Bazel/Buck/Gradle (they
146+
retrieve artefacts from the build server if the hash matches, skipping the need
147+
for a local build).
148+
149+
It would be possible to leave the hash out of the lockfile but this means you
150+
don't get a warning if somebody is producing different tarballs for some reason.

0 commit comments

Comments
 (0)