Repository: lingui/js-lingui
Pull request #734 (2020-09-12 16:07:10)

File extension of ES modules will be .mjs
#731 Hi this PR attempts to solve this issue.

vercel[bot]: This pull request is being automatically deployed with Vercel ([learn more](https://vercel.link/github-learn-more)).  
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: [https://vercel.com/lingui-js/js-lingui/d690twirt](https://vercel.com/lingui-js/js-lingui/d690twirt)  
✅ Preview: *In Progress*


codecov[bot]: # [Codecov](https://codecov.io/gh/lingui/js-lingui/pull/734?src=pr&el=h1) Report
> Merging [#734](https://codecov.io/gh/lingui/js-lingui/pull/734?src=pr&el=desc) into [next](https://codecov.io/gh/lingui/js-lingui/commit/8adf7c162129b8771074e8c677d32f94755c3dba?el=desc) will **increase** coverage by `0.35%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/lingui/js-lingui/pull/734/graphs/tree.svg?width=650&height=150&src=pr&token=CuJ1VD5BY5)](https://codecov.io/gh/lingui/js-lingui/pull/734?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##             next     #734      +/-   ##
==========================================
+ Coverage   83.85%   84.21%   +0.35%     
==========================================
  Files          38       38              
  Lines        1214     1216       +2     
  Branches      319      320       +1     
==========================================
+ Hits         1018     1024       +6     
+ Misses        120      115       -5     
- Partials       76       77       +1     
```


| [Impacted Files](https://codecov.io/gh/lingui/js-lingui/pull/734?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [packages/cli/src/api/catalog.ts](https://codecov.io/gh/lingui/js-lingui/pull/734/diff?src=pr&el=tree#diff-cGFja2FnZXMvY2xpL3NyYy9hcGkvY2F0YWxvZy50cw==) | `83.33% <100.00%> (+2.68%)` | :arrow_up: |
| [packages/cli/src/api/utils.ts](https://codecov.io/gh/lingui/js-lingui/pull/734/diff?src=pr&el=tree#diff-cGFja2FnZXMvY2xpL3NyYy9hcGkvdXRpbHMudHM=) | `73.17% <100.00%> (+0.67%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/lingui/js-lingui/pull/734?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/lingui/js-lingui/pull/734?src=pr&el=footer). Last update [8adf7c1...9b43789](https://codecov.io/gh/lingui/js-lingui/pull/734?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

laszbalo: I am actually not super happy how I test if the compiled catalog is in ES module. Because the user can override the `namespace` parameter from the command line, I can not just use the `catalog.config.namespace` field. For a more robust alternative, I could have modified the the `catalog.writeCompiled()` method to take `namespace` as its third argument.

Patch:
diff --git a/package.json b/package.json
index 3a4de96c..6b022e5b 100644
--- a/package.json
+++ b/package.json
@@ -31,13 +31,13 @@
     "@babel/preset-env": "^7.11.5",
     "@babel/preset-react": "^7.10.4",
     "@babel/preset-typescript": "^7.10.4",
-    "@testing-library/react": "^11.0.2",
+    "@testing-library/react": "^11.0.4",
     "@types/babel-types": "^7.0.9",
-    "@types/jest": "^26.0.13",
+    "@types/jest": "^26.0.14",
     "@types/ramda": "^0.27.17",
     "@types/react": "^16.9.49",
-    "@typescript-eslint/eslint-plugin": "^4.1.0",
-    "@typescript-eslint/parser": "^4.1.0",
+    "@typescript-eslint/eslint-plugin": "^4.1.1",
+    "@typescript-eslint/parser": "^4.1.1",
     "babel-code-frame": "^6.26.0",
     "babel-core": "7.0.0-bridge.0",
     "babel-eslint": "^10.1.0",
@@ -48,9 +48,9 @@
     "enzyme": "^3.11.0",
     "enzyme-adapter-react-16": "^1.15.4",
     "enzyme-to-json": "^3.5.0",
-    "eslint": "^7.8.1",
+    "eslint": "^7.9.0",
     "eslint-plugin-import": "^2.22.0",
-    "eslint-plugin-jest": "^24.0.0",
+    "eslint-plugin-jest": "^24.0.2",
     "eslint-plugin-node": "^11.1.0",
     "eslint-plugin-promise": "^4.1.1",
     "eslint-plugin-react": "^7.20.6",
@@ -73,12 +73,12 @@
     "npm-cli-login": "^0.1.1",
     "ora": "^5.1.0",
     "pkg-up": "^3.1.0",
-    "prettier": "^2.1.1",
+    "prettier": "^2.1.2",
     "react": "^16.13.1",
     "react-dom": "^16.13.1",
     "regenerator-runtime": "^0.13.7",
     "rimraf": "^3.0.2",
-    "rollup": "^2.26.11",
+    "rollup": "^2.28.1",
     "rollup-plugin-babel": "^4.4.0",
     "rollup-plugin-commonjs": "^10.1.0",
     "rollup-plugin-node-resolve": "^5.2.0",
@@ -90,9 +90,9 @@
     "strip-ansi": "^6.0.0",
     "targz": "^1.0.1",
     "tmp": "^0.2.1",
-    "ts-jest": "^26.3.0",
-    "typescript": "^4.0.2",
-    "webpack": "^4.44.1"
+    "ts-jest": "^26.4.0",
+    "typescript": "^4.0.3",
+    "webpack": "^4.44.2"
   },
   "workspaces": [
     "packages/*"
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 95bbf3bb..2375ed6e 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -66,7 +66,7 @@
     "@types/normalize-path": "^3.0.0",
     "@types/papaparse": "^5.2.2",
     "mockdate": "^3.0.2",
-    "typescript": "^4.0.2"
+    "typescript": "^4.0.3"
   },
   "peerDependencies": {
     "@babel/core": "^7.0.0",
diff --git a/packages/cli/src/api/catalog.ts b/packages/cli/src/api/catalog.ts
index 68c775b0..cf1cf193 100644
--- a/packages/cli/src/api/catalog.ts
+++ b/packages/cli/src/api/catalog.ts
@@ -251,8 +251,9 @@ export class Catalog {
     this.locales.forEach((locale) => this.write(locale, catalogs[locale]))
   }
 
-  writeCompiled(locale: string, compiledCatalog: string) {
-    const filename = this.path.replace(LOCALE, locale) + ".js"
+  writeCompiled(locale: string, compiledCatalog: string, namespace?: string) {
+    const ext = `.${ namespace === "es" ? "mjs": "js"}`
+    const filename = this.path.replace(LOCALE, locale) + ext
 
     const basedir = path.dirname(filename)
     if (!fs.existsSync(basedir)) {
diff --git a/packages/cli/src/api/extractors/babel.ts b/packages/cli/src/api/extractors/babel.ts
index 4ede39c2..1aef493c 100644
--- a/packages/cli/src/api/extractors/babel.ts
+++ b/packages/cli/src/api/extractors/babel.ts
@@ -1,11 +1,14 @@
-import { transformFileSync } from "@babel/core"
+import { transformFileSync, DEFAULT_EXTENSIONS } from "@babel/core"
 
 import linguiExtractMessages from "@lingui/babel-plugin-extract-messages"
 
 import { BabelOptions, ExtractorType } from "./types"
 import { projectType } from "../detect"
 
-const babelRe = /\.jsx?$/i
+const babelRe = new RegExp(
+	"\\.(" + DEFAULT_EXTENSIONS.map(ext => ext.slice(1)).join("|") + ")$",
+	"i"
+)
 
 const extractor: ExtractorType = {
   match(filename) {
diff --git a/packages/cli/src/lingui-compile.ts b/packages/cli/src/lingui-compile.ts
index 6d8a66d3..e8c412c2 100644
--- a/packages/cli/src/lingui-compile.ts
+++ b/packages/cli/src/lingui-compile.ts
@@ -31,6 +31,9 @@ function command(config, options) {
 
   console.error("Compiling message catalogs…")
 
+  if(config.pseudoLocale) {
+    config.locales.push(config.pseudoLocale)
+  }
   config.locales.forEach((locale) => {
     const [language] = locale.split(/[_-]/)
     if (locale !== config.pseudoLocale && !plurals[language]) {
@@ -44,10 +47,13 @@ function command(config, options) {
     }
 
     catalogs.forEach((catalog) => {
-      const messages = catalog.getTranslations(locale, {
-        fallbackLocale: config.fallbackLocale,
-        sourceLocale: config.sourceLocale,
-      })
+      const messages = catalog.getTranslations(
+        locale === config.pseudoLocale ? config.sourceLocale: locale,
+        {
+          fallbackLocale: config.fallbackLocale,
+          sourceLocale: config.sourceLocale,
+        }
+      )
 
       if (!options.allowEmpty) {
         const missing = R.values(messages)
@@ -72,13 +78,14 @@ function command(config, options) {
         }
       }
 
+      const namespace = options.namespace || config.compileNamespace
       const compiledCatalog = createCompiledCatalog(locale, messages, {
         strict: false,
-        namespace: options.namespace || config.compileNamespace,
+        namespace,
         pseudoLocale: config.pseudoLocale,
       })
 
-      const compiledPath = catalog.writeCompiled(locale, compiledCatalog)
+      const compiledPath = catalog.writeCompiled(locale, compiledCatalog, namespace)
 
       if (options.typescript) {
         const typescriptPath = compiledPath.replace(/\.jsx?$/, "") + ".d.ts"
@@ -111,7 +118,7 @@ if (require.main === module) {
     .option("--typescript", "Create Typescript definition for compiled bundle")
     .option(
       "--namespace <namespace>",
-      "Specify namespace for compiled bundle. Ex: cjs(default) -> module.exports, window.test -> window.test"
+      "Specify namespace for compiled bundle. Ex: cjs(default) -> module.exports, es -> export, window.test -> window.test"
     )
     .on("--help", function () {
       console.log("\n  Examples:\n")
diff --git a/packages/core/npm/esm/index.js b/packages/core/npm/esm/index.js
new file mode 100644
index 00000000..7a8b32ee
--- /dev/null
+++ b/packages/core/npm/esm/index.js
@@ -0,0 +1,6 @@
+import i18nModule from '../index.js'
+
+export const setupI18n = i18nModule.setupI18n
+export const I18n = i18nModule.I18n
+export const i18n = i18nModule.i18n
+export const formats = i18nModule.formats
diff --git a/packages/core/npm/esm/package.json b/packages/core/npm/esm/package.json
new file mode 100644
index 00000000..3dbc1ca5
--- /dev/null
+++ b/packages/core/npm/esm/package.json
@@ -0,0 +1,3 @@
+{
+  "type": "module"
+}
diff --git a/packages/core/package.json b/packages/core/package.json
index 9aa33bf4..95a0b5d2 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -27,11 +27,16 @@
   "engines": {
     "node": ">=10.0.0"
   },
+  "exports": {
+    "require": "./index.js",
+    "import": "./esm/index.js"
+  },
   "files": [
     "LICENSE",
     "README.md",
     "index.js",
-    "cjs/"
+    "cjs/",
+    "esm/"
   ],
   "dependencies": {
     "@babel/runtime": "^7.11.2",
diff --git a/yarn.lock b/yarn.lock
index 13a2c714..0029ae96 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1571,10 +1571,10 @@
   dependencies:
     defer-to-connect "^2.0.0"
 
-"@testing-library/dom@^7.23.0":
-  version "7.24.1"
-  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.24.1.tgz#0e8acd042070f2c1b183fbfe5c0d38b3194ad3c0"
-  integrity sha512-TemHWY59gvzcScGiE5eooZpzYk9GaED0TuuK4WefbIc/DQg0L5wOpnj7MIEeAGF3B7Ekf1kvmVnQ97vwz4Lmhg==
+"@testing-library/dom@^7.24.2":
+  version "7.24.2"
+  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.24.2.tgz#6d2b7dd21efbd5358b98c2777fc47c252f3ae55e"
+  integrity sha512-ERxcZSoHx0EcN4HfshySEWmEf5Kkmgi+J7O79yCJ3xggzVlBJ2w/QjJUC+EBkJJ2OeSw48i3IoePN4w8JlVUIA==
   dependencies:
     "@babel/code-frame" "^7.10.4"
     "@babel/runtime" "^7.10.3"
@@ -1584,13 +1584,13 @@
     dom-accessibility-api "^0.5.1"
     pretty-format "^26.4.2"
 
-"@testing-library/react@^11.0.2":
-  version "11.0.2"
-  resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.0.2.tgz#4588cc537085907bd1d98b531eb247dbbf57b1cc"
-  integrity sha512-iOuNNHt4ZgMH5trSKC4kaWDcKzUOf7e7KQIcu7xvGCd68/w1EegbW89F9T5sZ4IjS0gAXdvOfZbG9ESZ7YjOug==
+"@testing-library/react@^11.0.4":
+  version "11.0.4"
+  resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.0.4.tgz#c84082bfe1593d8fcd475d46baee024452f31dee"
+  integrity sha512-U0fZO2zxm7M0CB5h1+lh31lbAwMSmDMEMGpMT3BUPJwIjDEKYWOV4dx7lb3x2Ue0Pyt77gmz/VropuJnSz/Iew==
   dependencies:
     "@babel/runtime" "^7.11.2"
-    "@testing-library/dom" "^7.23.0"
+    "@testing-library/dom" "^7.24.2"
 
 "@types/aria-query@^4.2.0":
   version "4.2.0"
@@ -1735,10 +1735,10 @@
     jest-diff "^25.2.1"
     pretty-format "^25.2.1"
 
-"@types/jest@^26.0.13":
-  version "26.0.13"
-  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.13.tgz#5a7b9d5312f5dd521a38329c38ee9d3802a0b85e"
-  integrity sha512-sCzjKow4z9LILc6DhBvn5AkIfmQzDZkgtVVKmGwVrs5tuid38ws281D4l+7x1kP487+FlKDh5kfMZ8WSPAdmdA==
+"@types/jest@^26.0.14":
+  version "26.0.14"
+  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.14.tgz#078695f8f65cb55c5a98450d65083b2b73e5a3f3"
+  integrity sha512-Hz5q8Vu0D288x3iWXePSn53W7hAjP0H7EQ6QvDO9c7t46mR0lNOLlfuwQ+JkVxuhygHzlzPX+0jKdA3ZgSh+Vg==
   dependencies:
     jest-diff "^25.2.1"
     pretty-format "^25.2.1"
@@ -1882,20 +1882,32 @@
   dependencies:
     "@types/yargs-parser" "*"
 
-"@typescript-eslint/eslint-plugin@^4.1.0":
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.1.0.tgz#7d309f60815ff35e9627ad85e41928d7b7fd443f"
-  integrity sha512-U+nRJx8XDUqJxYF0FCXbpmD9nWt/xHDDG0zsw1vrVYAmEAuD/r49iowfurjSL2uTA2JsgtpsyG7mjO7PHf2dYw==
+"@typescript-eslint/eslint-plugin@^4.1.1":
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.1.1.tgz#78d5b18e259b13c2f4ec41dd9105af269a161a75"
+  integrity sha512-Hoxyt99EA9LMmqo/5PuWWPeWeB3mKyvibfJ1Hy5SfiUpjE8Nqp+5QNd9fOkzL66+fqvIWSIE+Ett16LGMzCGnQ==
   dependencies:
-    "@typescript-eslint/experimental-utils" "4.1.0"
-    "@typescript-eslint/scope-manager" "4.1.0"
+    "@typescript-eslint/experimental-utils" "4.1.1"
+    "@typescript-eslint/scope-manager" "4.1.1"
     debug "^4.1.1"
     functional-red-black-tree "^1.0.1"
     regexpp "^3.0.0"
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/experimental-utils@4.1.0", "@typescript-eslint/experimental-utils@^4.0.1":
+"@typescript-eslint/experimental-utils@4.1.1":
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.1.1.tgz#52ff4e37c93113eb96385a4e6d075abece1ea72d"
+  integrity sha512-jzYsNciHoa4Z3c1URtmeT/bamYm8Dwfw6vuN3WHIE/BXb1iC4KveAnXDErTAZtPVxTYBaYn3n2gbt6F6D2rm1A==
+  dependencies:
+    "@types/json-schema" "^7.0.3"
+    "@typescript-eslint/scope-manager" "4.1.1"
+    "@typescript-eslint/types" "4.1.1"
+    "@typescript-eslint/typescript-estree" "4.1.1"
+    eslint-scope "^5.0.0"
+    eslint-utils "^2.0.0"
+
+"@typescript-eslint/experimental-utils@^4.0.1":
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.1.0.tgz#263d7225645c09a411c8735eeffd417f50f49026"
   integrity sha512-paEYLA37iqRIDPeQwAmoYSiZ3PiHsaAc3igFeBTeqRHgPnHjHLJ9OGdmP6nwAkF65p2QzEsEBtpjNUBWByNWzA==
@@ -1907,14 +1919,14 @@
     eslint-scope "^5.0.0"
     eslint-utils "^2.0.0"
 
-"@typescript-eslint/parser@^4.1.0":
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.1.0.tgz#9b0409411725f14cd7faa81a664e5051225961db"
-  integrity sha512-hM/WNCQTzDHgS0Ke3cR9zPndL3OTKr9OoN9CL3UqulsAjYDrglSwIIgswSmHBcSbOzLmgaMARwrQEbIumIglvQ==
+"@typescript-eslint/parser@^4.1.1":
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.1.1.tgz#324b4b35e314075adbc92bd8330cf3ef0c88cf3e"
+  integrity sha512-NLIhmicpKGfJbdXyQBz9j48PA6hq6e+SDOoXy7Ak6bq1ebGqbgG+fR1UIDAuay6OjQdot69c/URu2uLlsP8GQQ==
   dependencies:
-    "@typescript-eslint/scope-manager" "4.1.0"
-    "@typescript-eslint/types" "4.1.0"
-    "@typescript-eslint/typescript-estree" "4.1.0"
+    "@typescript-eslint/scope-manager" "4.1.1"
+    "@typescript-eslint/types" "4.1.1"
+    "@typescript-eslint/typescript-estree" "4.1.1"
     debug "^4.1.1"
 
 "@typescript-eslint/scope-manager@4.1.0":
@@ -1925,11 +1937,24 @@
     "@typescript-eslint/types" "4.1.0"
     "@typescript-eslint/visitor-keys" "4.1.0"
 
+"@typescript-eslint/scope-manager@4.1.1":
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.1.1.tgz#bdb8526e82435f32b4ccd9dd4cec01af97b48850"
+  integrity sha512-0W8TTobCvIIQ2FsrYTffyZGAAFUyIbEHq5EYJb1m7Rpd005jrnOvKOo8ywCLhs/Bm17C+KsrUboBvBAARQVvyA==
+  dependencies:
+    "@typescript-eslint/types" "4.1.1"
+    "@typescript-eslint/visitor-keys" "4.1.1"
+
 "@typescript-eslint/types@4.1.0":
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.1.0.tgz#edbd3fec346f34e13ce7aa176b03b497a32c496a"
   integrity sha512-rkBqWsO7m01XckP9R2YHVN8mySOKKY2cophGM8K5uDK89ArCgahItQYdbg/3n8xMxzu2elss+an1TphlUpDuJw==
 
+"@typescript-eslint/types@4.1.1":
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.1.1.tgz#57500c4a86b28cb47094c1a62f1177ea279a09cb"
+  integrity sha512-zrBiqOKYerMTllKcn+BP+i1b7LW/EbMMYytroXMxUTvFPn1smkCu0D7lSAx29fTUO4jnwV0ljSvYQtn2vNrNxA==
+
 "@typescript-eslint/typescript-estree@4.1.0":
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.1.0.tgz#394046ead25164494218c0e3d6b960695ea967f6"
@@ -1944,6 +1969,20 @@
     semver "^7.3.2"
     tsutils "^3.17.1"
 
+"@typescript-eslint/typescript-estree@4.1.1":
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.1.1.tgz#2015a84d71303ecdb6f46efd807ac19a51aab490"
+  integrity sha512-2AUg5v0liVBsqbGxBphbJ0QbGqSRVaF5qPoTPWcxop+66vMdU1h4CCvHxTC47+Qb+Pr4l2RhXDd41JNpwcQEKw==
+  dependencies:
+    "@typescript-eslint/types" "4.1.1"
+    "@typescript-eslint/visitor-keys" "4.1.1"
+    debug "^4.1.1"
+    globby "^11.0.1"
+    is-glob "^4.0.1"
+    lodash "^4.17.15"
+    semver "^7.3.2"
+    tsutils "^3.17.1"
+
 "@typescript-eslint/visitor-keys@4.1.0":
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.1.0.tgz#b2d528c9484e7eda1aa4f86ccf0432fb16e4d545"
@@ -1952,6 +1991,14 @@
     "@typescript-eslint/types" "4.1.0"
     eslint-visitor-keys "^2.0.0"
 
+"@typescript-eslint/visitor-keys@4.1.1":
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.1.1.tgz#bb05664bf4bea28dc120d1da94f3027d42ab0f6f"
+  integrity sha512-/EOOXbA2ferGLG6RmCHEQ0lTTLkOlXYDgblCmQk3tIU7mTPLm4gKhFMeeUSe+bcchTUsKeCk8xcpbop5Zr/8Rw==
+  dependencies:
+    "@typescript-eslint/types" "4.1.1"
+    eslint-visitor-keys "^2.0.0"
+
 "@vercel/build-utils@2.5.1":
   version "2.5.1"
   resolved "https://registry.yarnpkg.com/@vercel/build-utils/-/build-utils-2.5.1.tgz#2f687c2d82464dd85e0ed8130bc01e5dac9b11a4"
@@ -4299,10 +4346,10 @@ eslint-plugin-import@^2.22.0:
     resolve "^1.17.0"
     tsconfig-paths "^3.9.0"
 
-eslint-plugin-jest@^24.0.0:
-  version "24.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.0.0.tgz#6b1c460c529104c7d16d889e76fe708b281c4d14"
-  integrity sha512-a0G7hSDbuBCW4PNT6MVpAyfnGbUDOqxzOyhR6wT2BIBnR7MhvfAqd6KKfsTjX+Z3gxzIHiEsihzdClU4cSc6qQ==
+eslint-plugin-jest@^24.0.2:
+  version "24.0.2"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.0.2.tgz#4bf0fcdc86289d702a7dacb430b4363482af773b"
+  integrity sha512-DSBLNpkKDOpUJQkTGSs5sVJWsu0nDyQ2rYxkr0Eh7nrkc5bMUr/dlDbtTj3l8y6UaCVsem6rryF1OZrKnz1S5g==
   dependencies:
     "@typescript-eslint/experimental-utils" "^4.0.1"
 
@@ -4373,10 +4420,10 @@ eslint-visitor-keys@^2.0.0:
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
   integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
 
-eslint@^7.8.1:
-  version "7.8.1"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.8.1.tgz#e59de3573fb6a5be8ff526c791571646d124a8fa"
-  integrity sha512-/2rX2pfhyUG0y+A123d0ccXtMm7DV7sH1m3lk9nk2DZ2LReq39FXHueR9xZwshE5MdfSf0xunSaMWRqyIA6M1w==
+eslint@^7.9.0:
+  version "7.9.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.9.0.tgz#522aeccc5c3a19017cf0cb46ebfd660a79acf337"
+  integrity sha512-V6QyhX21+uXp4T+3nrNfI3hQNBDa/P8ga7LoQOenwrlEFXrEnUEE+ok1dMtaS3b6rmLXhT1TkTIsG75HMLbknA==
   dependencies:
     "@babel/code-frame" "^7.0.0"
     "@eslint/eslintrc" "^0.1.3"
@@ -6201,7 +6248,7 @@ jest-snapshot@^26.4.2:
     pretty-format "^26.4.2"
     semver "^7.3.2"
 
-jest-util@26.x, jest-util@^26.3.0:
+jest-util@^26.1.0, jest-util@^26.3.0:
   version "26.3.0"
   resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.3.0.tgz#a8974b191df30e2bf523ebbfdbaeb8efca535b3e"
   integrity sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw==
@@ -7940,10 +7987,10 @@ prepend-http@^2.0.0:
   resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
   integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
 
-prettier@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.1.tgz#d9485dd5e499daa6cb547023b87a6cf51bee37d6"
-  integrity sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==
+prettier@^2.1.2:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5"
+  integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==
 
 pretty-bytes@^5.1.0:
   version "5.3.0"
@@ -8697,10 +8744,10 @@ rollup-pluginutils@^2.6.0, rollup-pluginutils@^2.8.1:
   dependencies:
     estree-walker "^0.6.1"
 
-rollup@^2.26.11:
-  version "2.26.11"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.26.11.tgz#4fc31de9c7b83d50916fc8395f8c3d24730cdaae"
-  integrity sha512-xyfxxhsE6hW57xhfL1I+ixH8l2bdoIMaAecdQiWF3N7IgJEMu99JG+daBiSZQjnBpzFxa0/xZm+3pbCdAQehHw==
+rollup@^2.28.1:
+  version "2.28.1"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.28.1.tgz#ceedca3cdb013c2fa8f22f958a29c203368159ea"
+  integrity sha512-DOtVoqOZt3+FjPJWLU8hDIvBjUylc9s6IZvy76XklxzcLvAQLtVAG/bbhsMhcWnYxC0TKKcf1QQ/tg29zeID0Q==
   optionalDependencies:
     fsevents "~2.1.2"
 
@@ -10044,22 +10091,22 @@ tree-kill@^1.2.2:
   resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
   integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
 
-ts-jest@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.3.0.tgz#6b2845045347dce394f069bb59358253bc1338a9"
-  integrity sha512-Jq2uKfx6bPd9+JDpZNMBJMdMQUC3sJ08acISj8NXlVgR2d5OqslEHOR2KHMgwymu8h50+lKIm0m0xj/ioYdW2Q==
+ts-jest@^26.4.0:
+  version "26.4.0"
+  resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.4.0.tgz#903c7827f3d3bc33efc2f91be294b164400c32e3"
+  integrity sha512-ofBzoCqf6Nv/PoWb/ByV3VNKy2KJSikamOBxvR3E6eVdIw10GwAXoyvMWXXjZJK2s6S27ZE8fI+JBTnGaovl6Q==
   dependencies:
     "@types/jest" "26.x"
     bs-logger "0.x"
     buffer-from "1.x"
     fast-json-stable-stringify "2.x"
-    jest-util "26.x"
+    jest-util "^26.1.0"
     json5 "2.x"
     lodash.memoize "4.x"
     make-error "1.x"
     mkdirp "1.x"
     semver "7.x"
-    yargs-parser "18.x"
+    yargs-parser "20.x"
 
 ts-node@8.9.1:
   version "8.9.1"
@@ -10182,10 +10229,10 @@ typescript@3.9.3:
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a"
   integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==
 
-typescript@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2"
-  integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==
+typescript@^4.0.3:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5"
+  integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==
 
 underscore@^1.7.0:
   version "1.10.2"
@@ -10485,10 +10532,10 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1:
     source-list-map "^2.0.0"
     source-map "~0.6.1"
 
-webpack@^4.44.1:
-  version "4.44.1"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.1.tgz#17e69fff9f321b8f117d1fda714edfc0b939cc21"
-  integrity sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ==
+webpack@^4.44.2:
+  version "4.44.2"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.2.tgz#6bfe2b0af055c8b2d1e90ed2cd9363f841266b72"
+  integrity sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==
   dependencies:
     "@webassemblyjs/ast" "1.9.0"
     "@webassemblyjs/helper-module-context" "1.9.0"
@@ -10718,7 +10765,12 @@ yaml@^1.10.0, yaml@^1.7.2, yaml@^1.9.2:
   resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
   integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
 
-yargs-parser@18.x, yargs-parser@^18.1.2:
+yargs-parser@20.x:
+  version "20.2.0"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.0.tgz#944791ca2be2e08ddadd3d87e9de4c6484338605"
+  integrity sha512-2agPoRFPoIcFzOIp6656gcvsg2ohtscpw2OINr/q46+Sq41xz2OYLqx5HRHabmFU1OARIPAYH5uteICE7mn/5A==
+
+yargs-parser@^18.1.2:
   version "18.1.3"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
   integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==


Test patch:
diff --git a/packages/cli/src/api/catalog.test.ts b/packages/cli/src/api/catalog.test.ts
index f6138d87..fcfafaca 100644
--- a/packages/cli/src/api/catalog.test.ts
+++ b/packages/cli/src/api/catalog.test.ts
@@ -12,6 +12,7 @@ import {
   MergeOptions,
   order,
 } from "./catalog"
+import { createCompiledCatalog} from "./compile"
 
 import { copyFixture } from "../tests"
 import { ExtractedMessageType, MessageType } from "./types"
@@ -804,3 +805,50 @@ describe("order", function () {
     expect(Object.keys(orderedCatalogs)).toMatchSnapshot()
   })
 })
+
+describe("writeCompiled", function () {
+  it("saves ES modules to .mjs files", function () {
+    const localeDir = copyFixture(fixture("locales", "initial"))
+    const catalog = new Catalog(
+      {
+        name: "messages",
+        path: path.join(localeDir, "{locale}", "messages"),
+        include: [],
+        exclude: [],
+      },
+      mockConfig()
+    )
+
+    const namespace = "es"
+    const compiledCatalog = createCompiledCatalog("en", {}, { namespace })
+    // Test that the file extension of the compiled catalog is `.mjs`
+    expect(catalog.writeCompiled("en", compiledCatalog, namespace)).toMatch(/\.mjs$/)
+  })
+
+  it("saves anything else than ES modules to .js files", function () {
+    const localeDir = copyFixture(fixture("locales", "initial"))
+    const catalog = new Catalog(
+      {
+        name: "messages",
+        path: path.join(localeDir, "{locale}", "messages"),
+        include: [],
+        exclude: [],
+      },
+      mockConfig()
+    )
+
+    let compiledCatalog = createCompiledCatalog("en", {}, {})
+    // Test that the file extension of the compiled catalog is `.js`
+    expect(catalog.writeCompiled("en", compiledCatalog)).toMatch(/\.js$/)
+
+    compiledCatalog = createCompiledCatalog("en", {}, { namespace: "cjs" })
+    expect(catalog.writeCompiled("en", compiledCatalog)).toMatch(/\.js$/)
+
+    compiledCatalog = createCompiledCatalog("en", {}, { namespace: "window.test" })
+    expect(catalog.writeCompiled("en", compiledCatalog)).toMatch(/\.js$/)
+
+    compiledCatalog = createCompiledCatalog("en", {}, { namespace: "global.test" })
+    expect(catalog.writeCompiled("en", compiledCatalog)).toMatch(/\.js$/)
+
+  })
+})
diff --git a/packages/cli/src/api/extract.test.ts b/packages/cli/src/api/extract.test.ts
index ab131cf4..6f5ed850 100644
--- a/packages/cli/src/api/extract.test.ts
+++ b/packages/cli/src/api/extract.test.ts
@@ -10,13 +10,18 @@ describe("extract", function () {
   let extract, babel, typescript
 
   beforeAll(() => {
+
+    // References to the real match functions
+    const babelMatch = require("./extractors/babel").default.match
+    const tsMatch = require("./extractors/typescript").default.match
+
     jest.doMock("./extractors/babel", () => ({
-      match: jest.fn((filename) => filename.endsWith(".js")),
+      match: jest.fn(babelMatch),
       extract: jest.fn(),
     }))
 
     jest.doMock("./extractors/typescript", () => ({
-      match: jest.fn((filename) => filename.endsWith(".ts")),
+      match: jest.fn(tsMatch),
       extract: jest.fn(),
     }))
 
@@ -31,6 +36,10 @@ describe("extract", function () {
 
         components: {
           "Babel.js": "",
+          "Babel.jsx": "",
+          "Babel.es6": "",
+          "Babel.es": "",
+          "Babel.mjs": "",
           "Typescript.ts": "",
           forbidden: {
             "apple.js": "",
@@ -60,33 +69,80 @@ describe("extract", function () {
     expect(babel.match).toHaveBeenCalledWith(
       path.join("src", "components", "Babel.js")
     )
-    expect(babel.match).toHaveBeenCalledWith(path.join("src", "index.html"))
+    expect(babel.match).toHaveBeenCalledWith(
+      path.join("src", "components", "Babel.jsx")
+    )
+    expect(babel.match).toHaveBeenCalledWith(
+      path.join("src", "components", "Babel.es6")
+    )
+    expect(babel.match).toHaveBeenCalledWith(
+      path.join("src", "components", "Babel.es")
+    )
+    expect(babel.match).toHaveBeenCalledWith(
+      path.join("src", "components", "Babel.mjs")
+    )
+		
+    expect(babel.match).toHaveBeenCalledWith(
+			path.join("src", "index.html")
+    )
 
     // This file is ignored
     expect(babel.extract).not.toHaveBeenCalledWith(
       path.join("src", "index.html")
     )
 
+    const extractArgs = [
+      "locale", 
+      { babelOptions: {}, ignore: ["forbidden"]}
+    ]
     expect(babel.extract).toHaveBeenCalledWith(
       path.join("src", "components", "Babel.js"),
-      "locale",
-      { babelOptions: {}, ignore: ["forbidden"] }
+      ...extractArgs
+    )
+    expect(babel.extract).toHaveBeenCalledWith(
+      path.join("src", "components", "Babel.jsx"),
+      ...extractArgs
+    )
+    expect(babel.extract).toHaveBeenCalledWith(
+      path.join("src", "components", "Babel.es6"),
+      ...extractArgs
+    )
+    expect(babel.extract).toHaveBeenCalledWith(
+      path.join("src", "components", "Babel.es"),
+      ...extractArgs
+    )
+    expect(babel.extract).toHaveBeenCalledWith(
+      path.join("src", "components", "Babel.mjs"),
+      ...extractArgs
     )
     expect(babel.extract).not.toHaveBeenCalledWith(
       path.join("src", "components", "Typescript.ts"),
-      "locale",
-      { babelOptions: {}, ignore: ["forbidden"] }
+      ...extractArgs
     )
 
     expect(typescript.extract).not.toHaveBeenCalledWith(
       path.join("src", "components", "Babel.js"),
-      "locale",
-      { babelOptions: {}, ignore: ["forbidden"] }
+      ...extractArgs
+    )
+    expect(typescript.extract).not.toHaveBeenCalledWith(
+      path.join("src", "components", "Babel.jsx"),
+      ...extractArgs
+    )
+    expect(typescript.extract).not.toHaveBeenCalledWith(
+      path.join("src", "components", "Babel.es6"),
+      ...extractArgs
+    )
+    expect(typescript.extract).not.toHaveBeenCalledWith(
+      path.join("src", "components", "Babel.es"),
+      ...extractArgs
+    )
+    expect(typescript.extract).not.toHaveBeenCalledWith(
+      path.join("src", "components", "Babel.mjs"),
+      ...extractArgs
     )
     expect(typescript.extract).toHaveBeenCalledWith(
       path.join("src", "components", "Typescript.ts"),
-      "locale",
-      { babelOptions: {}, ignore: ["forbidden"] }
+      ...extractArgs
     )
   })
 })