Module System Guide
Module System Guide
This guide explains how we use ES Modules (ESM) throughout the theme and how to handle common module-related challenges.
Overview
The theme uses ES Modules (ESM) exclusively, configured through:
package.json
:"type": "module"
tsconfig.json
:"module": "ESNext"
- Astro’s native ESM support
Import Patterns
1. Component Imports
// ✅ Correct - Use relative paths for local components
import Button from "../components/ui/Button";
import { Card } from "../components/ui/Card";
// ✅ Correct - Use aliases for common paths
import { cn } from "@/lib/utils";
import type { ButtonProps } from "@/components/ui/Button";
// ❌ Incorrect - Avoid CommonJS require
const Button = require("../components/ui/Button");
2. Type Imports
// ✅ Correct - Use type imports for types only
import type { ThemeConfig } from "./types";
import type { VariantProps } from "class-variance-authority";
// ❌ Incorrect - Don't mix value and type imports
import { ThemeConfig, generateTheme } from "./types";
3. Asset Imports
// ✅ Correct - Direct imports for assets
import logo from "@/assets/logo.png";
import styles from "./styles.module.css";
// ✅ Correct - Dynamic imports when needed
const Component = await import("./Component.tsx");
Common Pitfalls
1. File Extensions
ESM requires explicit file extensions:
// ✅ Correct
import { Button } from "./Button.tsx";
import { theme } from "./theme.js";
import { styles } from "./styles.css";
// ❌ Incorrect
import { Button } from "./Button";
2. Package Imports
Some packages may not support ESM. Solutions:
// ✅ Option 1: Use dynamic import
const legacy = await import("legacy-package");
// ✅ Option 2: Use ESM wrapper
import legacyWrapper from "legacy-package-esm";
// ✅ Option 3: Use URL import
import data from "data:text/javascript,export default 'hello!'";
3. JSON Imports
JSON imports require explicit assertions:
// ✅ Correct
import config from "./config.json" assert { type: "json" };
// ❌ Incorrect
import config from "./config.json";
Module Resolution
1. Path Aliases
We use TypeScript path aliases for cleaner imports:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@lib/*": ["src/lib/*"]
}
}
}
// Usage
import { Button } from "@components/ui/Button";
import { cn } from "@lib/utils";
2. Type Resolution
TypeScript automatically resolves types from:
.ts
,.tsx
,.d.ts
filespackage.json
"types"
field- Bundled type declarations
Dynamic Imports
Use dynamic imports for:
- Code splitting
- Conditional loading
- Loading non-ESM modules
// ✅ Correct - Async component loading
const DynamicComponent = await import("./DynamicComponent.tsx");
// ✅ Correct - Conditional import
if (condition) {
const { feature } = await import("./feature.js");
}
// ✅ Correct - Error boundary
try {
const module = await import("./module.js");
} catch (error) {
console.error("Module failed to load:", error);
}
Best Practices
-
Use ESM Consistently
// ✅ Correct export const component = () => {}; export default component; // ❌ Avoid module.exports = component;
-
Handle Async Operations
// ✅ Correct const data = await fetchData(); export default data; // ❌ Avoid export default new Promise(...);
-
Type Exports
// ✅ Correct export type Props = { // ... }; // ❌ Avoid type Props = { // ... }; module.exports = Props;
Troubleshooting
1. ESM Compatibility
If a package doesn’t support ESM:
- Check for an ESM-compatible alternative
- Use dynamic import with
await import()
- Create an ESM wrapper
2. Type Errors
Common type resolution errors:
- Missing file extensions
- Incorrect path aliases
- Mixed ESM/CJS imports
3. Build Issues
If you encounter build errors:
- Check
package.json
“type” field - Verify file extensions
- Ensure all dependencies support ESM
- Check bundler configuration
Migration Guide
When converting CommonJS to ESM:
-
Update package.json:
{ "type": "module" }
-
Convert requires to imports:
// Before const { join } = require("path"); // After import { join } from "path";
-
Convert exports:
// Before module.exports = { // ... }; // After export default { // ... };
-
Add file extensions:
// Before import { util } from "./util"; // After import { util } from "./util.js";
Testing
When writing tests with ESM:
// ✅ Correct
import { test, expect } from "vitest";
import { component } from "./component";
test("component", () => {
// ...
});
// ❌ Incorrect
const test = require("vitest");
development
documentation
esm