Advanced (Diperbarui: 2/6/2026)

Tree Shaking dengan Claude Code: mengurangi bundle secara aman

Perbaiki tree shaking dengan Claude Code: ESM, sideEffects, pengukuran, jebakan, dan contoh runnable.

Tree Shaking dengan Claude Code: mengurangi bundle secara aman

Tree shaking dalam bahasa sederhana

Tree shaking adalah proses pada production build yang menghapus export JavaScript atau TypeScript yang tidak dipakai dari bundle akhir. Tujuannya sederhana: jangan kirim kode yang tidak dibutuhkan halaman saat ini ke browser. Hasilnya bisa terasa pada download, parsing, execution, dan waktu awal sebelum pengguna bisa berinteraksi.

Bundler tidak bisa menebak niat developer. Ia membaca import, export, field sideEffects di package.json, transformasi CommonJS, dan kode top-level yang berjalan saat module di-import. Karena itu, proyek nyata sering mengalami dua masalah: helper yang tidak dipakai tetap masuk bundle, atau sideEffects: false membuat CSS dan polyfill yang penting ikut hilang.

Claude Code paling berguna kalau tugasnya terukur. Minta ia mencatat ukuran bundle saat ini, mencari dependency CommonJS, memeriksa barrel file, mengidentifikasi file dengan side effect, lalu mengubah area kecil dan memverifikasi production build. Ini alur yang Masa pakai saat membersihkan proyek Vite, React, dan Astro tanpa merusak tampilan.

flowchart LR
  A["source files"] --> B["ESM import/export graph"]
  B --> C["bundler tree shaking"]
  C --> D["minified production bundle"]
  B --> E["side effects kept"]
  E --> D
  D --> F["measure bytes and gzip"]

Mulai dari dokumentasi resmi

Perilaku tree shaking berbeda di setiap bundler. Sebelum mengubah kode production, gunakan dokumentasi resmi sebagai dasar.

TopikLink resmiYang dicek
webpackTree ShakingsideEffects, ESM, production build
opsi webpackoptimization.sideEffectscara webpack membaca field sideEffects
Rollup/ViteRollup treeshakehindari perubahan global yang agresif
detail Rolluptreeshake.moduleSideEffectspertahankan module setup
esbuildTree shakinganalisis ESM dan pengukuran metafile

Poin pentingnya: tree shaking bukan penghapus teks acak. Ia mengikuti graph dependency ESM yang statis dan menjaga kode ketika penghapusan bisa mengubah perilaku runtime. CommonJS, namespace import, object default yang besar, serta import CSS atau polyfill di top-level sering membuat hasilnya lebih konservatif.

Prompt yang tepat untuk Claude Code

Mulai dari investigasi, bukan langsung edit. sideEffects: false yang dipasang terlalu luas bisa menyembunyikan regresi visual atau setup yang hilang.

Investigasi kenapa tree shaking lemah di production bundle repository ini.
Buat tabel berisi ukuran build saat ini, chunk utama, dependency berat,
dependency CommonJS, dan barrel exports.
Untuk setiap perubahan yang diusulkan, sertakan risiko, dampak ukuran, dan command verifikasi.
CSS, polyfills, analytics, dan global setup tidak boleh terhapus.

Saat melakukan perubahan, batasi scope.

Pada pass ini, kerjakan hanya src/utils dan src/components/index.ts.
Ubah default object exports menjadi named exports dan update file yang meng-import.
Setelah perubahan, jalankan npm run build dan pengukuran ukuran bundle.
Jika API publik berubah, sisakan re-export yang kompatibel.

Dengan begitu Claude Code tidak hanya mengejar kode yang terhapus, tetapi menjaga perilaku sambil mengurangi bundle.

Contoh minimal yang bisa dijalankan

Contoh kecil ini membandingkan default object export dengan named exports memakai esbuild.

mkdir tree-shaking-lab
cd tree-shaking-lab
npm init -y
npm install --save-dev esbuild
mkdir src scripts

Gunakan package.json berikut.

{
  "name": "tree-shaking-lab",
  "version": "1.0.0",
  "type": "module",
  "private": true,
  "sideEffects": false,
  "scripts": {
    "measure": "node scripts/measure-tree-shaking.mjs"
  },
  "devDependencies": {
    "esbuild": "^0.25.0"
  }
}

Versi yang kurang ideal mengumpulkan helper dalam satu object.

// src/bad-utils.ts
const utils = {
  formatIdr(amount: number): string {
    return new Intl.NumberFormat("id-ID", {
      style: "currency",
      currency: "IDR"
    }).format(amount);
  },
  heavyReport(rows: number[]): string {
    const body = rows.map((row) => `row:${row}`).join("\n");
    return `report\n${body}\n${"=".repeat(4000)}`;
  },
  debugOnly(): string {
    return "debug:" + "x".repeat(4000);
  }
};

export default utils;

Versi yang lebih mudah di-tree-shake mengekspor fungsi satu per satu.

// src/good-utils.ts
export function formatIdr(amount: number): string {
  return new Intl.NumberFormat("id-ID", {
    style: "currency",
    currency: "IDR"
  }).format(amount);
}

export function heavyReport(rows: number[]): string {
  const body = rows.map((row) => `row:${row}`).join("\n");
  return `report\n${body}\n${"=".repeat(4000)}`;
}

export function debugOnly(): string {
  return "debug:" + "x".repeat(4000);
}

Buat dua entry file.

// src/bad-entry.ts
import utils from "./bad-utils";

console.log(utils.formatIdr(1200));
// src/good-entry.ts
import { formatIdr } from "./good-utils";

console.log(formatIdr(1200));

Tambahkan script pengukuran.

// scripts/measure-tree-shaking.mjs
import { gzipSync } from "node:zlib";
import { build } from "esbuild";

async function bundle(entryPoint) {
  const result = await build({
    entryPoints: [entryPoint],
    bundle: true,
    minify: true,
    format: "esm",
    treeShaking: true,
    write: false,
    metafile: true
  });

  const code = result.outputFiles[0].text;
  return {
    entryPoint,
    bytes: Buffer.byteLength(code),
    gzipBytes: gzipSync(code).byteLength,
    inputs: Object.keys(result.metafile.inputs)
  };
}

const rows = await Promise.all([
  bundle("src/bad-entry.ts"),
  bundle("src/good-entry.ts")
]);

console.table(rows);

Jalankan:

npm run measure

Di proyek nyata, jangan hanya melihat raw bytes. Catat nama chunk, gzip, Brotli, dan Lighthouse Total Blocking Time. Untuk mengetahui dependency mana yang tetap berada di graph, kombinasikan workflow ini dengan panduan bundle analysis.

Use case 1: merapikan module utilitas

Kemenangan paling cepat sering ada di utils/index.ts atau helpers.ts. Jika date, currency, CSV, Markdown, dan debug helper bercampur, satu fungsi kecil bisa membuat analisis menjadi sulit.

Minta Claude Code melakukan perubahan kecil.

Pisahkan src/utils berdasarkan tujuan.
Ubah pengguna menjadi named imports dan re-export hanya helper publik dari index.ts.
Jika ada Date.now, console, localStorage, atau fetch di top-level,
pindahkan ke dalam fungsi.

Bentuk yang lebih sehat:

// src/utils/formatDate.ts
export function formatDate(date: Date, locale = "id-ID"): string {
  return new Intl.DateTimeFormat(locale).format(date);
}
// src/utils/index.ts
export { formatDate } from "./formatDate";
export { formatIdr } from "./formatIdr";
// src/pages/invoice.ts
import { formatIdr } from "../utils/formatIdr";

export function invoiceLabel(total: number): string {
  return `Total: ${formatIdr(total)}`;
}

Barrel file tidak selalu buruk. Ia menjadi berisiko ketika menjalankan setup, membuat rantai export * from terlalu luas, atau menarik module yang tidak berhubungan. Untuk kode aplikasi, direct import biasanya lebih jelas; untuk library publik, barrel tipis bisa dipertahankan demi kompatibilitas.

Use case 2: internal UI library

Pada UI package internal, import { Button } from "@acme/ui" bisa ikut mengevaluasi Modal, DatePicker, Chart, icon set, CSS, dan theme setup. Jika semua component berbagi satu entry besar, named exports saja tidak cukup.

Pisahkan subpath entry.

{
  "name": "@acme/ui",
  "type": "module",
  "sideEffects": [
    "**/*.css",
    "./src/setup-theme.ts"
  ],
  "exports": {
    ".": "./dist/index.js",
    "./button": "./dist/button.js",
    "./modal": "./dist/modal.js"
  }
}

Consumer cukup import entry yang diperlukan.

import { Button } from "@acme/ui/button";

Jangan memakai sideEffects: false tanpa audit. Field itu berarti import module tidak menjalankan pekerjaan luar yang harus dipertahankan. CSS, polyfills, custom element registration, dan theme setup harus masuk array sideEffects jika perlu tetap berjalan.

Use case 3: lazy-load dependency khusus admin

Markdown processor, PDF generator, chart, dan rich text editor sering tidak diperlukan pada first load halaman publik. Gunakan tree shaking untuk membuang export yang tidak dipakai, lalu code splitting untuk memindahkan fitur admin ke chunk terpisah.

// src/features/admin/loadMarkdownPreview.ts
export async function renderMarkdown(markdown: string): Promise<string> {
  const [{ unified }, remarkParse, remarkHtml] = await Promise.all([
    import("unified"),
    import("remark-parse"),
    import("remark-html")
  ]);

  const file = await unified()
    .use(remarkParse.default)
    .use(remarkHtml.default)
    .process(markdown);

  return String(file);
}

Dynamic import bukan pengganti tree shaking. Ia hanya memindahkan kode ke chunk berikutnya; kalau chunk itu masih berat karena CommonJS, ukurannya tetap besar. Ukur terpisah antara kode yang keluar dari initial bundle dan kode yang hilang di dalam lazy chunk.

Use case 4: menerbitkan package npm

Jika menerbitkan library, berikan entry ESM yang mudah dianalisis oleh bundler pengguna. Hanya menyediakan main CommonJS membuat tree shaking di aplikasi frontend lebih sulit.

{
  "name": "@masa/formatters",
  "type": "module",
  "sideEffects": false,
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    },
    "./currency": {
      "types": "./dist/currency.d.ts",
      "import": "./dist/currency.js"
    }
  }
}

Pakai sideEffects: false hanya jika package benar-benar bebas side effect saat di-import. Jika ia mengimpor CSS, memasang polyfill, mendaftarkan global, atau memulai analytics, list file tersebut di sideEffects.

Jebakan dan failure case

JebakanGejalaPerbaikan
Babel atau TypeScript terlalu cepat emit CommonJSunused exports tetap adapertahankan ESM sampai bundler
sideEffects: false terlalu luasCSS atau polyfill hilanglist file dengan side effect
default object exporthelper tidak dipakai tetap ikutpecah menjadi named exports
barrel menjalankan setup top-levelimport satu component jadi beratbarrel hanya re-export
mengukur dev buildangka menyesatkanbandingkan production, minify, gzip
global moduleSideEffects: falsesetup code terhapusvalidasi per package atau file
namespace importanalisis lebih konservatifgunakan named imports spesifik

Regresi visual kecil adalah yang paling berbahaya. Test yang hanya mengecek DOM bisa lulus walaupun CSS hilang. Perlakukan ini seperti performance optimization: cek build, layar penting, dan perilaku yang terlihat pengguna.

Pasang bundle budget di CI

Sekali dibersihkan, bundle bisa membesar lagi pada dependency update berikutnya. Mulai dengan budget gzip sederhana.

// scripts/check-bundle-budget.mjs
import { statSync } from "node:fs";
import { gzipSync } from "node:zlib";
import { readFileSync } from "node:fs";

const file = "dist/assets/index.js";
const maxGzipBytes = 160 * 1024;
const raw = readFileSync(file);
const gzipBytes = gzipSync(raw).byteLength;

if (gzipBytes > maxGzipBytes) {
  console.error(`Bundle budget exceeded: ${gzipBytes} > ${maxGzipBytes}`);
  process.exit(1);
}

console.log({
  file,
  bytes: statSync(file).size,
  gzipBytes
});

Jalankan setelah build.

npm run build
node scripts/check-bundle-budget.mjs

Budget pertama jangan terlalu ideal. Mulai dari gzip size saat ini dengan sedikit margin, lalu minta alasan ketika PR menambah ukuran. Jika aplikasi masih terasa lambat, cek juga gambar, font, latency API, dan hydration dengan panduan speed optimization.

Checklist review untuk Claude Code

Review PR tree shaking ini.
1. Apakah unused exports benar-benar hilang dari production bundle?
2. Apakah CSS, polyfills, dan registration files tetap dipertahankan?
3. Apakah ESM bertahan sampai tahap analisis bundler?
4. Apakah direct imports merusak kompatibilitas API publik?
5. Apa hasil build, tests, layar penting, dan bundle budget?
Sertakan file names dan bukti command untuk setiap jawaban.

Checklist ini mengubah refactor menjadi review kualitas sebelum publish. Dalam pekerjaan Masa, perubahan sideEffects belum selesai sebelum layar login, billing, dan admin dibuka untuk mengecek style serta initialization behavior.

Sudut monetisasi

Tree shaking bukan hanya kebersihan teknis. First load yang lebih ringan mengurangi hambatan sebelum pengguna membaca artikel, membuka product page, mengisi signup, atau mengirim form konsultasi. Untuk media teknis seperti ClaudeCodeLab, halaman contoh kode atau landing page yang berat melemahkan jalur iklan dan konsultasi.

ClaudeCodeLab bisa mengaudit bundle Vite, Next.js, Astro, dan internal UI library, lalu mengubah temuan menjadi tree shaking, code splitting, dan CI bundle budget. Untuk konsultasi yang fokus, siapkan package.json, build config, route penting, dan bundle report terbaru.

Ringkasan

Tree shaking bekerja saat ESM, sideEffects yang akurat, side effect yang terkendali, dan measurement berkelanjutan berjalan bersama. Claude Code paling berguna jika diberi tugas kecil yang bisa diverifikasi: investigasi, split, update import, ukur, lalu review failure case.

Saya menjalankan contoh minimal artikel ini secara lokal dengan npm run measure dan memastikan bad entry serta good entry menghasilkan ukuran output berbeda. Pada proyek nyata, angka bergantung pada dependency dan konfigurasi; selalu ukur production build sendiri dan tulis dulu side effect mana yang harus dipertahankan.

#Claude Code #tree shaking #bundle size #ES Modules #frontend optimization
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.