- 投稿日:
ローカルで動作するNode.jsのライブラリ(node_modules
)欲しくないですか?欲しいですよね?という訳で作ってみました。
要件としてはTypeScriptで実装出来、Jestでテスト可能で、ESLintでLintが可能というところです。
確認環境
Env | Ver |
---|---|
@swc/cli | 0.1.62 |
@swc/core | 1.3.93 |
@swc/jest | 0.2.29 |
@types/jest | 29.5.5 |
@types/node | 20.8.6 |
@typescript-eslint/eslint-plugin | 6.7.5 |
@typescript-eslint/parser | 6.7.5 |
eslint | 8.51.0 |
eslint-config-prettier | 9.0.0 |
eslint-plugin-jest | 27.4.2 |
jest | 29.7.0 |
jest-watch-typeahead | 2.2.2 |
prettier | 3.0.3 |
ts-jest | 29.1.1 |
typescript | 5.2.2 |
Node.js | 20.8.0 |
npm | 10.1.0 |
サンプル実装
https://github.com/Lycolia/ts-library-example
実装について
フォルダ構成
monorepoでmainからlibrary/singleやlibrary/multi/hoge, piyoを参照するような構成です。
root
└─packages
├─library // ライブラリ側
│ └─packages
│ ├─multi // 複数ファイルのライブラリ
│ │ └─src
│ │ └─utils
│ │ ├─hoge
│ │ └─piyo
│ └─single // 単一ファイルのライブラリ
│ └─src
└─main // ライブラリを使う側
└─src
└─libs
実装時のポイント
これが全てというわけではないと思いますが、一旦今回作ったもののポイントを解説していきます。利用側がtsc
を利用しない場合、ライブラリ側と利用側で構成は別々になります。
またこの実装はエディタにVSCodeを利用し、import
するパスが相対パスであることを前提に説明しています。
ライブラリ側のポイント
ビルドに関して
まずビルドに関してはtsc
でやります。
これはTypeScriptで開発するためにはビルド成果物として.d.ts
ファイルが必要になるのと、Jestを通すためにビルド成果物がCommonJS形式(以下CJS)である必要があるためです。CJSが吐けるなら何でもいいとは思いますが、.d.ts
も必要になるので、tsc
を使うのが無難な選択肢だと思います。
テストファイルを出力したくないので、tsconfig.json
はビルドと開発で分けます。
またtsconfig.json
は各ワークスペースのルートに置いて置く必要があります。これはTypeScriptがtsconfig.json
のパスを起点として動作するためです。
ビルド用の設定ポイントとしては以下の通りです。
"compilerOptions"
"module": "NodeNext"
- これがないとパス解決が上手く行きません
"moduleResolution": "NodeNext"
- これがないとパス解決が上手く行きません
"declaration": true,
.d.ts
の出力に使います
"outDir": "./dist",
- ビルド成果物の出力先です
"exclude": ["src/**/*.spec.ts"]
- ビルド時にテストファイルは不要なので無視します
"include": ["src/**/*"]
- 参照するソースコードです
Jestに関して
ビルドにtsc
を使うため、Jestのローダーとしてもts-jestを利用します。公式ではbabelが推奨されているようですが、構成方法が不明だったので諦めました。BabelはJest公式の案内通りにやっても多分上手く行きません。
jest.config.js
には以下の設定を追加します。
preset: 'ts-jest'
開発用の設定について
開発とビルドでtsconfig.json
を分けるので、開発用のも必要です。これはビルド用から"exclude": ["src/**/*.spec.ts"]
を抜くだけです。
ライブラリを外部参照させる方法
基本的にはpackage.json
に外部参照させるための定義を書くことによって行います。
この辺りの仕様はdocs.npmjs.comにはなく、nodejs.orgにあります。
単一ファイルの外部参照
単一ファイルを外部参照(import出来るように)するときに使う手法です。
import
をimport { hoge } from '@my-lib/example'
みたいな書き方をしたい時に必要になるやつです。
以下の様にビルド成果物の.js
のパスをpackage.json
に追加してやると出来るようになります。
"main": "./dist/index.js",
以下の書き方でも同様に可能です。
"exports": {
".": {
"default": "./dist/index.js"
}
},
上記には他にtypes
というフィールドがあり、本来ここに.d.ts
を追加するのですが、TypeScriptが解決してくれるので、なくても動きます。一応ない場合はファイル探索を行うようなので、あった方が少しだけパフォーマンスが上がるかもしれません。
複数ファイルの外部参照
複数ファイルを外部参照(import出来るように)するときに使う手法です。
基本的に何もしなくてよいですが、import { hoge } from '@my-lib/example'
みたいな書き方もしたい場合は以下の記述が必要です。
"main": "./dist/core/index.js"
ここで指定していないものはimport { hoge } from '@my-lic/example/dist/hoge'
みたいにして参照します。dist
がダサくて嫌な場合は適当な名前に変えます。
参考までに@actions/githubはバージョン6.0.0時点でdist
に相当する部分をlib
にしており、import { Context } from '@actions/github/lib/context';
の様にして参照するようになっています。
exports
を使ってdist
を隠すことも出来るとは思うのですが面倒なので試してません。
ライブラリ側を利用する側のポイント
今回利用する側はビルドにswcを使う想定ですが、たぶんなんでも動くと思います。参考までにswc-loader + webpackでも動きました。
開発用の設定について
tsconfig.json
に以下の設定があれば恐らく最低限大丈夫だと思います。
"compilerOptions"
"module": "NodeNext"
- これがないとパス解決が上手く行きません
"moduleResolution": "NodeNext"
- これがないとパス解決が上手く行きません
"include": ["src/**/*"]
- 参照するソースコードです
ライブラリのインストール方法について
以下のように相対パスを指定するとインストールできます。
npm i ../package/library/package/single
消すときはパッケージ名を指定すれば消せます。
npm un @lycolia/library-example-single
試したけどダメだったやつら
色々してる過程で試行錯誤した名残。
ライブラリ側のJestでbabelを使おうとやったこと
以下の二通りの設定は試しましたが、どっちもダメだったので諦めました。何よりtscを使うならts-jestの方が楽なのは確定的に明らかですし…。
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
],
presets: [
['@babel/preset-env', {targets: {node: 'current'}, modules: 'commonjs'}],
'@babel/preset-typescript',
],
ライブラリ側をswcでビルド
.swcrc
を以下の設定にしても
"module": {
"type": "commonjs"
},
以下の出力がされるため、jest.spyOn()
が上手く動かない(get
が邪魔でhello
が見れない
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "hello", {
enumerable: true,
get: function() {
return hello;
}
});
const hello = (param)=>{
console.log(param);
};
どの道これでは.d.ts
が出せないので、あんま意味ないなと…。
参考にしたもの
実装方法は@actions/githubが一番単純で参考になると思います。
- 仕様
- 実装
- Jest周り
実装はビルド成果物とビルド前のコード、package.json
やtsconfig.json
辺りがどうなっているのかを参考にしました。
- 投稿日:
ESM化が叫ばれて久しいですが、未だにJestはESMとCJSが混在したコードを処理してくれません。
Getting StartedにもSWCに対する言及がないので、きっともう忘れられているのでしょう。swc-project/jestの方も特にやる気はなさそうだし、やりたければ自分でPR書きましょうって感じだと思います。きっと。
確認環境
node_modules配下にESMで作られたモジュールが存在し、コードはTypeScript、トランスパイルにはSWCを利用する。
Env | Ver |
---|---|
@swc/cli | 0.1.62 |
@swc/core | 1.3.92 |
@swc/jest | 0.2.29 |
@swc/register | 0.1.10 |
jest | 29.7.0 |
typescript | 5.2.2 |
やったけど意味がなかったこと
package.json
のtype
をmodule
にするjest.config.js
のtransformIgnorePatterns
にESMモジュールのパスだけ除外する設定を書く- 上記に加えて
transform.jsc.path
にpkg-name: ['node_modules/pkg-name']
を追記する node --experimental-vm-modules node_modules/jest/bin/jest.js
で実行する- 多少マシになったがコケるものはコケる
- ESMで書かれたモジュールを丸ごとモックする
- 一切効果なし
所感
多分Webpackでバンドルしてnode_modules
の中身も外も関係ない状態にするのが一番無難なのではないかと思いました。
Node.jsの組み込みテストランナーにすれば解決するかな?と思ったものの、こちらは現状SWCでは使えそうにないので諦めました。
参考までに以下のコマンドで走らさせられます。
node --require @swc/register --test ./src/**/*.spec.ts
取り敢えずESMに引っかったモジュールはCJS時のバージョンを維持しておくことにしましたが、このままだとSWC使えないし、なんとかなって欲しいですね。Webpack使えば解決できるのはわかるんですが、このために使いたくないので、テストを重視する場合、Vitestを持つViteが有力候補になって来そうです。
2024-02-17追記
esbuild + Node.js built-in test runnerの組み合わせであればテストはできるが肝心の実行ができず無意味だった
- 投稿日:
新しくセットアップしたTypeScriptのプロジェクトでauto-importが機能しなかったので原因を調べた。
ここ数年ちまちま起きて、そろそろイラついて我慢の限界を覚えたので…。
確認環境
Env | Ver |
---|---|
VSCode | 1.83.0 |
typescript | 4.8.4 |
再現方法
再現用のサンプルリポジトリでsrc/index.ts
の下図コードに対してauto-importが発動する操作を行う。
発生条件
恐らく以下を全て満たすときにauto-import操作をしようとした時に発生する。地味にややこしい。
node_modules
配下に存在し、@types/
モジュールを持たないものでかつ、同一プロジェクト内でimportされておらず、package.json
のdependencies
に書かれていない。
因みにhoge/piyo/fuga
のようなモジュールはhoge
だけがdependencies
にいてもauto-importが動かない。これを全部package.json
のdependencies
に書いていくのは目眩がするので正直auto-importを諦めて自分でimportを調べて書くのが無難だと思う。
発生原因
TypeScript 4.0で実装されたSmarter Auto-Importsのせい。
node_modules
をクロールすると重すぎるので@types/
だけ読み込んで、あとはpackage.json
のdependencies
も見るようにしたよという内容らしい。
解消方法
package.json
のdependencies
に全部のモジュールを書いていくというのが解決方法になるが、気が遠くなるので諦めた方がいい。初回だけは苦痛でもimportパスをどうにかして調べて手動で書いて、あとはauto-importされるのを期待するのが良いだろう(そんな何度もimportしないと思うが…)
取り敢えず基本npm i
で入れて行き、スラッシュ区切りのやつは諦めるくらいがちょうどいいだろう。
再現用のサンプルリポジトリではpackage.json
のdependencies
に"firebase/app": "^10.4.0"
を追加することでsrc/index.ts
のinitializeApp
に対しauto-importが発動する様になるはずだ。
あとがき
早い話、node_modules
配下にあって@types/
を持たないものはTypeScriptに対応する気がないんだな程度に思っておくのが良いだろうが、TS化で@types/
を廃止したライブラリがある当たり、すごく微妙な感じがある。ちょっとなんとかしてほしい。
そもそもpackage.json
のdependencies
はnpm packageを公開する時に動作するために必要な依存関係を登録する場所で、型の補助をする場所ではなかったはずだ。
少なくともnpmjsのdevDependenciesには以下の記述がある。つまりモジュールを配布する時に動作に不要な依存関係を含まないようにするためにdevDependencies
があるということだ。つまりdependencies
には動作に必要なものだけを入れるべきで、モジュールを配布しない場合、このフィールドは不要になるはずである。
If someone is planning on downloading and using your module in their program, then they probably don't want or need to download and build the external test or documentation framework that you use.
In this case, it's best to map these additional items in a devDependencies object.
ただTypeScriptがdependencies
に書かないとauto-importが失敗すると言っているので使うものはdependencies
に入れるというのが良いのだろう。(配布しないモジュールだとしても)
ただそれにしてもauto-importを動かすためだけに、dependencies
に同一モジュールのスラッシュ違いを大量に入れていくのはバカバカしいと思う。
"dependencies": {
"firebase": "^10.4.0",
"firebase/app": "^10.4.0",
"firebase/database": "^10.4.0",
"firebase/analytics": "^10.4.0",
...
}
かといってinitializeApp
がfirebase/app
にあるなんて知らんわけで、全部のパスに総当りしていくのも嫌だし、一々リファレンス漁るのも面倒だし、なんかもうちょっといい具合になって欲しい…。
- 投稿日:
この話には賛否両論あると思うが、個人的に良いと思っている部分を話す。この文化は集団の和を保つ上では非常に有効に思う。
まず日本には「暗黙の了解」と言うものがかつてあった。今でも残っているものはあると思うが、大半は消失しただろう。これは多くの人が知っていることによってお互いが平等に快適に暮らせるものであったと考える。何故なら皆そのことだけは意識するからだ。
例えば電車の走る向きが変わった時に、席の向きを変えるのがあると思うが、アレを一人だけ拒んだ場合どうだろう?その人はいいかも知れないが、他の人は迷惑するだろう。勿論必ず迷惑するとは限らないが、迷惑するかしないかを標準化出来る効力はあると思う。
これが思想の強制と言われてしまうと返す言葉もないのだが、集団生活にはルールが必要だ。これは全ての文化でそうだ。日本に限らない。例えば法律がない国はない。どんな国にでも法律はある。但し法律を杓子定規に適用したり、個々人のマナーにまで適応するのはいささか無理がある。
そこで「暗黙の了解」である。民間人が暗黙の了解を持ち合い、それによって必要最低限のコミュニケーションで快適な生活ができるというのは実に合理的であり、素晴らしいことだ。そこには余計な配慮はいらない。勿論、一定数いる無理解な人物に対しては労力が掛かるが、おおよそ大抵のケースでは不要だろう。
しかし昨今ではこの「暗黙の了解」は個人主義の否定だとか、全体主義と言われ忘れられ始めている。結果として何をするにも相手の了承を得ないといけなくなり、互いに面倒になってしまったと感じる。しかも下手に声をかければ相手が逆上してきて加害されることさえないとは言えない世の中だ。
意見の押しつけだとか、個の否定と言う人もいるかもしれないが、果たしてどちらの方がより幸せになれるだろうか?もし、貴方が誰の力を借りずとも山奥で一人で生活できると言うのであれば咎めはしないが、現実は電車や町中、仕事先や学校などで人と関わらざるを得ない人が大半ではないだろうか?
私はもっと昭和に戻るというか、そういった昔の文化を取り戻していけば昨今言われているストレス社会が緩和され、人々が暗黙的に協調し合うようになり、もっと楽に生きていけるのではないかと思う。
各々が好き勝手言っている社会は様々な衝突が多く、気苦労に耐えず、それはきっと大変なのだと思う。そういう意味で私はこの「暗黙の了解」と言うのはあったほうが良いのではないかと思っている。
勿論、この制度も完璧なものではなく、銀の弾丸にはなり得ない、結局のところこの手の選択肢という物は何を優先したときに一番合理的かと言うのが求めるところにあると思う。治安が悪く、ストレスフルであることを是とするなら個人主義も良いだろうし、治安を良くし、ストレスを減らし、協調的で和のある社会を目指すのであれば全体主義は一つの解になるだろうと言うのが私の見解だ。
日本はかつて国際社会から、その平和さ、治安の良さを見習うべきだと賞賛されていた国だったと思うが、欧米文化の浸透により、そういった美徳はことごとく失われたと思っている。だからこそ、かつて日本を蝕んでいた「暗黙の了解」と言う文化を取り戻し、国際的にも異質な文化を持つ国に戻って欲しいと願っている。
勿論「暗黙の了解」だけで今言ったことの全てが解決するとは思わない。これは鍵の一つでしかなく、本質的にはもっと沢山のことが必要だとは思うが、本題とズレてくるので今回はここで締め括り、また追々話していければと思う。
- 投稿日:
BTCの送金をしたくなったので、そのメモ
手順
幾つかサービスが存在するが今回はDMM Bitcoinを利用する。この記事はDMM Bitcoinの宣伝ではなく、私はDMMから一切報酬を受理していないことを付記しておく。
- まずウォレットを作成するため、サイトから適当に登録する。2024/03/01までに登録すると1000円貰えるのでありがたい。
- 私は金曜の20時半ごろに登録し、約2時間ほどで口座開設できた。最速1時間らしいがキャンペーンがあるのと、人間が登録作業をやってる雰囲気を感じたので時間がかかっているのだろう
- なお1000円の受け取り方は常識的に考えれば解ると思うので本記事では紹介しない
- ログインしてマイページを開き、画面左部のメニューを確認する
- 「日本円・暗号資産の入金」から「日本円入金(クイック入金)」に進む
- 自分の持っている銀行口座を選び入金作業を行う。5000円からなので注意したい。
- 恐らく即時入金されるはずなので、入金を確認したら「暗号資産取引」へ進む
- なんかウィンドウが開くが、このままではレバレッジ取引しかできないので、現物取引を行えるように設定する
- 画面上部のメニューから「現物取引(購入・売却)」を選ぶ
- すると現物取引用のウィンドウが開くので、普通にBTCを購入する。数量を入力して「Ask/買」を押せば買える
- まだこの段階ではウォレットに入っていないので「口座振替」を行う
- 「振替口座」を「トレードからウォレットへ」、「通貨/暗号資産の選択」をBTCに、「振替金額/数量」にウォレットに移送したい金額を入れる
- 最後に「日本円・暗号資産の出金」から「BTC出金」を選び、画面の指示通りにやれば出金できる
- なお出金には結構時間がかかる様なので気長に待つのが良い。私の場合3回ほどやったが全て15時間ほどかかった。
あとがき
以前bitFlyerを利用しようとしたことがあるのだが、ありとあらゆる操作が非常に面倒で使う気が起きず、入金したものの、何もせず出金して退会した経緯がある。それと比べるとDMM Bitcoinはまだ使いやすい。
なおbitFlyerでは1円からの入金に対応している様なので少額利用したい場合はbitFlyerを選ぶのも一つの選択肢かもしれない。