directory

  1. Project introduction
  2. Source code analysis
  3. implement
  4. conclusion

1. Project Introduction

Update-notifier is the yeoman team’s product. The reason for choosing this project is mainly recommended by the big man. Second, the project is only 15.3KB in size and features are not complicated.

Update-notifier Checks whether the current version of the package is the latest version. If not, an update message is displayed.

This project is usually used in scaffolding applications. You can see which projects use update-notifier on the NPM home page:

I checked, in fact, NPM only used update-notifier in the early version, but it is no longer used in the latest version. I guess the reason may be that there are still many problems in this project, I don’t know whether the team has decided to give up or something, but there are still about 20 issues open now. The last update was eight months ago.

It doesn’t matter, anyway we want to see its source is, just can’t absorb blindly, also want to distinguish among them already outdated thing, dialectically see.

Don’t say much, open source.

2. Source code analysis

2.1 package. Json

Package. json contains a lot of information about packages. A typical NPM project can start with this file directly.

Update-notifier package.json focuses on the following information:

  • Files: Optional configuration item, an array of files that specifies which files or folders to include when the project is installed as a dependency package
  • Dependencies: Dependencies package
  • DevDependencies: Dependencies of the development environment

files

  • Index.js: default entry file
  • check.js

dependencies

  • Boxen: a tool that prints boxes on the console
  • Chalk: Adds color to the console font
  • Configstore: a configuration loading tool that generates json files in the user configuration directory and stores them in $XDG_CONFIG_HOME or ~/.config. For example, C:\Users\ username \.config\ configStore \
  • Has-yarn: Checks whether the installation tool YARN exists
  • Import-lazy: lazily loads imported dependency packages
  • Is-ci: Checks whether the current environment is a CI server (Continuous integration server)
  • Is-installed-globally: checks whether dependency packages are globally installed by NPM
  • Is-npm: checks whether it is run as a script command of NPM or YARN
  • Is-yarn-global: checks whether the package is installed globally through YARN
  • Latest-version: obtains the latest version of a dependent package
  • Pupa: template string tool
  • Semver: Semantic versioning tool
  • Semver-diff: version comparison tool
  • Xdg-basedir: a tool for obtaining the basic directory path of XDG on Linux

devDependencies

  • Ava: Testing tool
  • Clear-module: clears the module cache
  • Fixture -stdout: intercepts input from the console
  • Deprecated: mock-require introduces the Node. js module
  • Strip-ansi: Removes ASCII escape characters from strings
  • Xo: EsLint-based tool for enforcing code format specifications

2.2 document

Let’s take a look at the apis in the update-notifier document:

  • Notifier = updateNotifier(options), instantiate the object
    • Options: indicates configuration options
      • PKG: indicates the packet information
        • Name: the package name
          • Version: indicates the package version
      • UpdateCheckInterval: indicates the update interval
      • ShouldNotifyInNpmScript: allow notification while the script is running
      • DistTag: Defines which version the latest version points to. Default is ‘latest’
  • Notifier. FetchInfo () : indicates the method to obtain the latest version, current version, current package type, and package name
  • notifier.notify(options?) : Output the update prompt method,
    • Options: Optional configuration object
      • Defer: When true, it will wait until the process exits
      • Message: information about the update prompt. The fields include packageName, currentVersion, latestVersion, and updateCommand(for example, NPM or YARN).
      • IsGlobal: indicates whether to use the command when prompted-gParameter NPM global installation (isYarnGlobal is omitted in the document, indicating that YARN is installed globally)
      • BoxenOptions: Indicates the border style of the text
  • –no-update-notifier: node runtime parameter. This parameter does not prompt for updates
  • NODE_ENV: Sets process.env.node_env totestWill not prompt for updates
  • No update will be prompted when the current environment is a CI server.

2.3 case example

NPM I/YARN install the dependency package. There is an example.js file in the source file.

Nothing happened.

After a lonely run, I looked at the comment in example.js and saw this:

// You have to run this file two times the first time
// This is because it never reports updates on the first run
// If you want to test your own usage, ensure you set an older version
Copy the code

Translation:

You have to run it twice, because the first time you run it you’re not going to see any updates;

If you want to test your own package, make sure your package version number is not up to date so that you can see the console print out updates.

Early say!

One, two, three, four, one more time:

Well, there it is.

Why is that?

Write this down in a notebook until we analyze the source code.

You can see that the instantiation method is called after the updateNotifier is introduced in example.js. Here we check a package called public-ip. The current version is 0.9.2 and we are prompted to update it to 4.0.4.

We can also check other packages, such as changing the value of name to vue, and two more runs will prompt us to update the Vue version:

2.4 the index. Js

If the main property is not configured in package.json, index.js is the entry file for the project by default. So open index.js:

Module. exports exports the UpdateNotifier class and instantiation methods.

Take a look at what’s in the UpdateNotifier class:

  • Constructor: a constructor
  • Check: Checks whether an update is required
  • FetchInfo: Asynchronous method to get information about the latest version
  • Notify: The method of output notification on the console

Here is only a rough look at the following method name, input parameter, output parameter, combined with the documentation, half look and half guess the function of each method, not necessarily correct;

It doesn’t matter if you’re right, we’re going to look at the implementation details of each of these methods, and if you’re wrong, you can come back and fix it at the end.

In the previous section, we used the updateNotifier instantiation method as an example, so let’s see what the instantiation method does:

module.exports = options= > {
  const updateNotifier = new UpdateNotifier(options);
  updateNotifier.check();
  return updateNotifier;
};
Copy the code

These three lines of code sum up to:

New an UpdateNotifier object, call the check method, and return the instantiated object.

Here, break it down sentence by sentence.

2.4.1 Constructor

What happens when you new an object?

  1. Create a new empty JS object (i.e{});
  2. Then add to the empty object __proto__Property, and link it to the constructor’s prototype object.
const B=new A();
B.__prototype__===A.prototype;// true`
Copy the code
  1. The constructor’s this scope points to the object;
  2. Finally, we execute the constructor function, which returns the newly generated object if the constructor returns no value; Returns the return value of the constructor if it has a return value.

Okay, so when we call the instantiation method, the constructor is executed.

The update-notifier constructor does the following:

  • Save the name and version in the input parameter PKG object tothis.options;
  • Create the ConfigStore instance objectthis.configAnd save the current update time;

Configstore generates an update-notifier-[package name]. Json file in the user configuration path ~/. Config/configStore /. This file reads as follows:

  • If you manually change the value of optOut to true, the program will no longer prompt for updates;
  • LastUpdateCheck is the timestamp of the last update;
  • The update object is version update information.

In addition, of course, the constructor also does:

  • Validation of necessary parameters, such as name and versionNot empty check;
  • Optional configuration itemInitialize the default valuesFor example, the default value of updateCheckInterval is 1 day
  • Check the environmentCheck whether the current NODE_ENV is test, whether the run-time parameter is –no-update-notifier, and whether it is a CI server. If one of the values is yes, no update is prompted.

2.4.2 the check

Determine whether the configuration item needs to be updated according to the parameters in the configuration item. If the configuration item needs to be updated, proceed as follows:

this.update = this.config.get('update');
Copy the code

If there is an update object in the json file, delete the update object from the json file and start a child process to execute the script in the check.js file:

// Start the child process to execute check.js
spawn(process.execPath, [path.join(__dirname, 'check.js'), JSON.stringify(this.options)], {
      detached: true.stdio: 'ignore'
    }).unref(); // unref allows the parent process to exit without waiting for the child process to exit
Copy the code

Check. js does only one thing:

Call the fetchInfo() asynchronous method to update the update object in the local JSON file if the current version is not the latest:

const update = await updateNotifier.fetchInfo();

if(update.type && update.type ! = ='latest') {
    updateNotifier.config.set('update', update);
}
Copy the code

🍉 Note that fetchInfo is asynchronous, so there is no way to update the update object in the JSON file immediately after executing check. This results in the notify() method in example.js not fetching the update object for the first time (theoretically, as long as your hand is fast enough, Not just the first time, but until the fetchInfo callback).

After checking (), let’s look at what notify() does.

2.4.3 notify

const suppressForNpm = !this.shouldNotifyInNpmScript && isNpm().isNpmOrYarn;
    if(! process.stdout.isTTY || suppressForNpm || !this.update || ! semver().gt(this.update.latest, this.update.current)) {
    return this;
}
Copy the code

The if judgment at the beginning determines whether to prompt for an update. No prompt is printed in any of the following cases:

  1. ! Process.stdout. isTTY: No output terminal
  2. SuppressForNpm: If it is run as NPM or YARN script but disallow prompt is configured
  3. ! This. update: No update object was retrieved
  4. ! Gt (this.update.latest, this.update.current) : current version number >= latest version number

See the problem? If there is no update object in the local JSON file, the this.update in the check method from the previous step will not get the value and will not be prompted for updates.

Also, the update prompt here is actually the result of the last request, not the actual latest version.

For example, if you manually change the latest value in the JSON file to 6.6.666 and execute node example, you will see that 6.6.666 is printed:

Now you see why there was nothing on the first run?

The update object in the json file does not have a value, so check() does not have a value of this.update. Notify () does not have a value of this.update. So I just return this.

I added a note to the source code, welcome erratum, for reference only: github.com/youzouzou/u…

3, hands-on implementation

The update- Notifier process is as follows:

Summary of core functions:

Pull the latest version information and save it to a local JSON file, so that the information can be retrieved in the next run and compared with the current version. If the current version number is less than the latest version, output a prompt message on the console.

There are actually three packages for the core functions: Last-version, Semver, and ConfigStore. The rest are toolkits for styling or judging environments.

Mimicking this core idea, I wrote a stripped-down version that only implemented the core features:

withlast-versionAsynchronously pull the latest version information, used in the callback functionsemverCompares whether the current version is less than the latest version, and if so, prints the update prompt directly on the console.

Instead of using ConfigStore to store the results of the last request, or to open a child process, I wait for the callback to end and then print the update directly, so every run pulls the actual latest version.

The implementation code is as follows:

const latestVersion = require('latest-version'); // To get the latest version of the NPM package
const semver = require('semver'); // Semantic versioning tools

class MyUpdateNotifier {
  constructor(options) {
    this.check(options)
  }
  async check(options) {
    const { name, version } = options;

    if(! name) {console.error("Package name not obtained");
      return;
    }
    if(! version) {console.error("Current version not available");
      return;
    }

    const latest = await latestVersion(name); // Obtain the latest version number
    console.log(name + "Latest version number", latest)
    if (semver.gt(latest, version)) { // Whether the latest version is greater than the current version
      console.log("Please" + name + "From" + version + "Updated to the latest" + latest)
    }
  }
}

module.exports = MyUpdateNotifier;
Copy the code

Test it out:

Success! ✅

Demo address: github.com/youzouzou/m…

The update-notifier update is not the latest, but it does not block the parent process, making it more slippery to use.

4, summarize

Before although also read some source code, but did not finish reading, did not write analysis notes, this is the first time I read a project’s source code.

The update- Notifier project is well documented, the code is logical, and the difficulty is not difficult. It is a good reading experience for beginners.


The articles

What’s it like to go from front to back

When a programmer meets a product manager who can write code……

Hand write a Webpack Plugin

Hand-write a Webpack loader

This pot I carry……

ES2021 new features

Beat Magic with Magic: front-end code normalization

Hand to hand to teach you to build scaffolding

Build NPM private library by hand

requestAnimationFrame