diff --git a/Herebyfile.mjs b/Herebyfile.mjs index dfb94e5d996b5..18a06923a21d6 100644 --- a/Herebyfile.mjs +++ b/Herebyfile.mjs @@ -2,6 +2,7 @@ import { CancelToken, } from "@esfx/canceltoken"; +import assert from "assert"; import chalk from "chalk"; import chokidar from "chokidar"; import esbuild from "esbuild"; @@ -46,7 +47,7 @@ import { void 0; const copyrightFilename = "./scripts/CopyrightNotice.txt"; -const copyright = memoize(async () => { +const getCopyrightHeader = memoize(async () => { const contents = await fs.promises.readFile(copyrightFilename, "utf-8"); return contents.replace(/\r\n/g, "\n"); }); @@ -76,7 +77,7 @@ export const generateLibs = task({ run: async () => { await fs.promises.mkdir("./built/local", { recursive: true }); for (const lib of libs()) { - let output = await copyright(); + let output = await getCopyrightHeader(); for (const source of lib.sources) { const contents = await fs.promises.readFile(source, "utf-8"); @@ -187,10 +188,13 @@ 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: await copyright() }, + banner: { js: copyright + banner }, bundle: true, outfile, platform: "node", @@ -205,12 +209,10 @@ function createBundler(entrypoint, outfile, taskOptions = {}) { }; if (taskOptions.exportIsTsObject) { - // We use an IIFE so we can inject the footer, and so that "ts" is global if not loaded as a module. - options.format = "iife"; - // Name the variable ts, matching our old big bundle and so we can use the code below. - options.globalName = "ts"; - // If we are in a CJS context, export the ts namespace. - options.footer = { js: `\nif (typeof module !== "undefined" && module.exports) { module.exports = ts; }` }; + // 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 @@ -227,13 +229,25 @@ function createBundler(entrypoint, outfile, taskOptions = {}) { 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 = [ { - name: "fix-require", + 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); }); }, @@ -450,7 +464,7 @@ export = ts; * @param {string} contents */ async function fileContentsWithCopyright(contents) { - return await copyright() + contents.trim().replace(/\r\n/g, "\n") + "\n"; + return await getCopyrightHeader() + contents.trim().replace(/\r\n/g, "\n") + "\n"; } const lssl = task({ diff --git a/scripts/checkModuleFormat.mjs b/scripts/checkModuleFormat.mjs index 6c461d370a32e..3991969472245 100644 --- a/scripts/checkModuleFormat.mjs +++ b/scripts/checkModuleFormat.mjs @@ -5,31 +5,37 @@ import { __importDefault, __importStar, } from "tslib"; +import { + pathToFileURL, +} from "url"; // This script tests that TypeScript's CJS API is structured // as expected. It calls "require" as though it were in CWD, // so it can be tested on a separate install of TypeScript. const require = createRequire(process.cwd() + "/index.js"); +const typescript = process.argv[2]; +const resolvedTypeScript = pathToFileURL(require.resolve(typescript)).toString(); -console.log(`Testing ${process.argv[2]}...`); -const ts = require(process.argv[2]); +console.log(`Testing ${typescript}...`); // See: https://github.com/microsoft/TypeScript/pull/51474#issuecomment-1310871623 -/** @type {[fn: (() => any), shouldSucceed: boolean][]} */ +/** @type {[fn: (() => Promise), shouldSucceed: boolean][]} */ const fns = [ - [() => ts.version, true], - [() => ts.default.version, false], - [() => __importDefault(ts).version, false], - [() => __importDefault(ts).default.version, true], - [() => __importStar(ts).version, true], - [() => __importStar(ts).default.version, true], + [() => require(typescript).version, true], + [() => require(typescript).default.version, false], + [() => __importDefault(require(typescript)).version, false], + [() => __importDefault(require(typescript)).default.version, true], + [() => __importStar(require(typescript)).version, true], + [() => __importStar(require(typescript)).default.version, true], + [async () => (await import(resolvedTypeScript)).version, true], + [async () => (await import(resolvedTypeScript)).default.version, true], ]; for (const [fn, shouldSucceed] of fns) { let success = false; try { - success = !!fn(); + success = !!(await fn()); } catch { // Ignore @@ -43,4 +49,10 @@ for (const [fn, shouldSucceed] of fns) { process.exitCode = 1; } } -console.log("ok"); + +if (process.exitCode) { + console.log("fail"); +} +else { + console.log("ok"); +} diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 1ee2fefe16048..97be236ed0b9f 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2743,12 +2743,8 @@ export function skipWhile(array: readonly T[] | undefined, predi export function isNodeLikeSystem(): boolean { // This is defined here rather than in sys.ts to prevent a cycle from its // use in performanceCore.ts. - // - // We don't use the presence of `require` to check if we are in Node; - // when bundled using esbuild, this function will be rewritten to `__require` - // and definitely exist. return typeof process !== "undefined" && !!process.nextTick && !(process as any).browser - && typeof module === "object"; + && typeof require !== "undefined"; } diff --git a/src/typescript/typescript.ts b/src/typescript/typescript.ts index 83f991b83fad2..0df80b850032d 100644 --- a/src/typescript/typescript.ts +++ b/src/typescript/typescript.ts @@ -2,7 +2,6 @@ import { Debug, LogLevel, } from "./_namespaces/ts"; -import * as ts from "./_namespaces/ts"; // enable deprecation logging declare const console: any; @@ -23,4 +22,4 @@ if (typeof console !== "undefined") { }; } -export = ts; +export * from "./_namespaces/ts";