UNPKG

10.5 kBMarkdownView Raw
1# npmx
2
3A typical web project may consume hundreds of NPM packages, each developed
4independently by a random stranger somewhere on the internet. Each project
5has its own Git repository, is conceptually self-contained, and tries to
6maintain an API contract conforming to [SemVer](http://semver.org/). When
7there is a bug in one of these packages, a pull request is merged, the version
8is bumped, and a new release is published. The new release then begins a
9slow propagation across the internet, as each downstream project upgrades
10and reacts to any incompatibilities that may arise in a delightful symphony
11of distributed collaboration.
12
13At the root of this massive dependency tree is your client-side web application.
14Unlike everything else, it is one huge monolith. The bigger it grows,
15the more you begin to think "This is stupid -- we should break this thing up
16into 30 small NPM packages that can be reused and versioned independently."
17One heroic Saturday night, you create 30 Git repos and refactor everything.
18
19Eventually people start complaining. Your team isn't 30 strangers making
20frivolous little "left-pad" utilities. They are application developers
21creating a mission-critical product with messy business logic. The components
22interact in complex ways, and every time you change one package, you seem
23to be breaking other packages. It feels wrong to be cloning and building
2430 different Git repositories every day, when there's only 10 people on your team.
25Publishing is getting tedious. Running "npm link" is a minefield. This is no
26way to work!
27
28And so, you consolidate all your NPM packages into one central Git repository, and
29write a script that runs "npm install", "npm link", and "gulp" 30 times, in the
30right order. This is way better! In the past, when Bob made a big change to a
31core library and then left for a backpacking trip across Europe, it could take
32a week for Alice to upgrade to the new version and realize that something was broken.
33Even though Bob caused the trouble, his victims unfairly had to shoulder the cost
34of debugging it. Having a unified build means that Bob _cannot even merge his PR_
35(let alone publish a new release) without passing all the unit tests for every
36downstream project. Catching problems early makes everyone more efficient.
37Having a central repository forces library owners pay attention to the source code
38and PRs that consume their APIs; no more "out of sight, out of mind."
39
40There is just one problem... Builds are slowwwww. If "npm install" takes
411 minute (on a good day), then 30 installs take 30 minutes. Building 30 small projects
42is slower than building one big project. Other details like managing
43[shrinkwrap](https://docs.npmjs.com/cli/shrinkwrap) and publishing can be tricky.
44
45## NPMX is here to help!
46
47NPMX formalizes this model and makes it quick. It works completely within
48the conventional NPM system: Each package will still have its own Gulpfile.
49Each package can still run "npm install" without NPMX if desired.
50You are always free to move your projects around between Git repositories
51without any changes to package.json.
52
53But when you use NPMX, you get some big improvements:
54
55- Save time by installing all dependencies for all packages via a single
56 "npm install" invocation
57
58- NPMX automatically generates a shrinkwrap file for the entire repository.
59 NPM shrinkwrap is the only way to avoid maddening problems of "what are you talking
60 about, it works on my PC!"
61
62- All projects are automatically hooked up with "npm link" (using local
63 symlinks so multiple Git folders won't get cross-linked)
64
65- A dependency solver uses package.json to automatically determine
66 the build order.
67
68- Since each project has its own Gulpfile, NPMX can spawn multiple NodeJS
69 processes in parallel, making the build go significantly faster. (No matter
70 how many promises you write, your Gulpfile is still fundamentally single-threaded.)
71
72- Use a single command to run "npm publish" for many packages
73
74- Support for cyclic dependencies: For example, suppose that **my-gulp-task**
75 depends on **my-library**, but **my-library**'s Gulpfile has a devDependency
76 on **my-gulp-task**. NPMX can install the last published version of these
77 packages for the Gulpfile, while still creating local links for other
78 projects outside the cyclic dependency.
79
80# Usage
81
82## Building a repo that is configured for NPMX
83
841. Run "**npm install -g @microsoft/npmx**". To confirm that it's working,
85 run "npmx -h" which prints the version number and usage information.
86
872. From anywhere in your git working folder, run "**npmx install**". This
88 will install NPM modules in NPMX's "Common" folder.
89
90 NOTE: If you are troubleshooting build issues, try
91 "**npmx install --full-clean**" instead.
92
933. From anywhere in your Git working folder, run "**npmx link**". This creates
94 symlinks so that all the projects will reuse the packages from "common/node_modules"
95 (rather than having to run "npm install" in each project folder). It will
96 also link projects to the folders for their local dependencies, so that you don't need
97 to do "npm publish" to test your changes.
98
99 NOTE: The "**npmx.json**" config file specifies how this linking is performed.
100
101 > IMPORTANT: DO NOT run "npm install" inside project folders that have been linked
102 > by the NPMX tool. If you want to do that, you need to "**npmx unlink**" first.
103
1044. Do your initial build by running "**npmx rebuild**" . This will
105 recurse through each project folder and run "gulp clean", "gulp",
106 and "gulp test", and then give you a report of anything that failed to build.
107
108 NOTE: To suppress verbose output, use "**npmx rebuild -q**".
109
110## Pull -> Edit -> Build -> Run -> Push
111
112The above steps are only necessary when you need to do a clean full build (e.g.
113after pulling changes that affected common/package.json). Otherwise, you can
114run "gulp" in individual project folders as usual. Your daily workflow will
115look like this:
116
1171. Pull the latest changes from git.
118
1192. If something changed in the **common** folder, then you may need to update
120 your NPM:
121
122 > C:\MyRepo> **npmx install**
123 >
124 > C:\MyRepo> **npmx link**
125 >
126 > C:\MyRepo> **npmx rebuild -q**
127
1283. Debug a project:
129
130 > C:\MyRepo> **cd my-project**
131 >
132 > C:\MyRepo\my-project> **gulp serve**
133
134## If you need to modify your package.json
135
136If you need to add new dependencies to your package.json, you will need to
137regenerate the files in the common folder. Use these commands:
138
139> C:\MyRepo> **npmx generate**
140>
141> C:\MyRepo> **npmx link**
142
143This will change various generated files in common folder. You shuld include these
144changes in your Pull Request.
145
146The "**npmx generate**" command takes a long time. To speed up debugging, you can use
147"**npmx generate --lazy**" instead; however you must run the full "**npmx generate**"
148before submitting your PR.
149
150## Publishing your NPM packages
151
152To publish all NPM projects in your repository, run "**npmx publish**". You
153can select a subset of projects using the "**--include**" option followed by
154a [glob](https://en.wikipedia.org/wiki/Glob_(programming)) pattern. You can
155use the "**--registry**" option to specify a custom NPM registry, e.g. if you
156are testing with [Verdaccio](https://github.com/verdaccio/verdaccio).
157
158## Converting a repo to build with NPMX
159
160Currently you need to manually create an "npmx.json" configuration file
161at the root of your repository, which specifies the list of projects
162to be built. You also need to set up your "common" folder, and add the
163appropriate files to Git. (We are working on an "npmx init" command to
164simplify this.)
165
166## Detecting when new NPM dependencies are introduced
167
168Suppose that your NPMX repo has 30 different projects, and you want to keep track of
169what NPM packages people are using. When someone finds a new package and tries to add
170it to their project, you want to ask questions like: "Is this a good quality package?"
171"Are we already using a different library that does the same thing?" "Is the license
172allowed?" "How many other dependencies will this pull into our node_modules folder?"
173NPMX can alert you when this happens.
174
175In your **npmx.json** file, add these optional fields:
176
177```json
178 "reviewCategories": [ "published", "internal", "experiment" ],
179 "packageReviewFile": "common/PackageDependencies.json",
180```
181
182In this example, we defined three kinds of projects that we care about:
183Projects that we publish externally, projects kept internal to our company,
184and throwaway experiments. For each project in the repo, we will assign one
185of these categories as the "reviewCategory" field.
186
187The **PackageDependencies.json** file contains the list of approved packages.
188This file should be added to Git. It might look like this:
189
190```json
191{
192 "browserPackages": [
193 {
194 "name": "lodash",
195 "allowedCategories": [ "internal", "experiment" ]
196 }
197 ],
198 "nonBrowserPackages": [
199 {
200 "name": "gulp",
201 "allowedCategories": [ "published", "internal", "experiment" ]
202 },
203 {
204 "name": "some-crazy-tool",
205 "allowedCategories": [ "experiment" ]
206 }
207 ]
208}
209```
210
211Above, we specified that only our internal projects and experiments are allowed
212to use "lodash", whereas "gulp" is allowed everywhere. The "some-crazy-tool" library
213is being used by an experimental prototype, but should never be used in real projects.
214
215Note that NPMX distinguishes "**browserPackages**" from "**nonBrowserPackages**",
216since the approval criteria is generally different for these environments.
217
218Now, suppose someone changes their package.json to add "lodash" to a project that
219was designated as "published". When they run "npmx generate", NPMX will automatically
220rewrite the **PackageDependencies.json** file, appending "published" to the
221"allowedCategories" for "lodash". In other words, it automatically broadens the rules
222so that they describe reality. When the pull request is created, reviewers will spot this diff
223and can ask appropriate questions. Since our criteria is based on generalized categories,
224the reviewers aren't hassled about every little package.json change; a **PackageDependencies.json**
225diff only appears for genuinely interesting changes.