1 | # npmx
|
2 |
|
3 | A typical web project may consume hundreds of NPM packages, each developed
|
4 | independently by a random stranger somewhere on the internet. Each project
|
5 | has its own Git repository, is conceptually self-contained, and tries to
|
6 | maintain an API contract conforming to [SemVer](http://semver.org/). When
|
7 | there is a bug in one of these packages, a pull request is merged, the version
|
8 | is bumped, and a new release is published. The new release then begins a
|
9 | slow propagation across the internet, as each downstream project upgrades
|
10 | and reacts to any incompatibilities that may arise in a delightful symphony
|
11 | of distributed collaboration.
|
12 |
|
13 | At the root of this massive dependency tree is your client-side web application.
|
14 | Unlike everything else, it is one huge monolith. The bigger it grows,
|
15 | the more you begin to think "This is stupid -- we should break this thing up
|
16 | into 30 small NPM packages that can be reused and versioned independently."
|
17 | One heroic Saturday night, you create 30 Git repos and refactor everything.
|
18 |
|
19 | Eventually people start complaining. Your team isn't 30 strangers making
|
20 | frivolous little "left-pad" utilities. They are application developers
|
21 | creating a mission-critical product with messy business logic. The components
|
22 | interact in complex ways, and every time you change one package, you seem
|
23 | to be breaking other packages. It feels wrong to be cloning and building
|
24 | 30 different Git repositories every day, when there's only 10 people on your team.
|
25 | Publishing is getting tedious. Running "npm link" is a minefield. This is no
|
26 | way to work!
|
27 |
|
28 | And so, you consolidate all your NPM packages into one central Git repository, and
|
29 | write a script that runs "npm install", "npm link", and "gulp" 30 times, in the
|
30 | right order. This is way better! In the past, when Bob made a big change to a
|
31 | core library and then left for a backpacking trip across Europe, it could take
|
32 | a week for Alice to upgrade to the new version and realize that something was broken.
|
33 | Even though Bob caused the trouble, his victims unfairly had to shoulder the cost
|
34 | of 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
|
36 | downstream project. Catching problems early makes everyone more efficient.
|
37 | Having a central repository forces library owners pay attention to the source code
|
38 | and PRs that consume their APIs; no more "out of sight, out of mind."
|
39 |
|
40 | There is just one problem... Builds are slowwwww. If "npm install" takes
|
41 | 1 minute (on a good day), then 30 installs take 30 minutes. Building 30 small projects
|
42 | is 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 |
|
47 | NPMX formalizes this model and makes it quick. It works completely within
|
48 | the conventional NPM system: Each package will still have its own Gulpfile.
|
49 | Each package can still run "npm install" without NPMX if desired.
|
50 | You are always free to move your projects around between Git repositories
|
51 | without any changes to package.json.
|
52 |
|
53 | But 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 |
|
84 | 1. 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 |
|
87 | 2. 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 |
|
93 | 3. 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 |
|
104 | 4. 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 |
|
112 | The above steps are only necessary when you need to do a clean full build (e.g.
|
113 | after pulling changes that affected common/package.json). Otherwise, you can
|
114 | run "gulp" in individual project folders as usual. Your daily workflow will
|
115 | look like this:
|
116 |
|
117 | 1. Pull the latest changes from git.
|
118 |
|
119 | 2. 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 |
|
128 | 3. 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 |
|
136 | If you need to add new dependencies to your package.json, you will need to
|
137 | regenerate the files in the common folder. Use these commands:
|
138 |
|
139 | > C:\MyRepo> **npmx generate**
|
140 | >
|
141 | > C:\MyRepo> **npmx link**
|
142 |
|
143 | This will change various generated files in common folder. You shuld include these
|
144 | changes in your Pull Request.
|
145 |
|
146 | The "**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**"
|
148 | before submitting your PR.
|
149 |
|
150 | ## Publishing your NPM packages
|
151 |
|
152 | To publish all NPM projects in your repository, run "**npmx publish**". You
|
153 | can select a subset of projects using the "**--include**" option followed by
|
154 | a [glob](https://en.wikipedia.org/wiki/Glob_(programming)) pattern. You can
|
155 | use the "**--registry**" option to specify a custom NPM registry, e.g. if you
|
156 | are testing with [Verdaccio](https://github.com/verdaccio/verdaccio).
|
157 |
|
158 | ## Converting a repo to build with NPMX
|
159 |
|
160 | Currently you need to manually create an "npmx.json" configuration file
|
161 | at the root of your repository, which specifies the list of projects
|
162 | to be built. You also need to set up your "common" folder, and add the
|
163 | appropriate files to Git. (We are working on an "npmx init" command to
|
164 | simplify this.)
|
165 |
|
166 | ## Detecting when new NPM dependencies are introduced
|
167 |
|
168 | Suppose that your NPMX repo has 30 different projects, and you want to keep track of
|
169 | what NPM packages people are using. When someone finds a new package and tries to add
|
170 | it 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
|
172 | allowed?" "How many other dependencies will this pull into our node_modules folder?"
|
173 | NPMX can alert you when this happens.
|
174 |
|
175 | In your **npmx.json** file, add these optional fields:
|
176 |
|
177 | ```json
|
178 | "reviewCategories": [ "published", "internal", "experiment" ],
|
179 | "packageReviewFile": "common/PackageDependencies.json",
|
180 | ```
|
181 |
|
182 | In this example, we defined three kinds of projects that we care about:
|
183 | Projects that we publish externally, projects kept internal to our company,
|
184 | and throwaway experiments. For each project in the repo, we will assign one
|
185 | of these categories as the "reviewCategory" field.
|
186 |
|
187 | The **PackageDependencies.json** file contains the list of approved packages.
|
188 | This 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 |
|
211 | Above, we specified that only our internal projects and experiments are allowed
|
212 | to use "lodash", whereas "gulp" is allowed everywhere. The "some-crazy-tool" library
|
213 | is being used by an experimental prototype, but should never be used in real projects.
|
214 |
|
215 | Note that NPMX distinguishes "**browserPackages**" from "**nonBrowserPackages**",
|
216 | since the approval criteria is generally different for these environments.
|
217 |
|
218 | Now, suppose someone changes their package.json to add "lodash" to a project that
|
219 | was designated as "published". When they run "npmx generate", NPMX will automatically
|
220 | rewrite the **PackageDependencies.json** file, appending "published" to the
|
221 | "allowedCategories" for "lodash". In other words, it automatically broadens the rules
|
222 | so that they describe reality. When the pull request is created, reviewers will spot this diff
|
223 | and can ask appropriate questions. Since our criteria is based on generalized categories,
|
224 | the reviewers aren't hassled about every little package.json change; a **PackageDependencies.json**
|
225 | diff only appears for genuinely interesting changes.
|