Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v22.x backport] module: add module.stripTypeScriptTypes #56208

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1042,7 +1042,7 @@ Use this flag to enable [ShadowRealm][] support.
added: v22.6.0
-->

> 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.
Expand Down Expand Up @@ -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`.
Expand Down
101 changes: 101 additions & 0 deletions doc/api/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -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])`

<!-- YAML
added: REPLACEME
-->

> Stability: 1.1 - Active 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()`

<!-- YAML
Expand Down Expand Up @@ -1333,3 +1432,5 @@ returned object contains the following keys:
[realm]: https://tc39.es/ecma262/#realm
[source map include directives]: https://sourcemaps.info/spec.html#h.lmz475t4mvbx
[transferrable objects]: worker_threads.md#portpostmessagevalue-transferlist
[transform TypeScript features]: typescript.md#typescript-features
[type-stripping]: typescript.md#type-stripping
2 changes: 1 addition & 1 deletion doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down
4 changes: 2 additions & 2 deletions doc/api/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ changes:
description: Added `--experimental-transform-types` flag.
-->

> Stability: 1.0 - Early development
> Stability: 1.1 - Active development

## Enabling

Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions lib/internal/main/eval_string.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const {
markBootstrapComplete,
} = require('internal/process/pre_execution');
const { evalModuleEntryPoint, evalScript } = require('internal/process/execution');
const { addBuiltinLibsToObject, stripTypeScriptTypes } = require('internal/modules/helpers');

const { addBuiltinLibsToObject } = require('internal/modules/helpers');
const { stripTypeScriptModuleTypes } = require('internal/modules/typescript');
const { getOptionValue } = require('internal/options');

prepareMainThreadExecution();
Expand All @@ -24,7 +24,7 @@ markBootstrapComplete();

const code = getOptionValue('--eval');
const source = getOptionValue('--experimental-strip-types') ?
stripTypeScriptTypes(code) :
stripTypeScriptModuleTypes(code) :
code;

const print = getOptionValue('--print');
Expand Down
22 changes: 6 additions & 16 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@ const { pathToFileURL, fileURLToPath, isURL } = require('internal/url');
const {
pendingDeprecate,
emitExperimentalWarning,
isUnderNodeModules,
kEmptyObject,
setOwnProperty,
getLazy,
isUnderNodeModules,
isWindows,
} = require('internal/util');
const {
Expand All @@ -153,8 +153,8 @@ const {
setHasStartedUserCJSExecution,
stripBOM,
toRealPath,
stripTypeScriptTypes,
} = require('internal/modules/helpers');
const { stripTypeScriptModuleTypes } = require('internal/modules/typescript');
const packageJsonReader = require('internal/modules/package_json_reader');
const { getOptionValue, getEmbedderOptions } = require('internal/options');
const shouldReportRequiredModules = getLazy(() => process.env.WATCH_REPORT_DEPENDENCIES);
Expand All @@ -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');
Expand Down Expand Up @@ -1348,10 +1347,7 @@ 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);
source = stripTypeScriptModuleTypes(source, filename);
}
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
const isMain = mod[kIsMainSymbol];
Expand Down Expand Up @@ -1587,11 +1583,8 @@ 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);
const code = stripTypeScriptModuleTypes(source, filename);
module._compile(code, filename, 'commonjs');
}

Expand All @@ -1601,12 +1594,9 @@ 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);
const content = stripTypeScriptModuleTypes(source, filename);
let format;
const pkg = packageJsonReader.getNearestParentPackageJSON(filename);
// Function require shouldn't be used in ES modules.
Expand All @@ -1626,7 +1616,7 @@ function loadTS(module, filename) {
if (Module._cache[parentPath]) {
let parentSource;
try {
parentSource = stripTypeScriptTypes(fs.readFileSync(parentPath, 'utf8'), parentPath);
parentSource = stripTypeScriptModuleTypes(fs.readFileSync(parentPath, 'utf8'), parentPath);
} catch {
// Continue regardless of error.
}
Expand Down
5 changes: 3 additions & 2 deletions lib/internal/modules/esm/get_format.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,10 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
// Since experimental-strip-types depends on detect-module, we always return null
// if source is undefined.
if (!source) { return null; }
const { stripTypeScriptTypes, stringify } = require('internal/modules/helpers');
const { stringify } = require('internal/modules/helpers');
const { stripTypeScriptModuleTypes } = require('internal/modules/typescript');
const stringifiedSource = stringify(source);
const parsedSource = stripTypeScriptTypes(stringifiedSource, fileURLToPath(url));
const parsedSource = stripTypeScriptModuleTypes(stringifiedSource, fileURLToPath(url));
const detectedFormat = detectModuleFormat(parsedSource, url);
const format = `${detectedFormat}-typescript`;
if (format === 'module-typescript' && foundPackageJson) {
Expand Down
8 changes: 0 additions & 8 deletions lib/internal/modules/esm/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const {
RegExpPrototypeExec,
} = primordials;
const {
isUnderNodeModules,
kEmptyObject,
} = require('internal/util');

Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ const {
assertBufferSource,
loadBuiltinModule,
stringify,
stripTypeScriptTypes,
stripBOM,
urlToFilename,
} = require('internal/modules/helpers');
const { stripTypeScriptModuleTypes } = require('internal/modules/typescript');
const {
kIsCachedByESMLoader,
Module: CJSModule,
Expand Down Expand Up @@ -244,7 +244,7 @@ translators.set('require-commonjs', (url, source, isMain) => {
translators.set('require-commonjs-typescript', (url, source, isMain) => {
emitExperimentalWarning('Type Stripping');
assert(cjsParse);
const code = stripTypeScriptTypes(stringify(source), url);
const code = stripTypeScriptModuleTypes(stringify(source), url);
return createCJSModuleWrap(url, code);
});

Expand Down Expand Up @@ -459,7 +459,7 @@ translators.set('wasm', async function(url, source) {
translators.set('commonjs-typescript', function(url, source) {
emitExperimentalWarning('Type Stripping');
assertBufferSource(source, true, 'load');
const code = stripTypeScriptTypes(stringify(source), url);
const code = stripTypeScriptModuleTypes(stringify(source), url);
debug(`Translating TypeScript ${url}`);
return FunctionPrototypeCall(translators.get('commonjs'), this, url, code, false);
});
Expand All @@ -468,7 +468,7 @@ translators.set('commonjs-typescript', function(url, source) {
translators.set('module-typescript', function(url, source) {
emitExperimentalWarning('Type Stripping');
assertBufferSource(source, true, 'load');
const code = stripTypeScriptTypes(stringify(source), url);
const code = stripTypeScriptModuleTypes(stringify(source), url);
debug(`Translating TypeScript ${url}`);
return FunctionPrototypeCall(translators.get('module'), this, url, code, false);
});
Loading
Loading