web-dev-qa-db-ja.com

Webpackで、評価せずにスクリプトをインポートするにはどうすればよいですか?

私は最近いくつかのウェブサイト最適化作業に取り組んでおり、次のようなimportステートメントを使用して、webpackでコード分割を使用し始めます:

import(/* webpackChunkName: 'pageB-chunk' */ './pageB')

これはpageB-chunk.jsを正しく作成します。今度はprefetch pageAのこのチャンクが必要だとしましょう。pageAに次のステートメントを追加することで実行できます。

import(/* webpackChunkName: 'pageB-chunk' */ /* webpackPrefetch: true */ './pageB')

その結果、

<link rel="prefetch" href="pageB-chunk.js">

hTMLの先頭に追加されると、ブラウザはそれをプリフェッチします。

問題は、ここで使用するimportステートメントです。jsファイルをプリフェッチするだけでなく、jsファイルも評価します。つまり、そのjsファイルのコードが解析され、バイトコードにコンパイルされます。そのJSが実行されます。

これはモバイルデバイスでの非常に時間のかかる操作です。最適化したいのですが、プリフェッチの部分だけが必要です評価して実行の部分は不要です、後でユーザーインタラクションが発生したときに解析と評価自分をトリガーするため

enter image description here

↑↑↑↑↑↑↑↑最初の2つのステップのみをトリガーしたいのですが、画像は https://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/ から取得されます↑↑↑↑↑↑↑↑↑

確かに私はプリフェッチリンクを自分で追加することでこれを行うことができますが、これはプリフェッチリンクに配置する必要があるURLを知る必要があることを意味します、webpackはこのURLを確実に知っています。

これを達成する簡単な方法はありますか?

9
migcoder

更新: npmパッケージにすべてのものを含めて、チェックしてください! https://www.npmjs.com/package/webpack-prefetcher


数日間の調査の後、私はカスタマイズされたbabelプラグインを作成することになります...

つまり、プラグインは次のように機能します。

  • コード内のすべての import(args)ステートメントを収集します
  • import(args)に/ *プリフェッチが含まれる場合:true * /コメント
  • import()ステートメントから chunkId を見つけます
  • Prefetcher.fetch(chunkId)に置き換えます

プリフェッチャーは、webpack出力のマニフェストを含むヘルパークラスであり、プリフェッチリンクの挿入に役立ちます。

export class Prefetcher {
  static manifest = {
    "pageA.js": "/pageA.hash.js",
    "app.js": "/app.hash.js",
    "index.html": "/index.html"
  }
  static function fetch(chunkId) {
    const link = document.createElement('link')
    link.rel = "prefetch"
    link.as = "script"
    link.href = Prefetcher.manifest[chunkId + '.js']
    document.head.appendChild(link)
  }
}

使用例:

const pageAImporter = {
  prefetch: () => import(/* prefetch: true */ './pageA.js')
  load: () => import(/* webpackChunkName: 'pageA' */ './pageA.js')
}

a.onmousehover = () => pageAImporter.prefetch()

a.onclick = () => pageAImporter.load().then(...)

このプラグインの詳細はここにあります:

プリフェッチ-webpackから制御を取得

繰り返しますが、これは本当にハックな方法であり、私は好きではありません。Webpackチームにこれを実装させたい場合は、ここで投票してください。

機能:必要に応じて動的インポートをプリフェッチ

1
migcoder

[〜#〜]更新[〜#〜]

preload-webpack-pluginhtml-webpack-plugin と一緒に使用できます。これにより、構成でプリロードするものを定義でき、チャンクをプリロードするためのタグが自動的に挿入されます

今の時点でwebpack v4を使用している場合は、_preload-webpack-plugin@next_を使用してこのプラグインをインストールする必要があります。

_plugins: [
  new HtmlWebpackPlugin(),
  new PreloadWebpackPlugin({
    rel: 'preload',
    include: 'asyncChunks'
  })
]
_

_chunk.31132ae6680e598f8879.js_と_chunk.d15e7fdfc91b34bb78c4.js_などの動的に生成された名前を持つ2つの非同期スクリプトを生成するプロジェクトの場合、次のプリロードがドキュメントheadに挿入されます

_<link rel="preload" as="script" href="chunk.31132ae6680e598f8879.js">
<link rel="preload" as="script" href="chunk.d15e7fdfc91b34bb78c4.js">
_

更新2

すべての非同期チャンクをプリロードしたくないが、それを行うことができるのは一度だけ特定する場合

migcoderのバベルプラグイン を使用するか、次のように_preload-webpack-plugin_を使用できます。

  1. 最初に、_webpack magic comment_の例を使用して、その非同期チャンクに名前を付ける必要があります

    _import(/* webpackChunkName: 'myAsyncPreloadChunk' */ './path/to/file')
    _
  2. そして、プラグイン設定でそのような名前を使用します

    _plugins: [
      new HtmlWebpackPlugin(),   
      new PreloadWebpackPlugin({
        rel: 'preload',
        include: ['myAsyncPreloadChunk']
      }) 
    ]
    _

まず、scriptタグまたはlinkタグを指定してスクリプトをロードしたときのブラウザの動作を見てみましょう

  1. ブラウザがscriptタグを検出すると、それをロードして解析し、すぐに実行します
  2. asyncdeferタグの助けを借りて、解析と評価を遅らせることができるのは、DOMContentLoadedイベント
  3. スクリプトタグを挿入しない場合は、実行(評価)を遅らせることができます(linkでのみプリロードしてください)

現在、他にもいくつか推奨されていますお勧めできません簡単な方法は、スクリプト全体とstringまたはcomment(評価のためコメントまたは文字列の時間はほとんど無視されます)、実行する必要がある場合はFunction() constructorまたはevalを使用できますが、どちらも推奨されません


別のアプローチサービスワーカー:(これにより、ページの再読み込み後にキャッシュイベントが保持されるか、キャッシュが読み込まれた後にユーザーがオフラインになります)

最近のブラウザーでは、_service worker_を使用してリコース(JavaScript、画像、css何か)をフェッチしてキャッシュし、そのリコースのメインスレッドリクエストが発生したときに、そのリクエストをインターセプトして、解析しない方法でキャッシュからリコースを返すことができます。スクリプトをキャッシュにロードするときにスクリプトを評価して、Service Workerの詳細をご覧ください here

_self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('v1').then(function(cache) {
      return cache.addAll([
        '/sw-test/',
        '/sw-test/index.html',
        '/sw-test/style.css',
        '/sw-test/app.js',
        '/sw-test/image-list.js',
        '/sw-test/star-wars-logo.jpg',
        '/sw-test/gallery/bountyHunters.jpg',
        '/sw-test/gallery/myLittleVader.jpg',
        '/sw-test/gallery/snowTroopers.jpg'
      ]);
    })
  );
});

self.addEventListener('fetch', function(event) {
  event.respondWith(caches.match(event.request).then(function(response) {
    // caches.match() always resolves
    // but in case of success response will have value
    if (response !== undefined) {
      return response;
    } else {
      return fetch(event.request).then(function (response) {
        // response may be used only once
        // we need to save clone to put one copy in cache
        // and serve second one
        let responseClone = response.clone();

        caches.open('v1').then(function (cache) {
          cache.put(event.request, responseClone);
        });
        return response;
      }).catch(function () {
        // any fallback code here
      });
    }
  }));
});

_

ご覧のとおり、これはwebpack依存の問題ではありませんが、webpackの範囲外ですが、webpackを使用すると、バンドルを分割して利用できます。サービスワーカーの方が良い

1

私があなたが達成しようとしていることを理解していると仮定すると、特定のイベント(ボタンのクリックなど)の後にモジュールを解析して実行したいとします。あなたは単にそのイベント内にインポートステートメントを置くことができます:

element.addEventListener('click', async () => {
  const module = await import("...");
});
0
Eliya Cohen