Help us understand the problem. What is going on with this article?

React Component な npm のパッケージを作って TypeScript のプロジェクトで使う

はじめに

TypeScript のプロジェクトでいい感じのテキストフィールドをatomsディレクトリの下に用意して作っていましたが、機能が多くなったり、コンポーネントが分かれてくるとmoleculesに移動させたりと、めんどくさいので、npmのパッケージ化してしまえと思ったことが始まりです。

今回は create-react-app で作成したプロジェクトに取り込ませて、実際に私が行った、npmのパッケージ開発方法、手順を紹介します。

パッケージを取り込む用のプロジェクトを作る

とりあえず create-react-app でベースのプロジェクトを作成します。

ベースのプロジェクトが存在する方は不要です。

$ yarn create react-app my-app --template typescript
$ cd my-app
$ yarn start

スクリーンショット 2020-04-11 14.21.12.png

おなじみの画面が表示されましたね。

この画面の真ん中くらいにいい感じのテキストフィールドを表示しながら開発していきましょう。

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

ビルドのコマンドを用意します。

package.json
"scripts": {
  "build": "tsc"
}

tsconfig.json

tscでのコンパイルの設定を用意します。

tsconfig.json
{
  "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
  }
}

メインのファイルを作成

src/index.tsx
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に導入

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ファイルを作成します。

types/react-custom-text-field/index.d.ts
declare const _default: React.ComponentType;
export default _default;

型ファイルを参照させる

package.jsonに型ファイルのパスを追加します。

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

スクリーンショット 2020-04-11 17.12.26.png

テキストフィールドが表示されました。

パッケージの更新を監視できるようにする

パッケージの編集をして、プロジェクト側でインストールし直すのはかなりめんどくさいです。

パッケージ側のファイルを保存した際に、自動でビルドされ、ビルドで変更されたら、プロジェクト側にも反映されるようにします。
node_modules内にインストールされたreact-custom-text-fieldが開発しているディレクトリのシンボリックリンクになるイメージ)

パッケージ側の設定

ウォッチオプションをつけたtscコマンドを実行できるようにします。

package.json
"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

が起動されている状態で、パッケージファイルの更新をします。

src/index.tsx
 const ReactCustomTextField = () => {
   return (
     <div>
-      <input type="text" />
+      <input type="text" value="hoge" />
     </div>
   );
 };

スクリーンショット 2020-04-11 21.52.11.png

勝手に反映されました。

リリース 第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.

スクリーンショット 2020-04-11 22.00.57.png

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.jsonfilesを追加

package.json
"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.jsonfilesを追加

package.json
"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

スクリーンショット 2020-04-11 22.23.40.png

ようやく表示されましたね。

終わりに

今回作成したnpmのパッケージとGitHubのリンクはこちらです。

ゴリゴリ開発する部分を楽しみにしていたので、環境を整える部分でずっこけまくり、開発着手するときにはウキウキ感は薄くなってました。

@types/react-custom-text-fieldでインストールさせる方法はDefinitelyTypedにPull Request投げたり、取り込んでもらうまで時間がかかったりと大変です。
自分で解決できるようにする方法があったのでこの方法でやりました。

yarn publishする度に、勝手にそのバージョンでコミットされて、タグも生成されるので、Github等へのプッシュ(タグも)はかなり楽でした。
(yarn pusblishgit push origin [ブランチ名]git push origin --tagsして、後はGitHubでぽちぽち、かたかたするだけ)

参考文献

pure-adachi
ruby on rails や reactで何か作ってます。
https://github.com/pure-adachi
pure-system
大手生命保険会社各社をはじめ、自治体、大手企業など、日本を代表する企業様のIT化を推進・支援しています。
https://www.pure-system.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした