Connecting the API using TypeScript

Note

In Mappable Account, log in or create a new account. The main page will display a key that will work for any Mappable service.

The key is activated within 15 minutes after you receive it.

Regular connection

<!DOCTYPE html>
<html>
  <head>
    <!-- Replace YOUR_API_KEY with the real key -->
    <script src="https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US"></script>

    <script type="module" src="index.js"></script>
  </head>
  <body>
      <div id="app" style="width: 600px; height: 400px"></div>
  </body>
</html>
import type { MMapLocationRequest } from 'mappable';

async function initMap(): Promise<void> {
    await mappable.ready;

    const LOCATION: MMapLocationRequest = {
        center: [25.229762, 55.289311],
        zoom: 9
    };

    const { MMap, MMapDefaultSchemeLayer } = mappable;

    const map = new MMap(document.getElementById('app'), { location: LOCATION });
    map.addChild(new MMapDefaultSchemeLayer({}));
}

initMap();
{
  "compilerOptions": {
    "target": "es2015",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "esModuleInterop": true,
    "moduleResolution": "node",
    "typeRoots": [
      "./node_modules/@types",
      "./node_modules/@mappable-world/mappable-types"
    ],
    "paths": {
      "mappable": [
        "./node_modules/@mappable-world/mappable-types"
      ]
    }
  }
}
{
    "devDependencies": {
        "@mappable-world/mappable-types": "^0.0.10",
        "http-server": "14.1.1",
        "typescript": "5.2.2"
    },
    "scripts": {
        "compile": "./node_modules/.bin/tsc",
        "start": "npx http-server ."
    }
}

Set dependencies, compile TypeScript, and run a local server:

npm install
npm run compile
npm run start

Open the app

Specifics of regular connection

  1. In package.json, add a dev dependency on the @mappable-world/mappable-types package.

    We recommend installing the latest version:

    npm i --save-dev @mappable-world/mappable-types@latest

  2. In tsconfig.json, set compilerOptions.typeRoots with a list of paths to type files. Add a path to the @mappable-world/mappable-types package there to make the mappable namespace with types appear in the global scope.

    Note

    The mappable namespace contains all the class types provided by the JS API, but they are not available in the runtime environment until mappable.ready is resolved.

  3. In tsconfig.json, set compilerOptions.paths, informing the TS compiler that the contents of the imported mappable package should be searched for at the specified path. This enables you to import types in project files as if they were located not at @mappable-world/mappable-types, but in the mappable package:

    import type { MMapLocationRequest } from 'mappable';
    

    All types must be imported from the root.

    The internal structure isn't guaranteed and can change over time.

  4. Specify the type="module" attribute in the script tag that loads the compiled JS project so that the browser enables ESM support in JS files.

    If this is not done in the example, an error will occur:

    SyntaxError: Unexpected token 'export {}'

Connecting via top-level-await (recommended)

<!DOCTYPE html>
<html>
  <head>
    <!-- replace YOUR_API_KEY with the real key -->
    <script src="https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US"></script>
    <script type="module" src="index.js"></script>
  </head>
  <body>
    <div id="app" style="width: 600px; height: 400px"></div>
  </body>
</html>
import type { MMapLocationRequest } from 'mappable'
import { MMap, MMapDefaultSchemeLayer } from './lib/mappable.js'

const LOCATION: MMapLocationRequest = {
    center: [25.229762, 55.289311],
    zoom: 9
};

const map = new MMap(
    document.getElementById('app'),
    {
        location: LOCATION
    }
);

map.addChild(new MMapDefaultSchemeLayer());
await mappable.ready;

export const {MMap, MMapDefaultSchemeLayer} = mappable;
{
  "compilerOptions": {
    "target": "es2017",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "typeRoots": [
      "./node_modules/@types",
      "./node_modules/@mappable-world/mappable-types"
    ],
    "paths": {
      "mappable": [
        "./node_modules/@mappable-world/mappable-types"
      ]
    }
  }
}
{
    "devDependencies": {
        "@mappable-world/mappable-types": "^0.0.10",
        "http-server": "14.1.1",
        "typescript": "5.2.2"
    },
    "scripts": {
        "compile": "./node_modules/.bin/tsc",
        "start": "npx http-server ."
    }
}

Set dependencies, compile TypeScript, and run a local server:

npm install
npm run compile
npm run start

Open the app

Specifics

  1. Specify the type="module" attribute in the "script" tag that loads the compiled project JS to enable ECMAScript Modules (ESM) and top-level-await support:

    <script type="module" src="index.js"></script>
    
    ESM support when using bundlers

    If you're building a project using a bundler (for example, Webpack), add "type": "module" to package.json

  2. In tsconfig.json, for top-level-await to operate correctly, the compilerOptions.module parameter must be set to one of the following values: es2022, esnext, system, or preserve. The compilerOptions.target parameter must be set to es2017 or higher.

  3. In lib/mappable.ts, wait until the JS API is fully loaded and then export the necessary map components so that they can be used in other parts of the project:

    await mappable.ready;
    
    export const {MMap, MMapDefaultSchemeLayer} = mappable;
    
  4. The use of top-level-await in lib/mappable.ts guarantees that mappable.ready is executed before the map components are imported, so the project code can be shorter and cleaner (the asynchronous initMap function is no longer needed):

    import { MMap, MMapDefaultSchemeLayer } from './lib/mappable.js'
    
    const map = new MMap({...});
    

Connecting with Webpack

Method 1

<!DOCTYPE html>
<html>
    <head>
        <!-- replace YOUR_API_KEY with the real key -->
        <script src="https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US"></script>
        <script src="build/bundle.js"></script>
    </head>
    <body>
        <div id="app" style="width: 600px; height: 400px"></div>
    </body>
</html>
import { type MMapLocationRequest } from 'mappable';

async function initMap() {
  await mappable.ready;

  const LOCATION: MMapLocationRequest = {
    center: [25.229762, 55.289311],
    zoom: 9
  };

  const { MMap, MMapDefaultSchemeLayer } = mappable;

  const map = new MMap(document.getElementById('app'), {location: LOCATION});
  map.addChild(new MMapDefaultSchemeLayer({}));
}

initMap();
{
  "compilerOptions": {
    "target": "es2015",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "esModuleInterop": true,
    "moduleResolution": "node",
    "typeRoots": [
      "./node_modules/@types",
      "./node_modules/@mappable-world/mappable-types"
    ],
    "paths": {
      "mappable": [
        "./node_modules/@mappable-world/mappable-types"
      ]
    }
  },
}
const path = require('path');

module.exports = {
    mode: 'development',
    entry: './index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'build'),
    },
    devtool: 'cheap-source-map'
};
{
  "scripts": {
    "compile": "./node_modules/.bin/tsc",
    "build": "webpack",
    "start": "npx http-server ."
  },
  "devDependencies": {
    "@mappable-world/mappable-types": "^0.0.10",
    "http-server": "14.1.1",
    "typescript": "5.2.2",
    "webpack": "5.88.2",
    "webpack-cli": "5.1.4"
  }
}

Set dependencies, compile TypeScript, build a project, and run a local server:

npm install
npm run compile
npm run build
npm run start

Open the app

This method is no different from the previous connection. It only adds a step of building from project JS files into a single build/bundle.js

Method 2

<!DOCTYPE html>
<html>
    <head>
        <script src="build/bundle.js"></script>
    </head>
    <body>
        <div id="app" style="width: 600px; height: 400px"></div>
    </body>
</html>
import type { MMapLocationRequest } from '@mappable-world/mappable-types';
import {MMap, MMapDefaultSchemeLayer} from '@mappable-world/mappable-types';

const LOCATION: MMapLocationRequest = {
  center: [37.623082, 55.75254],
  zoom: 9
};

const map = new MMap(document.getElementById('app'), {location: LOCATION});
map.addChild(new MMapDefaultSchemeLayer({}));
{
  "compilerOptions": {
    "target": "es2015",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "esModuleInterop": true,
    "moduleResolution": "node",
    "typeRoots": [
      "./node_modules/@types",
      "./node_modules/@mappable-world/mappable-types"
    ]
  },
}
const path = require('path');

module.exports = {
    mode: 'development',
    entry: './index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'build'),
    },
    externals: {
      '@mappable-world/mappable-types': [
        `promise new Promise((resolve) => {
            if (typeof mappable !== 'undefined') {
              return mappable.ready.then(() => resolve(mappable));
            }

            const script = document.createElement('script');
            script.src = "https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US";
            script.onload = () => {
              mappable.ready.then(() => resolve(mappable));
            };
            document.head.appendChild(script);
          })`
      ]
    },
    devtool: 'cheap-source-map'
};
{
  "scripts": {
    "compile": "./node_modules/.bin/tsc",
    "build": "webpack",
    "start": "npx http-server ."
  },
  "devDependencies": {
    "@mappable-world/mappable-types": "^0.0.10",
    "http-server": "14.1.1",
    "typescript": "5.2.2",
    "webpack": "5.88.2",
    "webpack-cli": "5.1.4"
  }
}

Set dependencies, compile TypeScript, build a project, and run a local server:

npm install
npm run compile
npm run build
npm run start

Open the app

Specifics

  1. You no longer need to connect the <script> tag in the HTML document header — Webpack does this for you.

  2. In webpack.config.js, declare the external @mappable-world/mappable-types variable where you need to specify a path to the API loader and where the mappable.ready promise is resolved.

    This makes it possible to import JS API types and classes in the project code as if the code were delivered not via a global variable, but via an npm package @mappable-world/mappable-types:

    // Importing types
    import type { MMapLocationRequest } from '@mappable-world/mappable-types';
    
    // Importing runtime code
    import {MMap, MMapDefaultSchemeLayer} from '@mappable-world/mappable-types';
    
  3. tsconfig.json no longer needs the compilerOptions.paths.mappable setting.

  4. In this case, fully loaded JS API modules become available and mappable.ready execution is guaranteed in the built project JS file, so the project code can be shorter and cleaner (the asynchronous initMap function is no longer needed).

    import type { MMapLocationRequest } from '@mappable-world/mappable-types';
    import {MMap, MMapDefaultSchemeLayer} from '@mappable-world/mappable-types';
    
    const {MMap} = mappable;
    const map = new MMap({...});
    

Setting up an alias

Note that in the last example, import is performed from a package named @mappable-world/mappable-types. You can change this name using an alias in package.json:

{
  "devDependencies": {
    // ...
    "@mappable-world/mappable": "npm:@mappable-world/mappable-types@0.0.10",
    // ...
  }
}

After that, remember to update the external variable name in webpack.config.js:

module.exports = {
  //...
  externals: {
    '@mappable-world/mappable': [
      // ...
    ]
  }
}

After all the changes, you can import types and classes from the @mappable-world/mappable package:

import type { MMapLocationRequest } from '@mappable-world/mappable';
import {MMap, MMapDefaultSchemeLayer} from '@mappable-world/mappable';
Next