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データベースを作成し、中国語の文字をサポートする方法

推薦する

Linux 環境の Apache サーバーでセカンダリドメイン名を設定する方法の詳細な説明

この記事では、Linux 環境の Apache サーバーでセカンダリ ドメイン名を構成する方法につい...

antd+reactプロジェクトをviteに移行するためのソリューションの詳細な説明

Antd+react+webpackは、多くの場合、Reactテクノロジースタックに基づくフロントエ...

Docker で Maven プロジェクトをより速くビルドする

目次I. 概要2. 従来の多段階イメージ構築3. Buildkitを使用してイメージをビルドする4....

JavaScript はチェックボックスの選択機能を実装します

この記事の例では、すべてのチェックボックスの選択を実現するためのJavaScriptの具体的なコード...

SQLベースのクエリステートメント

目次1. 基本的なSELECT文1. 指定されたフィールドをクエリする3. エイリアスを設定する4....

vue-seamless-scrollがスクロールしていいねをするときのデータ同期の問題を解決する

VUE は vue-seamless-scroll を使用して、自動的にスクロールしていいねします。...

MySQL IDは1から増加し始め、不連続IDの問題を素早く解決します

mysql idは1から始まり、不連続なidの問題を解決するために自動的に増加します。強迫性障害の私...

Reactプロジェクトで要素を使用する方法

React プロジェクトで要素フレームワークを使用するのは今回が初めてです。非常に単純な問題に遭遇し...

VueでJSXを使用する方法

JSXとは何かJSX は Javascript の構文拡張であり、JSX = Javascript ...

Docker nginxは1つのホストを実装して複数のサイトを展開します

とあるサイトからレンタルした仮想マシンの有効期限が近づいており、更新料が200元以上かかります。Al...

Vue モバイル プロジェクトでページ キャッシュを実装する方法のサンプル コード

背景モバイル デバイスでは、ページ ジャンプ間のキャッシュが必須要件です。例: ホームページ =&g...

Vue プロジェクトでよく使用されるツール機能の概要

目次序文1. カスタムフォーカスコマンド1. 方法1 2. 方法2 3. 方法3 2. 入力ボックス...

HTML 代替カラーコードを実現する n 通りの方法 サンプルコード

この記事では、主に HTML のサンプル コードを紹介し、次のように交互に色を変更する方法を共有しま...

Vue2.x および Vue3.x のカスタム命令の使用方法とフック関数の原理を理解する

目次Vue2.x の使用法グローバル登録部分登録使用フック機能フック関数のパラメータVue3.x の...

Docker での Jenkins と Docker を使用した継続的デリバリー

1. 継続的デリバリーとは何かソフトウェア製品の出力プロセスは、ソフトウェアがいつでもリリースできる...