Logo 空の箱
Table of Contents
index

前回までのあらすじ

前回npm workspacesに再入門したうえで、Turborepoとの違いを確認した。

今回はnpm workspacespnpm workspacesの違いを見ることにする。

特に共通で使っているモジュールをうまくバンドルしてくれるか否かに着目する。

pnpm workspacesを構築していく

pnpmでプロジェクトを作る。

Terminal window
pnpm init

package.jsonが出来上がったら、pnpm以外のパッケージコマンド(npm, yarn)を禁止する設定を入れる。package.json.npmrcファイルに以下のように記述する。

package.json
{
"name": "pnpm-workspaces",
"version": "1.0.0",
"description": "",
"main": "index.js",
"engines": {
"pnpm": "10.7.1",
"npm": "please_use_pnpm_instead"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.7.1"
}
engine-strict=true

次にworkspaceの設定をする。pnpmの場合はpnpm-workspace.yamlにワークスペースを定義する。

pnpm-workspace.yaml
packages:
- 'packages/*'

前回と同じくa=ofetch, b=axiosでGitHubのissueを取得する。pnpmにはworkspaceを指定するオプションがないので、-Fで特定のパッケージを絞るコマンドを用意するといろいろ捗る。

package.json
{
"name": "pnpm-workspaces",
"version": "1.0.0",
"description": "",
"main": "index.js",
"engines": {
"pnpm": "10.7.1",
"npm": "please_use_pnpm_instead"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"a": "pnpm -F \"a\"",
"b": "pnpm -F \"b\""
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.7.1"
}

packageをそれぞれa, bとして用意。

TypeScriptを使えるようにする。

Terminal window
pnpm add typescript
npx tsc --init

ルートのpackage.jsonに入った。

aofetchを追加してみる。

Terminal window
pnpm a add ofetch

packages/anode_modulesが出来上がった。

比べてもこの通り。

引き続きbaxios、プロジェクト全体にzodを入れて前回の記事と同じ状況を作る。ソースは前と同じものをコピペして持ってきている。

Terminal window
pnpm-workspaces % pnpm b add axios
pnpm-workspaces % pnpm add zod

jsにトランスパイルする。

Terminal window
pnpm-workspaces % tsc

と、各ディレクトリにjsを吐き出してくれた。

中を見てみる。

packages/a/index.js
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const ofetch_1 = require("ofetch");
const types_1 = require("../types");
const fetchIssue = () => __awaiter(void 0, void 0, void 0, function* () {
const url = "https://api.github.com/repos/unjs/citty/issues?state=open";
const data = yield (0, ofetch_1.ofetch)(url, {
method: "GET",
headers: {
Accept: "application/vnd.github.v3+json",
},
});
const issues = data;
console.log("Fetched issues:", issues);
const parsedIssues = issues.map((issue) => (0, types_1.parseGitHubIssueResponse)(issue));
console.log("Parsed issues:", parsedIssues);
});
fetchIssue();

pnpmの場合、node_modulesはそれぞれのディレクトリにできるのでこれでよい。が、packages以下のディレクトリで結局ルートにinstallしてる依存関係を使うためには結局バンドルしてあげないとダメそう。

ということで、pnpmを使ってもやはりバンドラがないとモノレポとしてうまく機能しなさそうだ。

結論

  • ワークスペース機能は、ベースとして使いたいパッケージマネージャに依存する
  • npmでもpnpmでも依存関係を切り分けて管理できることには違いない

tsjsに吐き出してるのはtsc。だから、パッケージマネージャがそこをいい感じに書き換えたりとかはするわけない。当たり前のことだ。

Copilotに聞いてみたけどtsconfigをこねくり回してもダメそうでバンドラを使う方法しか知らなさそう