はじめに
TypeScript のプロジェクトでいい感じのテキストフィールドをatomsディレクトリの下に用意して作っていましたが、機能が多くなったり、コンポーネントが分かれてくるとmoleculesに移動させたりと、めんどくさいので、npmのパッケージ化してしまえと思ったことが始まりです。
今回は create-react-app で作成したプロジェクトに取り込ませて、実際に私が行った、npmのパッケージ開発方法、手順を紹介します。
パッケージを取り込む用のプロジェクトを作る
とりあえず create-react-app でベースのプロジェクトを作成します。
ベースのプロジェクトが存在する方は不要です。
$ yarn create react-app my-app --template typescript
$ cd my-app
$ yarn start
おなじみの画面が表示されましたね。
この画面の真ん中くらいにいい感じのテキストフィールドを表示しながら開発していきましょう。
npm のパッケージの雛形を作成
npm
のアカウント作成は3分でできるnpmモジュールを見ながら作りました。
ディレクトリと雛形を作成します。
$ mkdir react-custom-text-field
$ cd react-custom-text-field
$ yarn init
yarn init v1.19.0
question name (react-custom-text-field):
question version (1.0.0): 0.0.1
question description:
question entry point (index.js): ./dist/index.js
question repository url:
question author: pure-adachi
question license (MIT):
question private:
version
は開発なのでメジャーバージョンは0
です。
とりあえず0.0.1
にしておきました。
entry point
はビルドで生成する予定のファイルです。
必要なモジュールをインストール
- React関連
$ yarn add -D react react-dom @types/react @types/react-dom
- tscでコンパイルするので
$ yarn add -D typescript
package.json
ビルドのコマンドを用意します。
"scripts": {
"build": "tsc"
}
tsconfig.json
tsc
でのコンパイルの設定を用意します。
{
"compilerOptions": {
"target": "ES2015",
"module": "commonjs",
"jsx": "react",
"outDir": "./dist",
"strict": true,
"noImplicitAny": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
}
}
メインのファイルを作成
import React from "react";
const ReactCustomTextField = () => {
return (
<div>
<input type="text" />
</div>
);
};
export default ReactCustomTextField;
とりあえずベースとして、シンプルな入力フィールドにしました。
ビルド
$ yarn build
dist
ディレクトリが出来て、その中にindex.js
ファイルが生成されれば成功です。
$ tree dist/
dist/
└── index.js
パッケージをプロジェクトに取り込み
ベースのプロジェクトと、開発パッケージのディレクトリ構成を以下のように配置しています。
$ tree
.
├── my-app
〜〜〜
〜〜〜
└── react-custom-text-field
〜〜〜
〜〜〜
最初に create-react-app
で作成したプロジェクト側で、 react-custom-text-field
を取り込みます。
ローカルにあるパッケージなので、ディレクトリ指定で取り込めます。
$ yarn add ../react-custom-text-field/
App.tsx
に導入
import React from "react";
import logo from "./logo.svg";
import "./App.css";
+import ReactCustomTextField from "react-custom-text-field";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
+ <ReactCustomTextField />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
パッケージの型定義していないので、エラーが発生しました。
my-app/src/App.tsx
TypeScript error in /my-app/src/App.tsx(4,34):
Could not find a declaration file for module 'react-custom-text-field'. '/my-app/node_modules/react-custom-text-field/dist/index.js' implicitly has an 'any' type.
Try `npm install @types/react-custom-text-field` if it exists or add a new declaration (.d.ts) file containing `declare module 'react-custom-text-field';` TS7016
2 | import logo from "./logo.svg";
3 | import "./App.css";
> 4 | import ReactCustomTextField from "react-custom-text-field";
| ^
5 |
6 | function App() {
7 | return (
型解決をパッケージ側でする
.d.ts
ファイルを作成
ルートディレクトリにtypes
ディレクトリを作成して、その下に.d.ts
ファイルを作成します。
declare const _default: React.ComponentType;
export default _default;
型ファイルを参照させる
package.json
に型ファイルのパスを追加します。
"types": "./types/react-custom-text-field/index.d.ts"
取り込んでいるプロジェクトでインストールし直す
$ yarn remove react-custom-text-field
$ yarn add ../react-custom-text-field/
$ yarn start
テキストフィールドが表示されました。
パッケージの更新を監視できるようにする
パッケージの編集をして、プロジェクト側でインストールし直すのはかなりめんどくさいです。
パッケージ側のファイルを保存した際に、自動でビルドされ、ビルドで変更されたら、プロジェクト側にも反映されるようにします。
(node_modules
内にインストールされたreact-custom-text-field
が開発しているディレクトリのシンボリックリンクになるイメージ)
パッケージ側の設定
ウォッチオプションをつけたtsc
コマンドを実行できるようにします。
"scripts": {
"build": "tsc",
"build:watch": "tsc -w"
}
yarn link
を実行して、このパッケージにリンク付可能とさせます。
$ yarn link
yarn link v1.19.0
success Registered "react-custom-text-field".
info You can now run `yarn link "react-custom-text-field"` in the projects where you want to use this package and it will be used instead.
✨ Done in 0.05s.
ソースの変更を監視するビルドを実行させておきます。
$ yarn build:watch
プロジェクト側の設定
パッケージをシンボリックリンクさせる
$ yarn link react-custom-text-field
node_modules
の中身を見てみるとシンボリックリンクが作成されていることがわかります。
$ ls -l node_modules/ | grep react-custom-text-field
lrwxr-xr-x 1 adachi staff 56 4 11 21:46 react-custom-text-field -> ../../../../../.config/yarn/link/react-custom-tex
t-field
ホットロードの確認
$ yarn start
と
$ yarn build:watch
が起動されている状態で、パッケージファイルの更新をします。
const ReactCustomTextField = () => {
return (
<div>
- <input type="text" />
+ <input type="text" value="hoge" />
</div>
);
};
勝手に反映されました。
リリース 第1段
コマンド打つだけです。
$ yarn publish
yarn publish v1.19.0
[1/4] Bumping version...
info Current version: 0.0.1
question New version: 0.0.1
[2/4] Logging in...
[3/4] Publishing...
success Published.
[4/4] Revoking token...
info Not revoking login token, specified via config file.
✨ Done in 8.34s.
npm
のサイトで確認できました。
リリースしたパッケージをプロジェクトで使う
ディレクトリ指定でインストールしていたパッケージをnpm
からインストール
$ yarn unlink react-custom-text-field
$ yarn add react-custom-text-field
$ yarn start
コンパイルに失敗しました。
Failed to compile.
./src/App.tsx
Module not found: Can't resolve 'react-custom-text-field' in '/my-app/src'
実際にインストールされたファイルを確認してみます。
$ ls -l node_modules/react-custom-text-field/
total 24
-rw-r--r-- 1 adachi staff 26 4 11 21:59 README.md
-rw-r--r-- 1 adachi staff 439 4 11 21:59 package.json
drwxr-xr-x 3 adachi staff 96 4 11 22:05 src
-rw-r--r-- 1 adachi staff 370 4 11 21:59 tsconfig.json
drwxr-xr-x 3 adachi staff 96 4 11 22:05 types
dist
ディレクトリがない。。。。
ここにdist
ディレクトも含まれるようにパッケージ側を修正します。
リリース 第2段
package.json
にfiles
を追加
"files": [
"dist"
]
そしてリリース
$ yarn publish
yarn publish v1.19.0
[1/4] Bumping version...
info Current version: 0.0.1
question New version: 0.0.2
info New version: 0.0.2
[2/4] Logging in...
[3/4] Publishing...
success Published.
[4/4] Revoking token...
info Not revoking login token, specified via config file.
✨ Done in 6.86s.
もう一度インストールしなおして動作確認
$ yarn remove react-custom-text-field
$ yarn add react-custom-text-field
$ yarn start
見覚えのあるエラーが出ました。。。
my-app/src/App.tsx
TypeScript error in /my-app/src/App.tsx(4,34):
Could not find a declaration file for module 'react-custom-text-field'. '/my-app/node_modules/react-custom-text-field/dist/index.js' implicitly has an 'any' type.
Try `npm install @types/react-custom-text-field` if it exists or add a new declaration (.d.ts) file containing `declare module 'react-custom-text-field';` TS7016
2 | import logo from "./logo.svg";
3 | import "./App.css";
> 4 | import ReactCustomTextField from "react-custom-text-field";
| ^
5 |
6 | function App() {
7 | return (
node_modules/react-custom-text-field
の中身を確認してみます。
$ ls -l node_modules/react-custom-text-field/
total 16
-rw-r--r-- 1 adachi staff 26 4 11 22:15 README.md
drwxr-xr-x 3 adachi staff 96 4 11 22:15 dist
-rw-r--r-- 1 adachi staff 468 4 11 22:15 package.json
むしろdist
しかない。。。
発生したエラー、最初に見たエラーと同様、型定義されてないエラーですね。
types
ディレクトリに型があるので、types
も含まれるように修正します。
リリース 第3段
package.json
にfiles
を追加
"files": [
"dist",
"types"
]
そしてリリース
$ yarn publish
yarn publish v1.19.0
[1/4] Bumping version...
info Current version: 0.0.2
question New version: 0.0.3
info New version: 0.0.3
[2/4] Logging in...
[3/4] Publishing...
success Published.
[4/4] Revoking token...
info Not revoking login token, specified via config file.
✨ Done in 6.64s.
更にもう一度インストールしなおして動作確認
$ yarn remove react-custom-text-field
$ yarn add react-custom-text-field
$ yarn start
ようやく表示されましたね。
終わりに
今回作成したnpmのパッケージとGitHubのリンクはこちらです。
ゴリゴリ開発する部分を楽しみにしていたので、環境を整える部分でずっこけまくり、開発着手するときにはウキウキ感は薄くなってました。
@types/react-custom-text-field
でインストールさせる方法はDefinitelyTypedにPull Request投げたり、取り込んでもらうまで時間がかかったりと大変です。
自分で解決できるようにする方法があったのでこの方法でやりました。
yarn publish
する度に、勝手にそのバージョンでコミットされて、タグも生成されるので、Github
等へのプッシュ(タグも)はかなり楽でした。
(yarn pusblish
→ git push origin [ブランチ名]
→ git push origin --tags
して、後はGitHub
でぽちぽち、かたかたするだけ)