@vue/cli-serviceの実装からcache-loaderのcacheIdentifierの算出方法を読み解く

# はじめに

Vue.jsを使わないTypeScriptのプロジェクトで、ビルドを高速化するためにcache-loaderを導入してデフォルト設定で使っていたのですが、tsconfig.jsを変更した際にビルド結果が変わらないなど問題が発生してしまいました。

そこで、vue-cliで自動生成されるwebpackの設定ではcache-loaderにどのような設定をしているのか調べた結果、`cacheIdentifier`を設定していたため、この値がどのように算出されているのか実装を追ってみました。

具体的には下記の値です。

```js:vue&nbsp;inspect&nbsp;--mode&nbsp;production&nbsp;--rule&nbsp;js
/* config.module.rule('js') */
{
  test: /\.jsx?$/,
  exclude: [
    function () { /* omitted long function */ }
  ],
  use: [
    {
      loader: 'cache-loader',
      options: {
        cacheDirectory: '/path/to/node_modules/.cache/babel-loader',
        cacheIdentifier: '3f3945d2' // <- これ
      }
    },
    {
      loader: 'thread-loader'
    },
    {
      loader: 'babel-loader'
    }
  ]
}
```

# 対象バージョン

[vue-cli@3.2.2](https://github.com/vuejs/vue-cli/tree/v3.2.2)

# 実装

結論としては、ビルド結果に影響するような設定値やファイルを集めたオブジェクトを作り、[hash-sum](https://www.npmjs.com/package/hash-sum)で計算したhash値を`cacheIdentifier`に使用しているようです。

@vue/cli-plugin-typescriptの場合、ts-loaderとtypescriptのpackage.jsonから取得した`version`と、tsconfig.jsonの中身をhash計算に使用しています。

https://github.com/vuejs/vue-cli/blob/v3.2.2/packages/%40vue/cli-plugin-typescript/index.js#L31

```js:vue-cli/packages/@vue/cli-plugin-typescript/index.js&nbsp;から抜粋
  addLoader({
    loader: 'cache-loader',
    options: api.genCacheConfig('ts-loader', {
      'ts-loader': require('ts-loader/package.json').version,
      'typescript': require('typescript/package.json').version,
      modern: !!process.env.VUE_CLI_MODERN_BUILD
    }, 'tsconfig.json')
  })
```

上記で使われている`api.getCacheConfig`の実装を追っていくと、渡されたファイルに加え、@vue/cli-service, cache-loaderのpackage.jsonから取得した`version`、vue.config.jsの`chainWebpack`, `configureWebpack`の内容もhash計算対象に加えているようです。

https://github.com/vuejs/vue-cli/blob/v3.2.2/packages/%40vue/cli-service/lib/PluginAPI.js#L134

```js:vue-cli/packages/@vue/cli-service/lib/PluginAPI.js&nbsp;から抜粋
genCacheConfig (id, partialIdentifier, configFiles) {
  const fs = require('fs')
  const cacheDirectory = this.resolve(`node_modules/.cache/${id}`)

  const variables = {
    partialIdentifier,
    'cli-service': require('../package.json').version,
    'cache-loader': require('cache-loader/package.json').version,
    env: process.env.NODE_ENV,
    test: !!process.env.VUE_CLI_TEST,
    config: [
      this.service.projectOptions.chainWebpack,
      this.service.projectOptions.configureWebpack
    ]
  }

  if (configFiles) {
    // (中略) ファイルを読んでvariableに追加する処理
  }

  const cacheIdentifier = hash(variables)
  return { cacheDirectory, cacheIdentifier }
}
```

この実装によって、ソースコード以外にビルド結果に影響を及ぼすような設定が変更された場合にもcacheが無効化され、常にビルド結果が期待した内容になっているようです。

# @vue/cli-serviceの実装を参考にしたwebpack.config.js

調べた実装を元に、素のwebpackプロジェクトでcache-loaderの`cacheIdentifier`にhash値を設定する簡単なサンプルを組んでみました。

```webpack.config.js
const fs = require('fs')
const hash = require('hash-sum')

const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')

const cacheIdentifier = hash([
  require('typescript/package.json').version,
  require('ts-loader/package.json').version,
  require('cache-loader/package.json').version,
  require('./tsconfig.json'),
  fs.readFileSync('./webpack.config.js', 'utf-8'),
  process.env.NODE_ENV
])

module.exports = {
  mode: process.env.NODE_ENV || 'production',
  entry: ['./src/index.ts'],
  output: {
    path: __dirname + '/dist',
    filename: 'app.js'
  },
  resolve: {
    extensions: ['.js', '.json', '.ts']
  },
  module: {
    rules: [
      {
        test: /\.[jt]s$/,
        use: [
          {
            loader: 'cache-loader',
            options: {
              cacheDirectory: __dirname + '/node_modules/.cache/ts-loader',
              cacheIdentifier
            }
          },
          { loader: 'thread-loader' },
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true,
              happyPackMode: true
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new ForkTsCheckerWebpackPlugin({
      tslint: false,
      formatter: 'codeframe',
      checkSyntacticErrors: true
    })
  ]
}
```

リポジトリは下記においてあります。
https://github.com/clomie/typescript-webpack-cache-identifier-example