diff --git a/Herebyfile.mjs b/Herebyfile.mjs index 8447834a6338a..bd67e520c2351 100644 --- a/Herebyfile.mjs +++ b/Herebyfile.mjs @@ -1,6 +1,5 @@ // @ts-check import { CancelToken } from "@esfx/canceltoken"; -import assert from "assert"; import chalk from "chalk"; import chokidar from "chokidar"; import esbuild from "esbuild"; @@ -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: `})(typeof module !== "undefined" && module.exports ? module : { exports: ts });\nif (typeof module !== "undefined" && module.exports) { ts = module.exports; }` }; - - // 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; }); @@ -304,6 +251,7 @@ let printedWatchWarning = false; * @param {string} options.builtEntrypoint * @param {string} options.output * @param {Task[]} [options.mainDeps] + * @param {boolean} [options.reexportDefault] * @param {BundlerTaskOptions} [options.bundlerOptions] */ function entrypointBuildTask(options) { @@ -324,13 +272,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({ @@ -338,8 +286,19 @@ function entrypointBuildTask(options) { run: async () => { const outDir = path.dirname(options.output); await fs.promises.mkdir(outDir, { recursive: true }); - const moduleSpecifier = path.relative(outDir, options.builtEntrypoint); - await fs.promises.writeFile(options.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(options.output, lines.join("\n") + "\n"); }, }); @@ -404,7 +363,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 }; @@ -445,25 +404,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; `; /** @@ -504,7 +460,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/package-lock.json b/package-lock.json index 2c467651009a9..130c0800768d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@esfx/canceltoken": "^1.0.0", "@octokit/rest": "^20.1.1", "@types/chai": "^4.3.16", + "@types/diff": "^5.2.1", "@types/microsoft__typescript-etw": "^0.1.3", "@types/minimist": "^1.2.5", "@types/mocha": "^10.0.6", @@ -976,6 +977,12 @@ "integrity": "sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==", "dev": true }, + "node_modules/@types/diff": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.1.tgz", + "integrity": "sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==", + "dev": true + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -5011,6 +5018,12 @@ "integrity": "sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==", "dev": true }, + "@types/diff": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.1.tgz", + "integrity": "sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==", + "dev": true + }, "@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", diff --git a/package.json b/package.json index 4b300c1a0d6c5..a7389f387e60a 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@esfx/canceltoken": "^1.0.0", "@octokit/rest": "^20.1.1", "@types/chai": "^4.3.16", + "@types/diff": "^5.2.1", "@types/microsoft__typescript-etw": "^0.1.3", "@types/minimist": "^1.2.5", "@types/mocha": "^10.0.6", diff --git a/patchProcessGetBuiltin.cjs b/patchProcessGetBuiltin.cjs new file mode 100644 index 0000000000000..f147fca8b3cb6 --- /dev/null +++ b/patchProcessGetBuiltin.cjs @@ -0,0 +1,9 @@ +const _module = require("module"); + +/** @type {(name: string) => any} */ +function getBuiltinModule(name) { + if (!_module.isBuiltin(name)) return undefined; + return require(name); +} + +process.getBuiltinModule = getBuiltinModule; diff --git a/scripts/browserIntegrationTest.mjs b/scripts/browserIntegrationTest.mjs index f1fab43f2213c..f67f84105bcc1 100644 --- a/scripts/browserIntegrationTest.mjs +++ b/scripts/browserIntegrationTest.mjs @@ -28,7 +28,7 @@ for (const browserType of browsers) { await page.setContent(` - + `); diff --git a/scripts/checkModuleFormat.mjs b/scripts/checkModuleFormat.mjs index ed33b5fb00382..d6ab73eaf624a 100644 --- a/scripts/checkModuleFormat.mjs +++ b/scripts/checkModuleFormat.mjs @@ -19,7 +19,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], diff --git a/scripts/dtsBundler.mjs b/scripts/dtsBundler.mjs index e7ace12303e1d..b14e39992cd0d 100644 --- a/scripts/dtsBundler.mjs +++ b/scripts/dtsBundler.mjs @@ -357,6 +357,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; @@ -465,6 +467,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/.eslintrc.json b/src/.eslintrc.json index 697574c6a8ec5..3684d3c6fc87a 100644 --- a/src/.eslintrc.json +++ b/src/.eslintrc.json @@ -14,7 +14,10 @@ { "name": "clearInterval" }, { "name": "setImmediate" }, { "name": "clearImmediate" }, - { "name": "performance" } + { "name": "performance" }, + { "name": "require" }, + { "name": "__dirname" }, + { "name": "__filename" } ] }, "overrides": [ diff --git a/src/cancellationToken/cancellationToken.ts b/src/cancellationToken/cancellationToken.ts index 4676e9b14e0a3..97c5c7f0612fd 100644 --- a/src/cancellationToken/cancellationToken.ts +++ b/src/cancellationToken/cancellationToken.ts @@ -17,7 +17,7 @@ function pipeExists(name: string): boolean { return fs.existsSync(name); } -function createCancellationToken(args: string[]): ServerCancellationToken { +export function createCancellationToken(args: string[]): ServerCancellationToken { let cancellationPipeName: string | undefined; for (let i = 0; i < args.length - 1; i++) { if (args[i] === "--cancellationPipeName") { @@ -66,4 +66,3 @@ function createCancellationToken(args: string[]): ServerCancellationToken { }; } } -export = createCancellationToken; diff --git a/src/compiler/core.ts b/src/compiler/core.ts index d7f6fe0580d5f..a9005487dd901 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2730,5 +2730,5 @@ export function isNodeLikeSystem(): boolean { return typeof process !== "undefined" && !!process.nextTick && !(process as any).browser - && typeof require !== "undefined"; + && typeof (process as any).getBuiltinModule === "function"; } diff --git a/src/compiler/nodeGetBuiltinModule.ts b/src/compiler/nodeGetBuiltinModule.ts new file mode 100644 index 0000000000000..e8c26c8df53e8 --- /dev/null +++ b/src/compiler/nodeGetBuiltinModule.ts @@ -0,0 +1,12 @@ +export function nodeCreateRequire(path: string): NodeJS.Require { + const mod = nodeGetBuiltinModule("module") as typeof import("module") | undefined; + if (!mod) throw new Error("missing node:module"); + return mod.createRequire(path); +} + +function nodeGetBuiltinModule(moduleName: string): unknown { + if (typeof process === "undefined" || typeof (process as any).getBuiltinModule !== "function") { + throw new Error("process.getBuiltinModule is not supported in this environment."); + } + return (process as any).getBuiltinModule(moduleName); +} diff --git a/src/compiler/perfLogger.ts b/src/compiler/perfLogger.ts index f2476db967541..f02e8eb2c366c 100644 --- a/src/compiler/perfLogger.ts +++ b/src/compiler/perfLogger.ts @@ -1,3 +1,5 @@ +import { nodeCreateRequire } from "./nodeGetBuiltinModule.js"; + /** @internal */ export interface PerfLogger { logEvent(msg: string): void; @@ -27,6 +29,7 @@ export interface PerfLogger { let etwModule: typeof import("@microsoft/typescript-etw") | undefined; try { const etwModulePath = process.env.TS_ETW_MODULE_PATH ?? "./node_modules/@microsoft/typescript-etw"; + const require = nodeCreateRequire(import.meta.url); // require() will throw an exception if the module is not found // It may also return undefined if not installed properly diff --git a/src/compiler/performanceCore.ts b/src/compiler/performanceCore.ts index abff50eb8aae6..4086e39116ef1 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,8 @@ function tryGetPerformance() { if (isNodeLikeSystem()) { try { // By default, only write native events when generating a cpu profile or using the v8 profiler. - const { performance } = require("perf_hooks") as typeof import("perf_hooks"); + const require = nodeCreateRequire(import.meta.url); + const { performance } = require("perf_hooks"); return { shouldWriteNativeEvents: false, performance, diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index a8fdd4d156822..66aa231b71e92 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -47,6 +47,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[\\/]|[a-zA-Z0-9_\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 94e9bd57c83b6..985ac39753775 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/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 6c7a14535d2c5..2f9a3125dd52f 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -1,3 +1,4 @@ +import sourceMapSupport from "source-map-support"; import * as fakes from "./_namespaces/fakes.js"; import * as FourSlashInterface from "./_namespaces/FourSlashInterface.js"; import * as Harness from "./_namespaces/Harness.js"; @@ -4582,22 +4583,9 @@ function runCode(code: string, state: TestState, fileName: string): void { const generatedFile = ts.changeExtension(fileName, ".js"); const wrappedCode = `(function(ts, test, goTo, config, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled, ignoreInterpolations) {${code}\n//# sourceURL=${ts.getBaseFileName(generatedFile)}\n})`; - type SourceMapSupportModule = typeof import("source-map-support") & { - // TODO(rbuckton): This is missing from the DT definitions and needs to be added. - resetRetrieveHandlers(): void; - }; - // Provide the content of the current test to 'source-map-support' so that it can give us the correct source positions // for test failures. - let sourceMapSupportModule: SourceMapSupportModule | undefined; - try { - sourceMapSupportModule = require("source-map-support"); - } - catch { - // do nothing - } - - sourceMapSupportModule?.install({ + sourceMapSupport?.install({ retrieveFile: path => { return path === generatedFile ? wrappedCode : undefined!; @@ -4623,7 +4611,7 @@ function runCode(code: string, state: TestState, fileName: string): void { throw err; } finally { - sourceMapSupportModule?.resetRetrieveHandlers(); + sourceMapSupport?.resetRetrieveHandlers(); } } diff --git a/src/harness/harnessIO.ts b/src/harness/harnessIO.ts index fb0a80b71d457..4c00f974c79d7 100644 --- a/src/harness/harnessIO.ts +++ b/src/harness/harnessIO.ts @@ -1,3 +1,6 @@ +import * as Diff from "diff"; +import fs from "fs"; +import pathModule from "path"; import * as compiler from "./_namespaces/compiler.js"; import * as documents from "./_namespaces/documents.js"; import * as fakes from "./_namespaces/fakes.js"; @@ -54,14 +57,6 @@ export const virtualFileSystemRoot = "/"; function createNodeIO(): IO { const workspaceRoot = Utils.findUpRoot(); - let fs: any, pathModule: any; - if (require) { - fs = require("fs"); - pathModule = require("path"); - } - else { - fs = pathModule = {}; - } function deleteFile(path: string) { try { @@ -1018,7 +1013,6 @@ export namespace Compiler { } else if (original.text !== doc.text) { jsCode += `\r\n\r\n!!!! File ${Utils.removeTestPathPrefixes(doc.file)} differs from original emit in noCheck emit\r\n`; - const Diff = require("diff"); const expected = original.text; const actual = doc.text; const patch = Diff.createTwoFilesPatch("Expected", "Actual", expected, actual, "The full check baseline", "with noCheck set"); @@ -1514,8 +1508,7 @@ export namespace Baseline { IO.writeFile(actualFileName, encodedActual); } const errorMessage = getBaselineFileChangedErrorMessage(relativeFileName); - if (!!require && opts && opts.PrintDiff) { - const Diff = require("diff"); + if (opts && opts.PrintDiff) { const patch = Diff.createTwoFilesPatch("Expected", "Actual", expected, actual, "The current baseline", "The new version"); throw new Error(`${errorMessage}${ts.ForegroundColorEscapeSequences.Grey}\n\n${patch}`); } diff --git a/src/harness/harnessUtils.ts b/src/harness/harnessUtils.ts index d7989da8d6980..b049c51d5f416 100644 --- a/src/harness/harnessUtils.ts +++ b/src/harness/harnessUtils.ts @@ -1,3 +1,4 @@ +import vm from "vm"; import * as Harness from "./_namespaces/Harness.js"; import * as ts from "./_namespaces/ts.js"; @@ -6,7 +7,6 @@ export function encodeString(s: string): string { } export function evalFile(fileContents: string, fileName: string, nodeContext?: any) { - const vm = require("vm"); if (nodeContext) { vm.runInNewContext(fileContents, nodeContext, fileName); } diff --git a/src/testRunner/parallel/host.ts b/src/testRunner/parallel/host.ts index cbfc9b391c491..f6b1159b969c9 100644 --- a/src/testRunner/parallel/host.ts +++ b/src/testRunner/parallel/host.ts @@ -1,3 +1,11 @@ +import { fork } from "child_process"; +import { statSync } from "fs"; +import Mocha from "mocha"; +import ms from "ms"; +import os from "os"; +import path from "path"; +import readline from "readline"; +import tty from "tty"; import { configOption, globalTimeout, @@ -25,21 +33,16 @@ 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) { - const Mocha = require("mocha") as typeof import("mocha"); const Base = Mocha.reporters.Base; const color = Base.color; const cursor = Base.cursor; - const ms = require("ms") as typeof import("ms"); - const readline = require("readline") as typeof import("readline"); - const os = require("os") as typeof import("os"); - const tty = require("tty") as typeof import("tty"); const isatty = tty.isatty(1) && tty.isatty(2); - const path = require("path") as typeof import("path"); - const { fork } = require("child_process") as typeof import("child_process"); - const { statSync } = require("fs") as typeof import("fs"); - // NOTE: paths for module and types for FailedTestReporter _do not_ line up due to our use of --outFile for run.js + // NOTE: paths for module and types for FailedTestReporter _do not_ line up when bundled const FailedTestReporter = require(Utils.findUpFile("scripts/failed-tests.cjs")) as typeof import("../../../scripts/failed-tests.cjs"); const perfdataFileNameFragment = ".parallelperf"; diff --git a/src/testRunner/parallel/worker.ts b/src/testRunner/parallel/worker.ts index 4b61633e45a53..23c089ef35411 100644 --- a/src/testRunner/parallel/worker.ts +++ b/src/testRunner/parallel/worker.ts @@ -1,3 +1,4 @@ +import Mocha from "mocha"; import { createRunner, globalTimeout, @@ -39,9 +40,6 @@ export function start(importTests: () => Promise) { let exceptionsHooked = false; hookUncaughtExceptions(); - // Capitalization is aligned with the global `Mocha` namespace for typespace/namespace references. - const Mocha = require("mocha") as typeof import("mocha"); - /** * Mixin helper. * @param base The base class constructor. diff --git a/src/testRunner/unittests/skipJSDocParsing.ts b/src/testRunner/unittests/skipJSDocParsing.ts index 4fd6e87819628..45c4e67881e8c 100644 --- a/src/testRunner/unittests/skipJSDocParsing.ts +++ b/src/testRunner/unittests/skipJSDocParsing.ts @@ -1,10 +1,9 @@ +import * as Diff from "diff"; import * as Harness from "../_namespaces/Harness.js"; import * as ts from "../_namespaces/ts.js"; import * as Utils from "../_namespaces/Utils.js"; describe("unittests:: skipJSDocParsing", () => { - const Diff = require("diff"); - const kinds = [ ts.JSDocParsingMode.ParseAll, ts.JSDocParsingMode.ParseForTypeErrors, diff --git a/src/tsserver/nodeServer.ts b/src/tsserver/nodeServer.ts index 280b0d3b630df..a3429a3ebe7a4 100644 --- a/src/tsserver/nodeServer.ts +++ b/src/tsserver/nodeServer.ts @@ -1,3 +1,9 @@ +import childProcess from "child_process"; +import fs from "fs"; +import net from "net"; +import os from "os"; +import readline from "readline"; +import { nodeCreateRequire } from "../compiler/nodeGetBuiltinModule.js"; import { CharacterCodes, combinePaths, @@ -9,7 +15,6 @@ import { getDirectoryPath, getRootLength, LanguageServiceMode, - MapLike, noop, noopFileWatcher, normalizePath, @@ -38,24 +43,6 @@ interface LogOptions { logToFile?: boolean; } -interface NodeChildProcess { - send(message: any, sendHandle?: any): void; - on(message: "message" | "exit", f: (m: any) => void): void; - kill(): void; - pid: number; -} - -interface ReadLineOptions { - input: NodeJS.ReadableStream; - output?: NodeJS.WritableStream; - terminal?: boolean; - historySize?: number; -} - -interface NodeSocket { - write(data: string, encoding: string): boolean; -} - function parseLoggingEnvironmentString(logEnvStr: string | undefined): LogOptions { if (!logEnvStr) { return {}; @@ -124,41 +111,6 @@ function parseServerMode(): LanguageServiceMode | string | undefined { /** @internal */ export function initializeNodeSystem(): StartInput { const sys = Debug.checkDefined(ts.sys) as ts.server.ServerHost; - const childProcess: { - execFileSync(file: string, args: string[], options: { stdio: "ignore"; env: MapLike; }): string | Buffer; - } = require("child_process"); - - interface Stats { - isFile(): boolean; - isDirectory(): boolean; - isBlockDevice(): boolean; - isCharacterDevice(): boolean; - isSymbolicLink(): boolean; - isFIFO(): boolean; - isSocket(): boolean; - dev: number; - ino: number; - mode: number; - nlink: number; - uid: number; - gid: number; - rdev: number; - size: number; - blksize: number; - blocks: number; - atime: Date; - mtime: Date; - ctime: Date; - birthtime: Date; - } - - const fs: { - openSync(path: string, options: string): number; - close(fd: number, callback: (err: NodeJS.ErrnoException) => void): void; - writeSync(fd: number, buffer: Buffer, offset: number, length: number, position?: number): number; - statSync(path: string): Stats; - stat(path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; - } = require("fs"); class Logger implements Logger { private seq = 0; @@ -244,7 +196,7 @@ export function initializeNodeSystem(): StartInput { if (this.fd >= 0) { const buf = Buffer.from(s); // eslint-disable-next-line no-restricted-syntax - fs.writeSync(this.fd, buf, 0, buf.length, /*position*/ null!); // TODO: GH#18217 + fs.writeSync(this.fd, buf, 0, buf.length, /*position*/ null); // TODO: GH#18217 } if (this.traceToConsole) { console.warn(s); @@ -339,8 +291,9 @@ export function initializeNodeSystem(): StartInput { let cancellationToken: ts.server.ServerCancellationToken; try { - const factory = require("./cancellationToken"); - cancellationToken = factory(sys.args); + const require = nodeCreateRequire(import.meta.url); + const { createCancellationToken } = require("./cancellationToken.js"); + cancellationToken = createCancellationToken(sys.args); } catch (e) { cancellationToken = ts.server.nullCancellationToken; @@ -452,23 +405,6 @@ function parseEventPort(eventPortStr: string | undefined) { return eventPort !== undefined && !isNaN(eventPort) ? eventPort : undefined; } function startNodeSession(options: StartSessionOptions, logger: ts.server.Logger, cancellationToken: ts.server.ServerCancellationToken) { - const childProcess: { - fork(modulePath: string, args: string[], options?: { execArgv: string[]; env?: MapLike; }): NodeChildProcess; - } = require("child_process"); - - const os: { - homedir?(): string; - tmpdir(): string; - } = require("os"); - - const net: { - connect(options: { port: number; }, onConnect?: () => void): NodeSocket; - } = require("net"); - - const readline: { - createInterface(options: ReadLineOptions): NodeJS.EventEmitter; - } = require("readline"); - const rl = readline.createInterface({ input: process.stdin, output: process.stdout, @@ -476,7 +412,7 @@ function startNodeSession(options: StartSessionOptions, logger: ts.server.Logger }); class NodeTypingsInstallerAdapter extends ts.server.TypingsInstallerAdapter { - protected override installer!: NodeChildProcess; + protected override installer!: childProcess.ChildProcess; // This number is essentially arbitrary. Processing more than one typings request // at a time makes sense, but having too many in the pipe results in a hang // (see https://github.com/nodejs/node/issues/7657). @@ -546,7 +482,7 @@ function startNodeSession(options: StartSessionOptions, logger: ts.server.Logger const typingsInstaller = combinePaths(getDirectoryPath(sys.getExecutingFilePath()), "typingsInstaller.js"); this.installer = childProcess.fork(typingsInstaller, args, { execArgv }); - this.installer.on("message", m => this.handleMessage(m)); + this.installer.on("message", m => this.handleMessage(m as any)); // We have to schedule this event to the next tick // cause this fn will be called during @@ -563,7 +499,7 @@ function startNodeSession(options: StartSessionOptions, logger: ts.server.Logger class IOSession extends ts.server.Session { private eventPort: number | undefined; - private eventSocket: NodeSocket | undefined; + private eventSocket: net.Socket | undefined; private socketEventQueue: { body: any; eventName: string; }[] | undefined; /** No longer needed if syntax target is es6 or above. Any access to "this" before initialized will be a runtime error. */ private constructed: boolean | undefined; diff --git a/src/tsserver/server.ts b/src/tsserver/server.ts index dac80d8496f2f..d74ca4f04d645 100644 --- a/src/tsserver/server.ts +++ b/src/tsserver/server.ts @@ -1,3 +1,4 @@ +import os from "os"; import * as ts from "../typescript/typescript.js"; import { StartInput } from "./common.js"; import { initializeNodeSystem } from "./nodeServer.js"; @@ -53,4 +54,4 @@ function start({ args, logger, cancellationToken, serverMode, unknownServerMode, } ts.setStackTraceLimit(); -start(initializeNodeSystem(), require("os").platform()); +start(initializeNodeSystem(), os.platform()); 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/src/typingsInstaller/nodeTypingsInstaller.ts b/src/typingsInstaller/nodeTypingsInstaller.ts index 836b64473cb05..984d7b80c5b7e 100644 --- a/src/typingsInstaller/nodeTypingsInstaller.ts +++ b/src/typingsInstaller/nodeTypingsInstaller.ts @@ -1,3 +1,4 @@ +import { execFileSync } from "child_process"; import * as fs from "fs"; import * as path from "path"; @@ -79,10 +80,8 @@ interface ExecSyncOptions { cwd: string; encoding: "utf-8"; } -type ExecSync = (command: string, options: ExecSyncOptions) => string; export class NodeTypingsInstaller extends ts.server.typingsInstaller.TypingsInstaller { - private readonly nodeExecSync: ExecSync; private readonly npmPath: string; readonly typesRegistry: Map>; @@ -109,7 +108,6 @@ export class NodeTypingsInstaller extends ts.server.typingsInstaller.TypingsInst this.log.writeLine(`NPM location: ${this.npmPath} (explicit '${ts.server.Arguments.NpmLocation}' ${npmLocation === undefined ? "not " : ""} provided)`); this.log.writeLine(`validateDefaultNpmLocation: ${validateDefaultNpmLocation}`); } - ({ execSync: this.nodeExecSync } = require("child_process")); this.ensurePackageDirectoryExists(globalTypingsCacheLocation); @@ -174,7 +172,7 @@ export class NodeTypingsInstaller extends ts.server.typingsInstaller.TypingsInst this.log.writeLine(`Exec: ${command}`); } try { - const stdout = this.nodeExecSync(command, { ...options, encoding: "utf-8" }); + const stdout = execFileSync(command, { ...options, encoding: "utf-8" }); if (this.log.isEnabled()) { this.log.writeLine(` Succeeded. stdout:${indent(sys.newLine, stdout)}`); } 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/baselines/reference/APILibCheck.js b/tests/baselines/reference/APILibCheck.js index 76bacfbf7a675..4e5c1eeff1759 100644 --- a/tests/baselines/reference/APILibCheck.js +++ b/tests/baselines/reference/APILibCheck.js @@ -3,34 +3,47 @@ //// [package.json] { "name": "typescript", - "types": "/.ts/typescript.d.ts" + "type": "module", + "exports": "./lib/typescript.d.ts" } //// [package.json] { "name": "typescript-internal", - "types": "/.ts/typescript.internal.d.ts" + "type": "module", + "exports": "./lib/typescript.internal.d.ts" } //// [package.json] { "name": "tsserverlibrary", - "types": "/.ts/tsserverlibrary.d.ts" + "type": "module", + "exports": "./lib/tsserverlibrary.d.ts" } //// [package.json] { "name": "tsserverlibrary-internal", - "types": "/.ts/tsserverlibrary.internal.d.ts" + "type": "module", + "exports": "./lib/tsserverlibrary.internal.d.ts" +} + +//// [package.json] +{ + "name": "project", + "type": "module" } //// [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"; //// [index.js] -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3db5faa718232..5f60c7df8eeb4 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -13,5 +13,6 @@ See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ -import ts = require("./typescript.js"); -export = ts; +import ts from "./typescript.js"; +export * from "./typescript.js"; +export default ts; diff --git a/tests/cases/compiler/APILibCheck.ts b/tests/cases/compiler/APILibCheck.ts index 7abf10e9821e9..c2805372e7106 100644 --- a/tests/cases/compiler/APILibCheck.ts +++ b/tests/cases/compiler/APILibCheck.ts @@ -1,36 +1,54 @@ -// @module: commonjs +// @module: nodenext // @noImplicitAny: true // @strictNullChecks: true // @lib: es2018 // @exactOptionalPropertyTypes: true // @noTypesAndSymbols: 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";