|$ curl https://forge-ai.dev/api/markdown?path=docs/build-tools/compilers
$cat docs/babel-&-typescript-compiler.md
updated Recently·35 min read·published

Babel & TypeScript Compiler

Build ToolsCompilersIntermediate
Introduction

Babel and the TypeScript compiler (tsc) are the two foundational tools for transforming modern JavaScript and TypeScript into code that runs across browsers and environments. Babel focuses on syntax transformation and polyfilling, while tsc provides type checking and full TypeScript compilation.

Understanding when to use each — and how they complement each other — is essential for any serious JavaScript project. The industry trend has shifted toward using SWC or esbuild for speed, but Babel and tsc remain the most battle-tested and widely understood tools.

Babel — JavaScript Compiler

Babel is a toolchain for converting ECMAScript 2015+ code into a backwards-compatible version of JavaScript. It consists of three core stages: parsing, transforming, and code generation.

babel.config.js
JavaScript
1// Babel configuration — babel.config.js (project-wide)
2module.exports = {
3 presets: [
4 ["@babel/preset-env", {
5 targets: "> 0.25%, not dead",
6 useBuiltIns: "usage",
7 corejs: 3,
8 modules: false, // Preserve ES modules for tree shaking
9 }],
10 ["@babel/preset-react", {
11 runtime: "automatic", // React 17+ JSX transform
12 }],
13 "@babel/preset-typescript",
14 ],
15 plugins: [
16 "@babel/plugin-transform-runtime",
17 "babel-plugin-styled-components",
18 ["@babel/plugin-proposal-decorators", { legacy: true }],
19 ],
20};
FileScopeUse Case
babel.config.jsProject-wide (root)Monorepos, shared config
.babelrcFile-localSingle package
.babelrc.jsFile-local (JS)Dynamic config

info

Use babel.config.js (project-wide) over .babelrc (file-local) in monorepos. The project-wide config applies to all packages, while file-local configs apply only to the containing package — leading to inconsistent behavior.
Presets & Plugins

Presets are collections of plugins. Babel applies plugins in order (first to last) and presets in reverse order (last to first). Understanding this ordering is critical for correct transformation.

preset-order.js
JavaScript
1// Preset execution order: last to first
2// 1. @babel/preset-typescript
3// 2. @babel/preset-react
4// 3. @babel/preset-env
5
6// Plugin execution order: first to last
7// 1. @babel/plugin-transform-runtime
8// 2. babel-plugin-styled-components
9// 3. @babel/plugin-proposal-decorators
10
11module.exports = {
12 presets: [
13 ["@babel/preset-env", {
14 targets: {
15 browsers: ["> 1%", "last 2 versions", "not dead"],
16 node: "current",
17 },
18 modules: false,
19 loose: true,
20 bugfixes: true, // Opt-in for smaller output
21 }],
22 ["@babel/preset-react", {
23 runtime: "automatic",
24 importSource: "@emotion/react",
25 pragma: "React.createElement",
26 pragmaFrag: "React.Fragment",
27 }],
28 "@babel/preset-flow",
29 ],
30};
PresetPurposeKey Options
@babel/preset-envSmart transpilation based on targetstargets, useBuiltIns, corejs, modules
@babel/preset-reactJSX and React transformationsruntime, importSource, pragma
@babel/preset-typescriptStrip TypeScript types (no type check)isTSX, allExtensions, onlyRemoveTypeImports
@babel/preset-flowStrip Flow type annotationsall, ignoreExtensions

best practice

Enable bugfixes: true in @babel/preset-env. It merges narrow, bug-fix transforms into more general transforms when possible, producing significantly smaller output. For example, instead of converting every optional chaining usage, it might only convert the edge cases that actually need it.
Polyfills with core-js

Babel only transforms syntax, not APIs. To use modern APIs (like Array.prototype.includes or Promise) in older browsers, you need polyfills provided by core-js. Babel integrates with core-js via useBuiltIns.

polyfill-config.js
JavaScript
1// Three useBuiltIns strategies:
2
3// "entry" — Import ALL polyfills for target browsers
4// You add this once in your entry file:
5import "core-js/stable";
6import "regenerator-runtime/runtime";
7// Babel replaces these with specific imports for your targets
8
9// "usage" — Auto-import polyfills per-file (recommended)
10// Babel detects when you use a modern API and imports the polyfill:
11// Your code:
12const p = new Promise((resolve) => resolve());
13// Babel adds:
14require("core-js/modules/es.promise");
15require("core-js/modules/es.object.to-string");
16
17// false — No auto-polyfilling (default)
18// You must manually polyfill everything
19
20module.exports = {
21 presets: [
22 ["@babel/preset-env", {
23 targets: "> 0.25%, not dead",
24 useBuiltIns: "usage", // <-- recommended
25 corejs: { version: 3, proposals: true },
26 shippedProposals: true, // Include stage-4 proposals
27 }],
28 ],
29};
StrategyBundle SizeComplexityRecommendation
falseSmallestSimpleModern browsers only
"entry"LargestSimpleLegacy browser support
"usage"OptimizedComplexRecommended

warning

The useBuiltIns: "usage" option requires Babel to see every file in your project. If you use it with a bundler that also has loaders excluding node_modules, you may miss polyfills for dependencies. Use the include option in Webpack or Vite to ensure Babel processes files that need polyfilling.
TypeScript Compiler (tsc)

The TypeScript compiler (tsc) performs two critical functions: type checking and compilation to JavaScript. Unlike Babel, tsc validates types before emitting code, catching category errors at compile time.

tsconfig.json
JSON
1{
2 "compilerOptions": {
3 /* Project Setup */
4 "target": "ES2022",
5 "module": "ESNext",
6 "moduleResolution": "bundler",
7 "lib": ["ES2022", "DOM", "DOM.Iterable"],
8 "jsx": "react-jsx",
9 "outDir": "./dist",
10 "rootDir": "./src",
11 "declaration": true,
12 "declarationMap": true,
13 "sourceMap": true,
14
15 /* Strict Type-Checking */
16 "strict": true,
17 "noUncheckedIndexedAccess": true,
18 "noImplicitOverride": true,
19 "noPropertyAccessFromIndexSignature": true,
20
21 /* Module Resolution */
22 "baseUrl": ".",
23 "paths": {
24 "@/*": ["src/*"],
25 "@components/*": ["src/components/*"],
26 "@utils/*": ["src/utils/*"]
27 },
28 "resolveJsonModule": true,
29 "allowImportingTsExtensions": true,
30 "isolatedModules": true,
31
32 /* Output Control */
33 "removeComments": true,
34 "stripInternal": true,
35 "skipLibCheck": true,
36 "forceConsistentCasingInFileNames": true,
37
38 /* Build Performance */
39 "incremental": true,
40 "tsBuildInfoFile": ".tsbuildinfo"
41 },
42 "include": ["src"],
43 "exclude": ["node_modules", "dist", "**/*.test.ts"]
44}
CategoryOptionPurpose
StrictstrictEnable all strict type-checking options
StrictnoUncheckedIndexedAccessAdd undefined to indexed access types
OutputdeclarationGenerate .d.ts declaration files
OutputdeclarationMapSource maps for .d.ts files (Go to Definition)
ModulemoduleResolution"bundler" for modern bundlers (Vite, Webpack)
ModulepathsPath aliases matching bundler config
PerformanceincrementalBuild incrementally for faster recompilation
PerformanceskipLibCheckSkip type-checking .d.ts files (faster)

best practice

Enable noUncheckedIndexedAccess to prevent runtime errors from undefined property access. Without it, obj[key] is typed as T instead of T | undefined, hiding potential crashes.
Project References

TypeScript project references allow you to structure a codebase into smaller, independently compilable projects. They enable incremental builds, faster CI, and better separation of concerns in monorepos.

project-references.json
JSON
1// Root tsconfig.json (composite project)
2{
3 "compilerOptions": {
4 "composite": true,
5 "declaration": true,
6 "declarationMap": true,
7 "emitDeclarationOnly": true,
8 "incremental": true,
9 },
10 "references": [
11 { "path": "./packages/core" },
12 { "path": "./packages/ui" },
13 { "path": "./packages/utils" },
14 { "path": "./apps/web" }
15 ]
16}
17
18// packages/core/tsconfig.json
19{
20 "compilerOptions": {
21 "composite": true,
22 "outDir": "./dist",
23 "rootDir": "./src",
24 "declaration": true,
25 "declarationMap": true,
26 },
27 "include": ["src"]
28}
29
30// apps/web/tsconfig.json
31{
32 "compilerOptions": {
33 "composite": true,
34 "outDir": "./dist",
35 "rootDir": "./src",
36 },
37 "include": ["src"],
38 "references": [
39 { "path": "../../packages/core" },
40 { "path": "../../packages/ui" }
41 ]
42}
43
44// Build command — references build in dependency order
45// tsc --build tsconfig.json
46// This builds: core -> ui -> web (in parallel where possible)
🔥

pro tip

Use tsc --build with project references for monorepo builds. It builds packages in dependency order (with parallelism), only rebuilds changed packages, and caches declaration files. Combined with incremental: true, rebuilds are near-instant.
Babel vs. tsc: When to Use What
DimensionBabeltsc
Type CheckingNone (strips types)Full type checking
SpeedFast (no type check)Slower (full analysis)
Output SizeSmaller (target-based)Larger (transpile per target)
Plugin EcosystemMassive (5000+ plugins)None (no plugin system)
Polyfillscore-js integrationManual (no auto polyfill)
Declaration FilesNo (.d.ts generation)Yes (with declaration: true)
JSX TransformYes (automatic runtime)Yes (jsx: "react-jsx")
Best ForApplication bundlingLibrary/publishing
package.json
JavaScript
1// Recommended approach: Babel for builds, tsc for checks
2
3// package.json scripts
4{
5 "scripts": {
6 "build": "babel src --out-dir dist",
7 "typecheck": "tsc --noEmit",
8 "ci": "tsc --noEmit && npm run build && npm test"
9 }
10}
11
12// For libraries — use tsc for both checking and output
13{
14 "scripts": {
15 "build": "tsc",
16 "typecheck": "tsc --noEmit",
17 "prepublish": "npm run build"
18 }
19}

best practice

For application development: use Babel (or SWC) for fast builds and run tsc --noEmit in a separate process for type checking (often in CI or your editor). For library publishing: use tsc directly to generate declaration files that consumers rely on.
SWC as a Babel Alternative

SWC (Speedy Web Compiler) is a Rust-based drop-in replacement for Babel that offers 10-20x faster transpilation with near-complete Babel plugin parity.

.swcrc
JSON
1// SWC configuration (.swcrc)
2{
3 "jsc": {
4 "parser": {
5 "syntax": "typescript",
6 "tsx": true,
7 "decorators": true,
8 "decoratorVersion": "2022-03",
9 "dynamicImport": true
10 },
11 "transform": {
12 "react": {
13 "runtime": "automatic",
14 "refresh": true,
15 "development": false
16 },
17 "constModules": {
18 "globals": {
19 "process.env.NODE_ENV": "production"
20 }
21 }
22 },
23 "target": "es2021",
24 "externalHelpers": true,
25 "keepClassNames": true,
26 "minify": {
27 "compress": {
28 "arguments": false,
29 "booleans": true,
30 "drop_console": true,
31 "unused": true
32 },
33 "mangle": true,
34 "format": {
35 "comments": false
36 }
37 }
38 },
39 "module": {
40 "type": "es6",
41 "strict": true
42 }
43}
ToolLanguageRelative SpeedBabel CompatType Checking
BabelJavaScript1x (baseline)100%None
SWCRust10-20x95%None
esbuildGo20-30x70%None
tscTypeScript0.2-0.5xN/AFull
📝

note

SWC does not support every Babel plugin. Before migrating, audit your Babel plugins against SWC's supported list. Common missing transforms include babel-plugin-macros, babel-plugin-formatjs, and some GraphQL tag transforms.
Best Practices
Use Babel for application builds (bundler integration) and tsc for type checking
Enable isolatedModules in tsconfig — required by Babel and SWC transpilation
Use useBuiltIns: 'usage' with core-js@3 for minimal polyfill bundles
Set Babel's modules: false to preserve ES modules for bundler tree shaking
Run tsc --noEmit in CI, not just in editors — it catches missed type errors
Use project references for monorepo TypeScript projects
Migrate from Babel to SWC if your plugin set is compatible — 10-20x faster
Always generate declaration files (.d.ts) when publishing libraries
Use the automatic JSX runtime (React 17+) — no need to import React in every file
Keep tsconfig strict: true — it prevents entire categories of runtime bugs
$Blueprint — Engineering Documentation·Section ID: BT-COMPILERS-01·Revision: 1.0