Skip to content

Commit

Permalink
vm: add support for --experimental-strip-types
Browse files Browse the repository at this point in the history
  • Loading branch information
marco-ippolito committed Oct 5, 2024
1 parent 20d8b85 commit ecc1d82
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 20 deletions.
67 changes: 67 additions & 0 deletions doc/api/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ executed in specific contexts.
<!-- YAML
added: v0.3.1
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/55282
description: Added support for experimental strip types.
- version:
- v21.7.0
- v20.12.0
Expand Down Expand Up @@ -107,6 +110,16 @@ changes:
experimental modules API. We do not recommend using it in a production
environment. For detailed information, see
[Support of dynamic `import()` in compilation APIs][].
* `transform` {Object} An object containing options for type stripping.
Only available when the `--experimental-strip-types` flag is enabled.
* `mode` {string} The mode of type stripping. Possible values are:
* `strip-only`: Strip all types, without transforming TypeScript only features,
for more information read [type-stripping][].
* `transform`: Transforms TypeScript features, for info read [transform TypeScript features][].
* `sourceMap` {boolean} Generate source maps for the transformed code, if `mode` is `transform`,
otherwise it is not necessary.
The filename for the source map is the same as the filename of the script.
**Default:** `false`.

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

Expand Down Expand Up @@ -990,6 +1003,9 @@ const vm = require('node:vm');
<!-- YAML
added: v10.10.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/55282
description: Added support for experimental strip types.
- version:
- v21.7.0
- v20.12.0
Expand Down Expand Up @@ -1044,6 +1060,16 @@ changes:
* `contextExtensions` {Object\[]} An array containing a collection of context
extensions (objects wrapping the current scope) to be applied while
compiling. **Default:** `[]`.
* `transform` {Object} An object containing options for type stripping.
Only available when the `--experimental-strip-types` flag is enabled.
* `mode` {string} The mode of type stripping. Possible values are:
* `strip-only`: Strip all types, without transforming TypeScript only features,
for more information read [type-stripping][].
* `transform`: Transforms TypeScript features, for info read [transform TypeScript features][].
* `sourceMap` {boolean} Generate source maps for the transformed code, if `mode` is `transform`,
otherwise it is not necessary.
The filename for the source map is the same as the filename of the script.
**Default:** `false`.
* `importModuleDynamically`
{Function|vm.constants.USE\_MAIN\_CONTEXT\_DEFAULT\_LOADER}
Used to specify the how the modules should be loaded during the evaluation of
Expand Down Expand Up @@ -1287,6 +1313,9 @@ vm.measureMemory({ mode: 'detailed', execution: 'eager' })
<!-- YAML
added: v0.3.1
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/55282
description: Added support for experimental strip types.
- version:
- v21.7.0
- v20.12.0
Expand Down Expand Up @@ -1335,6 +1364,16 @@ changes:
experimental modules API. We do not recommend using it in a production
environment. For detailed information, see
[Support of dynamic `import()` in compilation APIs][].
* `transform` {Object} An object containing options for type stripping.
Only available when the `--experimental-strip-types` flag is enabled.
* `mode` {string} The mode of type stripping. Possible values are:
* `strip-only`: Strip all types, without transforming TypeScript only features,
for more information read [type-stripping][].
* `transform`: Transforms TypeScript features, for info read [transform TypeScript features][].
* `sourceMap` {boolean} Generate source maps for the transformed code, if `mode` is `transform`,
otherwise it is not necessary.
The filename for the source map is the same as the filename of the script.
**Default:** `false`.
The `vm.runInContext()` method compiles `code`, runs it within the context of
the `contextifiedObject`, then returns the result. Running code does not have
Expand Down Expand Up @@ -1364,6 +1403,9 @@ console.log(contextObject);
<!-- YAML
added: v0.3.1
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/55282
description: Added support for experimental strip types.
- version:
- v22.8.0
- v20.18.0
Expand Down Expand Up @@ -1443,6 +1485,16 @@ changes:
scheduled through `Promise`s and `async function`s) will be run immediately
after the script has run. They are included in the `timeout` and
`breakOnSigint` scopes in that case.
* `transform` {Object} An object containing options for type stripping.
Only available when the `--experimental-strip-types` flag is enabled.
* `mode` {string} The mode of type stripping. Possible values are:
* `strip-only`: Strip all types, without transforming TypeScript only features,
for more information read [type-stripping][].
* `transform`: Transforms TypeScript features, for info read [transform TypeScript features][].
* `sourceMap` {boolean} Generate source maps for the transformed code, if `mode` is `transform`,
otherwise it is not necessary.
The filename for the source map is the same as the filename of the script.
**Default:** `false`.
* Returns: {any} the result of the very last statement executed in the script.

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

`vm.runInThisContext()` compiles `code`, runs it within the context of the
Expand Down Expand Up @@ -1982,3 +2047,5 @@ const { Script, SyntheticModule } = require('node:vm');
[global object]: https://es5.github.io/#x15.1
[indirect `eval()` call]: https://es5.github.io/#x10.4.2
[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin
[transform TypeScript features]: typescript.md#typescript-features
[type-stripping]: typescript.md#type-stripping
47 changes: 27 additions & 20 deletions lib/internal/modules/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,35 +324,42 @@ let typeScriptParser;
*/
let typeScriptParsingMode;
/**
* Whether source maps are enabled for TypeScript parsing.
* @type {boolean}
*
* @returns {string} The TypeScript parsing mode
*/
let sourceMapEnabled;
function getTypeScriptParsingMode() {
if (typeScriptParsingMode) {
return typeScriptParsingMode;
}
typeScriptParsingMode = getOptionValue('--experimental-transform-types') ? 'transform' : 'strip-only';
return typeScriptParsingMode;
}

/**
* Load the TypeScript parser.
* @param {Function} parser - A function that takes a string of TypeScript code
* and returns an object with a `code` property.
* @returns {Function} The TypeScript parser function.
*/
function loadTypeScriptParser(parser) {
function loadTypeScriptParser() {
if (typeScriptParser) {
return typeScriptParser;
}

if (parser) {
typeScriptParser = parser;
} else {
const amaro = require('internal/deps/amaro/dist/index');
// Default option for Amaro is to perform Type Stripping only.
typeScriptParsingMode = getOptionValue('--experimental-transform-types') ? 'transform' : 'strip-only';
sourceMapEnabled = getOptionValue('--enable-source-maps');
// Curry the transformSync function with the default options.
typeScriptParser = amaro.transformSync;
}
const amaro = require('internal/deps/amaro/dist/index');
typeScriptParser = amaro.transformSync;
return typeScriptParser;
}

/**
*
* @param {string} source the source code
* @param {object} options the options to pass to the parser
* @returns {TransformOutput} an object with a `code` property.
*/
function parseTypeScript(source, options) {
const parse = loadTypeScriptParser();
return parse(source, options);
}

/**
* @typedef {object} TransformOutput
* @property {string} code The compiled code.
Expand All @@ -365,14 +372,13 @@ function loadTypeScriptParser(parser) {
*/
function stripTypeScriptTypes(source, filename) {
assert(typeof source === 'string');
const parse = loadTypeScriptParser();
const options = {
__proto__: null,
mode: typeScriptParsingMode,
sourceMap: sourceMapEnabled,
mode: getTypeScriptParsingMode(),
sourceMap: getOptionValue('--enable-source-maps'),
filename,
};
const { code, map } = parse(source, options);
const { code, map } = parseTypeScript(source, options);
if (map) {
// TODO(@marco-ippolito) When Buffer.transcode supports utf8 to
// base64 transformation, we should change this line.
Expand Down Expand Up @@ -488,6 +494,7 @@ module.exports = {
loadBuiltinModule,
makeRequireFunction,
normalizeReferrerURL,
parseTypeScript,
stripTypeScriptTypes,
stringify,
stripBOM,
Expand Down
44 changes: 44 additions & 0 deletions lib/vm.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const {
const {
ERR_CONTEXT_NOT_INITIALIZED,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
} = require('internal/errors').codes;
const {
validateArray,
Expand Down Expand Up @@ -67,6 +68,9 @@ const {
vm_dynamic_import_main_context_default,
vm_context_no_contextify,
} = internalBinding('symbols');
const { getOptionValue } = require('internal/options');
const { parseTypeScript } = require('internal/modules/helpers');
const { Buffer } = require('buffer');
const kParsingContext = Symbol('script parsing context');

/**
Expand Down Expand Up @@ -98,6 +102,7 @@ class Script extends ContextifyScript {
produceCachedData = false,
importModuleDynamically,
[kParsingContext]: parsingContext,
transform,
} = options;

validateString(filename, 'options.filename');
Expand All @@ -107,6 +112,9 @@ class Script extends ContextifyScript {
validateBuffer(cachedData, 'options.cachedData');
}
validateBoolean(produceCachedData, 'options.produceCachedData');
if (getOptionValue('--experimental-strip-types') && transform) {
code = stripTypeScriptTypes(code, options);
}

const hostDefinedOptionId =
getHostDefinedOptionId(importModuleDynamically, filename);
Expand Down Expand Up @@ -331,6 +339,7 @@ function compileFunction(code, params, options = kEmptyObject) {
parsingContext = undefined,
contextExtensions = [],
importModuleDynamically,
transform,
} = options;

validateString(filename, 'options.filename');
Expand Down Expand Up @@ -361,6 +370,9 @@ function compileFunction(code, params, options = kEmptyObject) {
const hostDefinedOptionId =
getHostDefinedOptionId(importModuleDynamically, filename);

if (getOptionValue('--experimental-strip-types') && transform) {
code = stripTypeScriptTypes(code, options);
}
return internalCompileFunction(
code, filename, lineOffset, columnOffset,
cachedData, produceCachedData, parsingContext, contextExtensions,
Expand Down Expand Up @@ -400,6 +412,38 @@ const vmConstants = {

ObjectFreeze(vmConstants);

function stripTypeScriptTypes(source, options) {
const { transform, filename } = options;
validateObject(transform, 'options.transform');
const {
mode,
sourceMap = false,
} = transform;
validateString(mode, 'options.transform.mode');
if (mode !== 'strip-only' && mode !== 'transform') {
throw new ERR_INVALID_ARG_VALUE('options.transform.mode', mode, 'must be either ' +
'"strip-only" or "transform"');
}

validateBoolean(sourceMap, 'options.transform.sourceMap');

const transformOptions = {
__proto__: null,
mode,
sourceMap,
filename,
};

const { code, map } = parseTypeScript(source, transformOptions);
if (map) {
// TODO(@marco-ippolito) When Buffer.transcode supports utf8 to
// base64 transformation, we should change this line.
const base64SourceMap = Buffer.from(map).toString('base64');
return `${code}\n\n//# sourceMappingURL=data:application/json;base64,${base64SourceMap}`;
}
return code;
}

module.exports = {
Script,
createContext,
Expand Down
Loading

0 comments on commit ecc1d82

Please sign in to comment.