Connecting the API with TypeScript

Usual connection

<!DOCTYPE html>
<html>
  <head>
    <!-- Substitute the value of the real key instead of YOUR_API_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 ."
    }
}

Install the dependencies, compile typescript and start the local server:

npm install
npm run compile
npm run start

Open application

Specificities of the usual connection

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

    It is recommended to install the latest version:

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

  2. In tsconfig.json we set CompilerOptions.typeRoots with a list of paths to file types. Adding the path to the package @mappable-world/mappable-types there, so that the namespace mappable with types appears in the global scope.

    Note

    The namespace mappable contains all the class types that the JS API provides, but they are not available in the runtime until the resolving mappable.ready.

  3. In tsconfig.json we set CompilerOptions.paths, in which we inform the ts compiler that when importing the package mappable, its content should be searched for in the specified path. Thanks to this, you can import types in project files as if they are not in the @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 is not guaranteed and may change over time.

  4. In the script tag that loads the compiled project js-file, specify the type="module" attribute so that the browser activates support for ESM 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>
    <!-- Substitute the value of the real key instead of YOUR_API_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 ."
    }
}

Install the dependencies, compile typescript and start the local server:

npm install
npm run compile
npm run start

Open application

Specificities

  1. In the script tag that loads the compiled project js, specify the attribute type="module" to activate support for ECMAScript Modules (ESM) and top-level-await:

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

    If a bundler is used to build the project, for example Webpack, then in the package.json file needs to add "type": "module"

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

  3. In the file lib/mappable.js we are waiting for the JS API to be fully loaded, after which we export the necessary map components for use in other parts of the project:

    await mappable.ready;
    
    export const {MMap, MMapDefaultSchemeLayer} = mappable;
    
  4. Using top-level-await in lib/mappable.js guarantees the execution of mappable.ready before importing map components, so the project code can be written shorter and neater (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>
        <!-- Substitute the value of the real key instead of YOUR_API_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"
  }
}

Put the dependencies, compile typescript, build the project and run the local server:

npm install
npm run compile
npm run build
npm run start

Open application

This method is no different from the previous connection. Only the build step from the project js files is added to 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: [25.229762, 55.289311],
  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"
  }
}

Put the dependencies, compile typescript, build the project and run the local server:

npm install
npm run compile
npm run build
npm run start

Open application

Specificities

  1. It is no longer necessary to connect the tag <script> in the header of the HTML page. This is done by webpack itself.

  2. In webpack.config.js we declare an external variable @mappable-world/mappable-types, in which we specify the path to the API loader and the promise mappable.ready is resolved there.

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

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

  4. With this connection, fully loaded JS API modules become available in the assembled project js file and the execution of mappable.ready is guaranteed, so the project code can be written shorter and neater (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({...});
    

Alias

Note that in the last example, the import comes from a package named @mappable-world/mappable-types. This name can be changed using the alias in package.json:

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

Don't forget to update the name of the external variable in webpack.config.js:

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

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

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

Examples

Codesandbox