JavaScript 以外の静的リソースのバンドルの詳細

JavaScript 以外の静的リソースのバンドルの詳細

この記事はhttps://web.dev/bundling-non-js-resources/から翻訳されたものです。原文は変更されていません。

Web アプリケーションを開発しているとします。この場合、JavaScript モジュールだけでなく、Web Workers (これも JavaScript ですが、独自のビルド依存関係グラフを持ちます)、画像、CSS、フォント、 WebAssemblyモジュールなど、さまざまな他のリソースも扱うことになります。

静的リソースをロードする方法の 1 つは、HTML 内で直接参照することですが、通常は他の再利用可能なコンポーネントと論理的に結合されます。たとえば、カスタム ドロップダウン メニューのCSS JavaScript部分に関連付けられ、アイコン イメージはツールバー コンポーネントに関連付けられ、 WebAssemblyモジュールはJavaScriptグルーに関連付けられます。このような場合、 JavaScriptモジュールからアセットを直接参照し、対応するコンポーネントが読み込まれたときに動的にロードする方が便利で速くなることがよくあります。

ただし、大規模プロジェクト向けのビルド システムのほとんどは、バンドルやminimizeなど、コンテンツの追加の最適化と再編成を実行します。ビルド システムはコードを実行して結果がどうなるかを予測することはできません。また、 JavaScript内のすべての可能な文字列を反復処理して、それがリソース URL であるかどうかを判断する理由もありません。では、 JavaScriptコンポーネントによって読み込まれた動的リソースを「認識」し、ビルド製品に含めるにはどうすればよいでしょうか。

1. パッケージングツールでのカスタムインポート

一般的なアプローチは、既存の静的インポート構文を活用することです。一部のバンドラーはファイル拡張子によってフォーマットを自動的に検出しますが、他のバンドラーでは次の例のようにプラグインがカスタムURL Schemeを使用できるようにします。

// 通常の JavaScript importimport { loadImg } from './utils.js';

// 特別な「URL インポート」静的リソース import imageUrl from 'asset-url:./image.png';
'asset-url:./module.wasm' から wasmUrl をインポートします。
'js-url:./worker.js' から workerUrl をインポートします。

イメージURLをロードします。
WebAssembly.instantiateStreaming(fetch(wasmUrl));
新しいワーカー(workerUrl);

バンドラ プラグインは、認識できる拡張子またはURL Scheme (上記の例では asset-url: および js-url:) を含むインポートを検出すると、参照されているリソースをビルド グラフに追加し、最終的な宛先にコピーし、リソース タイプに適した最適化を実行して、実行時に使用するための最終的な URL を返します。

このアプローチの利点は、 JavaScriptインポート構文を再利用し、すべての URL が静的な相対パスであることを保証することで、ビルド システムがそのような依存関係を簡単に見つけられるようになることです。

ただし、明らかな欠点があります。ブラウザーはカスタム インポート スキームや拡張機能の処理方法を知らないため、このようなコードはブラウザーで直接動作しません。もちろん、すべてのコードを自分で管理していて、開発にバンドラーに依存している場合は、これは素晴らしいことのように思えます。ただし、手間を減らすために、ブラウザでJavaScriptモジュールを直接使用することがますます一般的になりつつあります (少なくとも開発中は)。小規模なdemoでは、本番環境でもバンドラーがまったく必要ない場合もあります。

2. ブラウザとバンドラの共通インポート構文

再利用可能なコンポーネントを開発している場合は、ブラウザで直接使用する場合でも、大規模なアプリケーションの一部として事前に構築する場合でも、あらゆる環境で機能するようにする必要があります。最新のバンドラーのほとんどは、次の JavaScript モジュール インポート構文を受け入れます。

new URL('./relative-path', import.meta.url)

特殊な構文のように見えますが、実際にはブラウザで直接使用できる有効なJavaScript式であり、バンドラーによって静的に検出および処理することもできます。

この構文を使用すると、前の例は次のように書き直すことができます。

// 通常のJavaScriptインポート
'./utils.js' から { loadImg } をインポートします。
loadImg(新しいURL('./image.png', import.meta.url));
WebAssembly.instantiateStreaming() は、
  フェッチ(新しいURL('./module.wasm'、import.meta.url))、
  { /* … */ }
);
新しいワーカー(新しい URL('./worker.js'、import.meta.url));


どのように動作するかを分析してみましょう: new URL(...)コンストラクターは、2 番目のパラメーターの絶対 URL に基づいて、最初のパラメーターの相対 URL に対応する URL を解決します。私たちの場合、2番目の引数はimport.meta.url [1]で、これは現在のJavaScriptモジュールのURLなので、最初の引数はそれに相対的な任意のパスにすることができます。

動的インポートと同様の利点と欠点があります。 import (...) を使用してコンテンツをインポートすることは可能ですが、バンドラーはURL importを含むインポートを特別に扱います。これは、コンパイル時に既知の依存関係を前処理し、コードをチャンク化して動的に読み込む方法として使用されます。

同様に、 new URL (...) をnew URL (relativeUrl, customAbsoluteBase) のように使用できますが、 new URL ('...', import.meta.url) 構文は、依存関係を前処理してメインの JavaScript アセットとバンドルするようにバンドラーに明示的に指示します。

3. あいまいな相対URL

バンドラーが他の一般的な構文 (たとえば、 new URLラッパーなしのfetch ('./module.wasm')) を検出できないのはなぜかと疑問に思うかもしれません。

その理由は、 importキーワードとは異なり、動的なリクエストは、解析中の現在の JavaScript ファイルではなく、ドキュメント自体を基準にしているためです。次のような構造があるとします。

インデックス.html:

<script src="src/main.js" type="module"></script>


ソース/

メイン.js

モジュール.wasm

main.jsからmodule.wasmをロードしたい場合、最初の直感としては fetch('./module.wasm') のような相対パス参照を使用することでしょう。

ただし、 fetch実行中のJavaScriptファイルの URL を認識しません。代わりに、ドキュメントを基準とした URL を解決します。したがって、 fetch ('./module.wasm') は、予期されるhttp://example.com/src/module.wasmではなく、 http://example.com/module.wasm読み込もうとすることになり、失敗します (または、運が悪ければ、予期したものとは異なるリソースを黙って読み込みます)。

相対 URL をnew URL ('...'、import.meta.url) でラップすることで、この問題を回避し、提供された URL がローダーに渡される前に、現在の JavaScript モジュールの URL (import.meta.url) を基準として解決されることを保証できます。

fetch ('./module.wasm') をfetch (new URL('./module.wasm', import.meta.url)) に置き換えるだけで、目的のWebAssemblyモジュールが正常に読み込まれると同時に、バンドラーはビルド時にこれらの相対パスを確実に見つけられるようになります。

4. ツールチェーンのサポート

4.1 パッケージングツール

次のバンドラーはすでに新しい URL 構文をサポートしています。

  • Webpack v5
  • Rollup (プラグイン経由でサポートされます: 汎用アセットの場合は @web/rollup-plugin-import-meta-assets、ワーカーの場合は @surma/rollup-plugin-off-main- thread 。)
  • Parcel v2 (beta)
  • Vite

5. Webアセンブリ

WebAssemblyを使用する場合、通常はWasmモジュールを手動でロードするのではなく、ツールチェーンによって生成されたJavaScriptグルー コードをインポートします。次のツールチェーンは、新しい URL(...) 構文を生成できます。

5.1 EmscriptenでコンパイルされたC/C++

Emscriptenツールチェーンを使用する場合、次のオプションを使用して、通常の JS コードではなく ES6 モジュール グルー コードを出力するように要求できます。

$ emcc 入力.cpp -o 出力.mjs
## mjs 拡張機能を使用しない場合:
$ emcc 入力.cpp -o 出力.js -s EXPORT_ES6


このオプションを使用すると、出力グルーコードはnew URL (...、import.meta.url) 構文を使用するため、バンドラーは関連する Wasm ファイルを自動的に見つけることができます。

-pthreadパラメータを追加することで、この構文はWebAssemblyスレッドのコンパイルもサポートできるようになります。

$ emcc 入力.cpp -o 出力.mjs -pthread
## mjs 拡張機能を使用しない場合:
$ emcc 入力.cpp -o 出力.js -s EXPORT_ES6 -pthread


この場合、生成されたWeb Worker同じ方法で参照され、バンドラーとブラウザによって正しく読み込まれます。

5.2 wasm-pack / wasm-bindgen でコンパイルされた Rust

wasm-pack --WebAssembly WebAssembly 用の主要な Rust ツールチェーン。複数の出力モードも備えています。

デフォルトでは、 WebAssembly ESM統合提案に依存するJavaScriptモジュールを出力します。この記事の執筆時点では、この提案はまだ実験段階であり、出力はWebpackとバンドルされている場合にのみ機能します。

あるいは、-target web 引数を使用して wasm-pack にブラウザ互換の ES6 モジュールを出力するように要求することもできます。

$ wasm-pack ビルド --target web


出力では、上で説明した新しい URL(..., import.meta.url) 構文が使用され、Wasm ファイルはバンドラーによって自動的に検出されます。

RustからWebAssemblyスレッドを使用する場合は、少し複雑になります。詳細についてはガイド[13]の対応するセクションを参照してください。

つまり、任意のスレッドAPIを使用することはできませんが、Rayon [14]を使用する場合は、Web上で実行できるワーカーを生成できるwasm-bingen-rayon [15]アダプタを試すことができます。 wasm-bindgen-rayonが使用するJavaScriptグルーには[16]新しいURL(...)構文も含まれているため、バンドラーによってWorkersを検出してインポートすることもできます。

6. 今後の輸入方法

6.1 インポート.meta.resolve

将来的に改善される可能性のある点の 1 つは、特殊なimport.meta.resolve(...)構文です。追加の引数を必要とせずに、現在のモジュールに関連するコンテンツをより直接的に解決できるようになります。

// 現在の構文 new URL('...', import.meta.url)

// 将来の構文 await import.meta.resolve('...')

また、 import構文と同じモジュール解決システムによって処理されるため、 import mapsマップやカスタム リゾルバとの統合も向上します。これは、URL などのランタイム API に依存しない静的構文であるため、バンドラーにとってより信頼性の高いシグナルでもあります。

import.meta.resolve実験的な機能として Node.js に実装されていますが、Web 上でどのように動作するかについては未解決の疑問がまだいくつか残っています。

6.2 インポートアサーション

インポートアサーションは、ECMAScript モジュールの外部の型をインポートできる新しい機能ですが、現時点では JSON 型のみがサポートされています。

foo.json

{ "答え": 42 }


メイン.mjs

'./foo.json' から json をインポートします。 assert { type: 'json' };
console.log(json.answer); // 42


(翻訳者注: このあまり直感的ではない構文の選択に関する興味深い情報もいくつかあります https://github.com/tc39/proposal-import-assertions/issues/12)

これらはバンドラーによって使用され、新しい URL 構文で現在サポートされているシナリオを置き換えることもできますが、インポートアサーションの型は 1 つずつサポートされる必要があります。現在はJSONのみがサポートされています。CSS CSSは近日中にリリースされる予定ですが、他の種類のリソース インポートには、より一般的なソリューションがまだ必要です。

この機能の詳細については、v8.dev [19]の機能説明を参照してください。

7. まとめ

ご覧のとおり、Web 上にJavaScript以外のリソースを含める方法はさまざまですが、それぞれに長所と短所があり、同時にすべてのツールチェーンで機能する方法はありません。将来の提案では、専用の構文を使用してこれらのリソースをインポートできるようになるかもしれませんが、まだそこまでには至っていません。

その日が来るまでは、 new URL (...、import.meta.url) 構文が最も有望なソリューションであり、現在、ブラウザー、さまざまなバンドラー、 WebAssemblyツールチェーンですでに動作しています。

非 JavaScript 静的リソースのパッケージ化に関するこの記事はこれで終わりです。非 JavaScript 静的リソースのパッケージ化に関する関連コンテンツの詳細については、123WORDPRESS.COM で以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

8. 参考文献

[1]; import.meta.url: https://v8.dev/features/modules#import-meta

[2]; 動的インポート: https://v8.dev/features/dynamic-import

[3]:コード分割: https://web.dev/reduce-javascript-payloads-with-code-splitting/

[4]:Webpack v5: https://webpack.js.org/guides/asset-modules/#url-assets

[5]:ロールアップ: https://rollupjs.org/

[6]: @web/rollup-plugin-import-meta-assets: https://modern-web.dev/docs/building/rollup-plugin-import-meta-assets/

[7]: @surma/rollup-plugin-off-main-thread: https://github.com/surma/rollup-plugin-off-main-thread

[8]: Parcel v2(ベータ版): https://v2.parceljs.org/languages/javascript/#url-dependencies

[9]:Vite: https://vitejs.dev/guide/assets.html#new-url-url-import-meta-url

[10]:WebAssembly: https://web.dev/webassembly-threads/#c

[11]: wasm-pack: https://github.com/rustwasm/wasm-pack

[12]:WebAssembly ESM統合提案: https://github.com/WebAssembly/esm-integration

[13]: 該当部分: https://web.dev/webassembly-threads/#rust

[14]:レイヨン: https://github.com/rayon-rs/rayon

[15]: wasm-bindgen-rayon: https://github.com/GoogleChromeLabs/wasm-bindgen-rayon

[16]: 以下も含まれています: https://github.com/GoogleChromeLabs/wasm-bindgen-rayon/blob/4cd0666d2089886d6e8731de2371e7210f848c5d/demo/index.js#L26

[17]: 実験的な機能: https://nodejs.org/api/esm.html#esm_import_meta_resolve_specifier_parent

[18]: まだ解決されていない問題がいくつかあります: https://github.com/WICG/import-maps/issues/79

[19]: v8.devでの機能説明: https://v8.dev/features/import-assertions

以下もご興味があるかもしれません:
  • Nuxt.js の静的リソースとパッケージ化操作

<<:  一般的な XHTML タグの紹介

>>:  MySQLデータベースを作成し、中国語の文字をサポートする方法

推薦する

MySQL 5.7.18 MSI インストール グラフィック チュートリアル

この記事では、参考までにMySQL 5.7.18 MSIインストールチュートリアルを紹介します。具体...

mysql 結合クエリ (左結合、右結合、内部結合)

1. MySQLの一般的な接続INNER JOIN (内部結合、または等価結合): 2 つのテーブ...

MySql8.0 のトランザクション分離レベルエラーの問題を解決する

目次MySql8.0 トランザクション分離レベルエラーの表示質問コマンドは次のように変更されますMy...

html-cssタグのスタイル設定が機能しない2つの理由

1 セミコロン「;」のない CSS スタイル2 タグが閉じられておらず、「>」がありません...

Vscode が Ubuntu にリモート接続する際のエラー問題の解決方法

1. 事件の背景:仕事上、Ubuntu への vscode リモート接続を使用する必要があります。 ...

MySQLのインデックス選択と最適化の詳細な説明

目次インデックスモデルB+ツリーインデックスの選択インデックスの最適化インデックスの選択性カバーイン...

WeChatアプレットは写真アップロード機能を実現

この記事の例では、WeChatアプレットで写真をアップロードするための具体的なコードを参考までに共有...

WeChatアプレットは固定ヘッダーとリストテーブルコンポーネントを実装します

目次必要:機能ポイントレンダリング実装のアイデア具体的なコード(react\taro3.0)特定のコ...

モバイルデバイス用のメタタグ設定の完全なリスト

序文以前フロントエンドを勉強していたとき、メタタグに対する私の理解はこの一文だけでした。 <メ...

CentOS7 環境での DHCP 設定チュートリアル

目次CentOS7環境での設定コマンド手順1. DHCP設定ファイルを設定する2. グローバル構成を...

HTML の類似タグと属性の違いの詳細な説明

【1】<i></i>タグと<em></em>タグ同じ...

大きなオフセットによる MySQL 制限ページングが遅い理由と最適化ソリューション

MySQL では通常、limit を使用してページ上のページング機能を完了しますが、データ量が大きな...

MySQL 5.7.16 無料インストール版のインストールと設定方法のグラフィックチュートリアル

この記事ではMySQL 5.7.16のインストールと設定方法を記録します。具体的な内容は以下のとおり...

Vue3における7種類のコンポーネント通信の詳細

目次1. Vue3コンポーネント通信方式2. Vue3通信の使い方2.1 小道具2.2 $エミット2...

MySQL の binlog ログと、binlog ログを使用してデータを回復する方法を説明します。

ご存知のとおり、binlog ログは MySQL データベースにとって非常に重要です。万が一、データ...