6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

trace-to-treeでNext.jsの起動タイムをトレースする

Last updated at Posted at 2024-03-27

開発環境で作ったNext13のプロジェクトが、どうも動きが遅い(ページ遷移など)ので、原因を調査してみることにしました。

それで見つけたのがzenn様による以下のページです。

ですが、この記事にしれっと出てくるtrace-to-tree.mjsという謎のライブラリ、これについて入手方法、使用方法が書かれていません。WEBを検索してみても日本語のサイトはほとんど出てこず、有効な手がかりを得たのがDEV COMMUNITY様のこの記事です。

なるほど、githubから必要なライブラリを入手すればいいのだと思い、実践してみました。

今回の使用環境

以下の通りです

  • Rocky Linux 9.1
  • Next13.4
  • NPM10.2.4
  • node20.11.0

また今回、トレースしたプロジェクトはnext13という少し紛らわしい名前になっています。

trace-to-tree.mjsを入手する

まずはgithubからNext.jsのライブラリをクローンしてしまいましょう(プロジェクト名とは別です)

# cd /var/www/html
# git clone https://github.com/vercel/next.js.git && cd next.js

使い方

上述のDEVのページを見る限り、

#node scripts/trace-to-tree.mjs /your/app/.next/trace

となっているので、

#node 使用元のtrace-to.tree.mjsのパス トレースしたいプロジェクトのtraceファイル

このようにパスを変えれば、どこに置いても、どれでも対応できるみたいです。なので自分のプロジェクトのnext13に移行しておきました(クローン後のnext.jsは削除しても問題ありません)

#cp next.js/scripts/trace-to-tree.mjs /next13

起動までの道のり

では、実行してみます。traceの場所は.next/traceとなっているので、以下のように記述します。

#node trace-to-tree.mjs .next/trace

すると

cannot find module 'event-stream'

こんなメッセージが出たので調べてみると、npmを入れ直した方がいいという意見が大半。ところがこれを実施しても解決せず。それでtrace-to-treeのmjsファイルを覗いてみるとevent-streamというライブラリをインポートしているような記述があります。

trace-to-tree.mjs
import fs from 'fs'
import eventStream from 'event-stream'
/*後略*/

だとすると、これはライブラリが足りないだけなのでは? そう思ってインストールしてみたら、普通にインストールされました。

#npm i event-stream

晴れて再度起動してみると、今度は

cannot open file or directory ../packages/next/dist/lib/picocolors.js

このような文言が表示されていました。どうもpicocolors.jsというライブラリは外部から呼び出しているようなので、それならばとこれもパッケージをインストールしてみました。ちなみにpicocolorsとはメッセージを蛍光色に変える働きを持ちます(参考の元記事の画像のように、カラフルになります)。

#npm i picocolors

それからmjsファイルを以下のように編集しておきます。

trace-to-tree.mjs
//インポート先を外部パスからライブラリへ
import {
  bold,
  blue,
  cyan,
  green,
  magenta,
  red,
  yellow,
} from 'picocolors'

すると、今度はblueが読めないというエラーが出ました。ここでかなり試行錯誤したのですが、解決の決め手はpicocolorsの公式サイトでした。

そこに使用方法がありましたが、このようにあります。

import pc from "picocolors"

つまり、オブジェクトからカラープロパティを呼び出す仕様のようでした(ローカルのpicocolors.jsとは別物なのかも知れません)。なので、この仕様にしたがい、trace-to-tree.mjsを再編集(色指定のメソッドにpcというオブジェクトを付与していく。blueならpc.blue)していきます。

59ページから163ページまでの、色分けの部分を以下のように書き換えました。

trace-to-tree.mjs
/*import {
  bold,
  blue,
  cyan,
  green,
  magenta,
  red,
  yellow,
} from 'picocolors'*/
import pc from 'picocolors';
/*中略*/
const formatDuration = (duration, isBold) => {
  const color = isBold ? pc.black : (x) => x
  if (duration < 1000) {
    return color(`${duration} µs`)
  } else if (duration < 10000) {
    return color(`${Math.round(duration / 100) / 10} ms`)
  } else if (duration < 100000) {
    return color(`${Math.round(duration / 1000)} ms`)
  } else if (duration < 1_000_000) {
    return color(pc.cyan(`${Math.round(duration / 1000)} ms`))
  } else if (duration < 10_000_000) {
    return color(pc.green(`${Math.round(duration / 100000) / 10}s`))
  } else if (duration < 20_000_000) {
    return color(pc.yellow(`${Math.round(duration / 1000000)}s`))
  } else if (duration < 100_000_000) {
    return color(pc.red(`${Math.round(duration / 1000000)}s`))
  } else {
    return color('🔥' + red(`${Math.round(duration / 1000000)}s`))
  }
}

const formatTimes = (event) => {
  const range = event.range - event.timestamp
  const additionalInfo = []
  if (event.total && event.total !== range)
    additionalInfo.push(`total ${formatDuration(event.total)}`)
  if (event.duration !== range)
    additionalInfo.push(`self ${formatDuration(event.duration, pc.black)}`)
  return `${formatDuration(range, additionalInfo.length === 0)}${
    additionalInfo.length ? ` (${additionalInfo.join(', ')})` : ''
  }`
}

const formatFilename = (filename) => {
  return cleanFilename(filename).replace(/.+[\\/]node_modules[\\/]/, '')
}

const cleanFilename = (filename) => {
  if (filename.includes('&absolutePagePath=')) {
    filename =
      'page ' +
      decodeURIComponent(
        filename.replace(/.+&absolutePagePath=/, '').slice(0, -1)
      )
  }
  filename = filename.replace(/.+!(?!$)/, '')
  return filename
}

const getPackageName = (filename) => {
  const match = /.+[\\/]node_modules[\\/]((?:@[^\\/]+[\\/])?[^\\/]+)/.exec(
    cleanFilename(filename)
  )
  return match && match[1]
}

const formatEvent = (event) => {
  let head
  switch (event.name) {
    case 'webpack-compilation':
      head = `${pc.bold(`${event.tags.name} compilation`)} ${formatTimes(event)}`
      break
    case 'webpack-invalidated-client':
    case 'webpack-invalidated-server':
      head = `${pc.bold(`${event.name.slice(-6)} recompilation`)} ${
        event.tags.trigger === 'manual'
          ? '(new page discovered)'
          : `(${formatFilename(event.tags.trigger)})`
      } ${formatTimes(event)}`
      break
    case 'add-entry':
      head = `${pc.blue('entry')} ${formatFilename(event.tags.request)}`
      break
    case 'hot-reloader':
      head = `${pc.bold(pc.green(`hot reloader`))}`
      break
    case 'export-page':
      head = `${event.name} ${event.tags.path}  ${formatTimes(event)}`
      break
    default:
      if (event.name.startsWith('build-module-')) {
        const { mergedChildren, childrenTimings, packageName } = event
        head = `${pc.magenta('module')} ${
          packageName
            ? `${pc.bold(pc.cyan(packageName))} (${formatFilename(event.tags.name)}${
                mergedChildren ? ` + ${mergedChildren}` : ''
              })`
            : formatFilename(event.tags.name)
        } ${formatTimes(event)}`
        if (childrenTimings && Object.keys(childrenTimings).length) {
          head += ` [${Object.keys(childrenTimings)
            .map((key) => `${key} ${formatDuration(childrenTimings[key])}`)
            .join(', ')}]`
        }
      } else {
        head = `${event.name} ${formatTimes(event)}`
      }
      break
  }
  if (event.children && event.children.length) {
    return head + '\n' + treeChildren(event.children.map(formatEvent))
  } else {
    return head
  }
}

これでやっとエラーなく起動することができました。

#node trace-to-tree.mjs .next/trace

トレースの結果

どうもmagic-uiというものの起動に手間取っているようです。ですが、それについて調べてみても根本的なTurbopackの起動にかかわるライブラリなので、修正不可だということでした。ただ、気になったのはどうも使用しているNextのバージョンが悪い(バグを持っている?)のかと思い、package.jsonのnextを13.4から13.5に書き換えてみました。

#remove -rf node_modules
#rm package-lock.json
#npm i

これでNextのバージョンを刷新すると、かなり起動速度が上がりました(magic-uiのレスポンス速度は変わらなかったですが)。他のライブラリも干渉していたのかも知れません。

そこで気になったライブラリとしてChakra UIですが、これについてよく調べてみると

Next13からはクライアントを使う必要がないと書かれています(下の方に高評価の意見があります)。しかも13.4までは起動に干渉していたらしく13.5からはその問題が改善されていたようですが、クライアント宣言をコメントアウトすると、更に高速化を実現できました。

ビルドでも試してみる

開発環境の起動をトレースできるなら、ビルドもトレースできると思い、試してみました。

準備として、ビルドできるようにnext.config.jsを書き換えます。Next13.5でビルドする場合はまず、以下のように下準備が必要です。

next.config.js
module.exports = {
    output: "export",
    trailingSlash: true,
}

あとはコマンドを実行するだけです。outというフォルダが作成されれば、ビルドは成功しています。

# npm run build

もう一度、さっきのコマンドを実行すると、先ほどと別の結果が表示されています。

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?