23
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptのimportmapを味方に付けよう

Last updated at Posted at 2022-09-28

概要

今日、ご紹介するのは、JavaScriptのimportmapです。

importmapは、ブラウザで実行するimport構文によって読み込まれるパッケージのURLを指定することができるようにします。

つまり、import構文にエイリアスをアサインすることが可能になります。

すなわち、ブラウザでimport React from "react";というふうに書けるようにできるのです。

これを実現するためには、パッケージをダウンロードするためのURLと、import構文で指定するエイリアス名を関連づける必要があります。

そのやり方を説明していきましょう!

importmapのJSONを書く

どこか任意なところに、新しいダイレクトリを作って、index.htmlというファイルを作成してください。

そこに、以下のようなHTMLテンプレートを使います。

import-maps-demo/index.html
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

</body>

</html>

次、<head>要素の中に<script>要素を追加します。

しかし!これは普通の<script>要素ではありません。

これは、importmapのスクリプト要素だと言って、読み込まれるJavaScriptパッケージとこのHTMLドキュメントで使うimport構文のエイリアスを関連づけるためのものです。

中身は、JSON形式で記入します。

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script type="importmap">
    {
      "imports": {
        "lit": "https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js",
        "react": "https://unpkg.com/react@18/umd/react.development.js",
        "react-dom": "https://unpkg.com/react-dom@18/umd/react-dom.development.js"
      }
    }
  </script>
</head>

JSONの"imports"の中に、このドキュメントで使いたいパッケージのURLとエイリアス名を記入します。

上記のように複数のURLを同時に指定することができます。

また、指定しても、すぐにはダウンロードされません。実際にJavaScriptの中で必要になったときに初めてダウンロードされます。あくまでも関連づけるためのimportmapなのです。

スクリプト要素の中で使用する場合

importmapを一度指定すると、ドキュメント全体でエイリアスを使ったimport構文が書けるようになります。

ただし、スクリプト要素のtype属性moduleと指定する必要があります

試しに、Litを使って、簡単な部品を<script type="module">の中で書いてみましょう。

<body>
  <simple-greeting name="Austin"></simple-greeting>
  
  <script type="module">
    import { LitElement, css, html } from 'lit';

    export class SimpleGreeting extends LitElement {
      static properties = {
        name: {},
      };
      static styles = css`
      :host {
        color: blue;
      }`;

      constructor() {
        super();
        this.name = 'World';
      }

      #handleClick = () => (this.name = "World");

      render() {
        return html`<p @click=${this.#handleClick}>Hello, ${this.name}!</p>`;
      }
    }
    customElements.define('simple-greeting', SimpleGreeting);
  </script>
</body>

結果

上記のindex.htmlのローカルファイルをChromeで開いて試してみた結果は以下の通りです。

ezgif.com-gif-maker (14).gif

無事にダウンロードしてLitを読み込んでくれましたね!

留意していただきたいのは、

  • lit-core.min.jsしかダウンロードされていないこと
  • ローカルで開いているけれど、問題なくダウンロードできていること
  • スクリプト要素ではlitというエイリアスでimportしていること

JavaScriptファイルの中で使う場合

次は、src属性を使ってimport構文を使う場合を紹介します。

進む前に、ローカルでファイルを開くではなく、HTTPを通してファイルを開けるようにしないといけないので簡単なNode.jsサーバーを書きます。

簡単なNode.jsサーバーでローカルHTTPホスティングをする

ChromeはHTTPプロトコルでないと、ESModuleによるインポートができないようになっているので、簡単なサーバーを書きます。

server.js
const http = require("http");
const fs = require("fs");
const path = require("path");

const server = http.createServer((req, res) => {
  const { url } = req;
  if (url.includes(".js")) {
    res.writeHead(200, { "Content-Type": "text/javascript" });
    const jsFilePath = path.resolve(__dirname, "." + url);
    const buffer = fs.readFileSync(jsFilePath);
    res.write(buffer);
    return res.end();
  }
  res.writeHead(200, { "Content-Type": "text/html" });
  const htmlFilePath = path.resolve(__dirname, "./index.html");
  const buffer = fs.readFileSync(htmlFilePath);
  res.write(buffer);
  return res.end();
});

server.listen(3000);

これを以下のように実行します。なお、Node.jsをローカルにインストールする必要があります。

node server.js

ここまでできれば問題なく進めます。

JavaScriptファイルでimport構文を書いてみる

まず、index.htmlでは以下のように外部JavaScriptファイルを指定するスクリプト要素を追加します。

index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script type="importmap">
    {
      "imports": {
        "lit": "https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js",
        "react": "https://unpkg.com/react@18/umd/react.development.js",
        "react-dom": "https://unpkg.com/react-dom@18/umd/react-dom.development.js"
      }
    }
  </script>
  <script src="./simple-greeting.js" type="module" defer></script>
</head>

<body>
  <simple-greeting name="Austin"></simple-greeting>
</body>

</html>

そして、同じダイレクトリにsimple-greeting.jsというファイルを作ります。

そこに、以下のロジックを入れます。

simple-greeting.js
import { LitElement, css, html } from "lit";

export class SimpleGreeting extends LitElement {
  static properties = {
    name: {},
  };
  static styles = css`
    :host {
      color: blue;
    }
  `;

  constructor() {
    super();
    this.name = "World";
  }

  #handleClick = () => (this.name = "World");

  render() {
    return html`<p @click=${this.#handleClick}>Hello, ${this.name}!</p>`;
  }
}
customElements.define("simple-greeting", SimpleGreeting);

結果

http://localhost:3000を開いてみると、先ほどと全く同じ結果が出ますが、simple-greeting.jsもインポートされます。

ezgif.com-gif-maker (15).gif

このようにすると、最近巷でよく聞くバンドル化不要説が少し有力に感じてきますね。

相対的パスを使ってpublicのパッケージをエイリアス化する

最後に、このimportmapを使って、スクリプト要素でサーバーのpublicダイレクトリに置かれているJavaScriptファイルのURLを指定してエイリアスを付ける方法を紹介したいです。

まず、index.htmlimportmapを以下のように更新します。

index.html
<head>
  <script type="importmap">
    {
      "imports": {
        "lit": "https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js",
        "react": "https://unpkg.com/react@18/umd/react.development.js",
        "react-dom": "https://unpkg.com/react-dom@18/umd/react-dom.development.js",
        "simple-greeting": "./simple-greeting.js"
      }
    }
  </script>
</head>

そしてsimple-greeting.jsでは以下のようにロジックを変更します。

simple-greeting.js
import { LitElement, css, html } from "lit";

export const tagName = "simple-greeting";

class SimpleGreeting extends LitElement {
  static properties = {
    name: {},
  };
  static styles = css`
    :host {
      color: blue;
    }
  `;

  constructor() {
    super();
    this.name = "World";
  }

  #handleClick = () => (this.name = "World");

  render() {
    return html`<p @click=${this.#handleClick}>Hello, ${this.name}!</p>`;
  }
}

export default SimpleGreeting;

また、<body>では以下のようにスクリプト要素を追加します。

index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script type="importmap">
    {
      "imports": {
        "lit": "https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js",
        "react": "https://unpkg.com/react@18/umd/react.development.js",
        "react-dom": "https://unpkg.com/react-dom@18/umd/react-dom.development.js",
        "simple-greeting": "./simple-greeting.js"
      }
    }
  </script>
</head>

<body>
  <simple-greeting name="Austin"></simple-greeting>
  <script type="module">
    import SimpleGreeting, { tagName } from "simple-greeting";
    window.customElements.define(tagName, SimpleGreeting)
  </script>
</body>

</html>

結果

先ほどの例と全く同じようにできます。

ezgif.com-gif-maker (16).gif

まとめ

この機能って最高に面白くないですか??

正直、筆者が最近発掘してきたブラウザ機能の中でこれは結構画期的に感じて好きです。

バンドル化が不要なプロジェクトにおいて、将来、こういう手法で外部パッケージの管理が一般的になるのかもしれません。

より詳しい情報を知りたい方は以下のREADMEをご覧ください。

注意点

UPDATE 2024/1/11

全てのエバーグリーンブラウザにシムなしでサポートされるようになった

Shimを使えば94%のブラウザをカバーできます。

23
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?