Proper source map support for development mode
See original GitHub issueHello. In order to speed up our development process, we have build a custom solution for the hypernova server which can load Javascript bundles over HTTP. With that, we can spin up a webpack dev server and reload the compiled bundle each time the source files change. That has been working wonders for the time being and we are really happy that we don’t have to compile the files over and over again.
In order to make development even better, we wanted to add source map support for the generated stack traces. Our current implementation is the following.
- Client asks for component
Foo
- We load the code for component
Foo
over the webpack dev server - We evaluate its code with
vm.runInNewContext
and we catch any errors generated - Should we catch any error, we load the source map file and we feed it into a
sourceMapConsumer
from Mozilla’s source-map. - We then map all the stack trace elements into new ones by querying the consumer.
- Finally we throw the modified error.
Code would look something like this
let scriptContent;
try {
const response = await axios.get(url);
scriptContent = response.data;
} catch (e) {
console.error('Error while getting remote file');
console.error(e);
throw e;
}
let module = require('module');
const wrappedCode = module.wrap(scriptContent);
const moduleRunner = vm.runInNewContext(wrappedCode, createContext(), {
displayErrors: true,
filename: 'ssr.node.js'
});
const { data } = await axios.get(`${url}.map`)
const sourceMapConsumer = await new sourceMap.SourceMapConsumer(data);
return () => {
const modExports = {};
const modContext = {};
try {
moduleRunner(modExports, require, modContext, __filename, __dirname);
} catch(error) {
// Get the trace lines
const trace = error.stack.split("\n").slice(1).map(l => l.trim());
const originalStackTrace = sourceMappedStackTrace(trace, sourceMapConsumer);
// Map them to the original files
// Construct the new stack trace
const newTrace = originalStackTrace.map((orig, index) => {
if(orig.source === null || orig.line === null || orig.column === null) {
// Something that we don't have a mapping for. Leave the original
return ` ${trace[index]}`;
}
let base = ' at ';
if(orig.name) {
base += `${orig.name} `;
}
base += `${orig.source}:${orig.line}:${orig.column}`;
return base;
})
.filter(e => e !== null)
// Throw the modified error
throw new Error(error.stack.split('\n')[0] + '\n' + newTrace.join('\n'))
}
return modContext.exports;
Although this works great for the time being, we wanted to catch “runtime” errors as well. That is, errors generated when (e.g. ReactDOM.renderToString
runs). To my understanding, there is no way to manipulate such errors from BatchManager.render
as the exceptions are caught and modified there.
We could submit a patch upstream to enable custom error handling if there is no other elegant way of manipulating error traces.
Issue Analytics
- State:
- Created 5 years ago
- Reactions:2
- Comments:8 (3 by maintainers)
@ljharb For example, the backtrace displayed on the browser is something along the lines of:
Note that:
ssr.bundle.js
) is loaded by hypernovaapp/assets/javascript/foo.js:3
Could it be that rolling our own implementation (with
vm.runInNewContext()
) instead of usingcreateGetComponent
is messing up the source maps?Thanks!
Gotcha. So you do have a source map for that bundle, you just want it somehow applied in the hypernova error.
That does seem like something that could be done inside hypernova itself. However, perhaps if you included https://www.npmjs.com/package/source-map-support yourself, it would be handled for you?