フリーダムの日記

GIS(地理情報システム)を中心に技術的なことを書いています。

TypeScript をはじめてみた!

はじめに

ArcGIS API for JavaScript を使用した簡単な Web マッピングアプリの開発に TypeScript を使用してみました。
ArcGIS API for JavaScript は、Google Map API と同じような Web マッピングアプリを開発するための API です。ArcGIS API for JavaScript は、Google Map とは異なり 空間検索などの GIS の機能やデータのビジュアライゼーションも豊富に対応しています。

今回は、ArcGIS API for JavaScript で TypeScript を使用するための準備として、開発環境の構築や簡単なサンプルアプリを作成までの手順を紹介したいと思います。また、TypeScript を使用するメリットについても紹介します。

f:id:freedom0625:20200614014411p:plain

サンプルアプリでは、Web マップを表示「レイヤーリストと凡例を追加」 の機能について JavaScript から TypeScript に変換した例を紹介します。

TypeScript で作成したサンプルアプリは以下になります。

GitHub にもアップしていますのでご参照ください。

github.com

TypeScript とは

Wikipedia からの情報の引用になりますが、TypeScript はマイクロソフトによって開発され、メンテナンスされているフリーでオープンソースプログラミング言語です。TypeScript は JavaScript に対して、省略も可能な静的型付けとクラスベースオブジェクト指向を加えた厳密なスーパーセットとなっています。TypeScript はクライアントサイド、あるいはサーバサイド (Node.js) で実行される JavaScript アプリケーションの開発にも利用できます。

TypeScript を使用するメリット

TypeScript は、最新の JavaScript の機能を利用することができ、VSCodeAtomVim などの IDE (統合開発環境) もサポートされています。
例えば、VSCode では、コードを入力したときに、コードの下に赤い波線で TypeError (型エラー) などのエラーが表示されるため、自分がミスしたことに気付き、その場ですぐに修正することで開発の効率も上がります。

以下の例で、幾つか TypeScript で使用できる最新の JavaScript 機能について説明します。

1. 非同期処理では、promise() に加えて、 async/await を利用することができます。

promises

function demoTheater() {
    getQuestion()
      .then(function question() {
          console.log(question);
          return "answer";
      })
}

demoTheater();

async/await

async function demoTheater() {
    console.log(await getQuestion());
    return "answer";
}

demoTheater();


2. async/await により動的にモジュールを読み込むことができます。

async function importModule() {
    const esriModule = './utils.js';
    const module = await import(esriModule);
    module.doStuff();
}

importModule();


3. Optional Chaining が使用することができます。
Optional Chaining とは、?. という新しい構文になります。
iOS の開発に使用する Swift の仕様でも Optional はあります。たとえば、obj?.foo は、obj != null ? obj.foo : undefined と同じ意味になります。

以下の構文では let x = foo?.bar.baz(); の例を示しています。

let x = foo?.bar.baz();

let x = (foo === null || foo === undefined) ?
    undefined :
    foo.bar.baz();

以下の構文では let x = foo ?? bar(); の例を示しています。

let x = foo ?? bar();

let x = (foo !== null && foo !== undefined) ?
    foo :
    bar();

TypeScript の開発環境のセットアップ

開発環境として、node や npm がインストールされている環境が必要となります。 その上で、以下のコマンドで TypeScript と ArcGIS API for JavaScript をインストールします。

npm install -g typescript
npm init --yes  
npm install --save @types/arcgis-js-api

サンプルアプリの作成

開発環境が準備できたらサンプルアプリを作成します。
サンプルアプリのフォルダは以下の構成となり、各ファイルを作成していきます。

root-folder/
  index.html
  package.json
  tsconfig.json
  app/
    main.ts

Web ページの作成

index.html に Web ページを作成します。
作成した Web ページの index.html は、以下の内容になります。

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
    <title>ArcGIS JSAPI 4.15 TypeScript Demo</title>
    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
      }
    </style>
    <link rel="stylesheet" href="https://js.arcgis.com/4.15/esri/themes/light/main.css">
    <script>
      var locationPath = location.pathname.replace(/\/[^\/]+$/, "");
      window.dojoConfig = {
        packages: [
          {
            name: "app",
            location: locationPath + "/app"
          }
        ]
      };
    </script>
    <script src="https://js.arcgis.com/4.15"></script>
  </head>
  <body>
    <div id="viewDiv"></div>
    <script>require(["app/main"]);</script>
  </body>
</html>

TypeScript ファイル

TypeScript ファイル(app/main.ts)を作成します。
ここに地図を表示する部分のコードを記述します。
地図の参照には、Web マップを使用します。 MapView に Web マップを読み込ませるために WebMap オブジェクトの portalItem プロパティで Web マップの ID を設定します。そして MapView の map プロパティに WebMap オブジェクトを参照する必要があります。
レイヤーの表示・非表示には、LayerList ウィジェットを使用しています。

import WebMap from "esri/WebMap";
import MapView from "esri/views/MapView";
import LayerList from "esri/widgets/LayerList";

import esri = __esri;

const map = new WebMap({
    portalItem: {
        id: "d5dda743788a4b0688fe48f43ae7beb9"
    }
});

const view = new MapView({
    container: "viewDiv",
    map: map
});

const layerList = new LayerList({
    view,
    listItemCreatedFunction: event => {
        const item: esri.ListItem = event.item;
        if (item.layer.type != 'group') {
            item.panel = {
                content: "legend",
                open: true,
            } as esri.ListItemPanel;
        }
    }
})

view.ui.add(layerList, "top-right");

tsconfig.json

TypeScriptをコンパイルするための構成ファイル tsconfig.json を作成します。

{
  "compilerOptions": {
    "module": "amd",
    "noImplicitAny": true,
    "esModuleInterop": true,
    "sourceMap": true,
    "jsx": "react",
    "jsxFactory": "tsx",
    "target": "es5",
    "experimentalDecorators": true,
    "preserveConstEnums": true,
    "suppressImplicitAnyIndexErrors": true
  },
  "include": [
    "./app/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

tsconfig.json のオプションは、TypeScrip コンパイラで使用するオプションと同じです。各オプションについて詳細は省きますが、注意すべき重要なオプションは次の通りです。

  • compilerOptions.esModuleInterop - true の場合、import x from 'xyz' のようなインポート構文を使用できるようにします。
  • compilerOptions.module - JavaScript API で必要に応じて、TypeScript コードを AMD モジュールにコンパイルします。
  • compilerOptions.target - サポートされているすべてのブラウザで動作するように ES5 で出力します。
  • include - コンパイルするファイルの配列。glob のようなファイルパターンを使用することができます。

コンパイル

アプリケーションのルートで、次のコマンドを実行できます。

tsc - このコマンドは tsconfig.json ファイルを見て、その設定に基づいて TypeScript をコンパイルします。
tsc -w - このコマンドは上記と同じですが、ファイルの変更を監視し、ファイルを編集すると再コンパイルします。

コンパイルを実行すると、app 直下の app/main.jsapp/main.js.map ファイルが作成されます。そして、コンパイルされた js ファイルを参照することで実行できます。

さいごに

今回は、簡単な サンプルを使用した TypeScript を紹介しましたので、次回は JSX も利用した例も紹介できればと思います。