ECMAScript modules

Author extension code with modern import/export syntax across all extension contexts.

Extension.js supports ECMAScript Modules (ESM) in background scripts, content scripts, extension pages, and regular application modules through its default Rspack pipeline.

When ESM is a good fit

  • You want consistent module syntax across extension and web code.
  • You are sharing code with modern ESM-first packages.
  • You need cleaner tree-shaking and explicit dependency boundaries.

Template examples

new

new template screenshot

Start a new-tab extension with modern module syntax and minimal setup.

npm
pnpm
yarn
npx extension@latest create my-extension --template=new

Repository: extension-js/examples/new

content

content template screenshot

Inject script logic into page content while keeping ESM-style authoring.

npm
pnpm
yarn
npx extension@latest create my-extension --template=content

Repository: extension-js/examples/content

Using ESM in existing projects

You can use ESM syntax directly in extension source files (.js, .mjs, .ts, .tsx, etc.) without custom bundler setup.

If your Node-side project files should also run as ESM (for example custom scripts or config conventions), set "type": "module" in package.json:

{
  "name": "my-extension",
  "version": "1.0",
  "description": "My Extension Example",
  "type": "module",
  "devDependencies": {
    "extension": "latest"
  },
  "scripts": {
    "dev": "extension dev",
    "start": "extension start",
    "build": "extension build"
  }
}

Manifest and service worker notes

For Manifest V3 background workers:

  • Set background.type to "module" when you want native module worker semantics.
  • Without module worker type, Extension.js uses classic worker chunk loading behavior.
{
  "background": {
    "service_worker": "src/background.ts",
    "type": "module"
  }
}

ESM vs CommonJS reminders

When writing ESM modules:

1) Relative imports should include file extensions in authoring/runtime-aware code paths

// my-file.mjs
import React from 'react'

// local imports
- import myImport from './myImport'
+ import myImport from './myImport.js'

2) Interop with non-ESM modules can differ

When importing CommonJS packages from ESM, prefer documented interop patterns from each package.

3) CommonJS globals are not available in strict ESM contexts

Avoid relying on require, module.exports, __filename, and __dirname inside ESM modules.

Handling environment variables in ECMAScript modules

Extension.js supports both:

  • process.env.EXTENSION_PUBLIC_*
  • import.meta.env.EXTENSION_PUBLIC_*

Use EXTENSION_PUBLIC_ for variables that should be available in extension code.

// example.mjs

console.log(import.meta.env.EXTENSION_PUBLIC_API_KEY);
console.log(process.env.EXTENSION_PUBLIC_API_KEY);

Next steps

Video walkthrough

Video demo soon: ecmascript modules extension workflow