From d08e608e02358a7a6ecd505fc9e248004e227c61 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 29 Apr 2024 20:14:17 -0700 Subject: [PATCH 1/5] Mark as type=module --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index fdb3153ea15ba..e294967ef2a30 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": { From f830b3d51ddfd4bdb9fe29903710ef566651ea86 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 2 May 2024 15:57:51 -0700 Subject: [PATCH 2/5] Update .gulp.js --- .gulp.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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); From 6af27e657579f64baa18de182d9e29cd221bde51 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 29 Apr 2024 20:27:10 -0700 Subject: [PATCH 3/5] Make things work in ESM with getBuiltinModule --- Herebyfile.mjs | 108 ++++++------------ bin/tsc | 2 +- bin/tsserver | 2 +- eslint.config.mjs | 20 +++- scripts/browserIntegrationTest.mjs | 3 +- scripts/checkModuleFormat.mjs | 2 +- scripts/dtsBundler.mjs | 3 + src/cancellationToken/cancellationToken.ts | 3 +- src/compiler/core.ts | 3 +- src/compiler/nodeGetBuiltinModule.ts | 24 ++++ src/compiler/performanceCore.ts | 4 +- src/compiler/sys.ts | 9 +- src/compiler/tracing.ts | 2 + src/harness/findUpDir.ts | 4 + src/testRunner/parallel/host.ts | 3 + src/tsserver/nodeServer.ts | 6 +- src/typescript/typescript.ts | 2 + testImportESM.mjs | 5 + testRequireESM.cjs | 2 + .../reference/APILibCheck.errors.txt | 48 ++++++++ tests/baselines/reference/APILibCheck.js | 33 ++++-- .../reference/api/tsserverlibrary.d.ts | 5 +- tests/cases/compiler/APILibCheck.ts | 36 ++++-- 23 files changed, 214 insertions(+), 115 deletions(-) create mode 100644 src/compiler/nodeGetBuiltinModule.ts create mode 100644 testImportESM.mjs create mode 100644 testRequireESM.cjs create mode 100644 tests/baselines/reference/APILibCheck.errors.txt diff --git a/Herebyfile.mjs b/Herebyfile.mjs index 7939e5eafbd9e..b1c085db55ef7 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: `})({ 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; }); @@ -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/eslint.config.mjs b/eslint.config.mjs index 34126cd0ee2f9..615b7fbb0b910 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,6 +16,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}"], @@ -170,11 +178,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, ], }, }, @@ -200,13 +204,17 @@ export default tseslint.config( { name: "setImmediate" }, { name: "clearImmediate" }, { name: "performance" }, + ...restrictedESMGlobals, ], }, }, { files: ["src/harness/**", "src/testRunner/**"], rules: { - "no-restricted-globals": "off", + "no-restricted-globals": [ + "error", + ...restrictedESMGlobals, + ], }, }, { diff --git a/scripts/browserIntegrationTest.mjs b/scripts/browserIntegrationTest.mjs index 7b3021f979350..f85c25a0f1036 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..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 6ad37bd2b48e1..34997301b1c25 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/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 e40d8826ca69c..91ffd75cb0c22 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2603,6 +2603,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..fff601c830c9f --- /dev/null +++ b/src/compiler/nodeGetBuiltinModule.ts @@ -0,0 +1,24 @@ +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. + 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/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 3aefbdf99397e..9c95d78677ebf 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[\\/]|[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/testRunner/parallel/host.ts b/src/testRunner/parallel/host.ts index 0df41fb73e2fd..f6b1159b969c9 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) { const Base = Mocha.reporters.Base; const color = Base.color; diff --git a/src/tsserver/nodeServer.ts b/src/tsserver/nodeServer.ts index 6e89f665fe436..d6a3c005719d5 100644 --- a/src/tsserver/nodeServer.ts +++ b/src/tsserver/nodeServer.ts @@ -3,6 +3,7 @@ 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, @@ -277,8 +278,9 @@ export function initializeNodeSystem(): StartInput { let cancellationToken: ts.server.ServerCancellationToken; try { - const factory = require("./cancellationToken.js"); - 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; 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/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"; From 208e0647c4c117a1aa0f3d3ba5184e9974bd6aa3 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 2 May 2024 15:40:38 -0700 Subject: [PATCH 4/5] Enable CI --- .github/workflows/ci.yml | 76 ++++++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20846e80149a6..92732b1bff0a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,21 +29,13 @@ jobs: - windows-latest - macos-14 node-version: - - '22' - - '20' - - '18' - - '16' - - '14' + - 23-nightly bundle: - 'true' include: - - node-version: '*' + - node-version: 23-nightly bundle: false os: ubuntu-latest - exclude: - # No Node 14 on ARM macOS - - node-version: '14' - os: macos-14 runs-on: ${{ matrix.os }} name: Test Node ${{ matrix.node-version }} on ${{ matrix.os }}${{ (!matrix.bundle && ' with --no-bundle') || '' }} @@ -55,12 +47,20 @@ jobs: with: node-version: ${{ matrix.node-version }} check-latest: true + + - run: | + echo "value=--no-warnings=ExperimentalWarning --experimental-require-module" >> "$GITHUB_OUTPUT" + name: Enable require(ESM) + id: node-options + - run: npm ci - name: Tests id: test # run tests, but lint separately run: npm run test -- --no-lint --bundle=${{ matrix.bundle }} + env: + NODE_OPTIONS: ${{ steps.node-options.outputs.value }} - name: Print baseline diff on failure if: ${{ failure() && steps.test.conclusion == 'failure' }} @@ -68,6 +68,8 @@ jobs: npx hereby baseline-accept git add tests/baselines/reference git diff --staged --exit-code + env: + NODE_OPTIONS: ${{ steps.node-options.outputs.value }} lint: runs-on: ubuntu-latest @@ -76,7 +78,7 @@ jobs: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: '*' + node-version: '23-nightly' check-latest: true - run: npm ci @@ -104,7 +106,7 @@ jobs: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: '*' + node-version: '23-nightly' check-latest: true - run: npm ci @@ -125,7 +127,7 @@ jobs: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: '*' + node-version: '23-nightly' check-latest: true - run: npm ci @@ -142,7 +144,7 @@ jobs: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: '*' + node-version: '23-nightly' check-latest: true - run: npm ci @@ -157,8 +159,14 @@ jobs: - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: '*' + node-version: '23-nightly' check-latest: true + + - run: | + echo "value=--no-warnings=ExperimentalWarning --experimental-require-module" >> "$GITHUB_OUTPUT" + name: Enable require(ESM) + id: node-options + - run: | npm --version # corepack enable npm @@ -168,12 +176,16 @@ jobs: - run: npm ci - run: npx hereby lkg + env: + NODE_OPTIONS: ${{ steps.node-options.outputs.value }} - run: | node ./scripts/addPackageJsonGitHead.mjs package.json npm pack mv typescript*.tgz typescript.tgz echo "package=$PWD/typescript.tgz" >> "$GITHUB_OUTPUT" id: pack + env: + NODE_OPTIONS: ${{ steps.node-options.outputs.value }} - name: Smoke test run: | @@ -189,6 +201,8 @@ jobs: node $GITHUB_WORKSPACE/scripts/checkModuleFormat.mjs typescript node $GITHUB_WORKSPACE/scripts/checkModuleFormat.mjs typescript/lib/tsserverlibrary + env: + NODE_OPTIONS: ${{ steps.node-options.outputs.value }} package-size: runs-on: ubuntu-latest @@ -206,7 +220,7 @@ jobs: - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: '*' + node-version: '23-nightly' check-latest: true - run: | npm --version @@ -240,7 +254,7 @@ jobs: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: '*' + node-version: 23-nightly check-latest: true - run: npm ci @@ -257,18 +271,30 @@ jobs: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: '*' + node-version: 23-nightly check-latest: true + + - run: | + echo "value=--no-warnings=ExperimentalWarning --experimental-require-module" >> "$GITHUB_OUTPUT" + name: Enable require(ESM) + id: node-options + - run: npm ci - name: Build tsc run: npx hereby tsc + env: + NODE_OPTIONS: ${{ steps.node-options.outputs.value }} - name: Clean run: npx hereby clean-src + env: + NODE_OPTIONS: ${{ steps.node-options.outputs.value }} - name: Self build run: npx hereby build-src --built + env: + NODE_OPTIONS: ${{ steps.node-options.outputs.value }} baselines: runs-on: ubuntu-latest @@ -277,8 +303,14 @@ jobs: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: '*' + node-version: 23-nightly check-latest: true + + - run: | + echo "value=--no-warnings=ExperimentalWarning --experimental-require-module" >> "$GITHUB_OUTPUT" + name: Enable require(ESM) + id: node-options + - run: npm ci - name: Remove all baselines @@ -286,11 +318,15 @@ jobs: - name: Run tests run: npm test &> /dev/null || exit 0 + env: + NODE_OPTIONS: ${{ steps.node-options.outputs.value }} - name: Accept baselines run: | npx hereby baseline-accept git add tests/baselines/reference + env: + NODE_OPTIONS: ${{ steps.node-options.outputs.value }} - name: Check baselines id: check-baselines @@ -309,6 +345,8 @@ jobs: git diff --staged > fix_baselines.patch exit 1 fi + env: + NODE_OPTIONS: ${{ steps.node-options.outputs.value }} - name: Upload baseline diff artifact if: ${{ failure() && steps.check-baselines.conclusion == 'failure' }} From 212ec1ae566c0adcac9a768a4925306cb7ccbfa7 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 11 Jul 2024 09:09:15 -0700 Subject: [PATCH 5/5] Make module format errors red --- scripts/checkModuleFormat.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/checkModuleFormat.mjs b/scripts/checkModuleFormat.mjs index d6ab73eaf624a..d37faf353bfa8 100644 --- a/scripts/checkModuleFormat.mjs +++ b/scripts/checkModuleFormat.mjs @@ -1,3 +1,4 @@ +import chalk from "chalk"; import { createRequire } from "module"; import { __importDefault, @@ -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(chalk.red(`${fn.toString()} unexpectedly ${status}.`)); process.exitCode = 1; } }