Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use sourcemap with evaluation TypeScript from string

I'll show the code first (node 10.15.3):

var ts = require("typescript");
require('source-map-support').install({
   environment: 'node',
   hookRequire: true
})
var content = "let a = 0;\n\nb = b * a";

var compilerOptions = { 
   module: ts.ModuleKind.CommonJS,
   inlineSourceMap: true 
};

var res1 = ts.transpileModule(content, {
  compilerOptions: compilerOptions,
  moduleName: "myModule2"
});
console.log(res1);
console.log('-------')
console.log(content)
console.log('-------')
console.log(res1.outputText)
console.log('-------')
eval(res1.outputText)

As a result of executing this code I want to have a traceback related to a given content variable (Error in line 3), but I constantly receive an error in line 2 - which is line of error in compiled version of code.

Here is an output

{ outputText:
   'var a = 0;\nb = b * a;\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9kdWxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsibW9kdWxlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUVWLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFBIn0=',
  diagnostics: [],
  sourceMapText: undefined }
-------
let a = 0;

b = b * a
-------
var a = 0;
b = b * a;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9kdWxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsibW9kdWxlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUVWLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFBIn0=
-------
SS: /root/ts-eval/exal.js undefined
SS: internal/modules/cjs/loader.js undefined
SS: internal/bootstrap/node.js undefined
ReferenceError: b is not defined
    at eval (eval at <anonymous> (/root/ts-eval/exal.js:24:1), <anonymous>:2:1)
    at Object.<anonymous> (/root/ts-eval/exal.js:24:1)
    at Module._compile (internal/modules/cjs/loader.js:701:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
    at Module.load (internal/modules/cjs/loader.js:600:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
    at Function.Module._load (internal/modules/cjs/loader.js:531:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:754:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)
like image 766
Oduvan Avatar asked May 10 '19 10:05

Oduvan


1 Answers

You are using inline source maps, and the documentation for source-map-support states:

To support files with inline source maps, the hookRequire options can be specified, which will monitor all source files for inline source maps.

You've set hookRequire to true. However, the passage I've quoted indicates source-map-support relies on hooking into require to detect inline source maps, and so if your code executes without passing through require then its source map won't be detected and source-map-support won't be able to fix the stack trace. Indeed if I replace your eval call with this code:

fs.writeFileSync("myModule2.js", res1.outputText);

require("./myModule2");

I get a stack trace like this, with the correct line number:

ReferenceError: b is not defined
    at Object.<anonymous> (/tmp/t4/module.ts:3:1)
[...]

The file name is module.ts because the option fileName has not been specified to ts.transpileModule. You could set it to myModule2.ts for consistency with moduleName.

Also, if you change your compiler options to also inline the sources, like this:

var compilerOptions = {
  module: ts.ModuleKind.CommonJS,
  inlineSourceMap: true,
  inlineSources: true,
};

You get a bit of a nicer stack trace. With compilerOptions like shown above and fileName as I suggested earlier, the trace is:

/tmp/t4/myModule2.ts:3
b = b * a
^
ReferenceError: b is not defined
    at Object.<anonymous> (/tmp/t4/myModule2.ts:3:1)

You can see before the ReferenceError a reference to the line of code that caused the problem.


The approach above is the simplest one that results in source-map-support fixing source references. Here is another, more complicated approach, which does not require saving any files to disk but requires customizing how source-map-support gets source code from a source file path. The comments in the source indicate what the new part do.

const fs = require("fs");
const ts = require("typescript");
const vm = require("vm");
const path = require("path");

// This establishes a mapping between sourcePaths and the actual source.
const sourcePathToSource = Object.create(null);

require("source-map-support").install({
  environment: "node",
  // Pass to source-map-support a custom function for retreiving sources
  // from source paths. This runs after source-map-support's default logic,
  // only if that logic fails to find the requested source.
  retrieveFile: (sourcePath) => sourcePathToSource[sourcePath],
});


const content = "let a = 0;\n\nb = b * a";

const compilerOptions = {
  module: ts.ModuleKind.CommonJS,
  sourceMap: true,
  inlineSources: true,
};

// The path that the ts module would have.
const tsPath = path.resolve("myModule2.ts");

const res1 = ts.transpileModule(content, {
  compilerOptions: compilerOptions,
  fileName: tsPath,
  moduleName: "myModule2"
});
console.log(res1);
console.log("-------");
console.log(content);
console.log("-------");
console.log(res1.outputText);
console.log("-------");

// The path that the compiled module would have.
const jsPath = path.resolve("myModule2.js");

// Establish the relationship between the path and the source.
sourcePathToSource[jsPath] = res1.outputText;
// Ditto for the source map file.
sourcePathToSource[path.resolve("myModule2.js.map")] = res1.sourceMapText;

vm.runInThisContext(res1.outputText, {
  filename: jsPath,
});

Running the code above results in this output:

/tmp/t4/myModule2.js:2
b = b * a;
^

ReferenceError: b is not defined
    at /tmp/t4/myModule2.ts:3:1
    at Script.runInThisContext (vm.js:123:20)
[...]

The source line numbers in the stack trace are modified by source-map-support to point to the right place but the source reference at the very beginning is not modified. The problem is with the regular expression that source-map-support uses. The regular expression requires the source file reference to be in parentheses (like (vm.js:123:20)). I've tried transforming the exception before source-map-support gets to it so that it fits the regular expression but source-map-support does not see the transformation.

like image 140
Louis Avatar answered Oct 31 '22 17:10

Louis