Skip to content

Commit 7c1529b

Browse files
vm: add support for --experimental-strip-types
1 parent 20d8b85 commit 7c1529b

File tree

4 files changed

+238
-20
lines changed

4 files changed

+238
-20
lines changed

doc/api/vm.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ executed in specific contexts.
5858
<!-- YAML
5959
added: v0.3.1
6060
changes:
61+
- version: REPLACEME
62+
pr-url: https://github.com/nodejs/node/pull/REPLACEME
63+
description: Added support for experimental strip types.
6164
- version:
6265
- v21.7.0
6366
- v20.12.0
@@ -107,6 +110,16 @@ changes:
107110
experimental modules API. We do not recommend using it in a production
108111
environment. For detailed information, see
109112
[Support of dynamic `import()` in compilation APIs][].
113+
* `transform` {Object} An object containing options for type stripping.
114+
Only available when the `--experimental-strip-types` flag is enabled.
115+
* `mode` {string} The mode of type stripping. Possible values are:
116+
* `strip-only`: Strip all types, without transforming TypeScript only features,
117+
for more information read [type-stripping][].
118+
* `transform`: Transforms TypeScript features, for info read [transform TypeScript features][].
119+
* `sourceMap` {boolean} Generate source maps for the transformed code, if `mode` is `transform`,
120+
otherwise it is not necessary.
121+
The filename for the source map is the same as the filename of the script.
122+
**Default:** `false`.
110123

111124
If `options` is a string, then it specifies the filename.
112125

@@ -990,6 +1003,9 @@ const vm = require('node:vm');
9901003
<!-- YAML
9911004
added: v10.10.0
9921005
changes:
1006+
- version: REPLACEME
1007+
pr-url: https://github.com/nodejs/node/pull/REPLACEME
1008+
description: Added support for experimental strip types.
9931009
- version:
9941010
- v21.7.0
9951011
- v20.12.0
@@ -1044,6 +1060,16 @@ changes:
10441060
* `contextExtensions` {Object\[]} An array containing a collection of context
10451061
extensions (objects wrapping the current scope) to be applied while
10461062
compiling. **Default:** `[]`.
1063+
* `transform` {Object} An object containing options for type stripping.
1064+
Only available when the `--experimental-strip-types` flag is enabled.
1065+
* `mode` {string} The mode of type stripping. Possible values are:
1066+
* `strip-only`: Strip all types, without transforming TypeScript only features,
1067+
for more information read [type-stripping][].
1068+
* `transform`: Transforms TypeScript features, for info read [transform TypeScript features][].
1069+
* `sourceMap` {boolean} Generate source maps for the transformed code, if `mode` is `transform`,
1070+
otherwise it is not necessary.
1071+
The filename for the source map is the same as the filename of the script.
1072+
**Default:** `false`.
10471073
* `importModuleDynamically`
10481074
{Function|vm.constants.USE\_MAIN\_CONTEXT\_DEFAULT\_LOADER}
10491075
Used to specify the how the modules should be loaded during the evaluation of
@@ -1287,6 +1313,9 @@ vm.measureMemory({ mode: 'detailed', execution: 'eager' })
12871313
<!-- YAML
12881314
added: v0.3.1
12891315
changes:
1316+
- version: REPLACEME
1317+
pr-url: https://github.com/nodejs/node/pull/REPLACEME
1318+
description: Added support for experimental strip types.
12901319
- version:
12911320
- v21.7.0
12921321
- v20.12.0
@@ -1335,6 +1364,16 @@ changes:
13351364
experimental modules API. We do not recommend using it in a production
13361365
environment. For detailed information, see
13371366
[Support of dynamic `import()` in compilation APIs][].
1367+
* `transform` {Object} An object containing options for type stripping.
1368+
Only available when the `--experimental-strip-types` flag is enabled.
1369+
* `mode` {string} The mode of type stripping. Possible values are:
1370+
* `strip-only`: Strip all types, without transforming TypeScript only features,
1371+
for more information read [type-stripping][].
1372+
* `transform`: Transforms TypeScript features, for info read [transform TypeScript features][].
1373+
* `sourceMap` {boolean} Generate source maps for the transformed code, if `mode` is `transform`,
1374+
otherwise it is not necessary.
1375+
The filename for the source map is the same as the filename of the script.
1376+
**Default:** `false`.
13381377
13391378
The `vm.runInContext()` method compiles `code`, runs it within the context of
13401379
the `contextifiedObject`, then returns the result. Running code does not have
@@ -1364,6 +1403,9 @@ console.log(contextObject);
13641403
<!-- YAML
13651404
added: v0.3.1
13661405
changes:
1406+
- version: REPLACEME
1407+
pr-url: https://github.com/nodejs/node/pull/REPLACEME
1408+
description: Added support for experimental strip types.
13671409
- version:
13681410
- v22.8.0
13691411
- v20.18.0
@@ -1443,6 +1485,16 @@ changes:
14431485
scheduled through `Promise`s and `async function`s) will be run immediately
14441486
after the script has run. They are included in the `timeout` and
14451487
`breakOnSigint` scopes in that case.
1488+
* `transform` {Object} An object containing options for type stripping.
1489+
Only available when the `--experimental-strip-types` flag is enabled.
1490+
* `mode` {string} The mode of type stripping. Possible values are:
1491+
* `strip-only`: Strip all types, without transforming TypeScript only features,
1492+
for more information read [type-stripping][].
1493+
* `transform`: Transforms TypeScript features, for info read [transform TypeScript features][].
1494+
* `sourceMap` {boolean} Generate source maps for the transformed code, if `mode` is `transform`,
1495+
otherwise it is not necessary.
1496+
The filename for the source map is the same as the filename of the script.
1497+
**Default:** `false`.
14461498
* Returns: {any} the result of the very last statement executed in the script.
14471499

14481500
This method is a shortcut to
@@ -1486,6 +1538,9 @@ const frozenContext = vm.runInNewContext('Object.freeze(globalThis); globalThis;
14861538
<!-- YAML
14871539
added: v0.3.1
14881540
changes:
1541+
- version: REPLACEME
1542+
pr-url: https://github.com/nodejs/node/pull/REPLACEME
1543+
description: Added support for experimental strip types.
14891544
- version:
14901545
- v21.7.0
14911546
- v20.12.0
@@ -1532,6 +1587,16 @@ changes:
15321587
experimental modules API. We do not recommend using it in a production
15331588
environment. For detailed information, see
15341589
[Support of dynamic `import()` in compilation APIs][].
1590+
* `transform` {Object} An object containing options for type stripping.
1591+
Only available when the `--experimental-strip-types` flag is enabled.
1592+
* `mode` {string} The mode of type stripping. Possible values are:
1593+
* `strip-only`: Strip all types, without transforming TypeScript only features,
1594+
for more information read [type-stripping][].
1595+
* `transform`: Transforms TypeScript features, for info read [transform TypeScript features][].
1596+
* `sourceMap` {boolean} Generate source maps for the transformed code, if `mode` is `transform`,
1597+
otherwise it is not necessary.
1598+
The filename for the source map is the same as the filename of the script.
1599+
**Default:** `false`.
15351600
* Returns: {any} the result of the very last statement executed in the script.
15361601

15371602
`vm.runInThisContext()` compiles `code`, runs it within the context of the
@@ -1982,3 +2047,5 @@ const { Script, SyntheticModule } = require('node:vm');
19822047
[global object]: https://es5.github.io/#x15.1
19832048
[indirect `eval()` call]: https://es5.github.io/#x10.4.2
19842049
[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin
2050+
[transform TypeScript features]: typescript.md#typescript-features
2051+
[type-stripping]: typescript.md#type-stripping

lib/internal/modules/helpers.js

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -324,35 +324,42 @@ let typeScriptParser;
324324
*/
325325
let typeScriptParsingMode;
326326
/**
327-
* Whether source maps are enabled for TypeScript parsing.
328-
* @type {boolean}
327+
*
328+
* @returns {string} The TypeScript parsing mode
329329
*/
330-
let sourceMapEnabled;
330+
function getTypeScriptParsingMode() {
331+
if (typeScriptParsingMode) {
332+
return typeScriptParsingMode;
333+
}
334+
typeScriptParsingMode = getOptionValue('--experimental-transform-types') ? 'transform' : 'strip-only';
335+
return typeScriptParsingMode;
336+
}
331337

332338
/**
333339
* Load the TypeScript parser.
334-
* @param {Function} parser - A function that takes a string of TypeScript code
335340
* and returns an object with a `code` property.
336341
* @returns {Function} The TypeScript parser function.
337342
*/
338-
function loadTypeScriptParser(parser) {
343+
function loadTypeScriptParser() {
339344
if (typeScriptParser) {
340345
return typeScriptParser;
341346
}
342-
343-
if (parser) {
344-
typeScriptParser = parser;
345-
} else {
346-
const amaro = require('internal/deps/amaro/dist/index');
347-
// Default option for Amaro is to perform Type Stripping only.
348-
typeScriptParsingMode = getOptionValue('--experimental-transform-types') ? 'transform' : 'strip-only';
349-
sourceMapEnabled = getOptionValue('--enable-source-maps');
350-
// Curry the transformSync function with the default options.
351-
typeScriptParser = amaro.transformSync;
352-
}
347+
const amaro = require('internal/deps/amaro/dist/index');
348+
typeScriptParser = amaro.transformSync;
353349
return typeScriptParser;
354350
}
355351

352+
/**
353+
*
354+
* @param {string} source the source code
355+
* @param {object} options the options to pass to the parser
356+
* @returns {TransformOutput} an object with a `code` property.
357+
*/
358+
function parseTypeScript(source, options) {
359+
const parse = loadTypeScriptParser();
360+
return parse(source, options);
361+
}
362+
356363
/**
357364
* @typedef {object} TransformOutput
358365
* @property {string} code The compiled code.
@@ -365,14 +372,13 @@ function loadTypeScriptParser(parser) {
365372
*/
366373
function stripTypeScriptTypes(source, filename) {
367374
assert(typeof source === 'string');
368-
const parse = loadTypeScriptParser();
369375
const options = {
370376
__proto__: null,
371-
mode: typeScriptParsingMode,
372-
sourceMap: sourceMapEnabled,
377+
mode: getTypeScriptParsingMode(),
378+
sourceMap: getOptionValue('--enable-source-maps'),
373379
filename,
374380
};
375-
const { code, map } = parse(source, options);
381+
const { code, map } = parseTypeScript(source, options);
376382
if (map) {
377383
// TODO(@marco-ippolito) When Buffer.transcode supports utf8 to
378384
// base64 transformation, we should change this line.
@@ -488,6 +494,7 @@ module.exports = {
488494
loadBuiltinModule,
489495
makeRequireFunction,
490496
normalizeReferrerURL,
497+
parseTypeScript,
491498
stripTypeScriptTypes,
492499
stringify,
493500
stripBOM,

lib/vm.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const {
3838
const {
3939
ERR_CONTEXT_NOT_INITIALIZED,
4040
ERR_INVALID_ARG_TYPE,
41+
ERR_INVALID_ARG_VALUE,
4142
} = require('internal/errors').codes;
4243
const {
4344
validateArray,
@@ -67,6 +68,9 @@ const {
6768
vm_dynamic_import_main_context_default,
6869
vm_context_no_contextify,
6970
} = internalBinding('symbols');
71+
const { getOptionValue } = require('internal/options');
72+
const { parseTypeScript } = require('internal/modules/helpers');
73+
const { Buffer } = require('buffer');
7074
const kParsingContext = Symbol('script parsing context');
7175

7276
/**
@@ -98,6 +102,7 @@ class Script extends ContextifyScript {
98102
produceCachedData = false,
99103
importModuleDynamically,
100104
[kParsingContext]: parsingContext,
105+
transform,
101106
} = options;
102107

103108
validateString(filename, 'options.filename');
@@ -107,6 +112,9 @@ class Script extends ContextifyScript {
107112
validateBuffer(cachedData, 'options.cachedData');
108113
}
109114
validateBoolean(produceCachedData, 'options.produceCachedData');
115+
if (getOptionValue('--experimental-strip-types') && transform) {
116+
code = stripTypeScriptTypes(code, options);
117+
}
110118

111119
const hostDefinedOptionId =
112120
getHostDefinedOptionId(importModuleDynamically, filename);
@@ -331,6 +339,7 @@ function compileFunction(code, params, options = kEmptyObject) {
331339
parsingContext = undefined,
332340
contextExtensions = [],
333341
importModuleDynamically,
342+
transform,
334343
} = options;
335344

336345
validateString(filename, 'options.filename');
@@ -361,6 +370,9 @@ function compileFunction(code, params, options = kEmptyObject) {
361370
const hostDefinedOptionId =
362371
getHostDefinedOptionId(importModuleDynamically, filename);
363372

373+
if (getOptionValue('--experimental-strip-types') && transform) {
374+
code = stripTypeScriptTypes(code, options);
375+
}
364376
return internalCompileFunction(
365377
code, filename, lineOffset, columnOffset,
366378
cachedData, produceCachedData, parsingContext, contextExtensions,
@@ -400,6 +412,38 @@ const vmConstants = {
400412

401413
ObjectFreeze(vmConstants);
402414

415+
function stripTypeScriptTypes(source, options) {
416+
const { transform, filename } = options;
417+
validateObject(transform, 'options.transform');
418+
const {
419+
mode,
420+
sourceMap = false,
421+
} = transform;
422+
validateString(mode, 'options.transform.mode');
423+
if (mode !== 'strip-only' && mode !== 'transform') {
424+
throw new ERR_INVALID_ARG_VALUE('options.transform.mode', mode, 'must be either ' +
425+
'"strip-only" or "transform"');
426+
}
427+
428+
validateBoolean(sourceMap, 'options.transform.sourceMap');
429+
430+
const transformOptions = {
431+
__proto__: null,
432+
mode,
433+
sourceMap,
434+
filename,
435+
};
436+
437+
const { code, map } = parseTypeScript(source, transformOptions);
438+
if (map) {
439+
// TODO(@marco-ippolito) When Buffer.transcode supports utf8 to
440+
// base64 transformation, we should change this line.
441+
const base64SourceMap = Buffer.from(map).toString('base64');
442+
return `${code}\n\n//# sourceMappingURL=data:application/json;base64,${base64SourceMap}`;
443+
}
444+
return code;
445+
}
446+
403447
module.exports = {
404448
Script,
405449
createContext,

0 commit comments

Comments
 (0)