From ec6a002ae3bbcbfbc2c3845f6144645bc7eb1299 Mon Sep 17 00:00:00 2001 From: Marco Ippolito Date: Sun, 20 Oct 2024 11:09:56 +0200 Subject: [PATCH 1/3] module: simplify ts under node_modules check PR-URL: https://github.com/nodejs/node/pull/55440 Reviewed-By: Ruben Bridgewater Reviewed-By: Benjamin Gruenbaum Reviewed-By: Matteo Collina Reviewed-By: Yagiz Nizipli Reviewed-By: Trivikram Kamat Reviewed-By: Chengzhong Wu --- lib/internal/modules/cjs/loader.js | 12 +----------- lib/internal/modules/esm/load.js | 8 -------- lib/internal/modules/helpers.js | 6 +++++- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index a1f95d0efaca27..adda4032d33e40 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -125,10 +125,10 @@ const { pathToFileURL, fileURLToPath, isURL } = require('internal/url'); const { pendingDeprecate, emitExperimentalWarning, - isUnderNodeModules, kEmptyObject, setOwnProperty, getLazy, + isUnderNodeModules, isWindows, } = require('internal/util'); const { @@ -170,7 +170,6 @@ const { ERR_REQUIRE_CYCLE_MODULE, ERR_REQUIRE_ESM, ERR_UNKNOWN_BUILTIN_MODULE, - ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING, }, setArrowMessage, } = require('internal/errors'); @@ -1348,9 +1347,6 @@ let emittedRequireModuleWarning = false; function loadESMFromCJS(mod, filename) { let source = getMaybeCachedSource(mod, filename); if (getOptionValue('--experimental-strip-types') && path.extname(filename) === '.mts') { - if (isUnderNodeModules(filename)) { - throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename); - } source = stripTypeScriptTypes(source, filename); } const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); @@ -1587,9 +1583,6 @@ function getMaybeCachedSource(mod, filename) { } function loadCTS(module, filename) { - if (isUnderNodeModules(filename)) { - throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename); - } const source = getMaybeCachedSource(module, filename); const code = stripTypeScriptTypes(source, filename); module._compile(code, filename, 'commonjs'); @@ -1601,9 +1594,6 @@ function loadCTS(module, filename) { * @param {string} filename The file path of the module */ function loadTS(module, filename) { - if (isUnderNodeModules(filename)) { - throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename); - } // If already analyzed the source, then it will be cached. const source = getMaybeCachedSource(module, filename); const content = stripTypeScriptTypes(source, filename); diff --git a/lib/internal/modules/esm/load.js b/lib/internal/modules/esm/load.js index d56dae3f001b1c..5ba13096b98047 100644 --- a/lib/internal/modules/esm/load.js +++ b/lib/internal/modules/esm/load.js @@ -4,7 +4,6 @@ const { RegExpPrototypeExec, } = primordials; const { - isUnderNodeModules, kEmptyObject, } = require('internal/util'); @@ -23,7 +22,6 @@ const { ERR_INVALID_URL, ERR_UNKNOWN_MODULE_FORMAT, ERR_UNSUPPORTED_ESM_URL_SCHEME, - ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING, } = require('internal/errors').codes; const { @@ -131,12 +129,6 @@ async function defaultLoad(url, context = kEmptyObject) { validateAttributes(url, format, importAttributes); - if (getOptionValue('--experimental-strip-types') && - (format === 'module-typescript' || format === 'commonjs-typescript') && - isUnderNodeModules(url)) { - throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(url); - } - return { __proto__: null, format, diff --git a/lib/internal/modules/helpers.js b/lib/internal/modules/helpers.js index 3153ecec198392..bd176eb5dd2837 100644 --- a/lib/internal/modules/helpers.js +++ b/lib/internal/modules/helpers.js @@ -16,6 +16,7 @@ const { ERR_INVALID_ARG_TYPE, ERR_INVALID_RETURN_PROPERTY_VALUE, ERR_INVALID_TYPESCRIPT_SYNTAX, + ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING, } = require('internal/errors').codes; const { BuiltinModule } = require('internal/bootstrap/realm'); @@ -28,7 +29,7 @@ const assert = require('internal/assert'); const { Buffer } = require('buffer'); const { getOptionValue } = require('internal/options'); -const { assertTypeScript, setOwnProperty, getLazy } = require('internal/util'); +const { assertTypeScript, setOwnProperty, getLazy, isUnderNodeModules } = require('internal/util'); const { inspect } = require('internal/util/inspect'); const lazyTmpdir = getLazy(() => require('os').tmpdir()); @@ -358,6 +359,9 @@ function parseTypeScript(source, options) { * @returns {TransformOutput} The stripped TypeScript code. */ function stripTypeScriptTypes(source, filename) { + if (isUnderNodeModules(filename)) { + throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename); + } assert(typeof source === 'string'); const options = { __proto__: null, From 2ee876c50a2204dc8a02d8b97c2acc1a21e222ec Mon Sep 17 00:00:00 2001 From: Marco Ippolito Date: Thu, 24 Oct 2024 20:27:58 +0200 Subject: [PATCH 2/3] module: add module.stripTypeScriptTypes PR-URL: https://github.com/nodejs/node/pull/55282 Fixes: https://github.com/nodejs/node/issues/54300 Reviewed-By: Yagiz Nizipli Reviewed-By: Joyee Cheung Reviewed-By: Chemi Atlow Reviewed-By: Paolo Insogna Reviewed-By: Chengzhong Wu Reviewed-By: James M Snell Reviewed-By: Richard Lau --- doc/api/module.md | 101 ++++++++++++++++ lib/internal/main/eval_string.js | 6 +- lib/internal/modules/cjs/loader.js | 10 +- lib/internal/modules/esm/get_format.js | 5 +- lib/internal/modules/esm/translators.js | 8 +- lib/internal/modules/helpers.js | 75 +----------- lib/internal/modules/typescript.js | 146 +++++++++++++++++++++++ lib/module.js | 3 +- test/parallel/test-bootstrap-modules.js | 1 + test/parallel/test-module-strip-types.js | 92 ++++++++++++++ 10 files changed, 358 insertions(+), 89 deletions(-) create mode 100644 lib/internal/modules/typescript.js create mode 100644 test/parallel/test-module-strip-types.js diff --git a/doc/api/module.md b/doc/api/module.md index 7a1928da5b05a2..0b86ddf8307e2f 100644 --- a/doc/api/module.md +++ b/doc/api/module.md @@ -352,6 +352,105 @@ changes: Register a module that exports [hooks][] that customize Node.js module resolution and loading behavior. See [Customization hooks][]. +## `module.stripTypeScriptTypes(code[, options])` + + + +> Stability: 1.0 - Early development + +* `code` {string} The code to strip type annotations from. +* `options` {Object} + * `mode` {string} **Default:** `'strip'`. Possible values are: + * `'strip'` Only strip type annotations without performing the transformation of TypeScript features. + * `'transform'` Strip type annotations and transform TypeScript features to JavaScript. + * `sourceMap` {boolean} **Default:** `false`. Only when `mode` is `'transform'`, if `true`, a source map + will be generated for the transformed code. + * `sourceUrl` {string} Specifies the source url used in the source map. +* Returns: {string} The code with type annotations stripped. + `module.stripTypeScriptTypes()` removes type annotations from TypeScript code. It + can be used to strip type annotations from TypeScript code before running it + with `vm.runInContext()` or `vm.compileFunction()`. + By default, it will throw an error if the code contains TypeScript features + that require transformation such as `Enums`, + see [type-stripping][] for more information. + When mode is `'transform'`, it also transforms TypeScript features to JavaScript, + see [transform TypeScript features][] for more information. + When mode is `'strip'`, source maps are not generated, because locations are preserved. + If `sourceMap` is provided, when mode is `'strip'`, an error will be thrown. + +_WARNING_: The output of this function should not be considered stable across Node.js versions, +due to changes in the TypeScript parser. + +```mjs +import { stripTypeScriptTypes } from 'node:module'; +const code = 'const a: number = 1;'; +const strippedCode = stripTypeScriptTypes(code); +console.log(strippedCode); +// Prints: const a = 1; +``` + +```cjs +const { stripTypeScriptTypes } = require('node:module'); +const code = 'const a: number = 1;'; +const strippedCode = stripTypeScriptTypes(code); +console.log(strippedCode); +// Prints: const a = 1; +``` + +If `sourceUrl` is provided, it will be used appended as a comment at the end of the output: + +```mjs +import { stripTypeScriptTypes } from 'node:module'; +const code = 'const a: number = 1;'; +const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' }); +console.log(strippedCode); +// Prints: const a = 1\n\n//# sourceURL=source.ts; +``` + +```cjs +const { stripTypeScriptTypes } = require('node:module'); +const code = 'const a: number = 1;'; +const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' }); +console.log(strippedCode); +// Prints: const a = 1\n\n//# sourceURL=source.ts; +``` + +When `mode` is `'transform'`, the code is transformed to JavaScript: + +```mjs +import { stripTypeScriptTypes } from 'node:module'; +const code = ` + namespace MathUtil { + export const add = (a: number, b: number) => a + b; + }`; +const strippedCode = stripTypeScriptTypes(code, { mode: 'transform', sourceMap: true }); +console.log(strippedCode); +// Prints: +// var MathUtil; +// (function(MathUtil) { +// MathUtil.add = (a, b)=>a + b; +// })(MathUtil || (MathUtil = {})); +// # sourceMappingURL=data:application/json;base64, ... +``` + +```cjs +const { stripTypeScriptTypes } = require('node:module'); +const code = ` + namespace MathUtil { + export const add = (a: number, b: number) => a + b; + }`; +const strippedCode = stripTypeScriptTypes(code, { mode: 'transform', sourceMap: true }); +console.log(strippedCode); +// Prints: +// var MathUtil; +// (function(MathUtil) { +// MathUtil.add = (a, b)=>a + b; +// })(MathUtil || (MathUtil = {})); +// # sourceMappingURL=data:application/json;base64, ... +``` + ### `module.syncBuiltinESMExports()` -> Stability: 1.0 - Early development +> Stability: 1.1 - Active development Enable experimental type-stripping for TypeScript files. For more information, see the [TypeScript type-stripping][] documentation. @@ -1096,7 +1096,7 @@ Enable module mocking in the test runner. added: v22.7.0 --> -> Stability: 1.0 - Early development +> Stability: 1.1 - Active development Enables the transformation of TypeScript-only syntax into JavaScript code. Implies `--experimental-strip-types` and `--enable-source-maps`. diff --git a/doc/api/module.md b/doc/api/module.md index 0b86ddf8307e2f..d0c3f82d71dae2 100644 --- a/doc/api/module.md +++ b/doc/api/module.md @@ -358,7 +358,7 @@ resolution and loading behavior. See [Customization hooks][]. added: REPLACEME --> -> Stability: 1.0 - Early development +> Stability: 1.1 - Active development * `code` {string} The code to strip type annotations from. * `options` {Object} diff --git a/doc/api/process.md b/doc/api/process.md index fe28d9e0dab49e..78c60861d69243 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -2009,7 +2009,7 @@ This value is therefore identical to that of `process.features.tls`. added: v22.10.0 --> -> Stability: 1.0 - Early development +> Stability: 1.1 - Active development * {boolean|string} diff --git a/doc/api/typescript.md b/doc/api/typescript.md index 4415d2ea5e34aa..d2680670a5f316 100644 --- a/doc/api/typescript.md +++ b/doc/api/typescript.md @@ -7,7 +7,7 @@ changes: description: Added `--experimental-transform-types` flag. --> -> Stability: 1.0 - Early development +> Stability: 1.1 - Active development ## Enabling @@ -50,7 +50,7 @@ To use TypeScript with full support for all TypeScript features, including added: v22.6.0 --> -> Stability: 1.0 - Early development +> Stability: 1.1 - Active development The flag [`--experimental-strip-types`][] enables Node.js to run TypeScript files. By default Node.js will execute only files that contain no