そろそろ大分ブラウザで使える様になった import attributes の話 (もしくは import ... with {type:json / css} の話)

そろそろ使えそうな import attributes は現状 caniuse で次の様な状態となります。 主要だと firefox だけダメみたいですね。(safari も css は対応していない模様？）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32143/a3fb3785-b293-e4bb-02ca-22d5b7694df1.png)

https://caniuse.com/?search=JavaScript%20statement%3A%20import%3A%20Import%20attributes%20(%60with%60%20syntax)

# で、 import attribute って何なの？

## プロポーザル

https://github.com/tc39/proposal-import-attributes

## MDN

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import/with

## 具体例

具体的に言うと次の様な構文です。 type は css と json が定義されています。

```js
// css の読み込み
import styles from 'https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css' with { type: 'css' };
// json の読み込み
import products from 'https://endoflife.date/api/windows.json' with {type:'json'};
// この取得した css は 次の様に適用することができる
document.adoptedStyleSheets = [styles];
// json を画面に表示している
document.getElementById('json').innerText = JSON.stringify(products);
```

## 動作サンプル

<details>
<summary>ソース</summary>

```js
import styles from 'https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css' with { type: 'css' };
import products from 'https://endoflife.date/api/windows.json' with {type:'json'};
console.log(styles);
console.log(products);
document.adoptedStyleSheets = [styles];
json.innerText = JSON.stringify(products);
```

</details>

<p class="codepen" data-height="300" data-default-tab="js,result" data-slug-hash="rNRoqMo" data-pen-title="import attribute test" data-user="juner" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/juner/pen/rNRoqMo">
  import attribute test</a> by juner clarinet (<a href="https://codepen.io/juner">@juner</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

# どこまで動作するかを調査するなら

dynamic import における `import()` メソッドに 第二引数が渡せること、それと type を渡せることを確認すると動作するかがわかります。

## サンプル

※ png / gif は存在しない type として使っているだけです。（ safari だと css import が対応していないからそこらへんはままわかるか……？

<details>
<summary>ソース</summary>

```js
import noop from "https://esm.sh/noop@1.0.1";
// #region type data
/** use types key:[type, dataUrl, isValid] */
const types = {
  json: [
    "json",
    "data:application/json;base64,e30=",
    (v) => v && JSON.stringify(v) === "{}"
  ],
  css: [
    "css",
    "data:text/css;base64,e30=",
    (v) =>
      typeof globalThis.CSSStyleSheet === "function" &&
      v instanceof CSSStyleSheet
  ],
  png: [
    "png",
    "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2NgYGD4DwABBAEAcCBlCwAAAABJRU5ErkJggg==",
    true
  ],
  gif: [
    "gif",
    "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQI12NgYAAAAAMAASDVlMcAAAAASUVORK5CYII=",
    true
  ]
};
// #endregion
/**
 * import attribute support check function
 * @param {[type: string, dataUrl: string, ok:boolean]}
 * @return {[supported: true, 'ok']|[supported: false, error:Error]}
 */
export async function enableWithTypeSyntax([type, dataUrl, ok]) {
  let enableWithTypeSyntax = false;
  const option = (() => {
    const option = {};
    const w = {
      type
    };
    Reflect.defineProperty(option, "with", {
      get: () => ((enableWithTypeSyntax = true), w)
    });
    return option;
  })();
  let result;
  try {
    const importMethod = new Function(
      "dataUrl",
      "option",
      "return import(dataUrl, option);"
    );
    result = await importMethod(dataUrl, option);
    if (!enableWithTypeSyntax) return [false, new Error("not with read.")];
    const isValid =
      typeof ok === "function" ? ok(result?.["default"], result) : !!ok;
    if (!isValid)
      return [
        false,
        new Error("type is invalid", {
          cause: {
            default: result?.default,
            module: result
          }
        })
      ];
    return [true, "ok"];
  } catch (e) {
    if (e instanceof SyntaxError)
      return [
        false,
        new Error("not supported. import attribute syntax", {
          cause: e
        })
      ];
    return [false, e];
  }
}
if (
  globalThis.template instanceof HTMLTemplateElement &&
  globalThis.target instanceof HTMLElement
) {
  for (const [typeName, settings] of Object.entries(types)) {
    const [supported, message] = await enableWithTypeSyntax(settings);
    outputSupportStatus(typeName, supported, message);
    if (message instanceof Error)
      console.error({typeName, supported, message});
    else
      console.log({typeName, supported, message});
  }

  function outputSupportStatus(typeName, supported, message) {
    const node = template.content.cloneNode(true);
    node.querySelector("tr").classList.add(supported ? "ok" : "ng");
    node.querySelector(".type").innerText = typeName;
    node.querySelector(".supported").innerText = supported
      ? "enable"
      : "disable";
    node.querySelector(".message").innerText = message?.message
      ? message?.message
      : message;
    target.appendChild(node);
  }
}
```

</details>

<p class="codepen" data-height="300" data-default-tab="js,result" data-slug-hash="BagYbKO" data-pen-title="import with testing" data-user="juner" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/juner/pen/BagYbKO">
  import with testing</a> by juner clarinet (<a href="https://codepen.io/juner">@juner</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>