概要
今日、ご紹介するのは、JavaScriptのimportmap
です。
importmap
は、ブラウザで実行するimport
構文によって読み込まれるパッケージのURLを指定することができるようにします。
つまり、import
構文にエイリアスをアサインすることが可能になります。
すなわち、ブラウザでimport React from "react";
というふうに書けるようにできるのです。
これを実現するためには、パッケージをダウンロードするためのURLと、import
構文で指定するエイリアス名を関連づける必要があります。
そのやり方を説明していきましょう!
importmapのJSONを書く
どこか任意なところに、新しいダイレクトリを作って、index.html
というファイルを作成してください。
そこに、以下のような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で開いて試してみた結果は以下の通りです。
無事にダウンロードしてLitを読み込んでくれましたね!
留意していただきたいのは、
-
lit-core.min.js
しかダウンロードされていないこと - ローカルで開いているけれど、問題なくダウンロードできていること
- スクリプト要素では
lit
というエイリアスでimport
していること
JavaScriptファイルの中で使う場合
次は、src属性を使ってimport
構文を使う場合を紹介します。
進む前に、ローカルでファイルを開くではなく、HTTPを通してファイルを開けるようにしないといけないので簡単なNode.js
サーバーを書きます。
簡単なNode.jsサーバーでローカルHTTPホスティングをする
ChromeはHTTPプロトコルでないと、ESModuleによるインポートができないようになっているので、簡単なサーバーを書きます。
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ファイルを指定するスクリプト要素を追加します。
<!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
というファイルを作ります。
そこに、以下のロジックを入れます。
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
もインポートされます。
このようにすると、最近巷でよく聞くバンドル化不要説
が少し有力に感じてきますね。
相対的パスを使ってpublic
のパッケージをエイリアス化する
最後に、このimportmap
を使って、スクリプト要素でサーバーのpublic
ダイレクトリに置かれているJavaScriptファイルのURLを指定してエイリアスを付ける方法を紹介したいです。
まず、index.html
のimportmap
を以下のように更新します。
<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
では以下のようにロジックを変更します。
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>
では以下のようにスクリプト要素を追加します。
<!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>
結果
先ほどの例と全く同じようにできます。
まとめ
この機能って最高に面白くないですか??
正直、筆者が最近発掘してきたブラウザ機能の中でこれは結構画期的に感じて好きです。
バンドル化が不要なプロジェクトにおいて、将来、こういう手法で外部パッケージの管理が一般的になるのかもしれません。
より詳しい情報を知りたい方は以下のREADMEをご覧ください。
注意点
UPDATE 2024/1/11
全てのエバーグリーンブラウザにシムなしでサポートされるようになった
Shimを使えば94%のブラウザをカバーできます。