Nuxtのサードパーティ製Javascriptの読み込む方法を色々試してみた

## サードパーティ製Javascriptを読み込ませる方法

Nuxtにサードパーティ製Javascript(以下外部ライブラリと称します）を読み込む方法は三つあります、[headメソッド](https://ja.nuxtjs.org/docs/2.x/components-glossary/pages-head)や[nuxt.config.js](https://ja.nuxtjs.org/docs/2.x/features/meta-tags-seo#%E3%82%B0%E3%83%AD%E3%83%BC%E3%83%90%E3%83%AB%E3%81%AA%E8%A8%AD%E5%AE%9A-1)での設定とpluginで読み込ませる方法です。

今回は開発中に外部ライブラリの導入にあたってheadメソッドとnuxt.config.jsで設定する方法を試していくつかの問題が起きてしまいまして、最後はpluginで読み込ませる方法を選ぶ経緯をまとめました。

## 方法１ headメソッド

特定なページに外部ライブラリを読み込ませるには一番手軽な方法です。

こんな感じで書けば外部ライブラリを読み込ませる事ができます。

```js
// layoutsもしくはpagesのvueで
head() {
    return {
      script: [
        {
          src: 'https://example.com/example.js',
          body: true,
          defer: true,
          hid: 'example',
          callback: () => {
            console.log('example js loaded');
          },
        },
      ],
    };
  },
```

nuxtはデフォルトで[vue-meta](https://vue-meta.nuxtjs.org/)を内包してるから、headメソッドは[vue-metaのcallback](https://vue-meta.nuxtjs.org/api/#callback)も使えます。

ちなみにcallbackは hid プロパティをつけないと動かないので注意してください(それではまったことあったので）

### この方法のデメリット

手軽で実装できる方法ですが、一番のデメリットは読み込ませる外部ライブラリの内部ロジックによるエラーが起こりやすいです。

特にnuxt-linkによるページ遷移（つまりCSRです)で外部ライブラリが再度読み込まれるとエラーが起きます。

例えば外部ライブラリはSSRの段階で特定の変数を宣言してまして、その後のページ遷移で再度読み込ませると、SSRで宣言した変数がすでに存在するエラーが起きます。
なので、**基本的にはこの方法はおすすめできません。**


## nuxt.config.jsでscriptを読み込む

こちらも手軽に実装できる方法です。

nuxt.config.jsに下記のように書けばすべてのページに該当JSを読み込ませます。

```javascript 
head: {
  script:[
    { src: 'https://example.com/tracker.js', defer: true },
  ]
}
```

### この方法のデメリット

最大のデメリットは全ページにJSが読み込ませることです。
外部ライブラリの機能を使わないページに読み込ませてもパフォーマンスを悪くするので、この方法もあんまりおすすめできません。

## pluginで読み込む

色々と調べましたが、結局一番安定なのはpluginで読み込む方法でした。

plugin単位で読み込みたい外部ライブラリを分けれるしロジックを一箇所集中できるのと、templateファイルのコードもきれいに保てます。

### 実装の流れ

1. 外部ライブラリをページロードイベント後にロードするヘルパーをまず実装します。
2. その後pluginで読み込みたい外部ライブラリを指定します。pluginのメソッドを inject します。
3. templateのmountedで呼び出して外部ライブラリを読み込ませます。

*ヘルパーのコード*

```javascript
// helpers/onDemand.js
export class onDemand {
  // src - 読み込みたいJSのパス
  // waitForPageLoad - page loadイベント後にscriptを読み込むかどうか
  constructor(src, waitForPageLoad = true) {
    this.isLoaded = false;
    this.isLoading = false;
    this.callbacks = [];
    this.src = src;
    this.waitForPageLoad = waitForPageLoad;
  }

  load(callback = () => {}) {
    if (this.isLoaded) return callback();

    this.callbacks.push(callback);

    if (!this.isLoading) {
      this.isLoading = true;
      if (!this.waitForPageLoad || document.readyState === 'complete')
        this._loadScript();
      else window.addEventListener('load', () => this._loadScript());
    }
  }

  _loadScript() {
    const script = document.createElement('script');
    script.src = this.src;
    script.onload = () => this._invokeCallbacks();
    document.getElementsByTagName('head')[0].appendChild(script);
  }

  _invokeCallbacks() {
    this.isLoaded = true;
    this.callbacks.forEach((callback) => callback());
  }
}
```

ソースコードは特に難しいことやってから説明は割愛します。
使い方は簡単で、基本的にconstructorに読み込みたい外部ライブラリのURLを渡せば良いです。

*pluginのコード*

今回 `autoline` という外部ライブラリを読み込むとして

```javascript
// plugins/autoline.client.js

import { onDemand as OnDemand } from '../helpers/onDemand';

export default (ctx, inject) => {
  // constructorに外部ライブラリのパスを渡す
  const loader = new OnDemand('https://autoline.link/main.js');

  inject('autoline', {
    // loadメソッドにcallbackを渡せば、JSの読み込み完了したら実行される。
    load(callback) {
      loader.load(callback);
    },
  });
};
```

`autoline` にloadメソッドをinjectして、すべてのtemplateから `$autoline` で呼び出せます。

nuxt.config.jsに追加するのも忘れずに。

```javascript
plugins: [
    // ...中略
    '~/plugins/autoline.client.js',
  ],
```

最後はtemplateのmountedでinjectされたメソッドを呼び出すだけ。
loadの引数はJSが読み込んだ後に実行したいメソッドです。

```javascript
mounted() {
    this.$autoline.load(this.callback());
},
```

これで特定ページだけかつpage loadイベント後に外部ライブラリを読み込ませることができました。


### pluginで読み込ませる前のpage load

外部ライブラリは全部page loadイベントの前に読み込まれてます。
![configで読み込むとpage loadの前に.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392079/ad0f36dd-fdb8-d606-7afc-0e93056a3eb8.png)

pluginで読み込ませる後のpage load

外部ライブラリはpage loadイベント後に読み込ませることになります。
![pluginでpage load後に読み込ませる.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392079/df6bb193-82ab-9d80-2180-665d5c28c057.png)

### パフォーマンス改善効果
pluginで上記の方法で読み込ませることでpage load timeのパフォーマンスは改善されました。
今回の例では約360ms改善できてます。


### 参考記事
[Nuxt Third Party Code Is Poison: Cure It In 3 Easy Steps](https://deltener.com/blog/nuxt-third-party-code-is-poison/)

記事の中に紹介されてる vue-observe-visibility でコンポーネントが画面に表示するタイミングで外部ライブラリを読み込む手法はパフォーマンス的にかなりいいのでぜひ読んでみてください。

## 最後に
外部ライブラリを読み込ませるのに色々と方法ありますが、やはりpluginで読み込むほうが良いと思います。
最後まで読んでくれてありがとうございます。
それではまた。