diff --git a/js/compiler/compiler.go b/js/compiler/compiler.go index e609daa0224..564dbe34f72 100644 --- a/js/compiler/compiler.go +++ b/js/compiler/compiler.go @@ -4,6 +4,7 @@ package compiler import ( _ "embed" // we need this for embedding Babel + "encoding/base64" "encoding/json" "errors" "fmt" @@ -210,9 +211,17 @@ func (c *Compiler) compileImpl( state := compilationState{srcMap: srcMap, compiler: c, wrapped: wrap} if wrap { conditionalNewLine := "" - if strings.Contains(code, "//# sourceMappingURL=") { + if index := strings.LastIndex(code, "//# sourceMappingURL="); index != -1 { // the lines in the sourcemap (if available) will be fixed by increaseMappingsByOne conditionalNewLine = "\n" + newCode, err := state.updateInlineSourceMap(code, index) + if err != nil { + c.logger.Warnf("while compiling %q, couldn't update its inline sourcemap which might lead "+ + "to some line numbers being off: %s", filename, err) + } else { + code = newCode + } + // if there is no sourcemap - bork only the first line of code, but leave the remaining ones. } code = "(function(module, exports){" + conditionalNewLine + code + "\n})\n" @@ -275,6 +284,29 @@ func newBabel() (*babel, error) { return result, err } +func (c *compilationState) updateInlineSourceMap(code string, index int) (string, error) { + nextnewline := strings.Index(code[index:], "\n") + if nextnewline == -1 { + nextnewline = len(code[index:]) + } + mapurl := code[index : index+nextnewline] + const base64EncodePrefix = "application/json;base64," + if startOfBase64EncodedSourceMap := strings.Index(mapurl, base64EncodePrefix); startOfBase64EncodedSourceMap != -1 { + startOfBase64EncodedSourceMap += len(base64EncodePrefix) + b, err := base64.StdEncoding.DecodeString(mapurl[startOfBase64EncodedSourceMap:]) + if err != nil { + return code, err + } + b, err = c.increaseMappingsByOne(b) + if err != nil { + return code, err + } + encoded := base64.StdEncoding.EncodeToString(b) + code = code[:index] + "//# sourcemappingurl=data:application/json;base64," + encoded + code[nextnewline:] + } + return code, nil +} + // increaseMappingsByOne increases the lines in the sourcemap by line so that it fixes the case where we need to wrap a // required file in a function to support/emulate commonjs func (c *compilationState) increaseMappingsByOne(sourceMap []byte) ([]byte, error) { diff --git a/js/initcontext_test.go b/js/initcontext_test.go index 096ce1d4a07..871f0c393c2 100644 --- a/js/initcontext_test.go +++ b/js/initcontext_test.go @@ -703,6 +703,76 @@ export default function () { require.Equal(t, "cool is cool\n\tat webpack:///./test1.ts:2:4(2)\n\tat r (webpack:///./test1.ts:5:4(3))\n\tat file:///script.js:4:2(4)\n", exception.String()) } +func TestSourceMapsInlinedCJS(t *testing.T) { + t.Parallel() + fs := fsext.NewMemMapFs() + // this example is from https://github.com/grafana/k6/issues/3689 generated with k6pack + data := ` +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var __publicField = (obj, key, value) => { + __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); + return value; +}; + +// script.ts +var script_exports = {}; +__export(script_exports, { + default: () => script_default +}); +module.exports = __toCommonJS(script_exports); + +// user.ts +var UserAccount = class { + constructor(name) { + __publicField(this, "name"); + __publicField(this, "id"); + this.name = name; + this.id = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); + throw "ooops"; + } +}; +function newUser(name) { + return new UserAccount(name); +} + +// script.ts +var script_default = () => { + const user = newUser("John"); + console.log(user); +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsic2NyaXB0LnRzIiwgInVzZXIudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImltcG9ydCB7IFVzZXIsIG5ld1VzZXIgfSBmcm9tIFwiLi91c2VyXCI7XG5cbmV4cG9ydCBkZWZhdWx0ICgpID0+IHtcbiAgY29uc3QgdXNlcjogVXNlciA9IG5ld1VzZXIoXCJKb2huXCIpO1xuICBjb25zb2xlLmxvZyh1c2VyKTtcbn07XG4iLCAiaW50ZXJmYWNlIFVzZXIge1xuICBuYW1lOiBzdHJpbmc7XG4gIGlkOiBudW1iZXI7XG59XG5cbmNsYXNzIFVzZXJBY2NvdW50IGltcGxlbWVudHMgVXNlciB7XG4gIG5hbWU6IHN0cmluZztcbiAgaWQ6IG51bWJlcjtcblxuICBjb25zdHJ1Y3RvcihuYW1lOiBzdHJpbmcpIHtcbiAgICB0aGlzLm5hbWUgPSBuYW1lO1xuICAgIHRoaXMuaWQgPSBNYXRoLmZsb29yKE1hdGgucmFuZG9tKCkgKiBOdW1iZXIuTUFYX1NBRkVfSU5URUdFUik7XG4gICAgdGhyb3cgXCJvb29wc1wiO1xuICB9XG59XG5cbmZ1bmN0aW9uIG5ld1VzZXIobmFtZTogc3RyaW5nKTogVXNlciB7XG4gIHJldHVybiBuZXcgVXNlckFjY291bnQobmFtZSk7XG59XG5cbmV4cG9ydCB7IFVzZXIsIG5ld1VzZXIgfTtcbiJdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7OztBQ0tBLElBQU0sY0FBTixNQUFrQztBQUFBLEVBSWhDLFlBQVksTUFBYztBQUgxQjtBQUNBO0FBR0UsU0FBSyxPQUFPO0FBQ1osU0FBSyxLQUFLLEtBQUssTUFBTSxLQUFLLE9BQU8sSUFBSSxPQUFPLGdCQUFnQjtBQUM1RCxVQUFNO0FBQUEsRUFDUjtBQUNGO0FBRUEsU0FBUyxRQUFRLE1BQW9CO0FBQ25DLFNBQU8sSUFBSSxZQUFZLElBQUk7QUFDN0I7OztBRGhCQSxJQUFPLGlCQUFRLE1BQU07QUFDbkIsUUFBTSxPQUFhLFFBQVEsTUFBTTtBQUNqQyxVQUFRLElBQUksSUFBSTtBQUNsQjsiLAogICJuYW1lcyI6IFtdCn0K +`[1:] + b, err := getSimpleBundle(t, "/script.js", data, fs) + require.NoError(t, err) + + bi, err := b.Instantiate(context.Background(), 0) + require.NoError(t, err) + _, err = bi.getCallableExport(consts.DefaultFn)(goja.Undefined()) + require.Error(t, err) + exception := new(goja.Exception) + require.ErrorAs(t, err, &exception) + // TODO figure out why those are not the same as the one in the previous test TestSourceMapsExternal + // likely settings in the transpilers + require.Equal(t, "ooops\n\tat file:///user.ts:13:4(28)\n\tat newUser (file:///user.ts:18:9(3))\n\tat script_default (file:///script.ts:4:29(4))\n", exception.String()) +} + func TestImportModificationsAreConsistentBetweenFiles(t *testing.T) { t.Parallel() fs := fsext.NewMemMapFs()