中華製npmを知ってますか？！ cnpmを紹介（ネタ）


Node.jsアドベントカレンダー21日目になります。
npmといえば、Node.jsやフロントエンド開発で欠かせないツールですよね。そんな **npmの中国版がある**ことをご存知でしょうか。

仕事柄、中国に行ったり、中国にいる人とやりとりしたりすることがありますが、その辺に関連した話です。実用性はほぼ無いので話のネタだと思ってもらえると幸いです。(アドカレ遅刻すみません)

> ![スクリーンショット 2020-12-22 11.54.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/dfd62619-9ec8-5a9b-523c-07fef82f6904.png "スクリーンショット 2020-12-22 11.54.59.png")
> [IoTLT vol70](https://iotlt.connpass.com/event/196360)で話をしたネタです。 スライドは[こちら](https://speakerdeck.com/n0bisuke2/zhong-hua-zhi-npmfalsecnpmgaarumo-yang-number-iotlt)

npmが何かについては触れませんのでご了承下さい。

## cnpm - 中国ミラー版のnpm

https://cnpmjs.org

> ![](https://i.gyazo.com/a392a76fd3d4dc5c2eeb068674d43813.png)

> For developers in China, please visit the China mirror.

中国の開発者向けに作られているnpmのミラーサイト及びツールです。

* https://github.com/cnpm/cnpm
* https://developer.aliyun.com/mirror/npm/package/romtool
* https://npm.taobao.org

### そもそもなぜcnpmが必要なのか - グレートファイヤーウォール問題

中国では[グレートファイアウォール(金盾)](https://ja.wikipedia.org/wiki/%E9%87%91%E7%9B%BE#:~:text=%E9%87%91%E7%9B%BE%EF%BC%88%E3%81%8D%E3%82%93%E3%81%98%E3%82%85%E3%82%93%E3%80%81%E4%B8%AD%E5%9B%BD%E8%AA%9E,%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E6%A7%8B%E7%AF%89%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%A7%E3%81%82%E3%82%8B%E3%80%82)というネット検閲の仕組みがあってGoogleやTwitterなどのサービスが使えないことが有名です。

試して無いですがおそらく、 **中国国内からnpmを利用しようとすると、全く利用できないか、一部のパッケージのDL・インストールが出来ない**という状況なのだと思います。

> ※中国以外の国のVPNを経由したり、香港SIMを使うなど回避方法はありますが、そういった回避方法を使わずに直で中国国内のネットワークを利用する際の話です。

見たところ、CNPMはtaobao(中国の楽天とかAmazonみたいなサイト)のドメイン下にミラーがある模様なので、中国国内のネットワークでも問題なく利用できるのでしょう。

## 怖いけどcnpmをインストールしてみた

**正直情報抜かれてそうで怖かったりもします**ので、試す場合は自己責任でお願いします。

### 実はnpm経由でインストールできる

**メタな感じがしますが、npmにcnpmがいます。**

> https://www.npmjs.com/package/cnpm

なので`npm i -g cnpm`でインストールできるのですが、どうやら`--registry`オプションでtaobaorのサーバーを指定してインストールすることでcnpmのインストールができる模様です。

```bash
$ npm i -g cnpm --registry=https://registry.npm.taobao.org
```

でcnpmをインストール可能です。

ちなみに、指定している、`https://registry.npm.taobao.org`が不安定で、アクセス出来たりできなかったりの差が激しかったです。(何の検証をしてるんだ......苦笑) コマンドとしてのcnpmを試すだけなら`--registry`オプション無しでインストールでも良いと思います。

```bash
$ npm i -g cnpm
```


###  インストールから時間かかる

僕が試した際には、 **6分強かかりました。**

> ![スクリーンショット 2020-12-22 15.05.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/b4f2e4dc-5b8e-a297-4b03-364e63d466ba.png "スクリーンショット 2020-12-22 15.05.11.png")

参考までに`npm i -g yarn`でyarnをインストールした場合は0.5秒でした。

> 参考: https://speakerdeck.com/n0bisuke2/zhong-hua-zhi-npmfalsecnpmgaarumo-yang-number-iotlt?slide=14

## cnpmを触ってみた

次は実際にcnpmのコマンドを見ていきます。

### npm initやnpm installなどは同様に使える

基本的なコマンド操作は同じです。

* パッケージインストール

`npm i`が`cnpm i`になるだけです。

```bash
$ cnpm i パッケージ名
```

* プロジェクト初期化

これも`npm init`が`cnpm init`になるだけです。

```bash
$ cnpm init -y
```

ちなみにこの際に出来上がるpackage.jsonはほぼ一緒でした。

>![スクリーンショット 2020-12-22 15.12.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/b9570c08-c2ad-b761-3bb4-58f0547e8649.png "スクリーンショット 2020-12-22 15.12.13.png")
 
### バージョンの違い、npxは使えないなど

手元の環境が

* Node.js v15.3.0
* npm v7.0.14
* cnpm v6.1.1

という状況で、若干の違いがありました。cnpmのバージョンがnpmの各バージョンの機能に追随出来てるかも不明です。

#### --saveオプション

例えば、最近のnpmはinstall時に`--save`を付けなくても、package.jsonに記載してくれますが、現状のcnpmは`--save`を付けないとダメでした。

#### cnpxは無かった

また、npxは使えるかなと思い、`cnpx ngrok http 3000`みたいな試し方をしてみましたが、npxのcnpm版はパッと見存在しなさそうでした。

### サンプルアプリケーションを作ってみる準備

実際にcnpm経由でインストールしたモジュールが問題なく使えるのかを試してみたく、mqttライブラリを使ったサンプルを作って試してみました。

```bash
$ cnpm init -y
```

```
Wrote to /Users/n0bisuke/Documents/ds/playground/test-cnpm/test2/package.json:

{
  "name": "test2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
```

初期化できました。
次にモジュールのインストールです。

### サーバーに到達できなかったり、タイムアウトなどのトラブル

```bash
$ cnpm i mqtt --save
```

ここでトラブルが発生しました。

> Get /bug-versions/latest from https://r.npm.taobao.org error: ConnectionTimeoutError: Connect timeout for 5000ms,

こんな感じのエラーでしたが単なるタイムアウトではなく、`--registry=https://registry.npm.taobao.org`を指定してcnpmのインストールしてるとnpmではなく`https://registry.npm.taobao.org`からモジュールをインストールしてこようとします。先程も書いたようにこの`https://registry.npm.taobao.org`が落ちてたり不安定だったりするとここでエラーになります。

これは大元のサーバーの挙動が怪しいという話なので、このタイムアウトの場合はもうしばらく待つしかなさそうです。

また、サーバーの調子がよく、ある程度インストールが進んだかなぁと行った段階でも

**Connect timeout for 60000ms......** 

といった **タイムアウトエラーが発生**しました。中国のサーバーに向かったり、内部でリダイレクトしたりをしてると思うので遅いのだと思うのですが、普通にタイムアウトするとは......

調べると`cnpm set timeout`でタイムアウトするまでの制限時間を伸ばすことが出来る模様です。もはやタイムアウトしてしまうのが前提なのかも

```bash
$ cnpm set timeout 120000
```

> ![スクリーンショット 2020-12-22 19.14.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/a1772dc4-08b7-674c-677b-8a2876141eee.png "スクリーンショット 2020-12-22 19.14.51.png")

こんな感じで改めてcnpm iで[mqttモジュール](https://www.npmjs.com/package/mqtt)をインストールできました。

```bash
$ cnpm i mqtt --save
⠋ [0/1] Installing concat-map@0.0.1[npminstall:get] retry GET https://r.npm.taobao.org/concat-stream after 100ms, retry left 4, error: Error [ConnectionTimeoutError]: Connect timeout for 60000ms, GET https://r.npm.taobao.org/concat-stream -2 (connected: false, keepalive socket: false, agent status: {"createSocketCount":27,"createSocketErrorCount":0,"closeSocketCount":27,"errorSocketCount":0,"timeoutSocketCount":26,"requestCount":187,"freeSockets":{},"sockets":{},"requests":{}}, socketHandledRequests: 1, socketHandledResponses: 0)
headers: {}
    at Timeout._onTimeout (/Users/n0bisuke/.nodebrew/node/v15.3.0/lib/node_modules/cnpm/node_modules/urllib/lib/urllib.js:946:15)
    at listOnTimeout (node:internal/timers:556:17)
    at processTimers (node:internal/timers:499:7) {
  requestId: 7,
  data: undefined,
  path: '/concat-stream',
  status: -2,
  headers: {},
  res: [Object]
}, status: -2, headers: {}, 
stack: ConnectionTimeoutError: Connect timeout for 60000ms, GET https://r.npm.taobao.org/concat-stream -2 (connected: false, keepalive socket: false, agent status: {"createSocketCount":27,"createSocketErrorCount":0,"closeSocketCount":27,"errorSocketCount":0,"timeoutSocketCount":26,"requestCount":187,"freeSockets":{},"sockets":{},"requests":{}}, socketHandledRequests: 1, socketHandledResponses: 0)
headers: {}
    at Timeout._onTimeout (/Users/n0bisuke/.nodebrew/node/v15.3.0/lib/node_modules/cnpm/node_modules/urllib/lib/urllib.js:946:15)
    at listOnTimeout (node:internal/timers:556:17)
    at processTimers (node:internal/timers:499:7)
✔ Installed 1 packages
✔ Linked 61 latest versions
✔ Run 0 scripts
peerDependencies WARNING mqtt@4.2.6 › ws@^7.3.1 requires a peer of bufferutil@^4.0.1 but none was installed
peerDependencies WARNING mqtt@4.2.6 › ws@^7.3.1 requires a peer of utf-8-validate@^5.0.2 but none was installed
✔ All packages installed (66 packages installed from npm registry, used 1m(network 1m), speed 10.75kB/s, json 62(114.02kB), tarball 581.6kB)
```

ふう。。。

### MQTTでPub/Subしてみる

* Pub -> cnpm経由でインストールしたmqttモジュール
* Sub -> npm経由でインストールしたmqttモジュール

という感じでcnpmとnpmでPub/Subしてみました。

先程mqttのインストールが完了したので、app.jsを書いてみます。

ここは通常と何ら変わりません。

* mqttでpublishするコード

```app.js
const mqtt = require('mqtt');
const client  = mqtt.connect('mqtt://test.mosquitto.org');
 
client.on('connect', () => {
  client.publish('n0bisuke', 'Hello cnpm');
});
```

```bash
$ node app.js
```

* mqttでsubscribeするコマンド

```bash
$ npx mqtt sub -h test.mosquitto.org -t n0bisuke 
Hello cnpm
```

こんな感じでPub/SubのMQTT通信ができています。

> ![](https://i.gyazo.com/83ddd89a205d00105f8abb631af966a6.gif)

インストール後は割と普通ですね

## cnpm限定コマンド

cnpm限定のコマンドもある模様です。

* cnpm sync -> npm側との差分を無くす
* cnpm i --by=npm -> npmからDL/インストール 

調べたらもっとあるかも

## まとめ

cnpmという中華製のnpmミラーがある！

**日本で使う意味はほぼ皆無** だと思うので、ネタ的な感じですね。

中国に行って開発することがあれば覚えておくと良いかもしれません。