Nativescript-cli: Logs and errors from devices always point to bundle/vendor files

Created on 9 May 2019  路  3Comments  路  Source: NativeScript/nativescript-cli

Just discovered that you try to read sourcemap while consuming logs.
Though seeing your implementation it can't work with wepback.
this line https://github.com/NativeScript/nativescript-cli/blob/master/lib/services/ios-log-filter.ts#L97 is checking for the file in the project folder. However with wepback that file is in the build folder much deeper.

bug medium device logs

Most helpful comment

As far as I can see This code can't work under the new 6.0 default; unless you are using the --env.hiddenSourceMap to generate the source maps externally... As using --env.hiddenSourceMap=blah will then create a blah/*.map files (but NOT put them in the bundle/vendor/worker.js files.)

Using--evn.sourceMap actually embeds the source map inside the bundle.js, vendor.js and *.worker.js files and not a separate .map file.

Several things up for discussion...

  1. if you do NOT passing either the sourceMap or hiddenSourceMap, I do not believe any source maps are created. So if I am correct, I think for consistency and to retain easy to use error tracking on non-release builds, --env.sourceMap should be enabled by default in 5.4.x and 6.0 on NON-release builds. This way source maps are ALWAYS available for the CLI in the normal run/debug usage. (i.e. tns run android or tns debug ios) This will also give chrome tools the sourcemap for debug mode. So effectively I think this should probably be the default setting for run or debug under the 6.0 webpack system.
  1. If you pass in --env.hiddenSourceMap; I don't know if it is worth adding the extra logic to the CLI to figure out where the source maps are and to read them in, as these source maps are not base64 encoded but raw json. They also aren't sent to the device (meaning chrome devtools probably can't use them, afaik). It might be worth it; but for those who are using this flag; the odds are very likely they are only doing it only for release mode builds, so I don't believe the CLI has any need to ever parse these types of source maps...

Now the logic to grab the source map from the bundle.js (or the other.js files); it is pretty trivial --

   // You can wrap this in a promise and do async calls but to show you how to easily I used sync... 
   // fileName is the path to where "bundle.js", "vendor.js" or "*.worker.js" files...
   const data = fs.readFileSync(fileName);
   // indexOf is faster than regex and supported on a native Buffer object
   const idx = data.indexOf("//# sourceMappingURL=");  
   if (idx < 0) {   return null;   /* No Source Map */   }

   // This line moves you to the position in the file, and extracts it, converts it from base64 into text string.
    const newData = Buffer.from(data.slice(idx+63).toString(), 'base64').toString('binary')

   // Finally parse the json data into a source map object...
    try {
        return JSON.parse(newData);
    } catch (err) {
        return null;
    }

And to parse it (your already including source-map library in the codebase)

 new SourceMap.SourceMapConsumer(sourceMap).then((consumer) => {
    let output = consumer.originalPositionFor({line: args.line, column: args.column});  
    console.log(output)...
  });

You might consider actually creating a consumer tracking object; and open up the bundle.js, vendor.js and *.worker.js files at startup. Pull out the sourcemaps; then as your logger is going through changing data; you can just do:
output( consumer[JSFileName].originalPositionFor(line, column); )
and it would then do inline replaces for the JS from "bundle.js line 500, column 30" to "app/main-page.ts line 25, column 5"... :grinning:

All 3 comments

As far as I can see This code can't work under the new 6.0 default; unless you are using the --env.hiddenSourceMap to generate the source maps externally... As using --env.hiddenSourceMap=blah will then create a blah/*.map files (but NOT put them in the bundle/vendor/worker.js files.)

Using--evn.sourceMap actually embeds the source map inside the bundle.js, vendor.js and *.worker.js files and not a separate .map file.

Several things up for discussion...

  1. if you do NOT passing either the sourceMap or hiddenSourceMap, I do not believe any source maps are created. So if I am correct, I think for consistency and to retain easy to use error tracking on non-release builds, --env.sourceMap should be enabled by default in 5.4.x and 6.0 on NON-release builds. This way source maps are ALWAYS available for the CLI in the normal run/debug usage. (i.e. tns run android or tns debug ios) This will also give chrome tools the sourcemap for debug mode. So effectively I think this should probably be the default setting for run or debug under the 6.0 webpack system.
  1. If you pass in --env.hiddenSourceMap; I don't know if it is worth adding the extra logic to the CLI to figure out where the source maps are and to read them in, as these source maps are not base64 encoded but raw json. They also aren't sent to the device (meaning chrome devtools probably can't use them, afaik). It might be worth it; but for those who are using this flag; the odds are very likely they are only doing it only for release mode builds, so I don't believe the CLI has any need to ever parse these types of source maps...

Now the logic to grab the source map from the bundle.js (or the other.js files); it is pretty trivial --

   // You can wrap this in a promise and do async calls but to show you how to easily I used sync... 
   // fileName is the path to where "bundle.js", "vendor.js" or "*.worker.js" files...
   const data = fs.readFileSync(fileName);
   // indexOf is faster than regex and supported on a native Buffer object
   const idx = data.indexOf("//# sourceMappingURL=");  
   if (idx < 0) {   return null;   /* No Source Map */   }

   // This line moves you to the position in the file, and extracts it, converts it from base64 into text string.
    const newData = Buffer.from(data.slice(idx+63).toString(), 'base64').toString('binary')

   // Finally parse the json data into a source map object...
    try {
        return JSON.parse(newData);
    } catch (err) {
        return null;
    }

And to parse it (your already including source-map library in the codebase)

 new SourceMap.SourceMapConsumer(sourceMap).then((consumer) => {
    let output = consumer.originalPositionFor({line: args.line, column: args.column});  
    console.log(output)...
  });

You might consider actually creating a consumer tracking object; and open up the bundle.js, vendor.js and *.worker.js files at startup. Pull out the sourcemaps; then as your logger is going through changing data; you can just do:
output( consumer[JSFileName].originalPositionFor(line, column); )
and it would then do inline replaces for the JS from "bundle.js line 500, column 30" to "app/main-page.ts line 25, column 5"... :grinning:

@NathanaelA you are right indeed. I did not talk about that point cause i am using my own webpack config with external sourceMap already ;)

Was this page helpful?
0 / 5 - 0 ratings