15 sから1 sまでの我々のVuejsアプリのロード時間を改善した方法


📝 文脈


LivSpaceはthree-way platform 住宅所有者、デザイナーと請負業者のために.Webアプリケーションに直面している我々の住宅所有者は、Livspaceハブです.本稿ではハブの性能改善について議論する.
LivSpaceのハブは、我々は1つの場所でのプロジェクト関連の更新とドキュメントのすべてを追跡するために住宅所有者のために開発したWebアプリです.それは彼らのプロジェクトの進捗状況を追跡するための単一のストップショップです.LivSpaceを通して彼らの家を設計する住宅所有者は、内部的に「顧客」と呼ばれています、そして、彼らのプロジェクトは内部的に「プロジェクト」と呼ばれています.記事の残りでは、私はlivspaceハブを「ハブ」と言います.

🗓 歴史


ハブは最初に、Laravelアプリとして、UIとバックエンドサーバーを提供して構築されました.Lavavelサーバーが残っていて、我々の代理層として使われる間、UIは後でVue Spaであるために分割されました.
最初の再構築のための主な目標は(スパに私たちのUIを分割)速度だった-私たちはできるだけ早くお客様に我々のアプリのスパバージョンを取得したい.それから、我々は全体的な建築を改善することに集中することができました.
これは明らかに(そして残念ながら)私たちの実装の方法にいくつかのトレードオフが付属しています.
これはハブ用の初期の高レベルアーキテクチャ図がVUE SPAにUIを分割した後のように見えます.

市場へのこのアプローチは、一緒にハプニングされたスパをもたらした.お客様が直面していた平均負荷時間は約15秒(非絞り)!🤯
ここでは私たちの灯台のスコアはシミュレートされたスロットルの下に見えたものです.

このポストでは、我々はそれを改善するために取ったステップについて話します、そして、我々が15秒のロード時間から1秒未満まで行った方法.

🏛 増分改良


フロントエンドとバックエンドのコードベースが別々であることを考えると、それは私たちに段階的に柔軟性を与え、繰り返しスタックの一部を改善しました.
我々は顧客のためのより良い経験にロードマップを設定し、これを3つの主要な目標に分類した.
laravelの依存関係を削除する
TLドクター
これをしたいという主な理由は、メンテナンスの難しさでした-レガシーコードと技術のまわりの専門知識の不足が我々に加わっている新しいチームメンバーと混合することでした.
我々は、薄いNodeJS Expressサーバーでこの層を交換しました.
2 )グラフ層を追加する
TLドクター
LivSpaceは、バックエンドでのマイクロサービスアーキテクチャ(驚きの驚き)を持ち、クライアント側のアプリは、任意のページをレンダリングするためにデータを取得するために複数のサービスへのAPI呼び出しをする必要があります.
それを念頭に置いて、私たちにとって(一般的な)感覚は、私たち(異なるサービスから)のためにこのデータを集めることができるGraphical層を加えるのを見ました.
これはまた、私たちは私たちの3アプリに小さなペイロードを提供する助け-ウェブ、Android、およびIOS.
これは、ポイント1と2を実装した後、ハブのための私たちの高レベルのアーキテクチャが今のように見えるものです.

お客様は、Webアプリ(VUEJs)、またはIOSとAndroidのネイティブアプリ(Reactnative)経由でハブにアクセスすることができます.
この記事の残りの部分については、我々は我々のWebアプリにした改善に焦点を当てるつもりです.私たちのVuejsアプリはNGinx Dockerイメージで構築され、KAbernetesクラスタにAWSでホストされ展開されます.
Webアプリケーションは、主にハブゲートウェイに-私たちのNodeJSプロキシ層に-ゲートウェイは、複数のサービス、主にDarzi -私たちのデータは、私たちのデータは、マイクロサービスのホスト全体からデータを集約する責任があります.
3 )フロントエンド負荷時間を減らす
TLドクター
フロントエンド側では、ハブのためのスパは、それが我々のユーザーのためによく目的を提供したので、適切と思われました.私たちは意識的にNuxtのような何かを使用しないように決定Nuxtと“再書き込み”としては本当によく最適化されたスパの上で私たちにかなり良いアプリを提供しないだろうし、またSEOはハブのための必要性ではないので、決定した.
私たちは、このポストの残りの部分の3点に集中して、我々がフロントエンドでパフォーマンスボトルネックを特定して、固定する方法について詳しく説明します.

👀 パフォーマンスボトルネックの識別


パフォーマンスのボトルネックを識別するのは、過去数年で開発されているいくつかの驚くほど素晴らしいツールのおかげで見えるかもしれないよりもはるかに簡単です.

分析問題
私たちは、かなり標準的なツールセットであるVuecli、Chrome DevTools、灯台を使いました.

Some of the steps mentioned might be specific to Vue, but these could easily be applied to your app in any other UI framework with alternative tools.


VueCLI3 いくつかの素晴らしい機能が付属し、そのような1つですvue ui プロジェクトの構成、依存関係、およびタスクを視覚化し、管理するための開発者向けGUIを提供します.
最も簡単な方法は、ビルドを分析することです.

Task > build > Run Task | Run Analyzer


ここで、アナライザの時間スナップショットのポイントを示します.


あなたが使ったならばWebpack Bundle Analyzer , これは身近に見えるかもしれないが、ちょうど非常に良いUIを持っています.
With vue ui , 私たちは簡単に私たちのアプリと依存関係の部分のビューを読むことができたように、統計解析を解析するために便利なテーブルビューを与えられたと膨らまれていた、解析し、我々の構築のgzipの側面.
我々は、我々のアプリの問題部分を識別する.
ベンダーファイル
  • Bootstrap Vue
  • MomentJS
  • 未使用のパッケージ及び資産
  • 私たちのビルドチャンクファイルが大規模だった- MBSの順序で.
  • 🛠 修正を場所に置く


    ブートストラップVue
    最初のコードベースは全体としてブートストラップVueをインポートしました.
    // Don't do this!
    import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
    
    これは明らかに我々が必要以上に多くを利用してしまうという意味で問題になります.
    ありがたいことに、ブートストラップVueは、我々は我々が必要なものだけをインポートし、我々のバンドルのサイズを減らすことができますツリーシェイブル可能なESMのビルドバリアントを持って、あなたはそれについての詳細を読むことができますhere .
    それから、我々の輸入は、変わりました.
    // --
    // This sort of a "single export" syntax allows us to import
    // only the specifics while bundlers can tree-shake 
    // and remove the unnecessary parts from the library.
    // --
    // Snippet is trimmed down for brevity.
    import {
      .
      .
      LayoutPlugin,
      CardPlugin,
      ModalPlugin,
      FormPlugin,
      NavPlugin,
      NavbarPlugin,
      .
      .
    } from "bootstrap-vue";
    

    Takeaway point: Whenever you're looking to add a new plugin to your app, always look for plugins that allow for tree-shaking.


    2 ) momentjs
    瞬間は/幻想的なライブラリだったが残念ながらend of life 少なくとも積極的な開発に関して.
    また、全体のlibで終わるので、問題になる木の揺れアルゴスでうまく動作しません.
    代替オプションとして、我々は先へ進んだdate-fns , それは私たちに私たちが望むすべてを与え、また小さな足跡を持っていた.
    未使用包装及び資産の除去
    これはほとんど手動での努力でした.私たちは、どのパッケージと資産が使用されていないかを確実に伝えることができたツールを見つけることができませんでした.
    いくつかの検索置換のVSCodeと過度の使用でいくつかの時間を費やした後、我々は不要なフォントファイル、画像、およびいくつかのスクリプトファイルを削除することができ、残りは削除されます.
    パッケージについてpackage.json ファイルとファイル構造は、使用されなかったパッケージとアプリケーションコードを特定するのに十分な洞察を与えました、そして、これらはほとんど1つの点で活発な開発にあったが、現在バックログに押された機能です.
    4)アプリケーションバンドルファイルサイズの縮小.
    4.1 ) Vueルータ性能の最適化
    Vueはいくつかのボックスの方法を最適化し、怠惰なロードルートとルート関連の資産を与える.怠慢なローディングルートは、WebPackがアプリケーションの依存グラフを生成し、したがってチャンクファイルのサイズを小さくする方法を最適化するのに役立ちます.
    私たちの最初のCodeBaseは私たちのルート上の任意の怠惰な負荷を持っていなかったので、単純な変更を修正main かなりの量でバンドルサイズ.Vueルータの設定を怠惰に扱っているものの断片がここにあります.
    // router/index.js
    // --
    // Adding webpackChunkName just gives a nicer more-readable
    // name to your chunk file.
    // --
    {
        path: "/callback",
        name: "OidcCallback",
        component: () =>
          import(
            /* webpackChunkName: "auth-callback" */ "../views/AuthCallback.vue"
          ),
      },
      {
        path: "/",
        name: "Home",
        component: () => import(/* webpackChunkName: "home" */ "../views/Home.vue"),
        children:[{...}]
      }
    }
    
    4.2 )静的資産の事前圧縮
    私たちの高レベルのアーキテクチャ図で見られるように、我々はNGinXサーバーからDockerを介して構築されたアプリケーションに役立ちます.
    NGinxは静的資産の動的圧縮を提供しますが、我々のテストを通して、我々はビルド時間で事前圧縮する資産が我々のファイルのためにより良い圧縮比をもたらして、いくつかのより多くのKBSを保存するのを助けたとわかりました!
    4.3予積載重要資産
    これは、我々のビルドステップに組み込むことを決めた灯台からのヒントです.基本理念はpreload あなたの(着陸)ページが必要とするすべての重要な資産.
    4.4 .スプリットチャンク
    スプリットチャンクを行う最も簡単な方法は、以下の設定を行うだけです.
    optimization: {
      splitChunks: {
        chunks: "all"
      }
    }
    
    しかし、我々は特定の重要なライブラリのための分割チャンクによって最良の結果を得たと我々のサードパーティのパッケージの残りの部分は、共通の塊に入った.
    以下に設定ファイルの設定を示します.
    // vue-config.js
    const path = require("path");
    const CompressionPlugin = require("compression-webpack-plugin");
    const PreloadPlugin = require("@vue/preload-webpack-plugin");
    
    const myCompressionPlug = new CompressionPlugin({
      algorithm: "gzip",
      test: /\.js$|\.css$|\.png$|\.svg$|\.jpg$|\.woff2$/i,
      deleteOriginalAssets: false,
    });
    
    const myPreloadPlug = new PreloadPlugin({
      rel: "preload",
      as(entry) {
        if (/\.css$/.test(entry)) return "style";
        if (/\.woff2$/.test(entry)) return "font";
        return "script";
      },
      include: "allAssets",
      fileWhitelist: [
        /\.woff2(\?.*)?$/i,
        /\/(vue|vendor~app|chunk-common|bootstrap~app|apollo|app|home|project)\./,
      ],
    });
    
    module.exports = {
      productionSourceMap: process.env.NODE_ENV !== "production",
      chainWebpack: (config) => {
        config.plugins.delete("prefetch");
        config.plugin("CompressionPlugin").use(myCompressionPlug);
        const types = ["vue-modules", "vue", "normal-modules", "normal"];
        types.forEach((type) =>
          addStyleResource(config.module.rule("stylus").oneOf(type))
        );
      },
      configureWebpack: {
        plugins: [myPreloadPlug],
        optimization: {
          splitChunks: {
            cacheGroups: {
              default: false,
              vendors: false,
              vue: {
                chunks: "all",
                test: /[\\/]node_modules[\\/]((vue).*)[\\/]/,
                priority: 20,
              },
              bootstrap: {
                chunks: "all",
                test: /[\\/]node_modules[\\/]((bootstrap).*)[\\/]/,
                priority: 20,
              },
              apollo: {
                chunks: "all",
                test: /[\\/]node_modules[\\/]((apollo).*)[\\/]/,
                priority: 20,
              },
              vendor: {
                chunks: "all",
                test: /[\\/]node_modules[\\/]((?!(vue|bootstrap|apollo)).*)[\\/]/,
                priority: 20,
              },
              // common chunk
              common: {
                test: /[\\/]src[\\/]/,
                minChunks: 2,
                chunks: "all",
                priority: 10,
                reuseExistingChunk: true,
                enforce: true,
              },
            },
          },
        },
      },
    };
    
    function addStyleResource(rule) {
      rule
        .use("style-resource")
        .loader("style-resources-loader")
        .options({
          patterns: [path.resolve(__dirname, "./src/styles/sass/*.scss")],
        });
    }
    
    nginxの設定には以下の行だけが必要です.
    # Enable gzip for pre-compressed static files
    gzip_static on;
    gzip_vary on;
    

    🎉 結果終了


    デスクトップ- [いいえ]クリアストレージ

    明確なストレージ- [いいえ]シミュレートされたthrottling

    デスクトップ- [はい]クリアストレージ

    モバイル- [はい]クリアストレージ

    🔮 将来計画


    我々はシミュレートされたスロットルの下で私たちの携帯電話の負荷時間を減らすために、目標は可能な限り低くなることです!これは私たちのゲートウェイとGraphel層を再訪問する必要があります、そして我々は間違いなく私たちのアップグレードの詳細を議論するパート2のブログを共有します.
    また、これらのように確実にネットワークレベルの最適化のいくつかのレベルを追加するには、これらのようにBrotli圧縮、キャッシュ、HTTP 2/3を探索している.もちろん、これは単にハブのためではなく、ウェブ・アプリケーションに面しているデザイナー直面しているベンダーとベンダーのためです.

    💻 我々は雇用している!


    我々は常に驚くべき才能を探している、私たちはLivspace工学で行う作業をチェックアウトするかhere . 我々は、役割の詳細を採用している、詳細を見つけるhere .