更新日:
投稿日:

確認環境

OpenWrt 24.10.0

やり方

opkg install perl
opkg install perlbase-essential
opkg install perlbase-json-pp
opkg install perlbase-scalar
opkg install perlbase-xsloader
opkg install perlbase-dynaloader
opkg install perlbase-list
opkg install perlbase-bytes
opkg install perlbase-findbin
opkg install perlbase-http-tiny

# OpenWrtにはPerlのSSLモジュールがないので親切な人が作ってくれてるものを使わせていただく
# https://jw2013.github.io/openwrt-packages/
opkg install libustream-mbedtls
wget https://jw2013.github.io/openwrt-packages/public.key
opkg-key add public.key
echo 'src/gz pkgs-by-jw2013 https://jw2013.github.io/openwrt-packages/24.10/x86_64' >> /etc/opkg/customfeeds.conf
opkg update
opkg install perl-net-ssleay
opkg install perl-io-socket-ssl
rm public.key

増加する容量

キビバイトやメビバイトのバイト変換やトータルビバイトはLLMに換算させた欠化を書いているので誤りが混ざっている可能性がある。大まかには合っていると思うが…。

pkg size byte
perl 1.27 MiB 1,331,692
perl-io-socket-ssl 124.00 KiB 126,976
perl-net-ssleay 153.61 KiB 157,297
perlbase-autoloader 3.04 KiB 3,113
perlbase-b 106.07 KiB 108,616
perlbase-base 4.27 KiB 4,372
perlbase-bytes 1.23 KiB 1,260
perlbase-class 3.02 KiB 3,092
perlbase-config 18.55 KiB 18,995
perlbase-cwd 11.31 KiB 11,581
perlbase-dynaloader 4.50 KiB 4,608
perlbase-errno 3.22 KiB 3,297
perlbase-essential 23.79 KiB 24,361
perlbase-fcntl 7.36 KiB 7,537
perlbase-feature 2.68 KiB 2,744
perlbase-file 72.30 KiB 74,035
perlbase-filehandle 1.81 KiB 1,853
perlbase-findbin 1.98 KiB 2,028
perlbase-http-tiny 12.98 KiB 13,292
perlbase-i18n 21.21 KiB 21,719
perlbase-integer 1.12 KiB 1,147
perlbase-io 67.71 KiB 69,335
perlbase-json-pp 11.96 KiB 12,247
perlbase-list 23.14 KiB 23,695
perlbase-locale 10.98 KiB 11,244
perlbase-mime 6.50 KiB 6,656
perlbase-params 4.02 KiB 4,116
perlbase-posix 40.45 KiB 41,421
perlbase-re 285.55 KiB 292,403
perlbase-scalar 1.60 KiB 1,638
perlbase-selectsaver 1.27 KiB 1,300
perlbase-socket 19.49 KiB 19,958
perlbase-symbol 1.94 KiB 1,987
perlbase-tie 18.40 KiB 18,842
perlbase-unicore 779.16 KiB 797,860
perlbase-utf8 1.24 KiB 1,270
perlbase-xsloader 2.54 KiB 2,601
total 3.08 MiB 3,230,188
更新日:
投稿日:

ローカルで動作する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出来るように)するときに使う手法です。

importimport { 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が一番単純で参考になると思います。

実装はビルド成果物とビルド前のコード、package.jsontsconfig.json辺りがどうなっているのかを参考にしました。