T-CREATOR

Motion(旧 Framer Motion)× Vite/Next/Turbopack:ツリーシェイク最適化とバンドル最小化の初期設定

Motion(旧 Framer Motion)× Vite/Next/Turbopack:ツリーシェイク最適化とバンドル最小化の初期設定

Motion(旧 Framer Motion)を使用したプロジェクトでは、豊富なアニメーション機能の反面、バンドルサイズが大きくなってしまう課題があります。特に本格的な Web アプリケーション開発では、パフォーマンスとユーザー体験の両立が重要になってきますね。

本記事では、Vite、Next.js、Turbopack の各バンドラーで Motion ライブラリのツリーシェイクを効率的に行い、バンドルサイズを最小化する具体的な設定方法をご紹介します。実際のコード例とともに、各バンドラーの特性を活かした最適化テクニックを学んでいただけるでしょう。

背景

Motion(旧 Framer Motion)の概要

Motion は React アプリケーション向けの強力なアニメーションライブラリです。2024 年に Framer Motion から名称変更され、より軽量で高性能なライブラリとして生まれ変わりました。

以下の図は、Motion ライブラリの主要な機能を示しています。

mermaidflowchart TD
    motion["Motion ライブラリ"] --> animations["アニメーション機能"]
    motion --> gestures["ジェスチャー機能"]
    motion --> layout["レイアウト機能"]

    animations --> keyframes["キーフレーム"]
    animations --> spring["スプリング"]
    animations --> timeline["タイムライン"]

    gestures --> drag["ドラッグ"]
    gestures --> hover["ホバー"]
    gestures --> tap["タップ"]

    layout --> shared["共有レイアウト"]
    layout --> reorder["リオーダー"]
    layout --> presence["プレゼンス"]

Motion の豊富な機能により、従来は複雑な CSS や JavaScript で実装していたアニメーションを、宣言的で直感的な API で実現できるようになります。

バンドルサイズの重要性

Web アプリケーションのバンドルサイズは、ユーザー体験に直接影響する重要な要素です。特にモバイルユーザーや回線速度の遅い環境では、初期読み込み時間がサービスの利用継続率に大きく関わってきます。

サイズ範囲読み込み時間(3G)ユーザー体験への影響
~ 100KB1 秒以内★★★ 優秀
100-300KB1-3 秒★★☆ 良好
300KB ~3 秒以上★☆☆ 改善必要

Motion(旧 Framer Motion)は多機能である反面、すべての機能を含んだ状態では 200KB 以上のサイズになることもあり、適切な最適化が必要になります。

ツリーシェイクとは何か

ツリーシェイクは、使用されていないコードを自動的に除去するバンドル最適化技術です。ES6 のインポート・エクスポート構文の静的解析により、実際に使用されている関数やクラスのみをバンドルに含めることができます。

以下の図で、ツリーシェイクの仕組みを理解しましょう。

mermaidflowchart LR
    library["ライブラリ全体<br/>(200KB)"] --> treeshake["ツリーシェイク<br/>解析"]
    treeshake --> used["使用中の機能<br/>(50KB)"]
    treeshake --> unused["未使用の機能<br/>(150KB)"]
    unused -.->|除去| bundle["最終バンドル<br/>(50KB)"]
    used --> bundle

この技術により、Motion ライブラリの必要な部分のみを含んだ軽量なバンドルを生成できるのです。

課題

Motion の大きなバンドルサイズ

Motion ライブラリは豊富な機能を提供する反面、デフォルトインポートを使用すると不要な機能まで含まれてしまいます。

一般的なインポート方法での問題点を見てみましょう。

typescript// 問題のあるインポート方法
import { motion } from 'motion';

このインポート方法では、motion オブジェクトに含まれるすべての機能がバンドルに含まれてしまいます。実際に使用するのがアニメーション機能のみであっても、ジェスチャーやレイアウト機能も一緒にバンドルされてしまうのです。

不要なコードの混入問題

Motion ライブラリの機能別サイズを以下の表で確認してください。

機能分類サイズ使用頻度最適化の効果
コアアニメーション45KB★★★ 高必須
ジェスチャー機能35KB★★☆ 中条件付き
レイアウト機能55KB★☆☆ 低大きな削減
3D 変換機能25KB★☆☆ 低大きな削減
SVG アニメーション20KB★☆☆ 低大きな削減

このように、プロジェクトで実際に使用する機能は限られていることが多く、適切な設定により大幅なサイズ削減が可能になります。

パフォーマンスへの影響

バンドルサイズの増加は、以下のようなパフォーマンス問題を引き起こします。

mermaidflowchart TD
    large["大きなバンドル"] --> download["ダウンロード時間増加"]
    large --> parse["JavaScriptパース時間増加"]
    large --> memory["メモリ使用量増加"]

    download --> fcp["First Contentful Paint遅延"]
    parse --> tti["Time to Interactive遅延"]
    memory --> runtime["ランタイムパフォーマンス低下"]

    fcp --> ux["ユーザー体験の悪化"]
    tti --> ux
    runtime --> ux

特にモバイルデバイスでは、CPU とメモリの制約により、これらの影響がより顕著に現れます。

解決策

ツリーシェイク設定の最適化

効果的なツリーシェイクを実現するには、バンドラーの設定とインポート方法の両方を最適化する必要があります。

基本的な最適化の流れを図で確認しましょう。

mermaidflowchart TD
    config["バンドラー設定"] --> import["インポート方法"]
    import --> analysis["静的解析"]
    analysis --> optimization["コード最適化"]
    optimization --> bundle["軽量バンドル"]

    config --> sideeffects["sideEffects設定"]
    config --> modules["ES Modules対応"]

    import --> named["名前付きインポート"]
    import --> specific["特定機能インポート"]

各段階での設定により、効率的なツリーシェイクが実現されます。

インポート方法の改善

Motion ライブラリを効率的に使用するための推奨インポート方法をご紹介します。

推奨されるインポート方法

typescript// 特定の機能のみをインポート
import { animate } from 'motion';
import { scroll } from 'motion';

この方法により、必要な機能のみがバンドルに含まれます。

さらに詳細な機能別インポート

typescript// アニメーション機能のみ
import { animate, timeline } from 'motion/animation';

// ジェスチャー機能のみ
import { drag } from 'motion/gestures';

// DOM要素向け機能のみ
import { motion } from 'motion/dom';

機能別のサブパッケージを使用することで、より細かい制御が可能になります。

バンドラー固有の設定

各バンドラーには、ツリーシェイクを効率化するための固有の設定があります。それぞれの特徴を活かした最適化アプローチを採用することが重要です。

具体例

Vite での設定例

Vite は高速な開発サーバーとビルドツールとして人気が高く、ES Modules をネイティブサポートしているため、効率的なツリーシェイクが可能です。

vite.config.js の基本設定

javascriptimport { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  build: {
    // ツリーシェイクの最適化
    rollupOptions: {
      output: {
        manualChunks: {
          // Motionを別チャンクに分離
          motion: ['motion'],
        },
      },
    },
  },
});

この設定により、Motion ライブラリを独立したチャンクとして分離し、キャッシュ効率を向上させます。

package.json での sideEffects 設定

json{
  "name": "motion-vite-app",
  "sideEffects": false,
  "dependencies": {
    "motion": "^10.18.0",
    "react": "^18.2.0"
  }
}

sideEffects を false に設定することで、より積極的なツリーシェイクが有効になります。

実際のコンポーネントでの使用例

typescript// 効率的なインポート方法
import { animate } from 'motion';
import { useEffect, useRef } from 'react';

const AnimatedComponent = () => {
  const elementRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (elementRef.current) {
      // 必要最小限の機能のみを使用
      animate(
        elementRef.current,
        { opacity: [0, 1], y: [20, 0] },
        { duration: 0.5 }
      );
    }
  }, []);

  return <div ref={elementRef}>アニメーション要素</div>;
};

Next.js での設定例

Next.js はサーバーサイドレンダリングとクライアントサイドハイドレーションの両方に対応する必要があるため、特別な配慮が必要です。

next.config.js の最適化設定

javascript/** @type {import('next').NextConfig} */
const nextConfig = {
  // 実験的なTurbopack使用時の設定
  experimental: {
    turbopack: true,
  },

  // Webpack設定のカスタマイズ
  webpack: (config, { isServer }) => {
    // クライアントサイドのみでMotionを使用
    if (!isServer) {
      config.optimization.splitChunks.chunks = 'all';
      config.optimization.splitChunks.cacheGroups = {
        motion: {
          test: /[\\/]node_modules[\\/]motion[\\/]/,
          name: 'motion',
          chunks: 'all',
        },
      };
    }

    return config;
  },
};

module.exports = nextConfig;

この設定により、Motion ライブラリをクライアントサイドでのみ読み込み、サーバーサイドでの不要な処理を回避できます。

動的インポートを活用したコンポーネント

typescriptimport dynamic from 'next/dynamic';
import { Suspense } from 'react';

// Motion機能を動的にインポート
const MotionComponent = dynamic(
  () => import('../components/AnimatedComponent'),
  {
    ssr: false, // サーバーサイドレンダリングを無効化
    loading: () => <div>読み込み中...</div>,
  }
);

const Page = () => {
  return (
    <main>
      <h1>メインコンテンツ</h1>
      <Suspense
        fallback={<div>アニメーション読み込み中...</div>}
      >
        <MotionComponent />
      </Suspense>
    </main>
  );
};

動的インポートにより、アニメーション機能が必要な時のみ Motion ライブラリを読み込むことができます。

App Router での使用例

typescript'use client'; // クライアントコンポーネントとして明示

import { motion } from 'motion/dom';
import { useState } from 'react';

const InteractiveButton = () => {
  const [isPressed, setIsPressed] = useState(false);

  return (
    <motion.button
      animate={{
        scale: isPressed ? 0.95 : 1,
        backgroundColor: isPressed ? '#3b82f6' : '#6366f1',
      }}
      transition={{ duration: 0.1 }}
      onMouseDown={() => setIsPressed(true)}
      onMouseUp={() => setIsPressed(false)}
      onMouseLeave={() => setIsPressed(false)}
    >
      インタラクティブボタン
    </motion.button>
  );
};

Turbopack での設定例

Turbopack は次世代のバンドラーとして、Rust ベースの高速処理とインクリメンタルビルドが特徴です。

turbopack.config.js の基本設定

javascriptmodule.exports = {
  // ES Modulesの最適化
  resolve: {
    mainFields: ['module', 'main'],
    conditionNames: ['import', 'require'],
  },

  // バンドル最適化
  optimization: {
    treeShaking: true,
    sideEffects: false,
  },
};

Turbopack では ES Modules が優先され、効率的なツリーシェイクが自動的に実行されます。

パッケージの設定調整

json{
  "name": "motion-turbopack-app",
  "type": "module",
  "dependencies": {
    "motion": "^10.18.0"
  },
  "devDependencies": {
    "@turbo/types": "^1.10.0"
  }
}

type フィールドを module に設定することで、ES Modules を前提とした最適化が有効になります。

Turbopack 専用のコンポーネント最適化

typescript// Turbopackの最適化を活用したインポート
import { animate, scroll, timeline } from 'motion';

const TurboOptimizedComponent = () => {
  // 複数のMotion機能を効率的に使用
  const handleComplexAnimation = async () => {
    // タイムラインアニメーション
    await timeline([
      ['#element1', { opacity: 1 }, { duration: 0.3 }],
      ['#element2', { y: 0 }, { duration: 0.4, at: 0.2 }],
    ]);

    // スクロール連動アニメーション
    scroll(animate('#element3', { rotateY: [0, 360] }), {
      target: document.querySelector('#scroll-container'),
    });
  };

  return (
    <div>
      <div id='element1'>要素1</div>
      <div id='element2'>要素2</div>
      <div id='element3'>要素3</div>
      <button onClick={handleComplexAnimation}>
        アニメーション実行
      </button>
    </div>
  );
};

Turbopack では、複数の機能を同時にインポートしても効率的な最適化が行われます。

パフォーマンス測定とバンドル分析

typescript// 開発時のバンドル分析
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';

const analyzeBundle = () => {
  if (process.env.ANALYZE === 'true') {
    return new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
      reportFilename: 'bundle-report.html',
    });
  }
  return null;
};

定期的なバンドル分析により、最適化の効果を定量的に測定できます。

まとめ

設定のポイント整理

Motion(旧 Framer Motion)の効果的な最適化には、以下の重要なポイントがあります。

最適化項目効果実装難易度推奨度
名前付きインポート★★★★☆☆必須
機能別インポート★★★★★☆強く推奨
動的インポート★★☆★★★推奨
バンドラー設定★★★★★☆強く推奨
sideEffects 設定★★☆★☆☆推奨

特に名前付きインポートと機能別インポートは、実装が簡単でありながら大きな効果が期待できるため、最優先で取り組むべき項目です。

パフォーマンス向上効果

適切な最適化を実施することで、以下のような顕著な改善効果が得られます。

バンドルサイズの削減効果

  • 最適化前:約 200KB
  • 最適化後:約 50-80KB
  • 削減率:60-75%

読み込み時間の改善

  • 3G 回線での初期読み込み:3 秒 → 1 秒
  • 4G 回線での初期読み込み:1 秒 → 0.3 秒
  • WiFi 環境での初期読み込み:0.5 秒 → 0.1 秒

これらの改善により、ユーザー体験の大幅な向上が実現できるでしょう。継続的な最適化と定期的な効果測定により、常に最良のパフォーマンスを維持することが重要です。

バンドラーの進化とともに、より効率的な最適化手法も登場してくるため、最新の技術動向にも注目していただければと思います。

関連リンク