2019年3月10日日曜日

JavaScriptで書いてたreact-nativeのnpm packageをTypeScriptで書き直したのでその手順的なお話

  • このエントリーをはてなブックマークに追加

たまに作っているnpm package。
JavaScriptで書いてたんだけど最近TypeScriptがんばりはじめてるし、TypeScriptで書き直してみようかな〜って思ってやってみた。
ちなみに今回修正したのはreact-native-store-versionっていうパッケージで、現在のアプリバージョンとストアで公開されてるアプリバージョンを比較して、それがnewなのかoldなのかequalなのかを判断してくれる感じ。

ということで今日はnpm packageのTypeScript化に伴う手順的なお話をば。

とりあえずもう一度のせておくパッケージ。
Starほしい。

react-native-store-version


■typescript関連のインストール/設定

まずはtypescriptのインストール

$ npm install --save-dev typescript @typescript-eslint/eslint-plugin
$ npx tsc --init

■.vscode/settings.jsonにeslint.validateを追加

{
  ...
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    {
      "language": "typescript",
      "autoFix": true
    },
    {
      "language": "typescriptreact",
      "autoFix": true
    }
  ]
}

■.eslintrc -> .eslintrc.jsにして編集/追加

import/no-extraneous-dependenciesにsrc/**/**を追加したのは、react-nativeを今回devDependenciesに入れているので、それが通常だとerror出てしまうので回避したいから。

module.exports = {
  "parser": "@typescript-eslint/parser",
  ...
  "plugins": [
    "@typescript-eslint"
  ],
  "parserOptions": {
    "sourceType": "module",
    "project": "./tsconfig.json"
  },
  "settings": {
    "import/extensions": [
      ".js",
      ".jsx",
      ".ts",
      ".tsx"
    ],
    "import/resolver": {
      "node": {
        "extensions": [
          ".js",
          ".jsx",
          ".ts",
          ".tsx"
        ]
      }
    }
  },
  "rules": {
    ...
    "import/no-extraneous-dependencies": [
      "error",
      {
        "devDependencies": ["src/**"],
        "optionalDependencies": false,
        "peerDependencies": false
      }
    ],
    "import/extensions": [
      ".js",
      ".jsx",
      ".ts",
      ".tsx"
    ]
  }
}

■tsc --initで作成したtsconfig.jsonを編集

includeにコンパイルしたいtsを指定。
filesは/index.d.tsにinterface定義して置いてあるんだけど、そもそもts内でinterface定義してあげたりすればここは必要なかったり。
ここら辺についての細かいところは後述。

{
  "compilerOptions": {
    "target": "ESNEXT",
    "module": "ESNext",
    "lib": [
      "es2015",
      "es2016",
      "es2017"
    ],
    "moduleResolution": "node",
    "outDir": "dist",
    "removeComments": true,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "esModuleInterop": true,
    "inlineSourceMap": true,
    "noEmitOnError": false,
    "inlineSources": true,
    "baseUrl": "."
  },
  "include": [
    "src/**/*"
  ],
  "files": [
    "index.d.ts"
  ]
}

■.eslintignoreと.gitignoreにdistフォルダを追加

// .eslintignore && .gitignore
dist/

■.npmignoreにsrcとtsconfig.jsonを追加

src
tsconfig.json

■package.jsonの編集

mainファイルをビルドされたjsにして、typesをindex.d.tsに指定。
ビルドコマンドを記述。
ついでにnpm install --save-dev @types/react-native react-nativeとしておく。

{
  ...
  "main": "dist/index.js",
  "types": "index.d.ts",
  ...
  "scripts": {
    "build": "tsc --project tsconfig.json"
  },
  ...
}

■js -> tsへの変換

思うがままに変更。
ちなみに自分はtypeを外部ファイルで自分で作ってみたので、先ほどpackage.jsonにてtypesを指定したり、tsconfig.jsonにてfilesを指定したのもある。
ただしここで面倒なのが外部定義ファイルという位置付けになってしまう。
今回tsconfig.jsonにてdeclarationをtrueにしていないのだけど、通常npm packageを作る際はtrueにしておいたほうがいいかと。
けどtrueにした場合外部定義ファイルは生成されるd.tsに含まれないし、さらにtsのファイル分だけd.tsができて個人的にはすごく気持ち悪いなぁと思ってしまった。
特に今回ファイルを分割してあるが、ほぼほぼtypeが流用できるということもあってtypeは別ファイルにしたかったっていうのもある。

とりあえずこのtypeに関するベストプラクティス的なものは見つけられていないんだけど、できたらtypeというか型定義はそれぞれのts内で定義してあげ、declaration:trueにしてあげるのがnpm packageを作る際にはいいんじゃないかなと思う。
その際にdeclarationが生成されるフォルダを指定するためにも別でtsconfigを作ってあげるなどしてprebuildしてあげたりするのがいいんじゃないかな。

■.circleci/config.ymlの編集

npm run buildしてくれるjobを定義して、deployするまえにbuildしてくれるようにする。

...
  build:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/repo
      - run: npm install
      - run:
          name: build script
          command: npm run build
...
            
workflows:
  version: 2
  test-deploy:
    jobs:
      ...
      - build:
          requires:
            - test
      - deploy:
          requires:
            - test
            - build
          filters:
            tags:
              only: /^v.*/
            branches:
              ignore: /.*/

■jestを用いてのtestを追加

jestを用いてtestを書いてみようかと。
__test__フォルダを作ってあげ、その中にindex.test.tsを入れる。
ついでにpackage.jsonの編集だったりbabelの設定したりとか諸々。

$ npm install --save-dev \
@babel/core \
@babel/plugin-proposal-class-properties \
@babel/preset-env \
@types/jest \
jest \
jest-fetch-mock \
ts-jest
// package.json
{
  ...
  "jest": {
    "preset": "react-native",
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js"
    ],
    "transform": {
      "^.+\\.(ts|tsx)$": "ts-jest"
    },
    "globals": {
      "ts-jest": {
        "tsConfig": "tsconfig.json",
        "diagnostics": false
      }
    },
    "testMatch": [
      "**/__tests__/**/*.test.(ts|tsx|js)"
    ],
    "testPathIgnorePatterns": [
      "/node_modules/"
    ]
  }
}
// babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          node: 'current',
        },
      },
    ],
  ],
  env: {
    test: {
      plugins: [
        '@babel/plugin-proposal-class-properties',
      ],
    },
  },
};

多分こんな感じだけどどこか抜けてる可能性もあるかもだけど、大雑把だけどとりあえずこんな感じ。
ちゃんと今度からはtest入れていこうかなというお気持ち表明でもある。

けど面倒なのがtype関連。
一つのd.tsにまとめあげたいけどそれができなかったりするとか、index.d.tsみたいなのを自分で作ると外部定義ファイルとなってdeclaration:trueにしても、中に含まれなかったりするんどえ頑張ってそもそも自分で一つのd.tsファイルを作り上げてdeclaration使って外部でも見れるようにしてあげないとvscodeでcommand + 左クリックで飛べなかったりとか色々とあったり。
ここらへんはnpm package作成の領域だからあまり情報が転がってなかったり、とりあえずいいやとかなりがちだからわからないことが多いところ。

とりあえず他のやつも全部typescript化がんばってしていきたいと思った的なみたいな感じ。

0 件のコメント:

コメントを投稿

Adsense