Migrating to ESM in ApostropheCMS
Starting with version 4.9.0, ApostropheCMS supports ECMAScript Modules (ESM). This guide will help you convert your existing project to use ESM.
Understanding ESM vs CommonJS
Before beginning the migration, it's important to understand a few key points:
- At the project level, you must choose either CommonJS (CJS) or ESM - they cannot be mixed
- Node packages can support both ESM and CJS through dual packaging
- All new ApostropheCMS starter kits and extension packages will use ESM going forward
Migration Steps
1. Update Package Configuration
Add the following to your package.json
:
json
{
"type": "module"
}
2. Modify App Configuration
In your app.js
, update the root configuration:
javascript
// Before
module.exports = {
// ... configuration
};
// After
export default {
root: import.meta,
// ... configuration
};
3. Update Import Statements
For Node Modules:
javascript
// Before
const express = require('express');
// After
import express from 'express';
For Local Files:
javascript
// Before
const myModule = require('./my-module');
// After
import myModule from './my-module.js'; // Note: .js extension is required
Common Patterns and Examples
Module Imports
javascript
// Before
const { myModule } = require('@my/module');
const { existsSync } = require('fs');
// After
import { myModule } from '@my/module';
import { existsSync } from 'fs';
Local Module Imports
javascript
// Before
const myWidget = require('./modules/my-widget');
const config = require('./config/index');
// After
import myWidget from './modules/my-widget.js';
import config from './config/index.js';
Best Practices
- Always include the
.js
extension for local file imports - Use named exports where appropriate to improve code clarity
- Consider updating your linting configuration to enforce ESM patterns
- Test thoroughly after migration, especially custom modules and extensions
Common Issues and Troubleshooting
Missing File Extensions
If you encounter errors like ERR_MODULE_NOT_FOUND
, ensure you've added .js
extensions to all local file imports.
__dirname and __filename
These CommonJS variables aren't available in ESM. Instead, use:
javascript
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Dynamic Imports
If you need to dynamically import modules:
javascript
// Before
const module = require(`./modules/${moduleName}`);
// After
const module = await import(`./modules/${moduleName}.js`);
For example,
javascript
// Instead of loading all locales upfront
import enTranslations from './locales/en.js';
import esTranslations from './locales/es.js';
import frTranslations from './locales/fr.js';
// You can load them dynamically based on user preference
async function loadTranslations(locale) {
const translations = await import(`./locales/${locale}.js`);
return translations.default;
}
Next Steps
- Update your CI/CD pipelines to account for ESM
- Review and update any custom scripts or tools
- Consider updating your development tools and IDE configurations
- Test thoroughly in all environments
Note: While converting to ESM is recommended for new projects and updates, existing projects using CommonJS will continue to work. Choose the best time for your team to make this transition.