Repository: lingui/js-lingui
Pull request #1336 (2023-01-12 13:30:48)

refactor(build): typescript + support inner exports
Fix size-limit issue 
And fix bundle sizr issue introduced by @message-format/parser exported from @lingui/core

Refactor and rewrite to typescript build scripts

vercel[bot]: [vc]: #36ouruj0Wr6eVCSzW9SEbj2EXx/Rbb2TJmRkEYhS/4c=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJqcy1saW5ndWkiLCJyb290RGlyZWN0b3J5IjpudWxsLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vbGluZ3VpL2pzLWxpbmd1aS84VGpHQ1pXOW9MMjc1NENlSHlWWWdmcGVSdmc5IiwicHJldmlld1VybCI6IiIsIm5leHRDb21taXRTdGF0dXMiOiJQRU5ESU5HIn1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Updated |
| :--- | :----- | :------ | :------ |
| **js-lingui** | 🔄 Building ([Inspect](https://vercel.com/lingui/js-lingui/8TjGCZW9oL2754CeHyVYgfpeRvg9)) |  | Jan 12, 2023 at 1:30PM (UTC) |


thekip: Still failing, that strange
thekip: It worked with flat exports, and started failing again because of multi-entry, probable i've created inner entry incorrectly
thekip: 🎉 builds!  Size testing failing because it's trying to compare with master and master has a failing code now. 
Martin005: @thekip
I have encountered several issues with your implementation:

1. After I built the library and released it locally with the `/scripts/verdaccio-release.js` script, I tried installing it in the current example `create-react-app` and running with `yarn start`, but I got the following error:
```
node:internal/modules/cjs/loader:535
      throw e;
      ^

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: No "exports" main defined in /home/martin/js-lingui/examples/create-react-app/node_modules/@lingui/conf/package.json
```

2. Following the [CONTRIBUTING.md](https://github.com/lingui/js-lingui/blob/main/CONTRIBUTING.md#contributing-the-code):

- When I run `release:test`, I get the following error:
```
node:internal/modules/cjs/loader:998
  throw err;
  ^

Error: Cannot find module '/home/martin/js-lingui/scripts/build'
```

- Also, when I run `tsc` (as a part of the `lint:all` command in the main `package.json`), I get the following error:

```
$ /home/martin/js-lingui/node_modules/.bin/tsc
packages/core/build/cjs/dev/index.d.ts:1:10 - error TS2305: Module '"../compile/compileMessage"' has no exported member 'compileMessage'.

1 import { compileMessage as compile } from "../compile/compileMessage";
           ~~~~~~~~~~~~~~

packages/core/build/esm/dev/index.d.ts:1:10 - error TS2305: Module '"../compile/compileMessage"' has no exported member 'compileMessage'.

1 import { compileMessage as compile } from "../compile/compileMessage";
           ~~~~~~~~~~~~~~
```

andrii-bodnar: We'd like to have a separate fix for the bug in `main` and separately the rest (just to not block the new version release)
thekip: > We'd like to have a separate fix for the bug in main and separately the rest (just to not block the new version release)
it's not possible to ship it separately, to fix the `main` building system should be changed.
You can disable or ignore size-limit issue for now and ship without it, code should work, it's just an issue with workflow in the monorepo. 

> Also, when I run tsc (as a part of the lint:all command in the main package.json), I get the following error:
Why it's not failing in CI? 

andrii-bodnar: @thekip what exactly went wrong in the #1328 that broke this size-check? It might be easier to rollback this change than dangerously change the build system.

It worked before and it's not a good idea to ignore this.
thekip: > so in short
this is a first time usage of the cross-linked library in client-side code
There are also lingui/react which states that uses core package, but actually it uses only typings, so in real js code imports to core are stripped
and currently when webpack starts resolving imports it goes to ./packages/core/ instead of ./package/core/dst because this is how yarn workspace linking works
When we deploy packages, it deployed from ./dst
but when thy used locally, they used from ./ {root of package}
it means  the changes should be bigger to support this
i will try to find a quick workaround for that


thekip: In my opinion it's better to "dangerously" change the build system (which in fact is not dangerously)
thekip: `release:test` and `lint:all` are fixed. 

Actually `release:test` are useless today, it's doing the same thing as in CI but with diffrent code. So issue was in file itself not in tests. 

Typings was failing because i marked `compileMessage` as `@internal`, and typescript stripped the typings of it. (that's exactly what i wanted to achieve). 

I deleted /dev folder with all content and reorganized it in different way. Probably previously (prior v3)  the `dev` was somehow exposed from the package, but right now in current `main` code it's no longer exposed, and users have no chance to use anything from there, so it's safe. 


Looking into issue with No "exports" with @lingui/conf
thekip: Fixed all issues, now it works in examples. But came up with better (less fragile) idea, i will try to implement it and look what i get
thekip: @Martin005 @andrii-bodnar was managed to simplify process and remove fragile parts. If you're afraid of changes, we can discuss how the process previously looks like, and how it's looks now. 

In short: 

We do not copy package.json from root of package to ./build, instead we publish package  from the package root. This is how it's done in most of the projects on the market. This simplifies things a lot and fix issues with size-check among with others. 

Patch:
diff --git a/package.json b/package.json
index 6298dd34..0594a963 100644
--- a/package.json
+++ b/package.json
@@ -16,17 +16,17 @@
     "lint:types": "tsc",
     "lint:eslint": "eslint packages",
     "lint:all": "yarn lint:eslint && yarn lint:types",
-    "release:build": "node ./scripts/build/",
-    "release:test": "node ./scripts/test.js",
+    "release:build": "node -r @swc-node/register ./scripts/build/index.ts",
+    "release:test": "node -r @swc-node/register  ./scripts/test.ts",
     "version:next": "lerna version --exact --force-publish --no-private --preid next --create-release github --conventional-commits --conventional-prerelease --yes",
     "version:latest": "lerna version --exact --force-publish --no-private --create-release github --conventional-commits --yes",
-    "release:latest": "lerna publish from-package --contents build --dist-tag latest --yes",
-    "release:next": "lerna publish from-package --contents build --canary --preid next --pre-dist-tag next --yes",
+    "release:latest": "lerna publish from-package --dist-tag latest --yes",
+    "release:next": "lerna publish from-package --canary --preid next --pre-dist-tag next --yes",
     "build:docs": "cd docs && ./vercel-build.sh",
     "size-limit": "size-limit"
   },
   "engines": {
-    "node": ">=10.0.0"
+    "node": ">=14.0.0"
   },
   "devDependencies": {
     "@babel/core": "^7.11.6",
@@ -37,11 +37,16 @@
     "@babel/preset-react": "^7.10.4",
     "@babel/preset-typescript": "^7.10.4",
     "@size-limit/preset-small-lib": "^4.10.1",
+    "@swc/jest": "^0.2.24",
+    "@swc/core": "^1.3.26",
+    "swc-node": "^1.0.0",
     "@testing-library/react": "^11.0.4",
     "@types/babel-types": "^7.0.9",
     "@types/jest": "^26.0.14",
+    "@types/node": "14.14.31",
     "@types/ramda": "^0.27.23",
     "@types/react": "^16.9.51",
+    "@types/rimraf": "^3.0.2",
     "@typescript-eslint/eslint-plugin": "^4.4.0",
     "@typescript-eslint/parser": "^4.4.0",
     "babel-code-frame": "^6.26.0",
@@ -76,7 +81,6 @@
     "node-emoji": "^1.10.0",
     "npm-cli-login": "^0.1.1",
     "ora": "^5.1.0",
-    "pkg-up": "^3.1.0",
     "prettier": "^2.1.2",
     "react": "^16.13.1",
     "react-dom": "^16.13.1",
@@ -94,7 +98,6 @@
     "set-tz": "^0.2.0",
     "size-limit": "^4.10.1",
     "strip-ansi": "^6.0.0",
-    "targz": "^1.0.1",
     "tmp": "^0.2.1",
     "ts-jest": "^26.4.1",
     "typescript": "^4.0.3",
diff --git a/packages/babel-plugin-extract-messages/package.json b/packages/babel-plugin-extract-messages/package.json
index c1dfc9dd..af2436d3 100644
--- a/packages/babel-plugin-extract-messages/package.json
+++ b/packages/babel-plugin-extract-messages/package.json
@@ -2,7 +2,7 @@
   "name": "@lingui/babel-plugin-extract-messages",
   "version": "3.15.0",
   "description": "Babel plugin for collecting messages from source code for internationalization",
-  "main": "index.js",
+  "main": "./build/index.js",
   "author": {
     "name": "Tomáš Ehrlich",
     "email": "tomas.ehrlich@gmail.com"
@@ -17,6 +17,11 @@
     "i9n",
     "translation"
   ],
+  "files": [
+    "LICENSE",
+    "README.md",
+    "build/"
+  ],
   "repository": {
     "type": "git",
     "url": "https://github.com/lingui/js-lingui.git"
diff --git a/packages/cli/package.json b/packages/cli/package.json
index e563709d..5d5d8f0e 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -18,9 +18,17 @@
     "name": "Tomáš Ehrlich",
     "email": "tomas.ehrlich@gmail.com"
   },
-  "main": "index.js",
+  "main": "./build/index.js",
   "bin": {
-    "lingui": "./lingui.js"
+    "lingui": "./build/lingui.js"
+  },
+  "exports": {
+    ".": {
+      "require": "./build/index.js"
+    },
+    "./api": {
+      "require": "./build/api/index.js"
+    }
   },
   "engines": {
     "node": ">=10.0.0"
@@ -28,10 +36,7 @@
   "files": [
     "LICENSE",
     "README.md",
-    "api",
-    "services",
-    "lingui.js",
-    "lingui-*.js"
+    "build/"
   ],
   "dependencies": {
     "@babel/generator": "^7.11.6",
diff --git a/packages/cli/src/api/compile.ts b/packages/cli/src/api/compile.ts
index 5b4daee2..b64350c1 100644
--- a/packages/cli/src/api/compile.ts
+++ b/packages/cli/src/api/compile.ts
@@ -1,6 +1,6 @@
 import * as t from "@babel/types"
 import generate, {GeneratorOptions} from "@babel/generator"
-import {compileMessage} from "@lingui/core"
+import {compileMessage} from "@lingui/core/compile"
 import pseudoLocalize from "./pseudoLocalize"
 
 export type CompiledCatalogNamespace = "cjs" | "es" | "ts" | string
diff --git a/packages/cli/src/api/detect.ts b/packages/cli/src/api/detect.ts
index 1b53b7c0..99803997 100644
--- a/packages/cli/src/api/detect.ts
+++ b/packages/cli/src/api/detect.ts
@@ -1,31 +1,43 @@
 import fs from "fs"
+import nodePath from "path"
 import pkgUp from "pkg-up"
+import cliPackageJson from "../../package.json";
 
 export const projectType = {
   CRA: "CRA",
   REACT: "REACT",
 }
 
-function getPackageJson() {
-  const packageJsonPath = pkgUp.sync()
+type PackageJson = {
+  name: string
+  dependencies: Record<string, string>
+  devDependencies: Record<string, string>
+}
+
+function getPackageJson(cwd?: string): {path: string, content: PackageJson} {
+  const packageJsonPath = pkgUp.sync({
+    cwd
+  })
 
   try {
     const json = fs.readFileSync(packageJsonPath, "utf8")
-    return JSON.parse(json)
+    return {path: packageJsonPath, content: JSON.parse(json)}
   } catch (e) {
     console.error(e)
     return null
   }
 }
 
-function hasDependency(pkg, name) {
+
+
+function hasDependency(pkg: PackageJson, name: string) {
   return (
     (pkg.dependencies && pkg.dependencies[name]) ||
     (pkg.devDependencies && pkg.devDependencies[name])
   )
 }
 
-function detectFramework(pkg) {
+function detectFramework(pkg: PackageJson) {
   if (hasDependency(pkg, "react-scripts")) {
     return projectType.CRA
   }
@@ -38,8 +50,13 @@ function detectFramework(pkg) {
 }
 
 export function detect() {
-  const pkg = getPackageJson()
+  let pkg = getPackageJson()
+
   if (!pkg) return null
 
-  return detectFramework(pkg)
+  if (pkg.content.name === cliPackageJson.name) {
+    pkg = getPackageJson(nodePath.dirname(pkg.path))
+  }
+
+  return detectFramework(pkg.content)
 }
diff --git a/packages/conf/package.json b/packages/conf/package.json
index 09b65076..6562560f 100644
--- a/packages/conf/package.json
+++ b/packages/conf/package.json
@@ -14,7 +14,7 @@
     "name": "Tomáš Ehrlich",
     "email": "tomas.ehrlich@gmail.com"
   },
-  "main": "index.js",
+  "main": "build/index.js",
   "engines": {
     "node": ">=10.0.0"
   },
diff --git a/packages/core/compile.entry.js b/packages/core/compile.entry.js
new file mode 100644
index 00000000..56dea88c
--- /dev/null
+++ b/packages/core/compile.entry.js
@@ -0,0 +1,4 @@
+/**
+ * this is Rollup Entry
+ */
+export * from "./src/compile"
diff --git a/packages/core/compile.js b/packages/core/compile.js
new file mode 100644
index 00000000..ca226eb9
--- /dev/null
+++ b/packages/core/compile.js
@@ -0,0 +1,5 @@
+/**
+ * this is used to point to the right source when development with workspaces
+ */
+// eslint-disable-next-line import/no-unresolved
+export * from "./build/compile"
diff --git a/packages/core/dev.js b/packages/core/dev.js
deleted file mode 100644
index da42ee8f..00000000
--- a/packages/core/dev.js
+++ /dev/null
@@ -1,1 +0,0 @@
-export * from "./src/dev"
diff --git a/packages/core/formats.js b/packages/core/formats.js
deleted file mode 100644
index 9d0e6854..00000000
--- a/packages/core/formats.js
+++ /dev/null
@@ -1,1 +0,0 @@
-export * from "./src/formats"
diff --git a/packages/core/npm/compile.js b/packages/core/npm/compile.js
new file mode 100644
index 00000000..6a8c16d3
--- /dev/null
+++ b/packages/core/npm/compile.js
@@ -0,0 +1,5 @@
+if (process.env.NODE_ENV === "production") {
+  module.exports = require("./cjs/compile.production.min.js")
+} else {
+  module.exports = require("./cjs/compile.development.js")
+}
diff --git a/packages/core/npm/dev.js b/packages/core/npm/dev.js
deleted file mode 100644
index bef168f5..00000000
--- a/packages/core/npm/dev.js
+++ /dev/null
@@ -1,5 +0,0 @@
-if (process.env.NODE_ENV === "production") {
-  module.exports = require("./cjs/dev.production.min.js")
-} else {
-  module.exports = require("./cjs/dev.development.js")
-}
diff --git a/packages/core/npm/esm/compile.js b/packages/core/npm/esm/compile.js
new file mode 100644
index 00000000..f7fcf4a8
--- /dev/null
+++ b/packages/core/npm/esm/compile.js
@@ -0,0 +1,9 @@
+import {
+    compileMessage as compileMessageProd
+} from './compile.production.min.js';
+
+import {
+    compileMessage as compileMessageDev
+} from './compile.development.js';
+
+export const compileMessage = process.env.NODE_ENV === 'production' ? compileMessageProd : compileMessageDev;
diff --git a/packages/core/npm/esm/dev.js b/packages/core/npm/esm/dev.js
deleted file mode 100644
index c65bea8c..00000000
--- a/packages/core/npm/esm/dev.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import {
-  i18n as i18nProd,
-  setupI18n as setupI18nProd,
-  formats as formatsProd,
-  I18n as I18nProd
-} from './dev.production.min.js';
-
-import {
-  i18n as i18nDev,
-  setupI18n as setupI18nDev,
-  formats as formatsDev,
-  I18n as I18nDev
-} from './dev.development.js';
-
-export const i18n = process.env.NODE_ENV === 'production' ? i18nProd : i18nDev;
-export const setupI18n = process.env.NODE_ENV === 'production' ? setupI18nProd : setupI18nDev;
-export const formats = process.env.NODE_ENV === 'production' ? formatsProd : formatsDev;
-export const I18n = process.env.NODE_ENV === 'production' ? I18nProd : I18nDev;
\ No newline at end of file
diff --git a/packages/core/package.json b/packages/core/package.json
index ad2ee199..fefda3be 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -3,9 +3,9 @@
   "version": "3.15.0",
   "sideEffects": false,
   "description": "I18n tools for javascript",
-  "main": "index.js",
-  "module": "esm/index.js",
-  "types": "cjs/index.d.ts",
+  "main": "./build/index.js",
+  "module": "./build/esm/index.js",
+  "types": "./build/cjs/index.d.ts",
   "author": {
     "name": "Tomáš Ehrlich",
     "email": "tomas.ehrlich@gmail.com"
@@ -27,21 +27,23 @@
     "url": "https://github.com/lingui/js-lingui/issues"
   },
   "engines": {
-    "node": ">=10.0.0"
+    "node": ">=14.0.0"
   },
   "exports": {
     ".": {
-      "require": "./index.js",
-      "import": "./esm/index.js"
+      "require": "./build/index.js",
+      "import": "./build/esm/index.js"
+    },
+    "./compile": {
+      "require": "./build/compile.js",
+      "import": "./build/esm/compile.js"
     },
     "./package.json": "./package.json"
   },
   "files": [
     "LICENSE",
     "README.md",
-    "index.js",
-    "cjs/",
-    "esm/"
+    "build/"
   ],
   "dependencies": {
     "@babel/runtime": "^7.11.2",
diff --git a/packages/core/src/compileMessage.ts b/packages/core/src/compile/compileMessage.ts
similarity index 95%
rename from packages/core/src/compileMessage.ts
rename to packages/core/src/compile/compileMessage.ts
index d2809f96..ef860384 100644
--- a/packages/core/src/compileMessage.ts
+++ b/packages/core/src/compile/compileMessage.ts
@@ -1,5 +1,5 @@
 import {Content, parse, Token} from "@messageformat/parser"
-import {CompiledMessage, CompiledMessageToken} from "./i18n"
+import {CompiledMessage, CompiledMessageToken} from "../i18n"
 
 type MapTextFn =  (value: string) => string;
 
@@ -52,6 +52,7 @@ function processTokens(tokens: Array<Token>, mapText?: MapTextFn): CompiledMessa
 }
 
 // Message -> (Params -> String)
+/** @internal */
 export function compileMessage(
   message: string,
   mapText: MapTextFn = (v) => v,
diff --git a/packages/core/src/compile/index.ts b/packages/core/src/compile/index.ts
new file mode 100644
index 00000000..7ba52a74
--- /dev/null
+++ b/packages/core/src/compile/index.ts
@@ -0,0 +1,1 @@
+export * from './compileMessage';
diff --git a/packages/core/src/dev/index.ts b/packages/core/src/dev/index.ts
deleted file mode 100644
index 32ce04ff..00000000
--- a/packages/core/src/dev/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import {compileMessage as compile} from "../compileMessage"
-import loadLocaleData from "./loadLocaleData"
-
-export { compile, loadLocaleData }
diff --git a/packages/core/src/dev/loadLocaleData.ts b/packages/core/src/dev/loadLocaleData.ts
deleted file mode 100644
index 83c3dfb1..00000000
--- a/packages/core/src/dev/loadLocaleData.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import * as plurals from "make-plural/plurals"
-
-export default (locale: string) => {
-  if (!locale) {
-    return
-  }
-
-  const [language] = locale.split(/[_-]/)
-  return { plurals: plurals[language] }
-}
diff --git a/packages/core/src/i18n.ts b/packages/core/src/i18n.ts
index f0359b8a..fc6dd4b7 100644
--- a/packages/core/src/i18n.ts
+++ b/packages/core/src/i18n.ts
@@ -1,7 +1,7 @@
 import { interpolate, UNICODE_REGEX } from "./context"
 import { isString, isFunction } from "./essentials"
 import { date, number } from "./formats"
-import * as icu from "./dev"
+import { compileMessage } from "./compile"
 import { EventEmitter } from "./eventEmitter"
 import type {PluralCategory} from "make-plural"
 
@@ -206,7 +206,7 @@ export class I18n extends EventEmitter<Events> {
 
     if (process.env.NODE_ENV !== "production") {
       translation = isString(translation)
-        ? icu.compile(translation)
+        ? compileMessage(translation)
         : translation
     }
 
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index d29ca6e6..1e1db531 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -10,10 +10,6 @@ export {
   Locales,
 } from "./i18n"
 
-export {
-  compileMessage
-} from './compileMessage'
-
 // Default i18n object
 import { setupI18n } from "./i18n"
 export const i18n = setupI18n()
diff --git a/packages/detect-locale/package.json b/packages/detect-locale/package.json
index d76925e9..9cf29ab9 100644
--- a/packages/detect-locale/package.json
+++ b/packages/detect-locale/package.json
@@ -4,8 +4,8 @@
   "sideEffects": false,
   "description": "@Lingui package to help you find the correct browser/server locale",
   "main": "index.js",
-  "module": "esm/index.js",
-  "types": "cjs/index.d.ts",
+  "module": "./build/esm/index.js",
+  "types": "./build/cjs/index.d.ts",
   "license": "MIT",
   "keywords": [
     "i18n",
@@ -31,17 +31,15 @@
   },
   "exports": {
     ".": {
-      "require": "./index.js",
-      "import": "./esm/index.js"
+      "require": "./build/index.js",
+      "import": "./build/esm/index.js"
     },
     "./package.json": "./package.json"
   },
   "files": [
     "LICENSE",
     "README.md",
-    "index.js",
-    "cjs/",
-    "esm/"
+    "build/"
   ],
   "devDependencies": {
     "jsdom": "^16.4.0"
diff --git a/packages/loader/package.json b/packages/loader/package.json
index b412d8f2..56e43710 100644
--- a/packages/loader/package.json
+++ b/packages/loader/package.json
@@ -2,8 +2,8 @@
   "name": "@lingui/loader",
   "version": "3.15.0",
   "description": "webpack loader for lingui message catalogs",
-  "types": "index.d.ts",
-  "main": "index.js",
+  "types": "./build/index.d.ts",
+  "main": "./build/index.js",
   "author": {
     "name": "Tomáš Ehrlich",
     "email": "tomas.ehrlich@gmail.com"
@@ -29,6 +29,11 @@
   "engines": {
     "node": ">=10.0.0"
   },
+  "files": [
+    "LICENSE",
+    "README.md",
+    "build/"
+  ],
   "dependencies": {
     "@babel/runtime": "^7.11.2",
     "@lingui/cli": "^3.15.0",
diff --git a/packages/macro/package.json b/packages/macro/package.json
index c332c9f3..45850898 100644
--- a/packages/macro/package.json
+++ b/packages/macro/package.json
@@ -2,7 +2,7 @@
   "name": "@lingui/macro",
   "version": "3.15.0",
   "description": "Macro for generating messages in ICU MessageFormat syntax",
-  "main": "index.js",
+  "main": "./build/index.js",
   "author": {
     "name": "Tomáš Ehrlich",
     "email": "tomas.ehrlich@gmail.com"
@@ -24,9 +24,7 @@
   "files": [
     "LICENSE",
     "README.md",
-    "*.js",
-    "global.d.ts",
-    "index.d.ts"
+    "build/"
   ],
   "dependencies": {
     "@babel/runtime": "^7.11.2",
diff --git a/packages/react/package.json b/packages/react/package.json
index 0ab46f84..1aa80594 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -4,8 +4,8 @@
   "sideEffects": false,
   "description": "React components for translations",
   "main": "index.js",
-  "module": "esm/index.js",
-  "types": "cjs/index.d.ts",
+  "module": "./build/esm/index.js",
+  "types": "./build/cjs/index.d.ts",
   "author": {
     "name": "Tomáš Ehrlich",
     "email": "tomas.ehrlich@gmail.com"
@@ -34,9 +34,7 @@
   "files": [
     "LICENSE",
     "README.md",
-    "index.js",
-    "cjs/",
-    "esm/"
+    "build/"
   ],
   "peerDependencies": {
     "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
diff --git a/packages/remote-loader/package.json b/packages/remote-loader/package.json
index e3dbcf30..fea82dea 100644
--- a/packages/remote-loader/package.json
+++ b/packages/remote-loader/package.json
@@ -5,8 +5,8 @@
   "private": true,
   "description": "Load remotely your translations",
   "main": "index.js",
-  "module": "esm/remote-loader.production.min.js",
-  "types": "cjs/index.d.ts",
+  "module": "./build/esm/remote-loader.production.min.js",
+  "types": "./build/cjs/index.d.ts",
   "author": {
     "name": "Sergio Moreno",
     "email": "sergiomorenoalbert@gmail.com"
@@ -35,9 +35,7 @@
   "files": [
     "LICENSE",
     "README.md",
-    "index.js",
-    "cjs/",
-    "esm/"
+    "build/"
   ],
   "dependencies": {
     "@lingui/core": "^3.15.0",
diff --git a/packages/remote-loader/src/browserCompiler.ts b/packages/remote-loader/src/browserCompiler.ts
index af054426..f454d561 100644
--- a/packages/remote-loader/src/browserCompiler.ts
+++ b/packages/remote-loader/src/browserCompiler.ts
@@ -1,4 +1,4 @@
-import { compileMessage } from "@lingui/core"
+import { compileMessage } from "@lingui/core/compile"
 
 export function createBrowserCompiledCatalog(messages: Record<string, any>) {
   return Object.keys(messages).reduce((obj, key: string) => {
diff --git a/packages/snowpack-plugin/package.json b/packages/snowpack-plugin/package.json
index 7b668b8c..8b4676f7 100644
--- a/packages/snowpack-plugin/package.json
+++ b/packages/snowpack-plugin/package.json
@@ -3,7 +3,7 @@
   "version": "3.11.0",
   "private": true,
   "description": "Snowpack plugin for Lingui message catalogs",
-  "main": "index.js",
+  "main": "./build/index.js",
   "license": "MIT",
   "keywords": [
     "snowpack-plugin",
@@ -26,6 +26,11 @@
   "engines": {
     "node": ">=10.0.0"
   },
+  "files": [
+    "LICENSE",
+    "README.md",
+    "build/"
+  ],
   "dependencies": {
     "@babel/runtime": "^7",
     "@lingui/cli": "^3.11.0",
diff --git a/scripts/build/babel.js b/scripts/build/babel.ts
similarity index 73%
rename from scripts/build/babel.js
rename to scripts/build/babel.ts
index 97a069ee..db923b88 100644
--- a/scripts/build/babel.js
+++ b/scripts/build/babel.ts
@@ -1,17 +1,19 @@
+import {BundleDef, BundleType} from "./bundles"
+import {getBundleOutputPaths} from "./packaging"
+import {asyncMkDirP, getPackageDir} from "./utils"
+
+import {transformFileSync} from '@babel/core';
 const fs = require("fs")
 const path = require("path")
 const chalk = require("chalk")
-const babel = require("babel-core")
 const ora = require("ora")
 
 const babelConfig = require("./babel.config")
-const Packaging = require("./packaging")
-const { asyncMkDirP } = require("./utils")
 
 const ignorePatterns = [/\.test.[jt]s$/, /fixtures/]
 
-function walk(base, relativePath = "") {
-  let files = []
+function walk(base: string, relativePath = ""): string[] {
+  let files: string[] = []
 
   fs.readdirSync(path.join(base, relativePath)).forEach(dirname => {
     const directory = path.join(relativePath, dirname)
@@ -34,14 +36,14 @@ function declarationFinder(typesPath) {
   return typesPath.map(p => fs.existsSync(p) && path.basename(p)).filter(Boolean)
 }
 
-module.exports = async function(bundle) {
-  const logKey = chalk.white.bold(bundle.entry)
+export default async function(bundle: BundleDef) {
+  const logKey = chalk.white.bold(bundle.packageName)
 
   const spinner = ora(logKey).start()
 
   try {
-    const resolvedEntry = require.resolve(bundle.entry)
-    const packageDir = path.dirname(resolvedEntry)
+    const packageDir = getPackageDir(bundle.packageName)
+
     const srcDir = path.join(packageDir, "src")
     const files = [
       ...walk(srcDir),
@@ -52,17 +54,17 @@ module.exports = async function(bundle) {
     ]
 
     for (const filename of files) {
-      const [mainOutputPath] = Packaging.getBundleOutputPaths(
-        "NODE",
+      const [mainOutputPath] = getBundleOutputPaths(
+        BundleType.NODE,
         filename,
-        bundle.entry
+        bundle.packageName
       )
 
       const outputDir = path.dirname(mainOutputPath)
       await asyncMkDirP(outputDir)
 
       if (!filename.endsWith(".d.ts")) {
-        const { code } = babel.transformFileSync(
+        const { code } = transformFileSync(
           path.join(srcDir, filename),
           babelConfig({ modules: true })
         )
diff --git a/scripts/build/bundles.js b/scripts/build/bundles.js
deleted file mode 100644
index f21d98b0..00000000
--- a/scripts/build/bundles.js
+++ /dev/null
@@ -1,89 +0,0 @@
-"use strict"
-
-const bundleTypes = {
-  UNIVERSAL: "UNIVERSAL",
-  NODE: "NODE",
-  NOOP: "NOOP",
-  ESM: "ESM",
-}
-
-const bundles = [
-  {
-    type: bundleTypes.UNIVERSAL,
-    entry: "@lingui/core",
-    externals: []
-  },
-
-  {
-    type: bundleTypes.UNIVERSAL,
-    entry: "@lingui/core",
-    label: "dev",
-    externals: []
-  },
-
-  {
-    type: bundleTypes.UNIVERSAL,
-    entry: "@lingui/react",
-    externals: ["@lingui/core"]
-  },
-  {
-    type: bundleTypes.UNIVERSAL,
-    entry: "@lingui/detect-locale",
-  },
-  {
-    type: bundleTypes.NODE,
-    entry: "@lingui/babel-plugin-extract-messages"
-  },
-  {
-    type: bundleTypes.NODE,
-    entry: "@lingui/snowpack-plugin"
-  },
-
-  {
-    type: bundleTypes.NODE,
-    entry: "@lingui/macro"
-  },
-
-  {
-    type: bundleTypes.NODE,
-    entry: "@lingui/cli"
-  },
-
-  {
-    type: bundleTypes.NODE,
-    entry: "@lingui/conf"
-  },
-
-  {
-    type: bundleTypes.NODE,
-    entry: "@lingui/loader"
-  },
-
-  {
-    type: bundleTypes.UNIVERSAL,
-    entry: "@lingui/remote-loader"
-  }
-]
-
-// Based on deep-freeze by substack (public domain)
-function deepFreeze(o) {
-  Object.freeze(o)
-  Object.getOwnPropertyNames(o).forEach(function(prop) {
-    if (
-      o[prop] !== null &&
-      (typeof o[prop] === "object" || typeof o[prop] === "function") &&
-      !Object.isFrozen(o[prop])
-    ) {
-      deepFreeze(o[prop])
-    }
-  })
-  return o
-}
-
-// Don't accidentally mutate config as part of the build
-deepFreeze(bundles)
-
-module.exports = {
-  bundleTypes,
-  bundles
-}
diff --git a/scripts/build/bundles.ts b/scripts/build/bundles.ts
new file mode 100644
index 00000000..0beae5a0
--- /dev/null
+++ b/scripts/build/bundles.ts
@@ -0,0 +1,98 @@
+export enum BundleType {
+  UNIVERSAL = "UNIVERSAL",
+  NODE = "NODE",
+  NOOP = "NOOP",
+  ESM = "ESM",
+}
+
+export type BundleDef = {
+  type: BundleType,
+  externals?: string[]
+
+  /**
+   * Optional. Default index.js
+   */
+  entry?: string;
+
+  /**
+   * Name of package in packages/* folder
+   */
+  packageName: string
+  label?: string,
+  // all following not used now
+  moduleType?: unknown,
+  global?: unknown,
+  babel?: unknown,
+};
+
+export const bundles: BundleDef[] = [
+  {
+    type: BundleType.UNIVERSAL,
+    packageName: "core",
+    externals: []
+  },
+  {
+    type: BundleType.UNIVERSAL,
+    packageName: "core",
+    entry: 'compile.entry.js',
+    label: 'compile',
+    externals: []
+  },
+  {
+    type: BundleType.UNIVERSAL,
+    packageName: 'react',
+    externals: ["@lingui/core"]
+  },
+  {
+    type: BundleType.UNIVERSAL,
+    packageName: 'detect-locale',
+  },
+  {
+    type: BundleType.NODE,
+    packageName: 'babel-plugin-extract-messages',
+  },
+  {
+    type: BundleType.NODE,
+    packageName: 'snowpack-plugin',
+  },
+  {
+    type: BundleType.NODE,
+    packageName: 'macro',
+  },
+  {
+    type: BundleType.NODE,
+    packageName: "cli"
+  },
+  {
+    type: BundleType.NODE,
+    packageName: "conf"
+
+  },
+  {
+    type: BundleType.NODE,
+    packageName: "loader"
+  },
+
+  {
+    type: BundleType.UNIVERSAL,
+    packageName: "remote-loader"
+  }
+]
+
+// Based on deep-freeze by substack (public domain)
+function deepFreeze(o) {
+  Object.freeze(o)
+  Object.getOwnPropertyNames(o).forEach(function(prop) {
+    if (
+      o[prop] !== null &&
+      (typeof o[prop] === "object" || typeof o[prop] === "function") &&
+      !Object.isFrozen(o[prop])
+    ) {
+      deepFreeze(o[prop])
+    }
+  })
+  return o
+}
+
+// Don't accidentally mutate config as part of the build
+deepFreeze(bundles)
diff --git a/scripts/build/index.js b/scripts/build/index.ts
similarity index 56%
rename from scripts/build/index.js
rename to scripts/build/index.ts
index 8c77ee62..4f463eb8 100644
--- a/scripts/build/index.js
+++ b/scripts/build/index.ts
@@ -1,30 +1,28 @@
 const argv = require("minimist")(process.argv.slice(2))
 
-const Bundles = require("./bundles")
-const Packaging = require("./packaging")
-const Stats = require("./stats")
-const { asyncRimRaf } = require("./utils")
+import {BundleType, bundles, BundleDef} from "./bundles"
+import {prepareNpmPackages} from "./packaging";
+import * as Stats from "./stats";
+import {asyncRimRaf} from "./utils";
 
-const rollup = require("./rollup")
-const babel = require("./babel")
-const noop = require("./noop")
-
-const { UNIVERSAL, NODE, NOOP } = Bundles.bundleTypes
+import rollup from "./rollup";
+import babel from "./babel";
+import noop from "./noop";
 
 const builders = {
-  [UNIVERSAL]: rollup,
-  [NODE]: babel,
-  [NOOP]: noop
+  [BundleType.UNIVERSAL]: rollup,
+  [BundleType.NODE]: babel,
+  [BundleType.NOOP]: noop
 }
 
 const requestedEntries = (argv._[0] || "")
   .split(",")
   .map(name => name.toLowerCase())
 
-function shouldSkipBundle(bundle, bundleType) {
+function shouldSkipBundle(bundle: BundleDef) {
   if (requestedEntries.length > 0) {
     const isAskingForDifferentEntries = requestedEntries.every(
-      requestedName => bundle.entry.indexOf(requestedName) === -1
+      requestedName => !bundle.packageName.includes(requestedName)
     )
     if (isAskingForDifferentEntries) {
       return true
@@ -38,8 +36,8 @@ async function buildEverything() {
 
   // Run them serially for better console output
   // and to avoid any potential race conditions.
-  for (const bundle of Bundles.bundles) {
-    if (shouldSkipBundle(bundle, bundle.type)) continue
+  for (const bundle of bundles) {
+    if (shouldSkipBundle(bundle)) continue
 
     const builder = builders[bundle.type]
     if (!builder) {
@@ -53,7 +51,7 @@ async function buildEverything() {
   console.log(Stats.printResults())
   if (argv.saveStats) Stats.saveResults()
 
-  await Packaging.prepareNpmPackages()
+  await prepareNpmPackages()
 }
 
 buildEverything()
diff --git a/scripts/build/modules.js b/scripts/build/modules.ts
similarity index 75%
rename from scripts/build/modules.js
rename to scripts/build/modules.ts
index 0d3baf6c..96dd2882 100644
--- a/scripts/build/modules.js
+++ b/scripts/build/modules.ts
@@ -1,7 +1,6 @@
-"use strict"
-
-const pkgUp = require("pkg-up")
-const { UNIVERSAL } = require("./bundles").bundleTypes
+import  { BundleType }  from "./bundles"
+import {getPackageDir} from "./utils"
+import * as path from "path"
 
 // For any external that is used in a DEV-only condition, explicitly
 // specify whether it has side effects during import or not. This lets
@@ -24,10 +23,10 @@ const knownGlobals = Object.freeze({
 })
 
 // Given ['react'] in bundle externals, returns { 'react': 'React' }.
-function getPeerGlobals(externals = [], bundleType) {
+export function getPeerGlobals(externals = [], bundleType) {
   const peerGlobals = {}
   externals.forEach(name => {
-    if (!knownGlobals[name] && bundleType === UNIVERSAL) {
+    if (!knownGlobals[name] && bundleType === BundleType.UNIVERSAL) {
       throw new Error("Cannot build UMD without a global name for: " + name)
     }
     peerGlobals[name] = knownGlobals[name]
@@ -36,9 +35,8 @@ function getPeerGlobals(externals = [], bundleType) {
 }
 
 // Determines node_modules packages that are safe to assume will exist.
-function getDependencies(bundleType, entry) {
-  const pkgJsonPath = pkgUp.sync({ cwd: require.resolve(entry) })
-  const packageJson = require(pkgJsonPath)
+export function getDependencies(packageName: string) {
+  const packageJson = require(path.join(getPackageDir(packageName), 'package.json'))
 
   // Both deps and peerDeps are assumed as accessible.
   return Array.from(
@@ -49,12 +47,6 @@ function getDependencies(bundleType, entry) {
   )
 }
 
-function getImportSideEffects() {
+export function getImportSideEffects() {
   return importSideEffects
 }
-
-module.exports = {
-  getImportSideEffects,
-  getPeerGlobals,
-  getDependencies
-}
diff --git a/scripts/build/noop.js b/scripts/build/noop.js
deleted file mode 100644
index adbff43e..00000000
--- a/scripts/build/noop.js
+++ /dev/null
@@ -1,31 +0,0 @@
-const fs = require("fs")
-const path = require("path")
-const chalk = require("chalk")
-const ora = require("ora")
-
-const Packaging = require("./packaging")
-const { asyncCopyTo } = require("./utils")
-
-module.exports = async function(bundle) {
-  const logKey = chalk.white.bold(bundle.entry)
-
-  const spinner = ora(logKey).start()
-
-  try {
-    const resolvedEntry = require.resolve(bundle.entry)
-    const packageDir = path.dirname(resolvedEntry)
-
-    const [mainOutputPath] = Packaging.getBundleOutputPaths(
-      "NOOP",
-      "index.js",
-      bundle.entry
-    )
-
-    const outputDir = path.dirname(mainOutputPath)
-    await asyncCopyTo(path.join(packageDir, "index.js"), mainOutputPath)
-  } catch (error) {
-    spinner.fail()
-    throw error
-  }
-  spinner.succeed()
-}
diff --git a/scripts/build/noop.ts b/scripts/build/noop.ts
new file mode 100644
index 00000000..b734e45e
--- /dev/null
+++ b/scripts/build/noop.ts
@@ -0,0 +1,29 @@
+import {BundleDef, BundleType} from "./bundles"
+import {getBundleOutputPaths} from "./packaging"
+import {asyncCopyTo, getPackageDir} from "./utils"
+
+const path = require("path")
+const chalk = require("chalk")
+const ora = require("ora")
+
+export default async function(bundle: BundleDef) {
+  const logKey = chalk.white.bold(bundle.packageName)
+
+  const spinner = ora(logKey).start()
+
+  try {
+    const packageDir = getPackageDir(bundle.packageName)
+
+    const [mainOutputPath] = getBundleOutputPaths(
+      BundleType.NOOP,
+      "index.js",
+      bundle.packageName
+    )
+
+    await asyncCopyTo(path.join(packageDir, "index.js"), mainOutputPath)
+  } catch (error) {
+    spinner.fail()
+    throw error
+  }
+  spinner.succeed()
+}
diff --git a/scripts/build/packaging.js b/scripts/build/packaging.js
deleted file mode 100644
index 410a2ac0..00000000
--- a/scripts/build/packaging.js
+++ /dev/null
@@ -1,99 +0,0 @@
-"use strict"
-
-const { existsSync, readdirSync, unlinkSync } = require("fs")
-const path = require("path")
-const Bundles = require("./bundles")
-const {
-  asyncCopyTo,
-  asyncExecuteCommand,
-  asyncExtractTar,
-  asyncRimRaf
-} = require("./utils")
-
-const { UNIVERSAL, NODE, NOOP, ESM } = Bundles.bundleTypes
-
-function getPackageName(name) {
-  return name.split("/", 2).reverse()[0]
-}
-
-function getBundleOutputPaths(bundleType, filename, packageName) {
-  const name = getPackageName(packageName)
-  const _filename = filename.replace(/^@lingui\//, "")
-
-  switch (bundleType) {
-    case NOOP:
-    case NODE:
-      return [`packages/${name}/build/${_filename}`]
-    case ESM:
-      return [`packages/${name}/build/esm/${_filename}`]
-    case UNIVERSAL:
-      return [`packages/${name}/build/cjs/${_filename}`]
-    // case UMD_DEV:
-    // case UMD_PROD:
-    //   return [
-    //     `packages/build/${packageName}/umd/${filename}`,
-    //     `build/dist/${filename}`
-    //   ]
-    default:
-      throw new Error("Unknown bundle type.")
-  }
-}
-
-function getTarOptions(tgzName, packageName) {
-  // Files inside the `yarn pack`ed archive start
-  // with "package/" in their paths. We'll undo
-  // this during extraction.
-  const CONTENTS_FOLDER = "package"
-  return {
-    src: tgzName,
-    dest: `packages/${packageName}/build`,
-    tar: {
-      entries: [CONTENTS_FOLDER],
-      map(header) {
-        if (header.name.indexOf(CONTENTS_FOLDER + "/") === 0) {
-          header.name = header.name.substring(CONTENTS_FOLDER.length + 1)
-        }
-      }
-    }
-  }
-}
-
-async function prepareNpmPackage(name) {
-  if (!existsSync(`packages/${name}/build`)) {
-    // We didn't build any npm packages.
-    return
-  }
-
-  await Promise.all([
-    asyncCopyTo("LICENSE", `packages/${name}/build/LICENSE`),
-    asyncCopyTo(
-      `packages/${name}/package.json`,
-      `packages/${name}/build/package.json`
-    ),
-    asyncCopyTo(
-      `packages/${name}/README.md`,
-      `packages/${name}/build/README.md`
-    ),
-    asyncCopyTo(`packages/${name}/npm`, `packages/${name}/build/`)
-  ])
-  const tgzName = (await asyncExecuteCommand(
-    `npm pack packages/${name}/build`
-  )).trim()
-  await asyncRimRaf(`packages/${name}/build/`)
-  await asyncExtractTar(getTarOptions(tgzName, name))
-  unlinkSync(tgzName)
-}
-
-async function prepareNpmPackages() {
-  const builtPackageFolders = readdirSync("packages/").filter(
-    dir => dir.charAt(0) !== "."
-  )
-
-  await Promise.all(builtPackageFolders.map(prepareNpmPackage))
-}
-
-module.exports = {
-  getPackageName,
-  getBundleOutputPaths,
-  prepareNpmPackages
-}
diff --git a/scripts/build/packaging.ts b/scripts/build/packaging.ts
new file mode 100644
index 00000000..ed7d46cc
--- /dev/null
+++ b/scripts/build/packaging.ts
@@ -0,0 +1,47 @@
+import {existsSync, readdirSync} from "fs"
+import {BundleType as BundleType} from "./bundles"
+
+import {
+  asyncCopyTo,
+} from "./utils"
+
+export function getBundleOutputPaths(bundleType: BundleType, filename: string, packageName: string) {
+  switch (bundleType) {
+    case BundleType.NOOP:
+    case BundleType.NODE:
+      return [`packages/${packageName}/build/${filename}`]
+    case BundleType.ESM:
+      return [`packages/${packageName}/build/esm/${filename}`]
+    case BundleType.UNIVERSAL:
+      return [`packages/${packageName}/build/cjs/${filename}`]
+    // case BundleType.UMD_DEV:
+    // case BundleType.UMD_PROD:
+    //   return [
+    //     `packages/build/${packageName}/umd/${filename}`,
+    //     `build/dist/${filename}`
+    //   ]
+    default:
+      throw new Error("Unknown bundle type.")
+  }
+}
+
+
+async function prepareNpmPackage(name: string) {
+  if (!existsSync(`packages/${name}/build`)) {
+    // We didn't build any npm packages.
+    return
+  }
+
+  await Promise.all([
+    asyncCopyTo("LICENSE", `packages/${name}/build/LICENSE`),
+    asyncCopyTo(`packages/${name}/npm`, `packages/${name}/build`)
+  ])
+}
+
+export async function prepareNpmPackages() {
+  const builtPackageFolders = readdirSync("packages/").filter(
+    dir => dir.charAt(0) !== "."
+  )
+
+  await Promise.all(builtPackageFolders.map(prepareNpmPackage))
+}
diff --git a/scripts/build/plugins/sizes.js b/scripts/build/plugins/sizes.ts
similarity index 60%
rename from scripts/build/plugins/sizes.js
rename to scripts/build/plugins/sizes.ts
index 7c0bb44b..cbd23eb3 100644
--- a/scripts/build/plugins/sizes.js
+++ b/scripts/build/plugins/sizes.ts
@@ -4,19 +4,18 @@
  * This source code is licensed under the MIT license found in the
  * LICENSE file in the root directory of this source tree.
  */
-"use strict"
-
 const gzip = require("gzip-size")
 
-module.exports = function sizes(options) {
+export default function sizes(options: {getSize(name: string, size: number, gzipSize: number)}) {
   return {
     name: "sizes",
     generateBundle(config, bundle) {
       for (const [name, obj] of Object.entries(bundle)) {
-        if (obj.code == null) continue
+        const code: string = (obj as any).code;
+        if (code == null) continue
 
-        const size = Buffer.byteLength(obj.code)
-        const gzipSize = gzip.sync(obj.code)
+        const size = Buffer.byteLength(code)
+        const gzipSize = gzip.sync(code)
 
         options.getSize(name, size, gzipSize)
       }
diff --git a/scripts/build/rollup.js b/scripts/build/rollup.ts
similarity index 74%
rename from scripts/build/rollup.js
rename to scripts/build/rollup.ts
index f60d4f13..6467e419 100644
--- a/scripts/build/rollup.js
+++ b/scripts/build/rollup.ts
@@ -1,4 +1,12 @@
-"use strict"
+import {BundleDef, BundleType} from "./bundles"
+import {asyncCopyTo, getPackageDir} from "./utils"
+import * as Modules from "./modules"
+import * as Packaging from "./packaging"
+import * as Stats from "./stats"
+
+import * as fs from "fs";
+import * as path from "path";
+import sizes from "./plugins/sizes";
 
 const { rollup } = require("rollup")
 const babel = require("rollup-plugin-babel")
@@ -10,23 +18,18 @@ const replace = require("rollup-plugin-replace")
 const typescript = require("rollup-plugin-typescript2")
 const ora = require("ora")
 const chalk = require("chalk")
-const path = require("path")
-const fs = require("fs")
 const argv = require("minimist")(process.argv.slice(2))
-const Stats = require("./stats")
-const sizes = require("./plugins/sizes")
 const codeFrame = require("babel-code-frame")
 const babelConfig = require("./babel.config")
 
-const Modules = require("./modules")
-const Packaging = require("./packaging")
-
-const UMD_DEV = "UMD_DEV"
-const UMD_PROD = "UMD_PROD"
-const NODE_DEV = "NODE_DEV"
-const NODE_PROD = "NODE_PROD"
-const ESM_PROD = "ESM_PROD"
-const ESM_DEV = "ESM_DEV"
+enum RollupBundleType {
+  UMD_DEV= "UMD_DEV",
+  UMD_PROD= "UMD_PROD",
+  NODE_DEV= "NODE_DEV",
+  NODE_PROD= "NODE_PROD",
+  ESM_PROD= "ESM_PROD",
+  ESM_DEV= "ESM_DEV",
+}
 
 const extensions = [".js", ".ts", ".tsx"]
 
@@ -42,7 +45,7 @@ process.on("unhandledRejection", (err) => {
 
 const forcePrettyOutput = argv.pretty
 
-function getBabelConfig(updateBabelOptions, bundleType, filename) {
+function getBabelConfig() {
   return Object.assign({}, babelConfig({ modules: false }), {
     babelrc: false,
     exclude: "node_modules/**",
@@ -51,7 +54,7 @@ function getBabelConfig(updateBabelOptions, bundleType, filename) {
   })
 }
 
-function getRollupOutputOptions(outputPath, format, globals, globalName) {
+function getRollupOutputOptions(outputPath: string, format, globals, globalName) {
   return {
     file: outputPath,
     format,
@@ -62,43 +65,43 @@ function getRollupOutputOptions(outputPath, format, globals, globalName) {
   }
 }
 
-function getFormat(bundleType) {
+function getFormat(bundleType: RollupBundleType) {
   switch (bundleType) {
-    case UMD_DEV:
-    case UMD_PROD:
+    case RollupBundleType.UMD_DEV:
+    case RollupBundleType.UMD_PROD:
       return 'umd'
-    case NODE_DEV:
-    case NODE_PROD:
+    case RollupBundleType.NODE_DEV:
+    case RollupBundleType.NODE_PROD:
       return 'cjs'
-    case ESM_PROD:
-    case ESM_DEV:
+    case RollupBundleType.ESM_PROD:
+    case RollupBundleType.ESM_DEV:
       return 'es'
   }
 }
 
-function getFilename(bundle, bundleType) {
-  const filename = bundle.label || bundle.entry
+function getFilename(bundle: BundleDef, bundleType: RollupBundleType) {
+  const filename = bundle.label || bundle.packageName
   switch (bundleType) {
-    case NODE_DEV:
-    case ESM_DEV:
-    case UMD_DEV:
+    case RollupBundleType.NODE_DEV:
+    case RollupBundleType.ESM_DEV:
+    case RollupBundleType.UMD_DEV:
       return `${filename}.development.js`
-    case UMD_PROD:
-    case NODE_PROD:
-    case ESM_PROD:
+    case RollupBundleType.UMD_PROD:
+    case RollupBundleType.NODE_PROD:
+    case RollupBundleType.ESM_PROD:
       return `${filename}.production.min.js`
   }
 }
 
-function isProductionBundleType(bundleType) {
+function isProductionBundleType(bundleType: RollupBundleType) {
   switch (bundleType) {
-    case UMD_DEV:
-    case NODE_DEV:
-    case ESM_DEV:
+    case RollupBundleType.UMD_DEV:
+    case RollupBundleType.NODE_DEV:
+    case RollupBundleType.ESM_DEV:
       return false
-    case UMD_PROD:
-    case NODE_PROD:
-    case ESM_PROD:
+    case RollupBundleType.UMD_PROD:
+    case RollupBundleType.NODE_PROD:
+    case RollupBundleType.ESM_PROD:
       return true
     default:
       throw new Error(`Unknown type: ${bundleType}`)
@@ -106,19 +109,17 @@ function isProductionBundleType(bundleType) {
 }
 
 function getPlugins(
-  entry,
+  packageName: string,
   externals,
   updateBabelOptions,
   filename,
-  bundleType,
-  globalName,
-  moduleType,
-  modulesToStub
+  bundleType: RollupBundleType,
 ) {
   const isProduction = isProductionBundleType(bundleType)
-  const isInGlobalScope = bundleType === UMD_DEV || bundleType === UMD_PROD
+  const isInGlobalScope = bundleType === RollupBundleType.UMD_DEV || bundleType === RollupBundleType.UMD_PROD
   const shouldStayReadable = forcePrettyOutput
-  const packageDir = path.join(path.dirname(require.resolve(entry)), "src")
+
+  const packageDir = path.join(getPackageDir(packageName), "src")
 
   return [
     // Use Node resolution mechanism.
@@ -143,7 +144,7 @@ function getPlugins(
     }),
 
     // Compile to ES5.
-    babel(getBabelConfig(updateBabelOptions, bundleType)),
+    babel(getBabelConfig()),
     // Turn process.env checks into constants.
     replace({
       "process.env.NODE_ENV": isProduction ? "'production'" : "'development'",
@@ -242,24 +243,23 @@ function handleRollupError(error) {
   }
 }
 
-async function build(bundle, bundleType) {
+
+async function build(bundle: BundleDef, bundleType: RollupBundleType) {
   const filename = getFilename(bundle, bundleType)
   const logKey =
     chalk.white.bold(filename) + chalk.dim(` (${bundleType.toLowerCase()})`)
   const format = getFormat(bundleType)
-  const packageName = Packaging.getPackageName(bundle.entry)
 
-  let resolvedEntry = require.resolve(bundle.entry)
 
   const shouldBundleDependencies =
-    bundleType === UMD_DEV || bundleType === UMD_PROD
+    bundleType === RollupBundleType.UMD_DEV || bundleType === RollupBundleType.UMD_PROD
   const peerGlobals = Modules.getPeerGlobals(
     bundle.externals,
     bundle.moduleType
   )
   let externals = Object.keys(peerGlobals)
   if (!shouldBundleDependencies) {
-    const deps = Modules.getDependencies(bundleType, bundle.entry)
+    const deps = Modules.getDependencies(bundle.packageName)
     externals = externals.concat(deps)
   }
 
@@ -269,7 +269,7 @@ async function build(bundle, bundleType) {
   )
 
   const rollupConfig = {
-    input: resolvedEntry,
+    input: path.resolve(getPackageDir(bundle.packageName), bundle.entry || 'index.js'),
     treeshake: {
       moduleSideEffects,
     },
@@ -283,20 +283,19 @@ async function build(bundle, bundleType) {
     },
     onwarn: handleRollupWarning,
     plugins: getPlugins(
-      bundle.entry,
+      bundle.packageName,
       externals,
       bundle.babel,
       filename,
       bundleType,
-      bundle.global,
-      bundle.moduleType,
-      bundle.modulesToStub
     ),
   }
   const [mainOutputPath, ...otherOutputPaths] = Packaging.getBundleOutputPaths(
-    bundleType === ESM_PROD || bundleType === ESM_DEV ? "ESM" : "UNIVERSAL",
+    bundleType === RollupBundleType.ESM_PROD || bundleType === RollupBundleType.ESM_DEV
+      ? BundleType.ESM
+      : BundleType.UNIVERSAL,
     filename,
-    packageName
+    bundle.packageName
   )
   const rollupOutputOptions = getRollupOutputOptions(
     mainOutputPath,
@@ -320,11 +319,12 @@ async function build(bundle, bundleType) {
   spinner.succeed()
 }
 
-module.exports = async function (bundle) {
-  await build(bundle, NODE_DEV)
-  await build(bundle, NODE_PROD)
-  await build(bundle, ESM_DEV)
-  await build(bundle, ESM_PROD)
+export default async function (bundle: BundleDef) {
+  await build(bundle, RollupBundleType.NODE_DEV)
+  await build(bundle, RollupBundleType.NODE_PROD)
+  await build(bundle, RollupBundleType.ESM_DEV)
+  await build(bundle, RollupBundleType.ESM_PROD)
+
   // await build(bundle, UMD_DEV)
   // await build(bundle, UMD_PROD)
 }
diff --git a/scripts/build/stats.js b/scripts/build/stats.ts
similarity index 91%
rename from scripts/build/stats.js
rename to scripts/build/stats.ts
index dfde476c..7ea3406e 100644
--- a/scripts/build/stats.js
+++ b/scripts/build/stats.ts
@@ -1,5 +1,3 @@
-"use strict"
-
 const Table = require("cli-table")
 const filesize = require("filesize")
 const chalk = require("chalk")
@@ -7,12 +5,12 @@ const join = require("path").join
 const fs = require("fs")
 const prevBuildResults = require("./results.json")
 
-const currentBuildResults = {
+export const currentBuildResults = {
   // Mutated during the build.
   bundleSizes: Object.assign({}, prevBuildResults.bundleSizes)
 }
 
-function saveResults() {
+export function saveResults() {
   fs.writeFileSync(
     join("scripts", "build", "results.json"),
     JSON.stringify(currentBuildResults, null, 2)
@@ -29,7 +27,7 @@ function percentChange(prev, current) {
   }
 }
 
-function printResults() {
+export function printResults() {
   const table = new Table({
     head: [
       chalk.gray.yellow("Bundle"),
@@ -65,9 +63,3 @@ function printResults() {
   })
   return table.toString()
 }
-
-module.exports = {
-  printResults,
-  saveResults,
-  currentBuildResults
-}
diff --git a/scripts/build/utils.js b/scripts/build/utils.js
deleted file mode 100644
index d774f928..00000000
--- a/scripts/build/utils.js
+++ /dev/null
@@ -1,92 +0,0 @@
-"use strict"
-
-const fs = require("fs")
-const ncp = require("ncp").ncp
-const path = require("path")
-const mkdirp = require("mkdirp")
-const rimraf = require("rimraf")
-const exec = require("child_process").exec
-const targz = require("targz")
-
-function asyncCopyTo(from, to) {
-  if (!fs.existsSync(from)) return
-
-  return asyncMkDirP(path.dirname(to)).then(
-    () =>
-      new Promise((resolve, reject) => {
-        ncp(from, to, error => {
-          if (error) {
-            // Wrap to have a useful stack trace.
-            reject(new Error(error))
-            return
-          }
-          resolve()
-        })
-      })
-  )
-}
-
-function asyncExecuteCommand(command) {
-  return new Promise((resolve, reject) =>
-    exec(command, (error, stdout) => {
-      if (error) {
-        reject(error)
-        return
-      }
-      resolve(stdout)
-    })
-  )
-}
-
-function asyncExtractTar(options) {
-  return new Promise((resolve, reject) =>
-    targz.decompress(options, error => {
-      if (error) {
-        reject(error)
-        return
-      }
-      resolve()
-    })
-  )
-}
-
-function asyncMkDirP(filepath) {
-  return new Promise((resolve, reject) =>
-    mkdirp(filepath, error => {
-      if (error) {
-        reject(error)
-        return
-      }
-      resolve()
-    })
-  )
-}
-
-function asyncRimRaf(filepath) {
-  return new Promise((resolve, reject) =>
-    rimraf(filepath, error => {
-      if (error) {
-        reject(error)
-        return
-      }
-      resolve()
-    })
-  )
-}
-
-function resolvePath(filepath) {
-  if (filepath[0] === "~") {
-    return path.join(process.env.HOME, filepath.slice(1))
-  } else {
-    return path.resolve(filepath)
-  }
-}
-
-module.exports = {
-  asyncCopyTo,
-  resolvePath,
-  asyncExecuteCommand,
-  asyncExtractTar,
-  asyncMkDirP,
-  asyncRimRaf
-}
diff --git a/scripts/build/utils.ts b/scripts/build/utils.ts
new file mode 100644
index 00000000..95d09ce6
--- /dev/null
+++ b/scripts/build/utils.ts
@@ -0,0 +1,46 @@
+import * as fsPromises from "fs/promises"
+import * as fs from "fs"
+import * as path from "path"
+import ncp from "ncp"
+import rimraf from "rimraf"
+
+export async function asyncCopyTo(from: string, to: string) {
+  if (!fs.existsSync(from)) return
+
+  await asyncMkDirP(path.dirname(to))
+
+  return new Promise<void>((resolve, reject) => {
+    ncp(from, to, error => {
+      if (error) {
+        // Wrap to have a useful stack trace.
+        reject(new Error(error))
+        return
+      }
+      resolve()
+    })
+  })
+
+}
+
+
+export function asyncMkDirP(filepath: string) {
+  return fsPromises.mkdir(filepath, {
+    recursive: true
+  })
+}
+
+export function asyncRimRaf(filepath: string) {
+  return new Promise<void>((resolve, reject) =>
+    rimraf(filepath, error => {
+      if (error) {
+        reject(error)
+        return
+      }
+      resolve()
+    })
+  )
+}
+
+export function getPackageDir(packageName: string): string {
+  return path.resolve(__dirname, "../../packages", packageName);
+}
diff --git a/scripts/jest/config.integration.js b/scripts/jest/config.integration.js
index ef42d6e2..62e60a15 100644
--- a/scripts/jest/config.integration.js
+++ b/scripts/jest/config.integration.js
@@ -1,43 +1,15 @@
-const { readdirSync, statSync } = require("fs")
-const { join } = require("path")
 const sourceConfig = require("./config.unit")
 
-// Find all folders in packages/* with package.json
-const packagesRoot = join(process.cwd(), "packages")
-const packages = readdirSync(packagesRoot).filter(dir => {
-  if (dir.charAt(0) === ".") {
-    return false
-  }
-  const packagePath = join(packagesRoot, dir, "package.json")
-  try {
-    require(packagePath)
-  } catch (error) {
-    return false
-  }
-
-  return true
-})
-
-const DEV_PACKAGES = ["jest-mocks"]
-
-// Create a module map to point React packages to the build output
-const moduleNameMapper = {}
-packages
-  .filter(name => !DEV_PACKAGES.includes(name))
-  .forEach(name => {
-    // Root entry point
-    moduleNameMapper[`^@lingui/${name}$`] = `<rootDir>/packages/${name}/build`
-    // Named entry points
-    moduleNameMapper[
-      `^@lingui/${name}/(.*)$`
-    ] = `<rootDir>/packages/${name}/build/$1`
-  })
+/**
+ * @type {import('jest').Config}
+ */
+module.exports = {
+  ...sourceConfig,
 
-module.exports = Object.assign({}, sourceConfig, {
   roots: ["<rootDir>/packages/"],
   testPathIgnorePatterns: ["/node_modules/"],
   // Redirect imports to the compiled bundles
-  moduleNameMapper,
+  moduleNameMapper: {},
   setupFiles: ['set-tz/utc'],
 
   // Exclude the build output from transforms
@@ -49,4 +21,4 @@ module.exports = Object.assign({}, sourceConfig, {
   haste: {
     throwOnModuleCollision: false,
   }
-})
+}
diff --git a/scripts/jest/config.unit.js b/scripts/jest/config.unit.js
index 33acd3a4..447b89d0 100644
--- a/scripts/jest/config.unit.js
+++ b/scripts/jest/config.unit.js
@@ -1,3 +1,9 @@
+const { pathsToModuleNameMapper } = require('ts-jest');
+const tsConfig = require( '../../tsconfig.json');
+
+/**
+ * @type {import('jest').Config}
+ */
 module.exports = {
   roots: ["<rootDir>/packages/"],
   rootDir: process.cwd(),
@@ -18,6 +24,9 @@ module.exports = {
   ],
   coverageReporters: ["html", "lcov", "text"],
   modulePathIgnorePatterns: ["/build"],
+  moduleNameMapper: pathsToModuleNameMapper(tsConfig.compilerOptions.paths, {
+    prefix: '<rootDir>/',
+  }),
 
   reporters: ["default", "jest-junit"],
   setupFiles: ['set-tz/utc'],
diff --git a/scripts/verdaccio-release.js b/scripts/verdaccio-release.js
index c9daebfd..5372a634 100644
--- a/scripts/verdaccio-release.js
+++ b/scripts/verdaccio-release.js
@@ -38,7 +38,7 @@ async function releaseInVerdaccio() {
 
   spinner.start("Publishing packages to local registry")
   await exec(
-    `npx lerna publish from-package --force-publish --no-git-tag-version --no-private --no-push --yes --allow-branch ${actualBranch} --contents build --registry="http://0.0.0.0:4873"
+    `npx lerna publish from-package --force-publish --no-git-tag-version --no-private --no-push --yes --allow-branch ${actualBranch} --registry="http://0.0.0.0:4873"
   `
   )
   spinner.succeed()
@@ -83,6 +83,7 @@ function exec(cmd, options) {
         resolve({ stdout, stderr })
       } else {
         reject({ error, stdout, stderr })
+        console.error(stderr)
         process.exit(1)
       }
     })
diff --git a/tsconfig.json b/tsconfig.json
index fab2cff6..fdc605c7 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,9 +6,13 @@
     "baseUrl": "./",
     "pretty": true,
     "esModuleInterop": true,
+    "stripInternal": true,
+    "resolveJsonModule": true,
     "target": "es2017",
     "paths": {
       "@lingui/core": ["./packages/core/src"],
+      "@lingui/core/compile": ["./packages/core/src/compile/index.ts"],
+      "@lingui/cli/api": ["./packages/cli/src/api"],
       "@lingui/react": ["./packages/react/src"],
       "@lingui/conf": ["./packages/conf"],
       "@lingui/macro": ["./packages/macro/src"]
diff --git a/yarn.lock b/yarn.lock
index 0a630c60..c75ee729 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -47,7 +47,7 @@
     semver "^5.4.1"
     source-map "^0.5.0"
 
-"@babel/generator@^7.11.5", "@babel/generator@^7.11.6", "@babel/generator@^7.14.5":
+"@babel/generator@^7.11.5", "@babel/generator@^7.11.6":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.5.tgz#848d7b9f031caca9d0cd0af01b063f226f52d785"
   integrity sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==
@@ -1166,6 +1166,13 @@
     slash "^3.0.0"
     strip-ansi "^6.0.0"
 
+"@jest/create-cache-key-function@^27.4.2":
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz#7448fae15602ea95c828f5eceed35c202a820b31"
+  integrity sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==
+  dependencies:
+    "@jest/types" "^27.5.1"
+
 "@jest/environment@^26.5.2":
   version "26.5.2"
   resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.5.2.tgz#eba3cfc698f6e03739628f699c28e8a07f5e65fe"
@@ -1301,6 +1308,17 @@
     "@types/yargs" "^15.0.0"
     chalk "^4.0.0"
 
+"@jest/types@^27.5.1":
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80"
+  integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==
+  dependencies:
+    "@types/istanbul-lib-coverage" "^2.0.0"
+    "@types/istanbul-reports" "^3.0.0"
+    "@types/node" "*"
+    "@types/yargs" "^16.0.0"
+    chalk "^4.0.0"
+
 "@jridgewell/resolve-uri@^3.0.3":
   version "3.0.7"
   resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe"
@@ -2441,6 +2459,105 @@
     tar-stream "^2.1.2"
     tmp "^0.1.0"
 
+"@swc-node/core@^1.9.2":
+  version "1.9.2"
+  resolved "https://registry.yarnpkg.com/@swc-node/core/-/core-1.9.2.tgz#9c126ace9c71acd8ad301b3823bde7925d8a58ab"
+  integrity sha512-tInCla6NO1HEQwhIc/K7PCOu4X3ppqw5xYNEMD7i41SyRuH7yp3u8x7x2cqeAD+6IAhJ5jKDPv2QRLPz7Xt3EA==
+
+"@swc-node/register@^1.4.0":
+  version "1.5.5"
+  resolved "https://registry.yarnpkg.com/@swc-node/register/-/register-1.5.5.tgz#0c9c6b8ea969b7ee2280e176debd1fde89986c06"
+  integrity sha512-SNpbRG8EOXShk3YAnC4suAVovYQ7oFOFdCVBA3J8hkO5qy0WHPVnlnMojTYI+8UT1CrfQ1QSUySaAARRvEdwjg==
+  dependencies:
+    "@swc-node/core" "^1.9.2"
+    "@swc-node/sourcemap-support" "^0.2.3"
+    colorette "^2.0.19"
+    debug "^4.3.4"
+    pirates "^4.0.5"
+    tslib "^2.4.1"
+
+"@swc-node/sourcemap-support@^0.2.3":
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/@swc-node/sourcemap-support/-/sourcemap-support-0.2.3.tgz#fbd049a409bb10d198dea4e5fff5795a33ddd9dd"
+  integrity sha512-LOC/z9HDUmTqSAF08YIRLThGGNZLdRrjcEdoQu/EANxPSVRedYgh4AiZlOX8sY8Rp1p7S/StOmZogJLuvR4mcA==
+  dependencies:
+    source-map-support "^0.5.21"
+    tslib "^2.4.1"
+
+"@swc/core-darwin-arm64@1.3.26":
+  version "1.3.26"
+  resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.26.tgz#43355315f0668a6a5366208f09678349bc0f44ee"
+  integrity sha512-FWWflBfKRYrUJtko2xiedC5XCa31O75IZZqnTWuLpe9g3C5tnUuF3M8LSXZS/dn6wprome1MhtG9GMPkSYkhkg==
+
+"@swc/core-darwin-x64@1.3.26":
+  version "1.3.26"
+  resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.26.tgz#462fc2e1377437b7c7bbdf5988f51adfeea3efa9"
+  integrity sha512-0uQeebAtsewqJ2b35aPZstGrylwd6oJjUyAJOfVJNbremFSJ5JzytB3NoDCIw7CT5UQrSRpvD3mU95gfdQjDGA==
+
+"@swc/core-linux-arm-gnueabihf@1.3.26":
+  version "1.3.26"
+  resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.26.tgz#fecd9c2e7d9b69c849907a83a5101a98c047d986"
+  integrity sha512-06T+LbVFlyciQtwrUB5/a16A1ju1jFoYvd/hq9TWhf7GrtL43U7oJIgqMOPHx2j0+Ps2R3S6R/UUN5YXu618zA==
+
+"@swc/core-linux-arm64-gnu@1.3.26":
+  version "1.3.26"
+  resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.26.tgz#82a8462212263f4e4f6691473d4c2839b73c2084"
+  integrity sha512-2NT/0xALPfK+U01qIlHxjkGdIj6F0txhu1U2v6B0YP2+k0whL2gCgYeg9QUvkYEXSD5r1Yx+vcb2R/vaSCSClg==
+
+"@swc/core-linux-arm64-musl@1.3.26":
+  version "1.3.26"
+  resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.26.tgz#050b7c1aa81d6f34769eb556c3a94c61a9b69aaa"
+  integrity sha512-64KrTay9hC0mTvZ1AmEFmNEwV5QDjw9U7PJU5riotSc28I+Q/ZoM0qcSFW9JRRa6F2Tr+IfMtyv8+eB2//BQ5g==
+
+"@swc/core-linux-x64-gnu@1.3.26":
+  version "1.3.26"
+  resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.26.tgz#e306778c2c1838350f588c8ae800e74434dc2b9a"
+  integrity sha512-Te8G13l3dcRM1Mf3J4JzGUngzNXLKnMYlUmBOYN/ORsx7e+VNelR3zsTLHC0+0jGqELDgqvMyzDfk+dux/C/bQ==
+
+"@swc/core-linux-x64-musl@1.3.26":
+  version "1.3.26"
+  resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.26.tgz#531d9ec7c37f56df5c6cc121db5dd6faff5e2c38"
+  integrity sha512-nqQWuSM6OTKepUiQ9+rXgERq/JiO72RBOpXKO2afYppsL96sngjIRewV74v5f6IAfyzw+k+AhC5pgRA4Xu/Jkg==
+
+"@swc/core-win32-arm64-msvc@1.3.26":
+  version "1.3.26"
+  resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.26.tgz#9c7f245903694484bd00c4da5142f24070094d0f"
+  integrity sha512-xx34mx+9IBV1sun7sxoNFiqNom9wiOuvsQFJUyQptCnZHgYwOr9OI204LBF95dCcBCZsTm2hT1wBnySJOeimYw==
+
+"@swc/core-win32-ia32-msvc@1.3.26":
+  version "1.3.26"
+  resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.26.tgz#56d83cc216218d78cc578f01499777cdfc0a4eeb"
+  integrity sha512-48LZ/HKNuU9zl8c7qG6IQKb5rBCwmJgysGOmEGzTRBYxAf/x6Scmt0aqxCoV4J02HOs2WduCBDnhUKsSQ2kcXQ==
+
+"@swc/core-win32-x64-msvc@1.3.26":
+  version "1.3.26"
+  resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.26.tgz#bb65bc0fff712c8ca3702d9c0adc59894ca54bae"
+  integrity sha512-UPe7S+MezD/S6cKBIc50TduGzmw6PBz1Ms5p+5wDLOKYNS/LSEM4iRmLwvePzP5X8mOyesXrsbwxLy8KHP65Yw==
+
+"@swc/core@^1.3.26":
+  version "1.3.26"
+  resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.26.tgz#6f7fe6ad54eac7ecffbdfa75d5c4300e2f96b8f6"
+  integrity sha512-U7vEsaLn3IGg0XCRLJX/GTkK9WIfFHUX5USdrp1L2QD29sWPe25HqNndXmUR9KytzKmpDMNoUuHyiuhpVrnNeQ==
+  optionalDependencies:
+    "@swc/core-darwin-arm64" "1.3.26"
+    "@swc/core-darwin-x64" "1.3.26"
+    "@swc/core-linux-arm-gnueabihf" "1.3.26"
+    "@swc/core-linux-arm64-gnu" "1.3.26"
+    "@swc/core-linux-arm64-musl" "1.3.26"
+    "@swc/core-linux-x64-gnu" "1.3.26"
+    "@swc/core-linux-x64-musl" "1.3.26"
+    "@swc/core-win32-arm64-msvc" "1.3.26"
+    "@swc/core-win32-ia32-msvc" "1.3.26"
+    "@swc/core-win32-x64-msvc" "1.3.26"
+
+"@swc/jest@^0.2.24":
+  version "0.2.24"
+  resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.24.tgz#35d9377ede049613cd5fdd6c24af2b8dcf622875"
+  integrity sha512-fwgxQbM1wXzyKzl1+IW0aGrRvAA8k0Y3NxFhKigbPjOJ4mCKnWEcNX9HQS3gshflcxq8YKhadabGUVfdwjCr6Q==
+  dependencies:
+    "@jest/create-cache-key-function" "^27.4.2"
+    jsonc-parser "^3.2.0"
+
 "@szmarczak/http-timer@^1.1.2":
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
@@ -2597,6 +2714,14 @@
   resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
   integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
 
+"@types/glob@*":
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/@types/glob/-/glob-8.0.0.tgz#321607e9cbaec54f687a0792b2d1d370739455d2"
+  integrity sha512-l6NQsDDyQUVeoTynNpC9uRvCUint/gSUXQA2euwmTuWGvPY5LSDUu6tkCtJB2SvGQlJQzLaKqcGZP4//7EDveA==
+  dependencies:
+    "@types/minimatch" "*"
+    "@types/node" "*"
+
 "@types/glob@^7.1.1":
   version "7.1.3"
   resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
@@ -2701,6 +2826,11 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.15.tgz#fe1cc3aa465a3ea6858b793fd380b66c39919766"
   integrity sha512-kwbcs0jySLxzLsa2nWUAGOd/s21WU1jebrEdtzhsj1D4Yps1EOuyI1Qcu+FD56dL7NRNIJtDDjcqIG22NwkgLw==
 
+"@types/node@14.14.31":
+  version "14.14.31"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055"
+  integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==
+
 "@types/node@^6.14.4", "@types/node@^6.14.7":
   version "6.14.10"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-6.14.10.tgz#d9ce598127eb0cc02821476862d11389cb01f6a4"
@@ -2777,6 +2907,14 @@
   dependencies:
     "@types/node" "*"
 
+"@types/rimraf@^3.0.2":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-3.0.2.tgz#a63d175b331748e5220ad48c901d7bbf1f44eef8"
+  integrity sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==
+  dependencies:
+    "@types/glob" "*"
+    "@types/node" "*"
+
 "@types/semver@^5.5.0":
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
@@ -2806,6 +2944,13 @@
   dependencies:
     "@types/yargs-parser" "*"
 
+"@types/yargs@^16.0.0":
+  version "16.0.5"
+  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.5.tgz#12cc86393985735a283e387936398c2f9e5f88e3"
+  integrity sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==
+  dependencies:
+    "@types/yargs-parser" "*"
+
 "@typescript-eslint/eslint-plugin@^4.4.0":
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.4.0.tgz#0321684dd2b902c89128405cf0385e9fe8561934"
@@ -3749,14 +3894,6 @@ bindings@^1.5.0:
   dependencies:
     file-uri-to-path "1.0.0"
 
-bl@^1.0.0:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7"
-  integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==
-  dependencies:
-    readable-stream "^2.3.5"
-    safe-buffer "^5.1.1"
-
 bl@^4.0.1, bl@^4.0.3:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
@@ -3939,24 +4076,6 @@ btoa-lite@^1.0.0:
   resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337"
   integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc=
 
-buffer-alloc-unsafe@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
-  integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
-
-buffer-alloc@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
-  integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
-  dependencies:
-    buffer-alloc-unsafe "^1.1.0"
-    buffer-fill "^1.0.0"
-
-buffer-fill@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
-  integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
-
 buffer-from@1.x, buffer-from@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
@@ -4281,7 +4400,7 @@ chokidar@^2.1.8:
   optionalDependencies:
     fsevents "^1.2.7"
 
-chownr@^1.0.1, chownr@^1.1.1, chownr@^1.1.2:
+chownr@^1.1.1, chownr@^1.1.2:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
   integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
@@ -4508,6 +4627,11 @@ colorette@^1.2.1, colorette@^1.2.2:
   resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
   integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
 
+colorette@^2.0.19:
+  version "2.0.19"
+  resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
+  integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
+
 colors@1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
@@ -5129,6 +5253,13 @@ debug@^3.1.0, debug@^3.2.5, debug@^3.2.6:
   dependencies:
     ms "^2.1.1"
 
+debug@^4.3.4:
+  version "4.3.4"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+  integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+  dependencies:
+    ms "2.1.2"
+
 debuglog@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
@@ -8184,7 +8315,7 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
   resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
   integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
 
-json5@2.x, json5@^2.1.2, json5@^2.2.0:
+json5@2.x, json5@^2.1.2:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
   integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
@@ -8198,6 +8329,11 @@ json5@^1.0.1:
   dependencies:
     minimist "^1.2.0"
 
+jsonc-parser@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76"
+  integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==
+
 jsonfile@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
@@ -9124,7 +9260,7 @@ ms@2.0.0:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
   integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
 
-ms@^2.0.0, ms@^2.1.1:
+ms@2.1.2, ms@^2.0.0, ms@^2.1.1:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
   integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
@@ -10127,6 +10263,11 @@ pirates@^4.0.1:
   dependencies:
     node-modules-regexp "^1.0.0"
 
+pirates@^4.0.5:
+  version "4.0.5"
+  resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
+  integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==
+
 pkg-dir@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
@@ -10693,14 +10834,6 @@ public-encrypt@^4.0.0:
     randombytes "^2.0.1"
     safe-buffer "^5.1.2"
 
-pump@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954"
-  integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==
-  dependencies:
-    end-of-stream "^1.1.0"
-    once "^1.3.1"
-
 pump@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
@@ -10950,7 +11083,7 @@ read@1, read@~1.0.1:
   dependencies:
     mute-stream "~0.0.4"
 
-"readable-stream@1 || 2", readable-stream@2, "readable-stream@2 || 3", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
+"readable-stream@1 || 2", readable-stream@2, "readable-stream@2 || 3", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6:
   version "2.3.7"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
   integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@@ -12092,6 +12225,14 @@ source-map-support@^0.5.11, source-map-support@^0.5.19, source-map-support@^0.5.
     buffer-from "^1.0.0"
     source-map "^0.6.0"
 
+source-map-support@^0.5.21:
+  version "0.5.21"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
+  integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
+  dependencies:
+    buffer-from "^1.0.0"
+    source-map "^0.6.0"
+
 source-map-url@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
@@ -12565,6 +12706,13 @@ svgo@^1.0.0:
     unquote "~1.1.1"
     util.promisify "~1.0.0"
 
+swc-node@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/swc-node/-/swc-node-1.0.0.tgz#73729b2ff68e1e7fad11fc04b84d3acf21982c3c"
+  integrity sha512-4+kibROq06E7Yj3kEcCRN1Ki2C/5i+5P1B7An9iS9PO1BQY5VzWFuLhV7y7xNxkZjc8FEaLCh97NGzs/XBfOMQ==
+  dependencies:
+    "@swc-node/register" "^1.4.0"
+
 symbol-tree@^3.2.4:
   version "3.2.4"
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
@@ -12585,29 +12733,6 @@ tapable@^1.0.0, tapable@^1.1.3:
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
   integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
 
-tar-fs@^1.8.1:
-  version "1.16.3"
-  resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509"
-  integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==
-  dependencies:
-    chownr "^1.0.1"
-    mkdirp "^0.5.1"
-    pump "^1.0.0"
-    tar-stream "^1.1.2"
-
-tar-stream@^1.1.2:
-  version "1.6.2"
-  resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
-  integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
-  dependencies:
-    bl "^1.0.0"
-    buffer-alloc "^1.2.0"
-    end-of-stream "^1.0.0"
-    fs-constants "^1.0.0"
-    readable-stream "^2.3.0"
-    to-buffer "^1.1.1"
-    xtend "^4.0.0"
-
 tar-stream@^2.1.0, tar-stream@^2.1.2:
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.3.tgz#1e2022559221b7866161660f118255e20fa79e41"
@@ -12632,13 +12757,6 @@ tar@^4.4.10, tar@^4.4.12, tar@^4.4.6, tar@^4.4.8:
     safe-buffer "^5.1.2"
     yallist "^3.0.3"
 
-targz@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/targz/-/targz-1.0.1.tgz#8f76a523694cdedfbb5d60a4076ff6eeecc5398f"
-  integrity sha1-j3alI2lM3t+7XWCkB2/27uzFOY8=
-  dependencies:
-    tar-fs "^1.8.1"
-
 teeny-request@6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-6.0.1.tgz#9b1f512cef152945827ba7e34f62523a4ce2c5b0"
@@ -12854,11 +12972,6 @@ to-arraybuffer@^1.0.0:
   resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
   integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
 
-to-buffer@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
-  integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
-
 to-fast-properties@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
@@ -13043,6 +13156,11 @@ tslib@^1, tslib@^1.10.0, tslib@^1.11.1, tslib@^1.11.2, tslib@^1.13.0, tslib@^1.8
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
   integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
 
+tslib@^2.4.1:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
+  integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
+
 tsutils@^3.17.1:
   version "3.17.1"
   resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"


Test patch:
diff --git a/packages/core/src/compileMessage.test.ts b/packages/core/src/compile/compileMessage.test.ts
similarity index 98%
rename from packages/core/src/compileMessage.test.ts
rename to packages/core/src/compile/compileMessage.test.ts
index d2de5bc7..e6acc6b0 100644
--- a/packages/core/src/compileMessage.test.ts
+++ b/packages/core/src/compile/compileMessage.test.ts
@@ -1,7 +1,7 @@
 import {compileMessage as compile} from "./compileMessage"
 import { mockEnv, mockConsole } from "@lingui/jest-mocks"
-import { interpolate } from "./context"
-import {Locale, Locales} from "./i18n"
+import { interpolate } from "../context"
+import {Locale, Locales} from "../i18n"
 
 describe("compile", () => {
   const englishPlurals = {
diff --git a/packages/core/src/dev/loadLocaleData.test.ts b/packages/core/src/dev/loadLocaleData.test.ts
deleted file mode 100644
index daff437c..00000000
--- a/packages/core/src/dev/loadLocaleData.test.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import loadLocaleData from "./loadLocaleData"
-
-jest.mock("make-plural/plurals")
-
-import * as plurals from 'make-plural/plurals';
-
-describe("loadLocaleData", () => {
-  it("should return null if locale not passed", () => {
-    expect(loadLocaleData(null)).toEqual(undefined)
-  })
-
-  it("should return a plural fn", () => {
-    const result = loadLocaleData("es_CA")
-    expect(result).toEqual({ plurals: plurals["es"] })
-    result.plurals()
-    expect(plurals["es"]).toHaveBeenCalled()
-  })
-})
diff --git a/scripts/test.js b/scripts/test.ts
similarity index 85%
rename from scripts/test.js
rename to scripts/test.ts
index 42bb659a..4ff4a6be 100644
--- a/scripts/test.js
+++ b/scripts/test.ts
@@ -1,14 +1,15 @@
 const argv = require("minimist")(process.argv.slice(2))
 
-const fs = require("fs")
-const path = require("path")
-const { execSync } = require("child_process")
+import * as fs from "fs"
+import * as path from "path"
+import {execSync} from "child_process"
+
 const chalk = require("chalk")
 const ora = require("ora")
 
 const EXAMPLES_DIR = path.resolve("examples")
 
-function listDirs(dir) {
+function listDirs(dir: string) {
   return fs
     .readdirSync(dir)
     .filter(dirname => fs.lstatSync(path.join(dir, dirname)).isDirectory())
@@ -26,7 +27,7 @@ const logHeading = message => console.log(chalk.bold("\n" + message + "\n"))
 
 if (!argv["skip-build"]) {
   logHeading("Build packages")
-  execSync("node scripts/build", { stdio: "inherit" })
+  execSync("yarn release:build", { stdio: "inherit" })
 }
 
 // TODO: Replace yalc with verdaccio