Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved stacktraces (minor) #4156

Merged
merged 15 commits into from
Apr 18, 2018
Merged

Conversation

timneutkens
Copy link
Member

@timneutkens timneutkens commented Apr 15, 2018

Fixes #3735 with the change to devtool.

This change will introduce proper source map support for all modules. It'll also improve recompile time by using cheap-module-source-map instead of source-map. In practice this only improves compile times and has no real downside when debugging.

When an error is thrown we call applySourcemaps which will load the sourcemap associated with each line stacktrace line, then it'll find the real line using the source-map module and return the newly constructed error line. This is very similar to how Vue handles server side errors.

Besides that I also noticed our componentDidCatch only worked in production mode, since react-hot-loader's appContainer implemented componentDidCatch. Now we catch the error in App and throw it up to AppContainer in development.

@timneutkens timneutkens changed the title Improved stacktraces [WIP] Improved stacktraces Apr 15, 2018
client/index.js Outdated
@@ -172,7 +188,9 @@ async function doRender ({ Component, props, hash, err, emitter: emitterProp = e

// We need to clear any existing runtime error messages
ReactDOM.unmountComponentAtNode(errorContainer)
renderReactElement(createElement(App, appProps), appContainer)
renderReactElement(<AppContainer errorReporter={ErrorDebugComponent} warnings={false}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we move this to here from the app.js.? I like this approach. But just need to know why?

And is it possible to use AppContainer it only in development?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<AppContainer> becomes an empty component in production, so there's no harm in including it. I've moved it here since our componentDidCatch in app.js has to be executed. Also this makes sure we keep the state of app.js (since we expose it to users now)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we have to do the webpack magic since in the webpack config we remove react-hot-loader completely (which is currently breaking the tests)

@@ -0,0 +1,55 @@
// @flow
import fetch from 'unfetch'
import {SourceMapConsumer} from 'source-map'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is a kind a big library. Could you check whether we are getting rid of this in the production mode.

If not, shall we do it with some webpack magic?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parent file (source-map-support) is only loaded in development using the same if/else require 👍 nonetheless i'm going to move it into the function for both client/server side.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timneutkens okay. Great.

}
</div>
)
await rewriteErrorTrace(error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We won't catch errors here. Better to call .then() and .catch here.

}
}

// On the server side the error comes from a single place, so `ErrorDebug` is rendered directly.
Copy link
Contributor

@arunoda arunoda Apr 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add more details on here?. The comment is not clear to me.

@arunoda
Copy link
Contributor

arunoda commented Apr 15, 2018

@timneutkens shall we write some test cases checking the lines of the error. Could be bit hard to write, but I think it's not impossible.

@timneutkens
Copy link
Member Author

Yeah we can definitely add tests for this, just wanted you to have a look first at the implementation 👍

@@ -33,12 +30,14 @@ async function rewriteTraceLine (trace: string): Promise<string> {

const mapPath = `${filePath}.map`

const fetch = require('unfetch')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any effect loading modules like this? I hope webpack still see them just like before.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The weird thing is, it does work for source-map, but not for unfetch 🤷‍♂️either way this module is only loaded in dev mode now, same way as ErrorDebug, so it doesn't matter 👍

@albinekb
Copy link
Contributor

I tested this and seeing more consistent build times and a drop from 8-10s to 2s consistent across my application, awesome stuff! 🎉

@timneutkens timneutkens changed the title [WIP] Improved stacktraces Improved stacktraces (minor) Apr 18, 2018
client/index.js Outdated
sourcemappedErr = await rewriteErrorTrace(err)
}

const str = stripAnsi(`${err.message}\n${err.stack}${err.info ? `\n\n${err.info.componentStack}` : ''}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should be the sourcemappedErr? But as discussed in slack rewriteErrorTrace actually mutates the err so it's the same, but for readability reasons.

</AppContainer>
)
}
return <>{children}</>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can remove the empty fragment here and just return children? or is there a clever reason as to why wrap it in a fragment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just didn't want to change behavior from what it was before.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good 👍

Copy link
Contributor

@albinekb albinekb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice! More readable than before 🕵️‍♀️

client/index.js Outdated
if (process.env.NODE_ENV !== 'production') {
sourcemappedErr = await rewriteErrorTrace(err)
await applySourcemaps(err)
Copy link
Contributor

@albinekb albinekb Apr 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌 This is great, so much easier to understand

@timneutkens
Copy link
Member Author

timneutkens commented Apr 18, 2018

The change to devtool resulted in:

Old:

DONE  Compiled successfully in 10545ms                                                                                                                                                                                                  11:56:02 AM

> Building page: /signin
Total precache size is about 0 B for 0 resources.
Total precache size is about 0 B for 0 resources.


 DONE  Compiled successfully in 16183ms                                                                                                                                                                                                  11:56:26 AM

Total precache size is about 0 B for 0 resources.
> Disposing inactive page(s): /, /_error, /me/settings
Total precache size is about 0 B for 0 resources.


 DONE  Compiled successfully in 11445ms                                                                                                                                                                                                  11:57:53 AM

Total precache size is about 0 B for 0 resources.
> Building page: /_error
Total precache size is about 0 B for 0 resources.


 DONE  Compiled successfully in 987ms                                                                                                                                                                                                    11:58:01 AM

Total precache size is about 0 B for 0 resources.
> Building page: /me/settings
Total precache size is about 0 B for 0 resources.


 DONE  Compiled successfully in 10997ms                                                                                                                                                                                                  11:59:09 AM

Total precache size is about 0 B for 0 resources.
> Building page: /me/settings/account
Total precache size is about 0 B for 0 resources.


 DONE  Compiled successfully in 21654ms                                                                                                                                                                                                  11:59:39 AM

Total precache size is about 0 B for 0 resources.
> Building page: /me/photos
Total precache size is about 0 B for 0 resources.


 DONE  Compiled successfully in 19294ms                                                                                                                                                                                                  12:00:21 PM

Total precache size is about 0 B for 0 resources.
> Disposing inactive page(s): /discover, /me, /signin, /_error, /me/settings
Total precache size is about 0 B for 0 resources.


 DONE  Compiled successfully in 10490ms                                                                                                                                                                                                  12:01:50 PM

New:

DONE  Compiled successfully in 2083ms                                                                                                                                                                                                   12:19:14 PM

Total precache size is about 0 B for 0 resources.


WAIT  Compiling...                                                                                                                                                                                                                      12:19:15 PM

Total precache size is about 0 B for 0 resources.


DONE  Compiled successfully in 439ms                                                                                                                                                                                                    12:19:16 PM

Total precache size is about 0 B for 0 resources.
> Building page: /
Total precache size is about 0 B for 0 resources.


DONE  Compiled successfully in 1366ms                                                                                                                                                                                                   12:20:28 PM

Total precache size is about 0 B for 0 resources.
> Building page: /discover
Total precache size is about 0 B for 0 resources.
Total precache size is about 0 B for 0 resources.


DONE  Compiled successfully in 2919ms                                                                                                                                                                                                   12:20:32 PM

From @ptomasroos testing 🙏

@arunoda
Copy link
Contributor

arunoda commented Apr 18, 2018

@timneutkens this is really well done.
I really like the new addition of tests. So, we won't break source-maps from release to release.

@arunoda arunoda merged commit 68626c5 into vercel:canary Apr 18, 2018
@albinekb
Copy link
Contributor

Could we do a canary release with this or are we waiting for something else? Would save us minutes each day 🎉

@ptomasroos
Copy link
Contributor

You can just alter this in your own webpack config @albinekb

@timneutkens timneutkens deleted the add/error-handling branch April 18, 2018 16:58
timneutkens added a commit that referenced this pull request Apr 18, 2018
lependu pushed a commit to lependu/next.js that referenced this pull request Jun 19, 2018
* Handle production errors correctly

* Improved source map support

* Make react-hot-loader hold state again

* Remove console.log

* Load modules on demand

* Catch errors in rewriteErrorTrace

* Update comment

* Update comment

* Remove source-map-support

* Load modules in next-dev

* Make sure error logged has sourcemaps too

* Add tests for production runtime errors

* Add tests for development runtime errors. Fix issue with client side errors in development

* Move functionality back to renderError now that error handling is consistent

* Rename to applySourcemaps
lependu pushed a commit to lependu/next.js that referenced this pull request Jun 19, 2018
timneutkens pushed a commit that referenced this pull request Jul 11, 2018
…de errors (#4764)

## What's wrong

This problem is specific to errors that happen on the client _after_ the initial mounting of the component. (The router has special logic to handle exceptions thrown in `getInitialProps` during a client-side navigation, and I've confirmed this logic is correct.)

Specifically, if the page is mounted, and you raise an exception on the page, the exception will cause  the error page to be mounted without ever invoking `getInitialProps` on the new App/Error page pairing.

This has been illustrated with multiple repros in #4574.

## Why is it broken

This regression was introduced two months ago in #4156, where the invocation of `getInitialProps` was removed from the app's top-level error handler. Specifically, [this line](https://github.com/zeit/next.js/pull/4156/files#diff-895656aeaccff5d7c0f56a113ede9662L147) was removed and [replaced by a comment](https://github.com/zeit/next.js/pull/4156/files#diff-895656aeaccff5d7c0f56a113ede9662R167) that says that "`App` will handle the calling of `getInitialProps`".

I believe the sentiment about "`App` will handle calling `getInitialProps`" is mistaken. In fact, it really doesn't make sense on its face, since it would require an instance lifecycle method of `App` (which is mounted immediately after the comment) to invoke the `static getInitialProps` method on the error page.

## How I fixed it

I've fixed this in a fork by restoring Lines 146 – 148 that were removed in #4156. I think this is the right fix, but Next.js's handling of `getInitialProps` could certainly be improved. (The code in [this conditional](https://github.com/zeit/next.js/blob/86d01706a67cee5c80796974d04c1e11cdff453a/client/index.js#L173) speaks to the unnecessary complexity around this.)
@lock lock bot locked as resolved and limited conversation to collaborators Apr 18, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Slow page build time in development with Next.js 5.0 (compared to Next.js 4.2.3)
4 participants