Category: reference
JavaScript Modules (ESM) — Import/Export ที่ควรรู้
เข้าใจ ES Modules, named exports, default exports, dynamic import(), re-exports และการใช้ร่วมกับ TypeScript และ Astro
สารบัญ
Named Export vs Default Export
// named-exports.ts — export หลายอย่างจาก file เดียว
export const BASE_URL = 'https://panupongws.com';
export const MAX_ITEMS = 10;
export function formatDate(date: Date): string {
return date.toLocaleDateString('th-TH');
}
export type Project = {
title: string;
status: 'active' | 'completed';
};
// default-export.ts — export หนึ่งอย่างเป็น default
export default function greet(name: string) {
return `สวัสดี ${name}`;
}
// การ import
import { BASE_URL, formatDate, type Project } from './named-exports';
import greet from './default-export';
// import พร้อม alias
import { formatDate as fmt } from './named-exports';
Best Practice: Named Exports
Default exports มีปัญหา:
- ชื่อ import ไม่ถูกบังคับ →
import anything from './thing'ชื่ออะไรก็ได้ - refactor tool (rename) ทำงานได้ยากกว่า
Named exports ดีกว่าในเกือบทุกกรณี:
// ✅ named — IDE รู้ชื่อที่ถูกต้อง, refactor ง่าย
export function ProjectCard({ title }: { title: string }) { ... }
import { ProjectCard } from './ProjectCard';
// ❌ default — ชื่อเปลี่ยนได้ตลอด
export default function({ title }) { ... }
import Card from './ProjectCard'; // หรือ import Foo from './ProjectCard'
Astro components ใช้ default export แต่ typescript code ทั่วไปควรใช้ named exports
Dynamic Import — โหลดเมื่อจำเป็น
// โหลด module เมื่อ user click
button.addEventListener('click', async () => {
const { formatDate } = await import('./utils');
console.log(formatDate(new Date()));
});
// conditional import ตาม environment
const utils = process.env.NODE_ENV === 'development'
? await import('./utils-dev')
: await import('./utils-prod');
Dynamic import มีประโยชน์สำหรับ:
- Code splitting — โหลด code เฉพาะส่วนที่จำเป็น
- เพิ่ม performance บน initial load
Re-exports — Barrel Files
รวม exports จากหลาย file ในที่เดียว:
// src/utils/index.ts — barrel file
export { formatDate, parseDate } from './date';
export { slugify, truncate } from './string';
export { cn } from './classnames';
export type { DateRange } from './date';
// ผู้ใช้ import จาก 1 จุด
import { formatDate, slugify } from '@/utils';
ข้อควรระวัง: barrel files ทำให้ tree-shaking ยากขึ้นในบางกรณี — อย่าใช้ถ้า module ใหญ่มากและใช้เพียงบางส่วน
Import Assertions (JSON)
// Astro/Vite รองรับ import JSON โดยตรง
import data from './data.json';
console.log(data.title);
// หรือ import as type (ไม่ถูก bundle)
import type { Config } from './config';
Path Aliases
// tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@utils/*": ["./src/utils/*"]
}
}
}
// ก่อนใช้ alias (relative path น่าหัวเสีย)
import { Card } from '../../../components/Card';
// หลังใช้ alias
import { Card } from '@components/Card';
Astro ตั้ง @/* → ./src/* ให้อยู่แล้วใน default tsconfig
ใช้กับ Astro
---
// .astro files ใช้ named imports ปกติ
import { getCollection, render } from 'astro:content';
import Layout from '@/layouts/Layout.astro';
import { formatDate } from '@/utils/date';
const entries = await getCollection('projects');
---
// utils/date.ts
export function formatDate(date: Date, locale = 'th-TH'): string {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(date);
}
export function getReadingTime(content: string): number {
const wordsPerMinute = 200;
const words = content.trim().split(/\s+/).length;
return Math.ceil(words / wordsPerMinute);
}
Tree Shaking
Bundler (Vite, Rollup) ลบ code ที่ไม่ได้ใช้ออกอัตโนมัติ — แต่ต้องใช้ named exports เพื่อให้ทำงานได้:
// utils.ts มี 10 functions
export function used() { ... } // ถูก include
export function notUsed() { ... } // ถูก remove ใน bundle
// import แบบนี้ tree-shaking ทำงานได้
import { used } from './utils';
// import * as ... — tree-shaking ทำงานไม่ได้เต็มที่
import * as utils from './utils'; // ดึง code ทั้งหมดมา