Use eval-source-map for Server Side Errors (#13123)

This switches to faster source maps in development for the server-side compilation on macOS.

We still need to figure out a story for Windows.
This commit is contained in:
Joe Haddad 2020-05-20 01:00:50 -04:00 committed by GitHub
parent 99ee63e110
commit d64e2e1cbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 54 additions and 17 deletions

View file

@ -12,10 +12,10 @@ export const base = curry(function base(
// https://webpack.js.org/configuration/devtool/#development
if (ctx.isDevelopment) {
if (ctx.isServer || process.platform === 'win32') {
if (process.platform === 'win32') {
// Non-eval based source maps are slow to rebuild, so we only enable
// them for the server and Windows. Unfortunately, eval source maps
// are not supported by Node.js, and are slow on Windows.
// them for Windows. Unfortunately, eval source maps are flagged as
// suspicious by Windows Defender and block HMR.
config.devtool = 'inline-source-map'
} else {
// `eval-source-map` provides full-fidelity source maps for the

View file

@ -1,4 +1,4 @@
import reactDevOverlayMiddleware from '@next/react-dev-overlay/lib/middleware'
import { getOverlayMiddleware } from '@next/react-dev-overlay/lib/middleware'
import { NextHandleFunction } from 'connect'
import { IncomingMessage, ServerResponse } from 'http'
import WebpackDevMiddleware from 'next/dist/compiled/webpack-dev-middleware'
@ -9,7 +9,7 @@ import webpack from 'webpack'
import { createEntrypoints, createPagesMapping } from '../build/entries'
import { watchCompilers } from '../build/output'
import getBaseWebpackConfig from '../build/webpack-config'
import { NEXT_PROJECT_ROOT_DIST_CLIENT, API_ROUTE } from '../lib/constants'
import { API_ROUTE, NEXT_PROJECT_ROOT_DIST_CLIENT } from '../lib/constants'
import { recursiveDelete } from '../lib/recursive-delete'
import {
BLOCKED_PAGES,
@ -133,6 +133,7 @@ export default class HotReloader {
private initialized: boolean
private config: any
private stats: any
private serverStats: any
private serverPrevDocumentHash: string | null
private prevChunkNames?: Set<any>
private onDemandEntries: any
@ -160,6 +161,7 @@ export default class HotReloader {
this.webpackHotMiddleware = null
this.initialized = false
this.stats = null
this.serverStats = null
this.serverPrevDocumentHash = null
this.config = config
@ -309,7 +311,11 @@ export default class HotReloader {
const buildTools = await this.prepareBuildTools(multiCompiler)
this.assignBuildTools(buildTools)
this.stats = ((await this.waitUntilValid()) as any).stats[0]
// [Client, Server]
;[
this.stats,
this.serverStats,
] = ((await this.waitUntilValid()) as any).stats
}
async stop(
@ -328,6 +334,7 @@ export default class HotReloader {
async reload(): Promise<void> {
this.stats = null
this.serverStats = null
await this.clean()
@ -335,7 +342,11 @@ export default class HotReloader {
const compiler = webpack(configs)
const buildTools = await this.prepareBuildTools(compiler)
this.stats = await this.waitUntilValid(buildTools.webpackDevMiddleware)
// [Client, Server]
;[
this.stats,
this.serverStats,
] = ((await this.waitUntilValid()) as any).stats
const oldWebpackDevMiddleware = this.webpackDevMiddleware
@ -361,9 +372,10 @@ export default class HotReloader {
onDemandEntries.middleware(),
webpackHotMiddleware,
errorOverlayMiddleware({ dir: this.dir }),
reactDevOverlayMiddleware({
getOverlayMiddleware({
rootDirectory: this.dir,
stats: () => this.stats,
serverStats: () => this.serverStats,
}),
]
}
@ -375,6 +387,7 @@ export default class HotReloader {
multiCompiler.compilers[1].hooks.done.tap(
'NextjsHotReloaderForServer',
(stats) => {
this.serverStats = stats
if (!this.initialized) {
return
}

View file

@ -64,7 +64,10 @@ async function getErrorByType(
id,
runtime: true,
error: event.reason,
frames: await getOriginalStackFrames(event.frames),
frames: await getOriginalStackFrames(
isNodeError(event.reason),
event.frames
),
}
}
default: {

View file

@ -30,15 +30,22 @@ export type OriginalStackFrame =
originalCodeFrame: null
}
export function getOriginalStackFrames(frames: StackFrame[]) {
return Promise.all(frames.map((frame) => getOriginalStackFrame(frame)))
export function getOriginalStackFrames(
isServerSide: boolean,
frames: StackFrame[]
) {
return Promise.all(
frames.map((frame) => getOriginalStackFrame(isServerSide, frame))
)
}
export function getOriginalStackFrame(
isServerSide: boolean,
source: StackFrame
): Promise<OriginalStackFrame> {
async function _getOriginalStackFrame(): Promise<OriginalStackFrame> {
const params = new URLSearchParams()
params.append('isServerSide', String(isServerSide))
for (const key in source) {
params.append(key, (source[key] ?? '').toString())
}

View file

@ -16,6 +16,7 @@ import { launchEditor } from './internal/helpers/launchEditor'
export type OverlayMiddlewareOptions = {
rootDirectory: string
stats(): webpack.Stats
serverStats(): webpack.Stats
}
export type OriginalStackFrameResponse = {
@ -26,7 +27,11 @@ export type OriginalStackFrameResponse = {
type Source = { map: () => RawSourceMap } | null
function getOverlayMiddleware(options: OverlayMiddlewareOptions) {
async function getSourceById(isFile: boolean, id: string): Promise<Source> {
async function getSourceById(
isServerSide: boolean,
isFile: boolean,
id: string
): Promise<Source> {
if (isFile) {
const fileContent: string | null = await fs
.readFile(id, 'utf-8')
@ -49,7 +54,9 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) {
}
try {
const compilation = options.stats()?.compilation
const compilation = isServerSide
? options.serverStats()?.compilation
: options.stats()?.compilation
const m = compilation?.modules?.find((m) => m.id === id)
return (
m?.source(
@ -71,7 +78,9 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) {
const { pathname, query } = url.parse(req.url, true)
if (pathname === '/__nextjs_original-stack-frame') {
const frame = (query as unknown) as StackFrame
const frame = (query as unknown) as StackFrame & {
isServerSide: 'true' | 'false'
}
if (
!(
(frame.file?.startsWith('webpack-internal:///') ||
@ -84,6 +93,7 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) {
return res.end()
}
const isServerSide = frame.isServerSide === 'true'
const moduleId: string = frame.file.replace(
/^(webpack-internal:\/\/\/|file:\/\/)/,
''
@ -91,7 +101,11 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) {
let source: Source
try {
source = await getSourceById(frame.file.startsWith('file:'), moduleId)
source = await getSourceById(
isServerSide,
frame.file.startsWith('file:'),
moduleId
)
} catch (err) {
console.log('Failed to get source map:', err)
res.statusCode = 500
@ -212,4 +226,4 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) {
}
}
export default getOverlayMiddleware
export { getOverlayMiddleware }

View file

@ -360,7 +360,7 @@ test('module init error not shown', async () => {
expect(await session.hasRedbox(true)).toBe(true)
expect(await session.getRedboxSource()).toMatchInlineSnapshot(`
"index.js (4:12) @ Module../index.js
"index.js (4:12) @ eval
2 | // top offset for snapshot
3 | import * as React from 'react';