Skip to content

Commit 6575b76

Browse files
marco-ippolitoruyadorno
authored andcommitted
module: add module.stripTypeScriptTypes
PR-URL: #55282 Backport-PR-URL: #56208Fixes: #54300 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Chemi Atlow <[email protected]> Reviewed-By: Paolo Insogna <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Richard Lau <[email protected]>
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+
constcode='const a: number = 1;';
307+
conststrippedCode=stripTypeScriptTypes(code);
308+
console.log(strippedCode);
309+
// Prints: const a = 1;
310+
```
311+
312+
```cjs
313+
const{stripTypeScriptTypes } =require('node:module');
314+
constcode='const a: number = 1;';
315+
conststrippedCode=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+
constcode='const a: number = 1;';
325+
conststrippedCode=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+
constcode='const a: number = 1;';
333+
conststrippedCode=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+
constcode=`
343+
namespace MathUtil{
344+
export const add = (a: number, b: number) => a + b;
345+
}`;
346+
conststrippedCode=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+
constcode=`
359+
namespace MathUtil{
360+
export const add = (a: number, b: number) => a + b;
361+
}`;
362+
conststrippedCode=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
constcode=getOptionValue('--eval');
2626
constsource=getOptionValue('--experimental-strip-types') ?
27-
stripTypeScriptTypes(code) :
27+
stripTypeScriptModuleTypes(code) :
2828
code;
2929

3030
constprint=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
constpackageJsonReader=require('internal/modules/package_json_reader');
159159
const{ getOptionValue, getEmbedderOptions }=require('internal/options');
160160
constshouldReportRequiredModules=getLazy(()=>process.env.WATCH_REPORT_DEPENDENCIES);
@@ -1347,7 +1347,7 @@ let emittedRequireModuleWarning = false;
13471347
functionloadESMFromCJS(mod,filename){
13481348
letsource=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
constcascadedLoader=require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
13531353
constisMain=mod[kIsMainSymbol];
@@ -1584,7 +1584,7 @@ function getMaybeCachedSource(mod, filename){
15841584

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

@@ -1596,7 +1596,7 @@ function loadCTS(module, filename){
15961596
functionloadTS(module,filename){
15971597
// If already analyzed the source, then it will be cached.
15981598
constsource=getMaybeCachedSource(module,filename);
1599-
constcontent=stripTypeScriptTypes(source,filename);
1599+
constcontent=stripTypeScriptModuleTypes(source,filename);
16001600
letformat;
16011601
constpkg=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
letparentSource;
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){returnnull;}
167-
const{ stripTypeScriptTypes, stringify }=require('internal/modules/helpers');
167+
const{ stringify }=require('internal/modules/helpers');
168+
const{ stripTypeScriptModuleTypes }=require('internal/modules/typescript');
168169
conststringifiedSource=stringify(source);
169-
constparsedSource=stripTypeScriptTypes(stringifiedSource,fileURLToPath(url));
170+
constparsedSource=stripTypeScriptModuleTypes(stringifiedSource,fileURLToPath(url));
170171
constdetectedFormat=detectModuleFormat(parsedSource,url);
171172
constformat=`${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-
constcode=stripTypeScriptTypes(stringify(source),url);
247+
constcode=stripTypeScriptModuleTypes(stringify(source),url);
248248
returncreateCJSModuleWrap(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-
constcode=stripTypeScriptTypes(stringify(source),url);
462+
constcode=stripTypeScriptModuleTypes(stringify(source),url);
463463
debug(`Translating TypeScript ${url}`);
464464
returnFunctionPrototypeCall(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-
constcode=stripTypeScriptTypes(stringify(source),url);
471+
constcode=stripTypeScriptModuleTypes(stringify(source),url);
472472
debug(`Translating TypeScript ${url}`);
473473
returnFunctionPrototypeCall(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
constassert=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
constlazyTmpdir=getLazy(()=>require('os').tmpdir());
@@ -314,75 +311,6 @@ function getBuiltinModule(id){
314311
returnnormalizedId ? require(normalizedId) : undefined;
315312
}
316313

317-
/**
318-
* The TypeScript parsing mode, either 'strip-only' or 'transform'.
319-
* @type{string}
320-
*/
321-
constgetTypeScriptParsingMode=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-
constloadTypeScriptParser=getLazy(()=>{
331-
assertTypeScript();
332-
constamaro=require('internal/deps/amaro/dist/index');
333-
returnamaro.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-
functionparseTypeScript(source,options){
343-
constparse=loadTypeScriptParser();
344-
try{
345-
returnparse(source,options);
346-
}catch(error){
347-
thrownewERR_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-
functionstripTypeScriptTypes(source,filename){
362-
if(isUnderNodeModules(filename)){
363-
thrownewERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename);
364-
}
365-
assert(typeofsource==='string');
366-
constoptions={
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-
constbase64SourceMap=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)