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


9

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

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

これでpageB-chunk.jsが正しく作成されます。ここで、このチャンクをpageAでプリフェッチしたいとします。これを行うには、次のステートメントをpageAに追加します。

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

その結果、

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

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

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

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

ここに画像の説明を入力してください

私は最初の2つのステップをトリガしたい↑↑↑↑↑↑↑↑、写真から来https://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/ ↑↑↑↑↑↑↑ ↑↑

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

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


if (false) import(…)-Webpackがデッドコード分析を行うことはないと思いますか?
ベルギ

どこで/とき、あなたが実際にモジュールを評価したいですか?これが、動的importコードの目的です。
ベルギ

私は今とても混乱しています。なぜ評価が重要なのですか?最後に、JSファイルはクライアントのブラウザーデバイスによって評価される必要があるためです。または、質問が正しく理解できません。
AmerllicA

@AmerllicA最終的にはいjsを評価する必要がありますが、このケースを考えてください:私のウェブサイトはA、Bの2ページを取得しました。 JS、ただし、このBのJSが評価される時間を制御できれば、訪問者がページAで「作品を実行」しようとしたときに不具合を作成するメインスレッドをブロックしないことを100%確認できます。 BのJSは、訪問者がページBを指すリンクをクリックした後ですが、その時点でBのJSがダウンロードされている可能性が高いので、少し時間をかけて評価します。
migcoder

確かにChrome v8のブログによると:v8.dev/blog/cost-of-javascript-2019、Workerスレッドと他の多くの技術を使用して、非常に高速なJS解析時間を達成するために多くの最適化を行いました。詳細はこちらのyoutube.comにあります/ watch?v = D1UJgiG4_NI。しかし、他のブラウザはまだそのような最適化を実装していません。
migcoder

回答:


2

更新

あなたは使用することができますプリロード-WebPACKの-プラグインHTML-WebPACKのは、プラグインあなたのチャンクをプリロードするために、それはあなたが設定でプリロードするかを定義できるようになると、それが自動的に挿入タグ意志

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

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

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

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

アップデート2

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

migcoderのbabelプラグインを使用するか、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. 解析と評価を遅らせることができるのはasync、イベントまでdeferタグと タグを使用するDOMContentLoaded場合のみです
  3. スクリプトタグを挿入しない場合は、実行(評価)を遅らせることができます(でプリロードするだけですlink

現在、スクリプト全体を出荷するか、コメントまたは文字列の評価時間はほとんど無視できるため、推奨されない方法が他にもあります。実行する必要があるときに、使用できるか、または両方が推奨されません。stringcommentFunction() constructoreval


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

最近のブラウザーではservice worker、リソース(JavaScript、画像、CSS何か)をフェッチしてキャッシュするために使用でき、そのリソースに対するメインスレッドリクエストが発生した場合、そのリクエストをインターセプトしてキャッシュからリソースを返すことができます。あなたはそれをキャッシュにロードしています

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を使用してバンドルを分割することで、Service Workerをより効率的に利用できます。


それでも私の問題点は、WebpackからファイルのURLを簡単に取得できないことです。SWを使用しても、どのファイルをプリキャッシュするかをSWに通知する必要があります... Webpackマニフェストプラグインはマニフェスト情報を生成できますSW、しかしそれはすべて稼働中である、つまりSWはマニフェストにリストされているすべてのファイルをプリキャッシュする以外に選択肢がないことを意味します...
migcoder

理想的には、webpackが/ * webpackOnlyPrefetch:true * /のような別の魔法のコメントを追加できることを願っています。これにより、遅延読み込み可能なチャンクごとにインポートステートメントを2回呼び出すことができます。
migcoder

1
有効なポイントである@migcoder(実行時に動的に生成されるため、ファイル名を取得できません)が
見つかれ

@migcoder回答を更新しました。問題を解決する答えをご覧ください
Tripurari Shankar

問題の一部を解決し、適切な非同期チャンクを除外できますが、私の最終的な目標は、プリフェッチが要求された非同期チャンクのみです。私は現在、このプラグインgithub.com/sebastian-software/babel-plugin-smart-webpack-importを見ています。それは、すべてのインポートステートメントを収集し、マジックコメントに基づいてコード変更を行う方法を示しています。 'webpackOnlyPrefetch:true'マジックコメントを使用して、インポートステートメントにプリフェッチコードを挿入する同様のプラグイン。
migcoder

1

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


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

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

  • コード内のすべてのimport(args)ステートメントを収集する
  • import(args)に/ * prefetch: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チームにこれを実装してもらいたい場合は、ここで投票してください。

機能:オンデマンドの動的インポートのプリフェッチ


0

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

element.addEventListener('click', async () => {
  const module = await import("...");
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.