Skip to content

Commit 69dd1e1

Browse files
marco-ippolitoRafaelGSS
authored andcommitted
module: add module.stripTypeScriptTypes
PR-URL: #55282Fixes: #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 0ea74f3 commit 69dd1e1

10 files changed

Lines changed: 358 additions & 88 deletions

File tree

‎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
[transferable 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
@@ -151,8 +151,8 @@ const {
151151
setHasStartedUserCJSExecution,
152152
stripBOM,
153153
toRealPath,
154-
stripTypeScriptTypes,
155154
}=require('internal/modules/helpers');
155+
const{ stripTypeScriptModuleTypes }=require('internal/modules/typescript');
156156
constpackageJsonReader=require('internal/modules/package_json_reader');
157157
const{ getOptionValue, getEmbedderOptions }=require('internal/options');
158158
constshouldReportRequiredModules=getLazy(()=>process.env.WATCH_REPORT_DEPENDENCIES);
@@ -1357,7 +1357,7 @@ let hasPausedEntry = false;
13571357
functionloadESMFromCJS(mod,filename){
13581358
letsource=getMaybeCachedSource(mod,filename);
13591359
if(getOptionValue('--experimental-strip-types')&&path.extname(filename)==='.mts'){
1360-
source=stripTypeScriptTypes(source,filename);
1360+
source=stripTypeScriptModuleTypes(source,filename);
13611361
}
13621362
constcascadedLoader=require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
13631363
constisMain=mod[kIsMainSymbol];
@@ -1599,7 +1599,7 @@ function getMaybeCachedSource(mod, filename) {
15991599

16001600
functionloadCTS(module,filename){
16011601
constsource=getMaybeCachedSource(module,filename);
1602-
constcode=stripTypeScriptTypes(source,filename);
1602+
constcode=stripTypeScriptModuleTypes(source,filename);
16031603
module._compile(code,filename,'commonjs');
16041604
}
16051605

@@ -1611,7 +1611,7 @@ function loadCTS(module, filename) {
16111611
functionloadTS(module,filename){
16121612
// If already analyzed the source, then it will be cached.
16131613
constsource=getMaybeCachedSource(module,filename);
1614-
constcontent=stripTypeScriptTypes(source,filename);
1614+
constcontent=stripTypeScriptModuleTypes(source,filename);
16151615
letformat;
16161616
constpkg=packageJsonReader.getNearestParentPackageJSON(filename);
16171617
// Function require shouldn't be used in ES modules.
@@ -1631,7 +1631,7 @@ function loadTS(module, filename) {
16311631
if(Module._cache[parentPath]){
16321632
letparentSource;
16331633
try{
1634-
parentSource=stripTypeScriptTypes(fs.readFileSync(parentPath,'utf8'),parentPath);
1634+
parentSource=stripTypeScriptModuleTypes(fs.readFileSync(parentPath,'utf8'),parentPath);
16351635
}catch{
16361636
// Continue regardless of error.
16371637
}

‎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
@@ -31,10 +31,10 @@ const {
3131
assertBufferSource,
3232
loadBuiltinModule,
3333
stringify,
34-
stripTypeScriptTypes,
3534
stripBOM,
3635
urlToFilename,
3736
}=require('internal/modules/helpers');
37+
const{ stripTypeScriptModuleTypes }=require('internal/modules/typescript');
3838
const{
3939
kIsCachedByESMLoader,
4040
Module: CJSModule,
@@ -248,7 +248,7 @@ translators.set('require-commonjs', (url, source, isMain) => {
248248
translators.set('require-commonjs-typescript',(url,source,isMain)=>{
249249
emitExperimentalWarning('Type Stripping');
250250
assert(cjsParse);
251-
constcode=stripTypeScriptTypes(stringify(source),url);
251+
constcode=stripTypeScriptModuleTypes(stringify(source),url);
252252
returncreateCJSModuleWrap(url,code);
253253
});
254254

@@ -463,7 +463,7 @@ translators.set('wasm', async function(url, source) {
463463
translators.set('commonjs-typescript',function(url,source){
464464
emitExperimentalWarning('Type Stripping');
465465
assertBufferSource(source,true,'load');
466-
constcode=stripTypeScriptTypes(stringify(source),url);
466+
constcode=stripTypeScriptModuleTypes(stringify(source),url);
467467
debug(`Translating TypeScript ${url}`);
468468
returnFunctionPrototypeCall(translators.get('commonjs'),this,url,code,false);
469469
});
@@ -472,7 +472,7 @@ translators.set('commonjs-typescript', function(url, source) {
472472
translators.set('module-typescript',function(url,source){
473473
emitExperimentalWarning('Type Stripping');
474474
assertBufferSource(source,true,'load');
475-
constcode=stripTypeScriptTypes(stringify(source),url);
475+
constcode=stripTypeScriptModuleTypes(stringify(source),url);
476476
debug(`Translating TypeScript ${url}`);
477477
returnFunctionPrototypeCall(translators.get('module'),this,url,code,false);
478478
});

‎lib/internal/modules/helpers.js‎

Lines changed: 1 addition & 73 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,74 +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-
385314
/** @type {import('internal/util/types')} */
386315
let_TYPES=null;
387316
/**
@@ -485,7 +414,6 @@ module.exports = {
485414
loadBuiltinModule,
486415
makeRequireFunction,
487416
normalizeReferrerURL,
488-
stripTypeScriptTypes,
489417
stringify,
490418
stripBOM,
491419
toRealPath,

0 commit comments

Comments
 (0)