|
| 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