preface

Since V7, NPM has introduced a very powerful feature called Workspaces. In addition, YARN and PNPM also have workspaces capabilities. However, in terms of usage, they are almost identical. So, if you learn NPM workspaces, you automatically learn YARN and PNPM.

An overview of

This paper will be introduced in four parts:

  1. What are workspaces;
  2. Multi-packet management;
  3. Multi-project management;
  4. Avoid pit;
  5. Conclusion;

What are workspaces?

As the name implies, workspaces are the concept of multi-spaces, which can be understood as multi-packages in NPM. It was originally intended for multi-package management, which makes it very easy to develop and manage multiple NPM packages in the same project:

  • It will promote all dependent packages in subpackages to the root directory for installation, improving the speed of package installation.
  • It initializes and automatically associates dependencies between subpackages (soft links);
  • Because of the same project, you can have sub-packages share processes such as ESLint, stylelint, Git hooks, Publish Flow, etc.

This design pattern originally came from Lerna, but Lerna is more capable of multi-package management, and the latest version of Lerna is fully compatible with NPM or YARN workspaces patterns. However, since this article is about Workspaces, those who are interested in Lerna can go to Lerna’s official website.

More package management

Multiple package management has been mentioned above for its advantages over single package management. So, we used examples to give students a taste of why WORKspaces are so highly touted by me.

Example demonstrates

The project address is posted on Github. If you are interested, you can check the source code.

1. Upgrade NPM to 7 or the latest version

npm i -g npm@latest
Copy the code

2. Create projects

mkdir demo-workspaces-multi-packages
Copy the code

3. Initialize the project

NPM init -y. ├ ─ package.jsonCopy the code

4. Declare that this project is a workspaces model

Package. json new configuration:

"private":"true"."workspaces": [
  "packages/*"].Copy the code

Packages /* here means that our subpackages are under packages folder. (The details and more usage of workspaces are not covered in this article, the documentation is very clear, this article focuses on practical use.)

5. Initialize the subpackagem1

Create subpackage m1:

├─ ├─ garbage, ├─ garbage, ├─ garbage, ├─ garbageCopy the code

Create main file index.js for m1:

Echo "exports. Name = 'Kitty" > > packages/m1 / index, js. ├ ─ ─ package. The json └ ─ ─ packages └ ─ ─ m1 ├ ─ ─ index. The js └ ─ ─ package.jsonCopy the code

6. Initialize the subpackagem2

Create subpackage m2 in the same way:

NPM init packages - w/m2 - y. ├ ─ ─ package. The json └ ─ ─ packages ├ ─ ─ m1 │ ├ ─ ─ index. The js │ └ ─ ─ package. The json └ ─ ─ m2 └ ─ ─ package.jsonCopy the code

Create m2 main file index.js:

├─ package.json ├─ echo "const {name} = require('m1')\nexports.name = name" >> ├─ package. ├── ├─ M1-0├ ─ ├─ ├─ ├.txt ├─ ├.txt ├─ ├.txtCopy the code

Since this requires (‘m1’), we need to add m1 dependency to m2 package.json:

npm i -S m1 --workspace=m2
Copy the code

7. Initialize subpackagesdemo

In order to see the effect, create a demo folder (multi-package management recommended to make a demo sub-package to test the overall effect) :

Echo "const {name} = require('m2')\nconsole.log(name)" >> Packages/index.js.├ ─ Package. The json └ ─ ─ packages ├ ─ ─ demo │ ├ ─ ─ index. The js │ └ ─ ─ package. The json ├ ─ ─ m1 │ ├ ─ ─ index. The js │ └ ─ ─ package. The json └ ─ ─ m2 ├ ─ ─ Index. Js └ ─ ─ package. JsonCopy the code

As an added bonus, the demo package is not distributed as it is. In order to prevent accidental distribution, we add the following to demo package.json:

"private":"true".Copy the code

Since this requires (‘m2’), we need to add the m2 dependency to demo package.json:

npm i -S m2 --workspace=demo
Copy the code

Let’s look at the project root directorynode_modules:Isn’t that interesting? It’s all soft links, links that point topackagesEach sub-package under the folder.

OK, after a long time, let’s run the demo to see the effect:

node packages/demo/index.js
#Output:
kitty
Copy the code

As you can see from the above example, WorkSpaces handles dependencies between local subpackages very nicely and makes it much more convenient for developers, especially when developing with multiple people. After the other person pulls the project, just run NPM Install to start the development, and the soft link will be set up automatically.

Next, let’s look at the WorkSpaces project when three-party packages are installed.

8. Install two packages of different versions

npm i -S vue@2 --workspace=m1
npm i -S vue@3 --workspace=m2
Copy the code

In this example, we want to see how it handles different versions of vue since our packages are all promoted to the root directory for installation. Will only vuE3 packages be installed?

Results:That way, we don’t have to worry about version conflicts,workspacesWell, apparently it worked out pretty well.

The key parameters--workspace

In the Workspaces project, a key parameter is –workspace. As you can see from the previous installation package to subpackage command, NPM i-s package name or NPM i-d package name is used. The only difference is that –workspace is added at the end.

Is this the same for other commands, such as run, version, publish, etc? The answer: Yes!

Alternatively, if all of our subpackages have a scprits command called test in package.json and we want to run the command for all of our subpackages at once, we can use NPM run test –workspaces. This is very convenient for our Lint verification or single test.

This concludes the role of Workspaces in multi-package management. It is worth mentioning that for multi-package management, Lerna is still recommended to be used in actual projects. It has a very mature process mechanism for version dependent automatic upgrade, package sending prompt, automatic Log generation (Change Log/Release Note), CI and so on.

Multi-project management

The current PMWork Spaces, I think, are very suitable for multi-purpose integration (Monorepo) management.

Example demonstrates

The project address is posted on Github. If you are interested, you can check the source code.

1. Create projects

mkdir demo-workspaces-multi-project
Copy the code

2. Initialize the project

NPM init -y. ├ ─ package.jsonCopy the code

3. Declare that this project is a workspaces model

Package. json new configuration:

"private":"true"."workspaces": [
  "projects/*"].Copy the code

4. Initialize the subprojectzoo

Create subproject zoo:

├─ ├─ garbage ├─ garbage ├─ garbage ├─ garbage ├─ garbage ├─ garbage ├─ garbageCopy the code

Create a template file named index.html with the following contents:

<! -- projects/zoo/index.html -->
<body>
  <h1>Welcome to Zoo!</h1>
  <div id="app"></div>
</body>
Copy the code

Create the project entry js file index.js with the following contents:

console.log('Zoo')
Copy the code

Install project build dependencies:

npm i -S webpack webpack-cli webpack-dev-server html-webpack-plugin webpack-merge --workspace=zoo

# projects/zoo/package.json"Private" : "true", "dependencies" : {" HTML - webpack - plugin ":" ^ 5.5.0 ", "webpack" : "^ 5.65.0", "webpack - cli" : "^ 4.9.1 webpack - dev -", "server" : "^ 4.7.2"}Copy the code

Create a WebPack configuration:

// projects/zoo/webpack/base.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')

function resolve(dir) {
  return path.join(__dirname, '.. / ' + dir)
}

exports.config = {
  entry: resolve('src/index.js'),

  plugins: [
    new HtmlWebpackPlugin({
      title: 'Zoo'.filename: 'index.html'.template: resolve('src/index.html'})})],exports.resolve = resolve
Copy the code
// projects/zoo/webpack/dev.config.js
const { config, resolve } = require('./base.config')
const { merge } = require('webpack-merge')

exports.default = merge(config, {
  mode: 'development'.output: {
    filename: 'bundle.js',}})Copy the code
// projects/zoo/webpack/prod.config.js
const { config, resolve } = require('./base.config')
const { merge } = require('webpack-merge')

exports.default = merge(config, {
  mode: 'production'.output: {
    filename: 'bundle.js',}})Copy the code

Zoo package.json add command:

"scripts": {
  "dev": "webpack-dev-server --config webpack/dev.config.js --open"."prod": "webpack --config webpack/prod.config.js"
},
Copy the code

Now it’s ready to run, just use it in the project root directory:

npm run dev --workspace=zoo
Copy the code

Can be developed locally.

Effect:

The same is true for prod.

5. Initialize subprojectsshop

Create sub project shop:

npm init -w projects/shop -y
Copy the code

The remaining steps are almost identical to initializing the child project zoo, so I won’t repeat them.

Final directory structure:

Shared

For Monorepo, sharing is one of the most important advantages. So, let’s do something shared.

1. Create a vm in the root directoryshareFolder, as a shared resource directory, and create shared filesFish.js:

mkdir share
mkdir share/js
touch share/js/Fish.js
Copy the code
// share/js/Fish.js
class Fish {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  swim() {
    console.log('swim~')}print() {
    return '🐟'}}module.exports = Fish
Copy the code

2. In the sub-projectwebpackConfiguration of newalias

Add the same alias to the zoo and shop subprojects:

resolve: {
  extensions: ['.js'].alias: {
    '$share': resolve('.. /.. /share'),}},Copy the code

Subproject zoo entry file changed to:

// projects/zoo/src/index.js
const Fish = require('$share/js/Fish')
const fish = new Fish()
document.getElementById('app').textContent = fish.print()
Copy the code

runzoothedevSee the effect:

After modifying the entry file for the subproject shop, the same effect will appear.

In the share folder, zoo and shop can be shared. All you need to do is add a webpack alias. 🎉

🤔 Think – Why do we useworkspacesCan’t we do collective projects the old-fashioned way?

Traditional way:

  1. The subprojects are grouped into one project. And unlike the one above,package.jsonThere is only one copy, in the root directory, where all the NPM packages in the project are installed, in the root directorypackage.jsonDefined in theThe development ofandThe deployment ofSubproject commands;
  2. The subprojects are grouped into one project. Unlike above, although there is a copy of the root directory and each subpackagepackage.json, but the basic build tools are installed in the root directory, as mentioned abovewebpack,webpack-cli,webpack-dev-server,html-webpack-plugin,webpack-merge, all installed in the root directory, and business-related NPM packages are installed in their respective sub-projects;
  3. The subprojects are grouped into one project. Different from above, each sub-package has its own copypackage.json, root directory Nonepackage.json;

Method 1 — Disadvantages:

  • Confusion of command;
  • Unable to handle NPM package conflicts between subprojects; (For example, project A wants to use WebPack4 and project B wants to use WebPack5; Or project A wants to use Vue2 and project B wants to use Vue3.)

Method 2 — Disadvantages:

  • If the subproject has the same package, it has to be installed repeatedly in each subproject;
  • It is also unable to deal with the problem of NPM package conflicts between sub-projects; (for example, project A wants to use webpack4 and project B wants to use webpack5)
  • If one day we want to remove project B, the cost is very high;

Method 3 — Disadvantages:

  • If the subproject has the same package, it has to be installed repeatedly in each subproject;

Using workspaces solves all of these problems perfectly!

In addition, for the existing project, I take over this project, for example, a Web, a Wap, and found that, because they belong to the same business, so there are a lot of code can be reused, and because it involves the two projects, make public code a NPM package and a little too kill on the wheel, so, The copy and paste model has been used in the past. This is obviously very inefficient. The mock service is also a separate set of word items, but the data for most interfaces is common except for the URL prefix. The most ridiculous thing is that hundreds of bank ICONS are identical. So, I’m going to combine them into one project. For me, Workspaces is a solution that minimizes the transformation of the original project.

How do you deploy it separately?

We want to deploy only project zoo on the build machine. What should we do?

1. Install dependency packages

npm install --production --workspace=zoo 
Copy the code

In this case, only the dependencies under the Zoo project will be installed on the builder.

2. Build

npm run prod --workspace=zoo 
Copy the code

In this case, the build is successful!

From the pit

NPM’s workspaces actually have hidden pits, so I’ll list them as well.

Pit 1: NPM install default mode pit

Starting with NPM V7, install installs the package declared in peerDependencies by default. The new project may have little impact, however, if you are retrofitting an existing project. Because of the unified management method, it is common to delete the lock file in the sub-project and use the unified lock management in the root directory. And then, when you do that, things can go wrong. Scenario: MY subproject uses WebPack4, and then all of our build-related tools (WebPack, Babel, PostCSS, ESLint, StyLint, etc.) are wrapped into the base package. One of the dependencies of these packages is used in the package.json declaration like this:

"peerDependencies": {
  "webpack": "^ 5.1.0"
},
Copy the code

Then, in the root directory, NPM install, and then run the subproject and find that the project can’t run. The reason is that the project installs the webpack5 version!

The solution

  • Option 1: in the subprojectpackage.jsonIs used to display the declarationwebpackVersion;
  • Solution 2: Go to Github and discuss with the author to fix the dependency package if his package is compatiblewebpack4Also compatible withwebpack5The statement should be changed to:"Webpack" : "^ 4.0.0 | | ^ 5.0.0"
  • Solution 3:npm install --legacy-peer-deps

Personally I really think this is the NPM author kicked in the head by a donkey. For YARN or PNPM, their workspaces do not use the default mode of peerDependencies. The author originally thought that if the NPM package developer declared peerDependencies, the project would not run if we did not install a matching version of the package during the use. For convenience, he used the default installation mode. However, this approach can lead to packages that do not conform to the written specification for peerDependencies, which can be used in conjunction with projects. Moreover, even if the authors of new packages start to pay attention to the writing specification, they can’t deal with the old packages that have been released. They can’t recycle them all and release them all over again.

Pit 2: minor version package conflicts

This is actually caused by personal carelessness.

For example, zoo uses the command NPM [email protected] to import vUE, and shop uses the command NPM [email protected] to import VUE. So, will the project have two versions of VUE? Don’t. For this reason, we can look at package.json under the zoo project:

"dependencies": {
  "html-webpack-plugin": "^ 5.5.0"."vue": "^ 2.2.1." "."webpack": "^ 5.65.0"."webpack-cli": "^ 4.9.1." "."webpack-dev-server": "^ 4.7.2." "."webpack-merge": "^ 5.8.0"
}
Copy the code

The penny dropped.

The solution

  • Plan 1: Actually remove^Can;
  • Option 2: We can use it when we installNPM I --save-exact [email protected] --workspace=zoo

conclusion

This article, using WorkSpaces for multi-package management, as well as multi-project management, demonstrates the power of Workspaces. Since the projects I personally manage have always been managed using NPM, there are unknown risks in trying to migrate to YARN or PNPM, and also, there have been attempts as some of the older packages yarN2 and PNPM do not work. For new projects, I would also recommend YARN2 or PNPM for management, which are more powerful than NPM.

This article comes from personal github blog, I think good friends can click a “like” ~ <( ̄▽ ̄)/

The source of multi-package management and multi-project management in the article is:

  • More package management
  • Multi-project management

Interested students can download and study by themselves.