diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8f8b2e0bf3272..0dfcc70b6abe2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -29,21 +29,15 @@ jobs:
           - windows-latest
           - macos-latest
         node-version:
+          - '23'
           - '22'
           - '20'
-          - '18'
-          - '16'
-          - '14'
         bundle:
           - 'true'
         include:
           - node-version: 'lts/*'
             bundle: false
             os: ubuntu-latest
-        exclude:
-          # No Node 14 on ARM macOS
-          - node-version: '14'
-            os: macos-latest
 
     runs-on: ${{ matrix.os }}
     name: Test Node ${{ matrix.node-version }} on ${{ matrix.os }}${{ (!matrix.bundle && ' with --no-bundle') || '' }}
@@ -55,6 +49,7 @@ jobs:
         with:
           node-version: ${{ matrix.node-version }}
           check-latest: true
+
       - run: npm ci
 
       - name: Tests
@@ -84,6 +79,8 @@ jobs:
       - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
         with:
           node-version: 'lts/*'
+          check-latest: true
+
       - run: npm ci
 
       - name: Run tests with coverage
@@ -109,6 +106,7 @@ jobs:
       - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
         with:
           node-version: 'lts/*'
+          check-latest: true
       - run: npm ci
 
       - name: Linter
@@ -135,6 +133,7 @@ jobs:
       - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
         with:
           node-version: 'lts/*'
+          check-latest: true
       - run: npm ci
 
       - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
@@ -155,6 +154,7 @@ jobs:
       - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
         with:
           node-version: 'lts/*'
+          check-latest: true
       - run: npm ci
 
       - name: Installing browsers
@@ -171,6 +171,7 @@ jobs:
       - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
         with:
           node-version: 'lts/*'
+          check-latest: true
       - run: npm ci
 
       - name: Build src
@@ -185,6 +186,8 @@ jobs:
       - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
         with:
           node-version: 'lts/*'
+          check-latest: true
+
       - run: |
           npm --version
           # corepack enable npm
@@ -233,6 +236,7 @@ jobs:
       - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
         with:
           node-version: 'lts/*'
+          check-latest: true
       - run: |
           npm --version
           # corepack enable npm
@@ -266,6 +270,7 @@ jobs:
       - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
         with:
           node-version: 'lts/*'
+          check-latest: true
       - run: npm ci
 
       - name: Build scripts
@@ -282,6 +287,8 @@ jobs:
       - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
         with:
           node-version: 'lts/*'
+          check-latest: true
+
       - run: npm ci
 
       - name: Build tsc
@@ -301,6 +308,8 @@ jobs:
       - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
         with:
           node-version: 'lts/*'
+          check-latest: true
+
       - run: npm ci
 
       - name: Remove all baselines
diff --git a/.gulp.js b/.gulp.js
index b167b3a3628d8..405da7c654476 100644
--- a/.gulp.js
+++ b/.gulp.js
@@ -1,5 +1,9 @@
-const cp = require("child_process");
-const path = require("path");
+import cp from "child_process";
+import path from "path";
+import url from "url";
+
+const __filename = url.fileURLToPath(new URL(import.meta.url));
+const __dirname = path.dirname(__filename);
 
 const argv = process.argv.slice(2);
 
diff --git a/Herebyfile.mjs b/Herebyfile.mjs
index fb96b072f1c23..dc8383e38e6ec 100644
--- a/Herebyfile.mjs
+++ b/Herebyfile.mjs
@@ -1,6 +1,5 @@
 // @ts-check
 import { CancelToken } from "@esfx/canceltoken";
-import assert from "assert";
 import chokidar from "chokidar";
 import esbuild from "esbuild";
 import { EventEmitter } from "events";
@@ -172,7 +171,6 @@ async function runDtsBundler(entrypoint, output) {
  * @param {BundlerTaskOptions} [taskOptions]
  *
  * @typedef BundlerTaskOptions
- * @property {boolean} [exportIsTsObject]
  * @property {boolean} [treeShaking]
  * @property {boolean} [usePublicAPI]
  * @property {() => void} [onWatchRebuild]
@@ -180,17 +178,15 @@ async function runDtsBundler(entrypoint, output) {
 function createBundler(entrypoint, outfile, taskOptions = {}) {
     const getOptions = memoize(async () => {
         const copyright = await getCopyrightHeader();
-        const banner = taskOptions.exportIsTsObject ? "var ts = {}; ((module) => {" : "";
-
         /** @type {esbuild.BuildOptions} */
         const options = {
             entryPoints: [entrypoint],
-            banner: { js: copyright + banner },
+            banner: { js: copyright },
             bundle: true,
             outfile,
             platform: "node",
             target: ["es2020", "node14.17"],
-            format: "cjs",
+            format: "esm",
             sourcemap: "linked",
             sourcesContent: false,
             treeShaking: taskOptions.treeShaking,
@@ -200,66 +196,17 @@ function createBundler(entrypoint, outfile, taskOptions = {}) {
         };
 
         if (taskOptions.usePublicAPI) {
-            options.external = ["./typescript.js"];
             options.plugins = options.plugins || [];
             options.plugins.push({
-                name: "remap-typescript-to-require",
+                name: "remap-typescript-to-public-api",
                 setup(build) {
-                    build.onLoad({ filter: /src[\\/]typescript[\\/]typescript\.ts$/ }, () => {
-                        return { contents: `export * from "./typescript.js"` };
+                    build.onResolve({ filter: /^(?:\.\.[\\/])*typescript[\\/]typescript\.js$/ }, () => {
+                        return { path: "./typescript.js", external: true };
                     });
                 },
             });
         }
 
-        if (taskOptions.exportIsTsObject) {
-            // Monaco bundles us as ESM by wrapping our code with something that defines module.exports
-            // but then does not use it, instead using the `ts` variable. Ensure that if we think we're CJS
-            // that we still set `ts` to the module.exports object.
-            options.footer = { js: `})({ get exports() { return ts; }, set exports(v) { ts = v; if (typeof module !== "undefined" && module.exports) { module.exports = v; } } })` };
-
-            // esbuild converts calls to "require" to "__require"; this function
-            // calls the real require if it exists, or throws if it does not (rather than
-            // throwing an error like "require not defined"). But, since we want typescript
-            // to be consumable by other bundlers, we need to convert these calls back to
-            // require so our imports are visible again.
-            //
-            // To fix this, we redefine "require" to a name we're unlikely to use with the
-            // same length as "require", then replace it back to "require" after bundling,
-            // ensuring that source maps still work.
-            //
-            // See: https://github.com/evanw/esbuild/issues/1905
-            const require = "require";
-            const fakeName = "Q".repeat(require.length);
-            const fakeNameRegExp = new RegExp(fakeName, "g");
-            options.define = { [require]: fakeName };
-
-            // For historical reasons, TypeScript does not set __esModule. Hack esbuild's __toCommonJS to be a noop.
-            // We reference `__copyProps` to ensure the final bundle doesn't have any unreferenced code.
-            const toCommonJsRegExp = /var __toCommonJS .*/;
-            const toCommonJsRegExpReplacement = "var __toCommonJS = (mod) => (__copyProps, mod); // Modified helper to skip setting __esModule.";
-
-            options.plugins = options.plugins || [];
-            options.plugins.push(
-                {
-                    name: "post-process",
-                    setup: build => {
-                        build.onEnd(async () => {
-                            let contents = await fs.promises.readFile(outfile, "utf-8");
-                            contents = contents.replace(fakeNameRegExp, require);
-                            let matches = 0;
-                            contents = contents.replace(toCommonJsRegExp, () => {
-                                matches++;
-                                return toCommonJsRegExpReplacement;
-                            });
-                            assert(matches === 1, "Expected exactly one match for __toCommonJS");
-                            await fs.promises.writeFile(outfile, contents);
-                        });
-                    },
-                },
-            );
-        }
-
         return options;
     });
 
@@ -305,6 +252,7 @@ let printedWatchWarning = false;
  * @param {string} options.output
  * @param {boolean} [options.enableCompileCache]
  * @param {Task[]} [options.mainDeps]
+ * @param {boolean} [options.reexportDefault]
  * @param {BundlerTaskOptions} [options.bundlerOptions]
  */
 function entrypointBuildTask(options) {
@@ -329,13 +277,13 @@ function entrypointBuildTask(options) {
                 const moduleSpecifier = path.relative(outDir, output);
                 const lines = [
                     `// This file is a shim which defers loading the real module until the compile cache is enabled.`,
-                    `try {`,
-                    `  const { enableCompileCache } = require("node:module");`,
-                    `  if (enableCompileCache) {`,
-                    `    enableCompileCache();`,
-                    `  }`,
-                    `} catch {}`,
-                    `module.exports = require("./${moduleSpecifier.replace(/[\\/]/g, "/")}");`,
+                    `import mod from "node:module";`,
+                    `if (mod.enableCompileCache) {`,
+                    `  mod.enableCompileCache();`,
+                    `}`,
+                    `// Keep this synchronous so downstream people who required this file do not see TLA.`,
+                    `const require = mod.createRequire(import.meta.url);`,
+                    `require("./${moduleSpecifier.replace(/[\\/]/g, "/")}");`,
                 ];
                 await fs.promises.writeFile(originalOutput, lines.join("\n") + "\n");
             },
@@ -355,13 +303,13 @@ function entrypointBuildTask(options) {
     });
 
     /**
-     * Writes a CJS module that reexports another CJS file. E.g. given
+     * Writes a module that reexports another file. E.g. given
      * `options.builtEntrypoint = "./built/local/tsc/tsc.js"` and
      * `options.output = "./built/local/tsc.js"`, this will create a file
      * named "./built/local/tsc.js" containing:
      *
      * ```
-     * module.exports = require("./tsc/tsc.js")
+     * export * from "./tsc/tsc.js";
      * ```
      */
     const shim = task({
@@ -369,8 +317,19 @@ function entrypointBuildTask(options) {
         run: async () => {
             const outDir = path.dirname(output);
             await fs.promises.mkdir(outDir, { recursive: true });
-            const moduleSpecifier = path.relative(outDir, options.builtEntrypoint);
-            await fs.promises.writeFile(output, `module.exports = require("./${moduleSpecifier.replace(/[\\/]/g, "/")}")`);
+            const moduleSpecifier = path.relative(outDir, options.builtEntrypoint).replace(/[\\/]/g, "/");
+            const lines = [
+                `export * from "./${moduleSpecifier}";`,
+            ];
+
+            if (options.reexportDefault) {
+                lines.push(
+                    `import _default from "./${moduleSpecifier}";`,
+                    `export default _default;`,
+                );
+            }
+
+            await fs.promises.writeFile(output, lines.join("\n") + "\n");
         },
     });
 
@@ -435,7 +394,7 @@ const { main: services, build: buildServices, watch: watchServices } = entrypoin
     builtEntrypoint: "./built/local/typescript/typescript.js",
     output: "./built/local/typescript.js",
     mainDeps: [generateLibs],
-    bundlerOptions: { exportIsTsObject: true },
+    reexportDefault: true,
 });
 export { services, watchServices };
 
@@ -477,25 +436,22 @@ export const watchMin = task({
     dependencies: [watchTsc, watchTsserver],
 });
 
-// This is technically not enough to make tsserverlibrary loadable in the
-// browser, but it's unlikely that anyone has actually been doing that.
 const lsslJs = `
-if (typeof module !== "undefined" && module.exports) {
-    module.exports = require("./typescript.js");
-}
-else {
-    throw new Error("tsserverlibrary requires CommonJS; use typescript.js instead");
-}
+import ts from "./typescript.js";
+export * from "./typescript.js";
+export default ts;
 `;
 
 const lsslDts = `
-import ts = require("./typescript.js");
-export = ts;
+import ts from "./typescript.js";
+export * from "./typescript.js";
+export default ts;
 `;
 
 const lsslDtsInternal = `
-import ts = require("./typescript.internal.js");
-export = ts;
+import ts from "./typescript.internal.js";
+export * from "./typescript.internal.js";
+export default ts;
 `;
 
 /**
@@ -536,7 +492,7 @@ const { main: tests, watch: watchTests } = entrypointBuildTask({
     description: "Builds the test infrastructure",
     buildDeps: [generateDiagnostics],
     project: "src/testRunner",
-    srcEntrypoint: "./src/testRunner/_namespaces/Harness.ts",
+    srcEntrypoint: "./src/testRunner/runner.ts",
     builtEntrypoint: "./built/local/testRunner/runner.js",
     output: testRunner,
     mainDeps: [generateLibs],
diff --git a/bin/tsc b/bin/tsc
index 19c62bf7a0004..bef1027ea20a1 100755
--- a/bin/tsc
+++ b/bin/tsc
@@ -1,2 +1,2 @@
 #!/usr/bin/env node
-require('../lib/tsc.js')
+import '../lib/tsc.js';
diff --git a/bin/tsserver b/bin/tsserver
index 7143b6a73ab8a..d90c1a2ece3ff 100755
--- a/bin/tsserver
+++ b/bin/tsserver
@@ -1,2 +1,2 @@
 #!/usr/bin/env node
-require('../lib/tsserver.js')
+import '../lib/tsserver.js';
diff --git a/eslint.config.mjs b/eslint.config.mjs
index bc30b791311be..fcfeef896bffc 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -17,6 +17,14 @@ const rulesDir = path.join(__dirname, "scripts", "eslint", "rules");
 const ext = ".cjs";
 const ruleFiles = fs.readdirSync(rulesDir).filter(p => p.endsWith(ext));
 
+const restrictedESMGlobals = [
+    { name: "__filename" },
+    { name: "__dirname" },
+    { name: "require" },
+    { name: "module" },
+    { name: "exports" },
+];
+
 export default tseslint.config(
     {
         files: ["**/*.{ts,tsx,cts,mts,js,cjs,mjs}"],
@@ -165,11 +173,7 @@ export default tseslint.config(
             // These globals don't exist outside of CJS files.
             "no-restricted-globals": [
                 "error",
-                { name: "__filename" },
-                { name: "__dirname" },
-                { name: "require" },
-                { name: "module" },
-                { name: "exports" },
+                ...restrictedESMGlobals,
             ],
         },
     },
@@ -204,6 +208,7 @@ export default tseslint.config(
                 { name: "setImmediate" },
                 { name: "clearImmediate" },
                 { name: "performance" },
+                ...restrictedESMGlobals,
             ],
             "local/no-direct-import": "error",
         },
@@ -211,7 +216,10 @@ export default tseslint.config(
     {
         files: ["src/harness/**", "src/testRunner/**"],
         rules: {
-            "no-restricted-globals": "off",
+            "no-restricted-globals": [
+                "error",
+                ...restrictedESMGlobals,
+            ],
             "regexp/no-super-linear-backtracking": "off",
             "local/no-direct-import": "off",
         },
diff --git a/package-lock.json b/package-lock.json
index d4d2c055ccfff..d84e42cd1de31 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -59,7 +59,7 @@
                 "which": "^3.0.1"
             },
             "engines": {
-                "node": ">=14.17"
+                "node": ">=20.19.0 <21 || >=22.12.0"
             }
         },
         "node_modules/@bcoe/v8-coverage": {
diff --git a/package.json b/package.json
index ef2c80f8b91c2..b6408f1e593a7 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
         "type": "git",
         "url": "https://github.com/microsoft/TypeScript.git"
     },
+    "type": "module",
     "main": "./lib/typescript.js",
     "typings": "./lib/typescript.d.ts",
     "bin": {
@@ -26,7 +27,7 @@
         "tsserver": "./bin/tsserver"
     },
     "engines": {
-        "node": ">=14.17"
+        "node": ">=20.19.0 <21 || >=22.12.0"
     },
     "files": [
         "bin",
diff --git a/scripts/browserIntegrationTest.mjs b/scripts/browserIntegrationTest.mjs
index 68c2a14e46d7f..21c598477a355 100644
--- a/scripts/browserIntegrationTest.mjs
+++ b/scripts/browserIntegrationTest.mjs
@@ -28,7 +28,8 @@ for (const browserType of browsers) {
 
     await page.setContent(`
     
-    
+    
+    
     
     
     `);
diff --git a/scripts/checkModuleFormat.mjs b/scripts/checkModuleFormat.mjs
index ed33b5fb00382..564b74e3e8da0 100644
--- a/scripts/checkModuleFormat.mjs
+++ b/scripts/checkModuleFormat.mjs
@@ -1,4 +1,5 @@
 import { createRequire } from "module";
+import pc from "picocolors";
 import {
     __importDefault,
     __importStar,
@@ -19,7 +20,7 @@ console.log(`Testing ${typescript}...`);
 /** @type {[fn: (() => Promise), shouldSucceed: boolean][]} */
 const fns = [
     [() => require(typescript).version, true],
-    [() => require(typescript).default.version, false],
+    [() => require(typescript).default.version, true],
     [() => __importDefault(require(typescript)).version, false],
     [() => __importDefault(require(typescript)).default.version, true],
     [() => __importStar(require(typescript)).version, true],
@@ -41,7 +42,7 @@ for (const [fn, shouldSucceed] of fns) {
         console.log(`${fn.toString()} ${status} as expected.`);
     }
     else {
-        console.log(`${fn.toString()} unexpectedly ${status}.`);
+        console.log(pc.red(`${fn.toString()} unexpectedly ${status}.`));
         process.exitCode = 1;
     }
 }
diff --git a/scripts/dtsBundler.mjs b/scripts/dtsBundler.mjs
index ade134287fe57..17ee501060e01 100644
--- a/scripts/dtsBundler.mjs
+++ b/scripts/dtsBundler.mjs
@@ -365,6 +365,8 @@ function isSelfReference(reference, symbol) {
  * @param {ts.Symbol} moduleSymbol
  */
 function emitAsNamespace(name, parent, moduleSymbol, needExportModifier) {
+    if (name === "default") return;
+
     assert(moduleSymbol.flags & ts.SymbolFlags.ValueModule, "moduleSymbol is not a module");
 
     const fullName = parent ? `${parent}.${name}` : name;
@@ -482,6 +484,7 @@ function emitAsNamespace(name, parent, moduleSymbol, needExportModifier) {
 
 emitAsNamespace("ts", "", moduleSymbol, /*needExportModifier*/ false);
 
+// TODO(jakebailey): require(ESM) - fix this
 write("export = ts;", WriteTarget.Both);
 
 const copyrightNotice = fs.readFileSync(path.join(__dirname, "CopyrightNotice.txt"), "utf-8");
diff --git a/src/compiler/core.ts b/src/compiler/core.ts
index ef83fefd533e0..4600600de622d 100644
--- a/src/compiler/core.ts
+++ b/src/compiler/core.ts
@@ -2588,6 +2588,5 @@ export function isNodeLikeSystem(): boolean {
     // use in performanceCore.ts.
     return typeof process !== "undefined"
         && !!process.nextTick
-        && !(process as any).browser
-        && typeof require !== "undefined";
+        && !(process as any).browser;
 }
diff --git a/src/compiler/nodeGetBuiltinModule.ts b/src/compiler/nodeGetBuiltinModule.ts
new file mode 100644
index 0000000000000..d3b1b49d6cdf7
--- /dev/null
+++ b/src/compiler/nodeGetBuiltinModule.ts
@@ -0,0 +1,21 @@
+export function nodeCreateRequire(path: string): (id: string) => any {
+    /* eslint-disable no-restricted-globals */
+    // If we're running in an environment that already has `require`, use it.
+    // We're probably in bun or a bundler that provides `require` even within ESM.
+    if (typeof require === "function" && typeof require.resolve === "function") {
+        return id => {
+            const p = require.resolve(id, { paths: [path] });
+            return require(p);
+        };
+    }
+    /* eslint-enable no-restricted-globals */
+
+    // Otherwise, try and build a `require` function from the `module` module.
+    if (typeof process === "undefined" || typeof process.getBuiltinModule !== "function") {
+        throw new Error("process.getBuiltinModule is not supported in this environment.");
+    }
+
+    const mod = process.getBuiltinModule("node:module");
+    if (!mod) throw new Error("missing node:module");
+    return mod.createRequire(path);
+}
diff --git a/src/compiler/performanceCore.ts b/src/compiler/performanceCore.ts
index 38d8badace719..1e707a7fd79a9 100644
--- a/src/compiler/performanceCore.ts
+++ b/src/compiler/performanceCore.ts
@@ -1,4 +1,5 @@
 import { isNodeLikeSystem } from "./_namespaces/ts.js";
+import { nodeCreateRequire } from "./nodeGetBuiltinModule.js";
 
 // The following definitions provide the minimum compatible support for the Web Performance User Timings API
 // between browsers and NodeJS:
@@ -31,7 +32,7 @@ function tryGetPerformance() {
     if (isNodeLikeSystem()) {
         try {
             // By default, only write native events when generating a cpu profile or using the v8 profiler.
-            // Some environments may polyfill this module with an empty object; verify the object has the expected shape.
+            const require = nodeCreateRequire(import.meta.url);
             const { performance } = require("perf_hooks") as Partial;
             if (performance) {
                 return {
diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts
index c8f176603002b..ca7d5199b5def 100644
--- a/src/compiler/sys.ts
+++ b/src/compiler/sys.ts
@@ -46,6 +46,7 @@ import {
     WatchOptions,
     writeFileEnsuringDirectories,
 } from "./_namespaces/ts.js";
+import { nodeCreateRequire } from "./nodeGetBuiltinModule.js";
 
 declare function setTimeout(handler: (...args: any[]) => void, timeout: number): any;
 declare function clearTimeout(handle: any): void;
@@ -1466,9 +1467,15 @@ export let sys: System = (() => {
     const byteOrderMarkIndicator = "\uFEFF";
 
     function getNodeSystem(): System {
+        // TODO(jakebailey): Only use createRequire for sys.require.
+        const require = nodeCreateRequire(import.meta.url);
+        const _path: typeof import("path") = require("path");
+        const _url: typeof import("url") = require("url");
+        const __filename = _url.fileURLToPath(new URL(import.meta.url));
+        const __dirname = _path.dirname(__filename);
+
         const nativePattern = /^native |^\([^)]+\)$|^(?:internal[\\/]|[\w\s]+(?:\.js)?$)/;
         const _fs: typeof import("fs") = require("fs");
-        const _path: typeof import("path") = require("path");
         const _os = require("os");
         // crypto can be absent on reduced node installations
         let _crypto: typeof import("crypto") | undefined;
diff --git a/src/compiler/tracing.ts b/src/compiler/tracing.ts
index 5d7138d1c22eb..ea5f75a6cfda9 100644
--- a/src/compiler/tracing.ts
+++ b/src/compiler/tracing.ts
@@ -22,6 +22,7 @@ import {
     UnionType,
 } from "./_namespaces/ts.js";
 import * as performance from "./_namespaces/ts.performance.js";
+import { nodeCreateRequire } from "./nodeGetBuiltinModule.js";
 
 /* Tracing events for the compiler. */
 
@@ -60,6 +61,7 @@ export namespace tracingEnabled {
 
         if (fs === undefined) {
             try {
+                const require = nodeCreateRequire(import.meta.url);
                 fs = require("fs");
             }
             catch (e) {
diff --git a/src/harness/findUpDir.ts b/src/harness/findUpDir.ts
index 20b1afe414ece..29008b44726be 100644
--- a/src/harness/findUpDir.ts
+++ b/src/harness/findUpDir.ts
@@ -4,10 +4,14 @@ import {
     join,
     resolve,
 } from "path";
+import { fileURLToPath } from "url";
 
 // search directories upward to avoid hard-wired paths based on the
 // build tree (same as scripts/build/findUpDir.js)
 
+const __filename = fileURLToPath(new URL(import.meta.url));
+const __dirname = dirname(__filename);
+
 export function findUpFile(name: string): string {
     let dir = __dirname;
     while (true) {
diff --git a/src/testRunner/parallel/host.ts b/src/testRunner/parallel/host.ts
index 400971655c6b4..f8d833bc3ab31 100644
--- a/src/testRunner/parallel/host.ts
+++ b/src/testRunner/parallel/host.ts
@@ -33,6 +33,9 @@ import {
 import * as ts from "../_namespaces/ts.js";
 import * as Utils from "../_namespaces/Utils.js";
 
+import { createRequire } from "module";
+const require = createRequire(import.meta.url);
+
 export function start(importTests: () => Promise): void {
     const Base = Mocha.reporters.Base;
     const color = Base.color;
diff --git a/src/typescript/typescript.ts b/src/typescript/typescript.ts
index 9e54bbe9c15b0..fa83f781938b1 100644
--- a/src/typescript/typescript.ts
+++ b/src/typescript/typescript.ts
@@ -23,3 +23,5 @@ if (typeof console !== "undefined") {
 }
 
 export * from "./_namespaces/ts.js";
+import * as ts from "./_namespaces/ts.js";
+export default ts;
diff --git a/testImportESM.mjs b/testImportESM.mjs
new file mode 100644
index 0000000000000..d4f29ec3a001a
--- /dev/null
+++ b/testImportESM.mjs
@@ -0,0 +1,5 @@
+import * as ts from "./built/local/typescript.js";
+import ts2 from "./built/local/typescript.js";
+
+console.log(ts.version);
+console.log(ts2.version);
diff --git a/testRequireESM.cjs b/testRequireESM.cjs
new file mode 100644
index 0000000000000..d6c1afae10252
--- /dev/null
+++ b/testRequireESM.cjs
@@ -0,0 +1,2 @@
+const ts = require("./built/local/typescript.js");
+console.log(ts.version);
diff --git a/tests/baselines/reference/APILibCheck.errors.txt b/tests/baselines/reference/APILibCheck.errors.txt
new file mode 100644
index 0000000000000..1dd48855ecd32
--- /dev/null
+++ b/tests/baselines/reference/APILibCheck.errors.txt
@@ -0,0 +1,48 @@
+tsserverlibrary.d.ts(17,15): error TS2498: Module '"typescript"' uses 'export =' and cannot be used with 'export *'.
+tsserverlibrary.internal.d.ts(17,15): error TS2498: Module '"typescript.internal"' uses 'export =' and cannot be used with 'export *'.
+
+
+==== node_modules/typescript/package.json (0 errors) ====
+    {
+        "name": "typescript",
+        "type": "module",
+        "exports": "./lib/typescript.d.ts"
+    }
+    
+==== node_modules/typescript-internal/package.json (0 errors) ====
+    {
+        "name": "typescript-internal",
+        "type": "module",
+        "exports": "./lib/typescript.internal.d.ts"
+    }
+    
+==== node_modules/tsserverlibrary/package.json (0 errors) ====
+    {
+        "name": "tsserverlibrary",
+        "type": "module",
+        "exports": "./lib/tsserverlibrary.d.ts"
+    }
+    
+==== node_modules/tsserverlibrary-internal/package.json (0 errors) ====
+    {
+        "name": "tsserverlibrary-internal",
+        "type": "module",
+        "exports": "./lib/tsserverlibrary.internal.d.ts"
+    }
+    
+==== package.json (0 errors) ====
+    {
+        "name": "project",
+        "type": "module"
+    }
+    
+==== index.ts (0 errors) ====
+    import * as ts from "typescript";
+    import tsDefault from "typescript";
+    import * as tsInternal from "typescript-internal";
+    import tsInternalDefault from "typescript-internal";
+    import * as tsserverlibrary from "tsserverlibrary";
+    import tsserverlibraryDefault from "tsserverlibrary";
+    import * as tsserverlibraryInternal from "tsserverlibrary-internal";
+    import tsserverlibraryInternalDefault from "tsserverlibrary-internal";
+    
\ No newline at end of file
diff --git a/tests/cases/compiler/APILibCheck.ts b/tests/cases/compiler/APILibCheck.ts
index d6620af33f468..6b71d6206115c 100644
--- a/tests/cases/compiler/APILibCheck.ts
+++ b/tests/cases/compiler/APILibCheck.ts
@@ -1,4 +1,4 @@
-// @module: commonjs
+// @module: nodenext
 // @noImplicitAny: true
 // @strictNullChecks: true
 // @lib: es2018
@@ -6,32 +6,50 @@
 // @noTypesAndSymbols: true
 // @noEmit: true
 
+// @link: /.ts/typescript.d.ts -> node_modules/typescript/lib/typescript.d.ts
 // @filename: node_modules/typescript/package.json
 {
     "name": "typescript",
-    "types": "/.ts/typescript.d.ts"
+    "type": "module",
+    "exports": "./lib/typescript.d.ts"
 }
 
+// @link: /.ts/typescript.internal.d.ts -> node_modules/typescript-internal/lib/typescript.internal.d.ts
 // @filename: node_modules/typescript-internal/package.json
 {
     "name": "typescript-internal",
-    "types": "/.ts/typescript.internal.d.ts"
+    "type": "module",
+    "exports": "./lib/typescript.internal.d.ts"
 }
 
+// @link: /.ts/tsserverlibrary.d.ts -> node_modules/tsserverlibrary/lib/tsserverlibrary.d.ts
 // @filename: node_modules/tsserverlibrary/package.json
 {
     "name": "tsserverlibrary",
-    "types": "/.ts/tsserverlibrary.d.ts"
+    "type": "module",
+    "exports": "./lib/tsserverlibrary.d.ts"
 }
 
+// @link: /.ts/tsserverlibrary.internal.d.ts -> node_modules/tsserverlibrary-internal/lib/tsserverlibrary.internal.d.ts
 // @filename: node_modules/tsserverlibrary-internal/package.json
 {
     "name": "tsserverlibrary-internal",
-    "types": "/.ts/tsserverlibrary.internal.d.ts"
+    "type": "module",
+    "exports": "./lib/tsserverlibrary.internal.d.ts"
+}
+
+// @filename: package.json
+{
+    "name": "project",
+    "type": "module"
 }
 
 // @filename: index.ts
-import ts = require("typescript");
-import tsInternal = require("typescript-internal");
-import tsserverlibrary = require("tsserverlibrary");
-import tsserverlibraryInternal = require("tsserverlibrary-internal");
+import * as ts from "typescript";
+import tsDefault from "typescript";
+import * as tsInternal from "typescript-internal";
+import tsInternalDefault from "typescript-internal";
+import * as tsserverlibrary from "tsserverlibrary";
+import tsserverlibraryDefault from "tsserverlibrary";
+import * as tsserverlibraryInternal from "tsserverlibrary-internal";
+import tsserverlibraryInternalDefault from "tsserverlibrary-internal";