Skip to content

Commit 6575b76

Browse files
marco-ippolitoruyadorno
authored andcommitted
module: add module.stripTypeScriptTypes
PR-URL: #55282 Backport-PR-URL: #56208 Fixes: #54300 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Richard Lau <rlau@redhat.com>
1 parent 0794861 commit 6575b76

File tree

10 files changed

+358
-89
lines changed

10 files changed

+358
-89
lines changed

doc/api/module.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,105 @@ changes:
270270
Register a module that exports [hooks][] that customize Node.js module
271271
resolution and loading behavior. See [Customization hooks][].
272272
273+
## `module.stripTypeScriptTypes(code[, options])`
274+
275+
<!-- YAML
276+
added: REPLACEME
277+
-->
278+
279+
> Stability: 1.0 - Early development
280+
281+
* `code` {string} The code to strip type annotations from.
282+
* `options` {Object}
283+
* `mode` {string} **Default:** `'strip'`. Possible values are:
284+
* `'strip'` Only strip type annotations without performing the transformation of TypeScript features.
285+
* `'transform'` Strip type annotations and transform TypeScript features to JavaScript.
286+
* `sourceMap` {boolean} **Default:** `false`. Only when `mode` is `'transform'`, if `true`, a source map
287+
will be generated for the transformed code.
288+
* `sourceUrl` {string} Specifies the source url used in the source map.
289+
* Returns: {string} The code with type annotations stripped.
290+
`module.stripTypeScriptTypes()` removes type annotations from TypeScript code. It
291+
can be used to strip type annotations from TypeScript code before running it
292+
with `vm.runInContext()` or `vm.compileFunction()`.
293+
By default, it will throw an error if the code contains TypeScript features
294+
that require transformation such as `Enums`,
295+
see [type-stripping][] for more information.
296+
When mode is `'transform'`, it also transforms TypeScript features to JavaScript,
297+
see [transform TypeScript features][] for more information.
298+
When mode is `'strip'`, source maps are not generated, because locations are preserved.
299+
If `sourceMap` is provided, when mode is `'strip'`, an error will be thrown.
300+
301+
_WARNING_: The output of this function should not be considered stable across Node.js versions,
302+
due to changes in the TypeScript parser.
303+
304+
```mjs
305+
import { stripTypeScriptTypes } from 'node:module';
306+
const code = 'const a: number = 1;';
307+
const strippedCode = stripTypeScriptTypes(code);
308+
console.log(strippedCode);
309+
// Prints: const a = 1;
310+
```
311+
312+
```cjs
313+
const { stripTypeScriptTypes } = require('node:module');
314+
const code = 'const a: number = 1;';
315+
const strippedCode = stripTypeScriptTypes(code);
316+
console.log(strippedCode);
317+
// Prints: const a = 1;
318+
```
319+
320+
If `sourceUrl` is provided, it will be used appended as a comment at the end of the output:
321+
322+
```mjs
323+
import { stripTypeScriptTypes } from 'node:module';
324+
const code = 'const a: number = 1;';
325+
const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' });
326+
console.log(strippedCode);
327+
// Prints: const a = 1\n\n//# sourceURL=source.ts;
328+
```
329+
330+
```cjs
331+
const { stripTypeScriptTypes } = require('node:module');
332+
const code = 'const a: number = 1;';
333+
const strippedCode = stripTypeScriptTypes(code, { mode: 'strip', sourceUrl: 'source.ts' });
334+
console.log(strippedCode);
335+
// Prints: const a = 1\n\n//# sourceURL=source.ts;
336+
```
337+
338+
When `mode` is `'transform'`, the code is transformed to JavaScript:
339+
340+
```mjs
341+
import { stripTypeScriptTypes } from 'node:module';
342+
const code = `
343+
namespace MathUtil {
344+
export const add = (a: number, b: number) => a + b;
345+
}`;
346+
const strippedCode = stripTypeScriptTypes(code, { mode: 'transform', sourceMap: true });
347+
console.log(strippedCode);
348+
// Prints:
349+
// var MathUtil;
350+
// (function(MathUtil) {
351+
// MathUtil.add = (a, b)=>a + b;
352+
// })(MathUtil || (MathUtil = {}));
353+
// # sourceMappingURL=data:application/json;base64, ...
354+
```
355+
356+
```cjs
357+
const { stripTypeScriptTypes } = require('node:module');
358+
const code = `
359+
namespace MathUtil {
360+
export const add = (a: number, b: number) => a + b;
361+
}`;
362+
const strippedCode = stripTypeScriptTypes(code, { mode: 'transform', sourceMap: true });
363+
console.log(strippedCode);
364+
// Prints:
365+
// var MathUtil;
366+
// (function(MathUtil) {
367+
// MathUtil.add = (a, b)=>a + b;
368+
// })(MathUtil || (MathUtil = {}));
369+
// # sourceMappingURL=data:application/json;base64, ...
370+
```
371+
273372
### `module.syncBuiltinESMExports()`
274373
275374
<!-- YAML
@@ -1251,3 +1350,5 @@ returned object contains the following keys:
12511350
[realm]: https://tc39.es/ecma262/#realm
12521351
[source map include directives]: https://sourcemaps.info/spec.html#h.lmz475t4mvbx
12531352
[transferrable objects]: worker_threads.md#portpostmessagevalue-transferlist
1353+
[transform TypeScript features]: typescript.md#typescript-features
1354+
[type-stripping]: typescript.md#type-stripping

lib/internal/main/eval_string.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ const {
1414
markBootstrapComplete,
1515
} = require('internal/process/pre_execution');
1616
const { evalModuleEntryPoint, evalScript } = require('internal/process/execution');
17-
const { addBuiltinLibsToObject, stripTypeScriptTypes } = require('internal/modules/helpers');
18-
17+
const { addBuiltinLibsToObject } = require('internal/modules/helpers');
18+
const { stripTypeScriptModuleTypes } = require('internal/modules/typescript');
1919
const { getOptionValue } = require('internal/options');
2020

2121
prepareMainThreadExecution();
@@ -24,7 +24,7 @@ markBootstrapComplete();
2424

2525
const code = getOptionValue('--eval');
2626
const source = getOptionValue('--experimental-strip-types') ?
27-
stripTypeScriptTypes(code) :
27+
stripTypeScriptModuleTypes(code) :
2828
code;
2929

3030
const print = getOptionValue('--print');

lib/internal/modules/cjs/loader.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ const {
153153
setHasStartedUserCJSExecution,
154154
stripBOM,
155155
toRealPath,
156-
stripTypeScriptTypes,
157156
} = require('internal/modules/helpers');
157+
const { stripTypeScriptModuleTypes } = require('internal/modules/typescript');
158158
const packageJsonReader = require('internal/modules/package_json_reader');
159159
const { getOptionValue, getEmbedderOptions } = require('internal/options');
160160
const shouldReportRequiredModules = getLazy(() => process.env.WATCH_REPORT_DEPENDENCIES);
@@ -1347,7 +1347,7 @@ let emittedRequireModuleWarning = false;
13471347
function loadESMFromCJS(mod, filename) {
13481348
let source = getMaybeCachedSource(mod, filename);
13491349
if (getOptionValue('--experimental-strip-types') && path.extname(filename) === '.mts') {
1350-
source = stripTypeScriptTypes(source, filename);
1350+
source = stripTypeScriptModuleTypes(source, filename);
13511351
}
13521352
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
13531353
const isMain = mod[kIsMainSymbol];
@@ -1584,7 +1584,7 @@ function getMaybeCachedSource(mod, filename) {
15841584

15851585
function loadCTS(module, filename) {
15861586
const source = getMaybeCachedSource(module, filename);
1587-
const code = stripTypeScriptTypes(source, filename);
1587+
const code = stripTypeScriptModuleTypes(source, filename);
15881588
module._compile(code, filename, 'commonjs');
15891589
}
15901590

@@ -1596,7 +1596,7 @@ function loadCTS(module, filename) {
15961596
function loadTS(module, filename) {
15971597
// If already analyzed the source, then it will be cached.
15981598
const source = getMaybeCachedSource(module, filename);
1599-
const content = stripTypeScriptTypes(source, filename);
1599+
const content = stripTypeScriptModuleTypes(source, filename);
16001600
let format;
16011601
const pkg = packageJsonReader.getNearestParentPackageJSON(filename);
16021602
// Function require shouldn't be used in ES modules.
@@ -1616,7 +1616,7 @@ function loadTS(module, filename) {
16161616
if (Module._cache[parentPath]) {
16171617
let parentSource;
16181618
try {
1619-
parentSource = stripTypeScriptTypes(fs.readFileSync(parentPath, 'utf8'), parentPath);
1619+
parentSource = stripTypeScriptModuleTypes(fs.readFileSync(parentPath, 'utf8'), parentPath);
16201620
} catch {
16211621
// Continue regardless of error.
16221622
}

lib/internal/modules/esm/get_format.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,10 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
164164
// Since experimental-strip-types depends on detect-module, we always return null
165165
// if source is undefined.
166166
if (!source) { return null; }
167-
const { stripTypeScriptTypes, stringify } = require('internal/modules/helpers');
167+
const { stringify } = require('internal/modules/helpers');
168+
const { stripTypeScriptModuleTypes } = require('internal/modules/typescript');
168169
const stringifiedSource = stringify(source);
169-
const parsedSource = stripTypeScriptTypes(stringifiedSource, fileURLToPath(url));
170+
const parsedSource = stripTypeScriptModuleTypes(stringifiedSource, fileURLToPath(url));
170171
const detectedFormat = detectModuleFormat(parsedSource, url);
171172
const format = `${detectedFormat}-typescript`;
172173
if (format === 'module-typescript' && foundPackageJson) {

lib/internal/modules/esm/translators.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ const {
3030
assertBufferSource,
3131
loadBuiltinModule,
3232
stringify,
33-
stripTypeScriptTypes,
3433
stripBOM,
3534
urlToFilename,
3635
} = require('internal/modules/helpers');
36+
const { stripTypeScriptModuleTypes } = require('internal/modules/typescript');
3737
const {
3838
kIsCachedByESMLoader,
3939
Module: CJSModule,
@@ -244,7 +244,7 @@ translators.set('require-commonjs', (url, source, isMain) => {
244244
translators.set('require-commonjs-typescript', (url, source, isMain) => {
245245
emitExperimentalWarning('Type Stripping');
246246
assert(cjsParse);
247-
const code = stripTypeScriptTypes(stringify(source), url);
247+
const code = stripTypeScriptModuleTypes(stringify(source), url);
248248
return createCJSModuleWrap(url, code);
249249
});
250250

@@ -459,7 +459,7 @@ translators.set('wasm', async function(url, source) {
459459
translators.set('commonjs-typescript', function(url, source) {
460460
emitExperimentalWarning('Type Stripping');
461461
assertBufferSource(source, true, 'load');
462-
const code = stripTypeScriptTypes(stringify(source), url);
462+
const code = stripTypeScriptModuleTypes(stringify(source), url);
463463
debug(`Translating TypeScript ${url}`);
464464
return FunctionPrototypeCall(translators.get('commonjs'), this, url, code, false);
465465
});
@@ -468,7 +468,7 @@ translators.set('commonjs-typescript', function(url, source) {
468468
translators.set('module-typescript', function(url, source) {
469469
emitExperimentalWarning('Type Stripping');
470470
assertBufferSource(source, true, 'load');
471-
const code = stripTypeScriptTypes(stringify(source), url);
471+
const code = stripTypeScriptModuleTypes(stringify(source), url);
472472
debug(`Translating TypeScript ${url}`);
473473
return FunctionPrototypeCall(translators.get('module'), this, url, code, false);
474474
});

lib/internal/modules/helpers.js

Lines changed: 1 addition & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ const {
1515
const {
1616
ERR_INVALID_ARG_TYPE,
1717
ERR_INVALID_RETURN_PROPERTY_VALUE,
18-
ERR_INVALID_TYPESCRIPT_SYNTAX,
19-
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING,
2018
} = require('internal/errors').codes;
2119
const { BuiltinModule } = require('internal/bootstrap/realm');
2220

@@ -27,9 +25,8 @@ const path = require('path');
2725
const { pathToFileURL, fileURLToPath } = require('internal/url');
2826
const assert = require('internal/assert');
2927

30-
const { Buffer } = require('buffer');
3128
const { getOptionValue } = require('internal/options');
32-
const { assertTypeScript, setOwnProperty, getLazy, isUnderNodeModules } = require('internal/util');
29+
const { setOwnProperty, getLazy } = require('internal/util');
3330
const { inspect } = require('internal/util/inspect');
3431

3532
const lazyTmpdir = getLazy(() => require('os').tmpdir());
@@ -314,75 +311,6 @@ function getBuiltinModule(id) {
314311
return normalizedId ? require(normalizedId) : undefined;
315312
}
316313

317-
/**
318-
* The TypeScript parsing mode, either 'strip-only' or 'transform'.
319-
* @type {string}
320-
*/
321-
const getTypeScriptParsingMode = getLazy(() =>
322-
(getOptionValue('--experimental-transform-types') ? 'transform' : 'strip-only'),
323-
);
324-
325-
/**
326-
* Load the TypeScript parser.
327-
* and returns an object with a `code` property.
328-
* @returns {Function} The TypeScript parser function.
329-
*/
330-
const loadTypeScriptParser = getLazy(() => {
331-
assertTypeScript();
332-
const amaro = require('internal/deps/amaro/dist/index');
333-
return amaro.transformSync;
334-
});
335-
336-
/**
337-
*
338-
* @param {string} source the source code
339-
* @param {object} options the options to pass to the parser
340-
* @returns {TransformOutput} an object with a `code` property.
341-
*/
342-
function parseTypeScript(source, options) {
343-
const parse = loadTypeScriptParser();
344-
try {
345-
return parse(source, options);
346-
} catch (error) {
347-
throw new ERR_INVALID_TYPESCRIPT_SYNTAX(error);
348-
}
349-
}
350-
351-
/**
352-
* @typedef {object} TransformOutput
353-
* @property {string} code The compiled code.
354-
* @property {string} [map] The source maps (optional).
355-
*
356-
* Performs type-stripping to TypeScript source code.
357-
* @param {string} source TypeScript code to parse.
358-
* @param {string} filename The filename of the source code.
359-
* @returns {TransformOutput} The stripped TypeScript code.
360-
*/
361-
function stripTypeScriptTypes(source, filename) {
362-
if (isUnderNodeModules(filename)) {
363-
throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename);
364-
}
365-
assert(typeof source === 'string');
366-
const options = {
367-
__proto__: null,
368-
mode: getTypeScriptParsingMode(),
369-
sourceMap: getOptionValue('--enable-source-maps'),
370-
filename,
371-
};
372-
const { code, map } = parseTypeScript(source, options);
373-
if (map) {
374-
// TODO(@marco-ippolito) When Buffer.transcode supports utf8 to
375-
// base64 transformation, we should change this line.
376-
const base64SourceMap = Buffer.from(map).toString('base64');
377-
return `${code}\n\n//# sourceMappingURL=data:application/json;base64,${base64SourceMap}`;
378-
}
379-
// Source map is not necessary in strip-only mode. However, to map the source
380-
// file in debuggers to the original TypeScript source, add a sourceURL magic
381-
// comment to hint that it is a generated source.
382-
return `${code}\n\n//# sourceURL=${filename}`;
383-
}
384-
385-
386314
/**
387315
* Enable on-disk compiled cache for all user modules being complied in the current Node.js instance
388316
* after this method is called.
@@ -485,7 +413,6 @@ module.exports = {
485413
loadBuiltinModule,
486414
makeRequireFunction,
487415
normalizeReferrerURL,
488-
stripTypeScriptTypes,
489416
stringify,
490417
stripBOM,
491418
toRealPath,

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy