Berry(yarn v2) + TypeScript + PnP + Workspace でプロジェクトを作ってみた感想

berry(yarn v2) がそろそろリリースということで、使い込んでみた。その感想や yak-shaving などについて。

このリポジトリ https://github.com/mizchi/berry-typescript-project

日本語での網羅的な解説はこちらの記事がくわしい https://qiita.com/dojineko/items/6f65fde3c47aed8b6318

本記事は pnp の仕組みと webpack, jest, typescript を設定する泥臭い話がメイン。

## 使ってみた感想

- npm とは完全に別系統に進化しつつある。互換があんまりない。
- **今対応するのは時期尚早でアーリーアダプターだけでよい**
- berry 自体が typescript で書かれているので、typescript 連携は揃っている。ただし @arcanis 氏の手が届く範囲で。
- pnp 対応でエコシステム全部見直す必要があり大変
- workspaces 対応は素晴らしいので使っていきたい

## pnp とは

https://classic.yarnpkg.com/en/docs/pnp/

`node_modules` にすべてのファイルを展開するのではなく、 `.yarn/cache` に zip ファイルを置く。なんやかんやあって hot cache で最大70% 速くなると↑ の記事には書いてある。その結果、 node_modules 内のパスで色々やる系のライブラリは色々と変更を迫られている。この記事もほぼ pnp 対応に費やされた。

大胆な変更だが、 npm 側にも tink というプロジェクトがあり、ほぼ同じような設計なので、npm でも似たような変更が予定されていた。ので、これ自体は yarn が勝手にやってるという感じでもない。

https://github.com/npm/tink

## 初期化

```bash
mkdir newprj
cd newprj
yarn set berry
yarn init
```

## typescript + pnpify

vscode はデフォルトでは `node_modules/@types/` の型を探索してしまうため、型定義ファイルが見つからない。これを pnpify で解決する。

```
yarn add @yarnpkg/pnpify
yarn pnpify --sdk # .vscode を書き換えられるので、コミットする必要がある
```

vscode で ts 拡張子のファイルを開き、右下の TypeScript のバージョン選択から、　 `Use workspace version: 3.7.5-pnpify` を選択

![](https://i.gyazo.com/36f65c4f30afb93169ebae9d002fb94b.gif)

これで VSCode 内で実行可能になった。

![](https://gyazo.com/7298cc459dbfb69c2e257793579b6fe8.png)

参考: https://yarnpkg.com/advanced/editor-sdks

## jest

jest-pnp-resolver を使う

yarn add jest-pnp-resolver -D

```ts
module.exports = {
  resolver: require.resolve("jest-pnp-resolver"),
  transform: {
    "^.+\\.tsx?$": "ts-jest"
  },
  testRegex: "(/__tests__/.*)\\.test\\.tsx?$",
  moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node", "wasm"]
};
```

## pnp + webpack

pnp-webpack-plugin を使えば webpack も使える。

```js
// webpack.config.js
const path = require("path");
const PnpWebpackPlugin = require(`pnp-webpack-plugin`);
module.exports = {
  resolve: {
    extensions: [".js", "json", ".ts", ".tsx", ".wasm"],
    plugins: [PnpWebpackPlugin]
  },
  resolveLoader: {
    plugins: [PnpWebpackPlugin.moduleLoader(module)]
  },
  entry: {
    main: path.join(__dirname, "packages/webapp/src/index")
  },
  output: {
    filename: "[name].js",
    path: path.join(__dirname, "dist")
  },
  module: {
    rules: [
      {
        test: /\.tsx?/,
        use: [
          {
            loader: "ts-loader",
            options: {
              transpileOnly: true
            }
          }
        ]
      }
    ]
  }
};
```

しかし一つ問題が発生した。`packages/webapp` を掘って、その下で `yarn add webpack webpack-cli -D` を行ったが、このディレクトリからの `yarn webpack` 実行時には `webpack-cli` が見つからず、 webpack の実行ができなかった。

おとなしくルートディレクトリにインストールした。

## .gitignore

ゼロインストールが目玉機能だが、普通のプロジェクトでやると git object が現実的なサイズに収まる気がしないので、とりあえずゼロインストールじゃない版の .gitignore を採用

https://github.com/yarnpkg/berry/issues/454#issuecomment-530312089

```
.yarn/*
!.yarn/releases
!.yarn/plugins
.pnp.*
dist
node_modules
```

node_modules は発生しない…はずなのだが、webpack を使うと `terser-webpack-plugin` が `node_modules/.cache` を生成していた。これは単に無視していいと思う

## workspaces

これに関しては yarn v1 よりはるかに素晴らしい。yarn v1 の workspaces はどの位置で yarn install を発行したかで頻繁に整合性が崩れていたが、 node_modules が生えずにルートディレクトリの`.yarn/cache` にちゃんとエイリアスが集約された。

```
packages/
  foo
  bar
  nested/
    nested-a
    package.json # "workspaces": ["packages/*"]
package.json # "workspaces": ["packages/*"]
```

この状態で foo から nested-a が import できた。（もとからできていたが、挙動が怪しかった）

## ライブラリ作者の考えること

相対パスから `node_modules` を探索する系の処理を自前で書いてしまうと、基本的に berry では動かなくなる。

## いつ使うか

現状、手数が多すぎる。それもほとんどが pnp 周りで発生したもので、エコシステムがどれぐらい yarn v2 に配慮するか、というところに、使い物になるかの分岐点がありそう。yarn v2 側が折れて node_modules のフォールバックをすることも考えられうる。

余談だが、 npm 社は経営不安で大規模なレイオフを敢行するなどしており、tink は開発が止まっている。もはや npm tink を実装するエンジニアリソースがないのではないかと、個人的に疑っている。github package registry の一般公開も済んだので、個人的には npm 社のインフラやリソースに依存したくない気持ちがある。npm/npm の issue も放置されがち。

経営不安な運営元のソフトウェアは、例えばこういうことが起きるので…

[Opera Softwareが最大876％の暴利ローンアプリで不正に荒稼ぎしてたことが判明、摘発されればOperaブラウザ終了の可能性も \- GIGAZINE](https://gigazine.net/news/20200120-opera-loan-app/)