Skip to content

[refactor] Move sourcemapping logic to dedicated file #81310

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,8 @@
"patchedDependencies": {
"[email protected]": "patches/[email protected]",
"[email protected]": "patches/[email protected]",
"@ampproject/[email protected]": "patches/@[email protected]"
"@ampproject/[email protected]": "patches/@[email protected]",
"@types/[email protected]": "patches/@[email protected]"
}
}
}
6 changes: 2 additions & 4 deletions packages/next/src/server/dev/hot-reloader-turbopack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,10 @@ import { FAST_REFRESH_RUNTIME_RELOAD } from './messages'
import { generateEncryptionKeyBase64 } from '../app-render/encryption-utils-server'
import { isAppPageRouteDefinition } from '../route-definitions/app-page-route-definition'
import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths'
import type { ModernSourceMapPayload } from '../lib/source-maps'
import { getNodeDebugType } from '../lib/utils'
import { isMetadataRouteFile } from '../../lib/metadata/is-metadata-route'
import {
setBundlerFindSourceMapImplementation,
type ModernSourceMapPayload,
} from '../patch-error-inspect'
import { setBundlerFindSourceMapImplementation } from '../patch-error-inspect'
import { getNextErrorFeedbackMiddleware } from '../../next-devtools/server/get-next-error-feedback-middleware'
import {
formatIssue,
Expand Down
67 changes: 8 additions & 59 deletions packages/next/src/server/dev/middleware-turbopack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ import { openFileInEditor } from '../../next-devtools/server/launch-editor'
import type { StackFrame } from 'next/dist/compiled/stacktrace-parser'
import {
SourceMapConsumer,
type BasicSourceMapConsumer,
type NullableMappedPosition,
} from 'next/dist/compiled/source-map08'
import type { Project, TurbopackStackFrame } from '../../build/swc/types'
import {
type ModernSourceMapPayload,
findApplicableSourceMapPayload,
} from '../lib/source-maps'
import { getSourceMapFromFile } from './get-source-map-from-file'
import { findSourceMap, type SourceMapPayload } from 'node:module'
import { findSourceMap } from 'node:module'
import { pathToFileURL } from 'node:url'
import { inspect } from 'node:util'

Expand Down Expand Up @@ -170,61 +173,6 @@ function createStackFrame(
} satisfies TurbopackStackFrame
}

/**
* https://tc39.es/source-map/#index-map
*/
interface IndexSourceMapSection {
offset: {
line: number
column: number
}
map: ModernRawSourceMap
}

// TODO(veil): Upstream types
interface IndexSourceMap {
version: number
file: string
sections: IndexSourceMapSection[]
}

interface ModernRawSourceMap extends SourceMapPayload {
ignoreList?: number[]
}

type ModernSourceMapPayload = ModernRawSourceMap | IndexSourceMap

/**
* Finds the sourcemap payload applicable to a given frame.
* Equal to the input unless an Index Source Map is used.
*/
function findApplicableSourceMapPayload(
frame: TurbopackStackFrame,
payload: ModernSourceMapPayload
): ModernRawSourceMap | undefined {
if ('sections' in payload) {
const frameLine = frame.line ?? 0
const frameColumn = frame.column ?? 0
// Sections must not overlap and must be sorted: https://tc39.es/source-map/#section-object
// Therefore the last section that has an offset less than or equal to the frame is the applicable one.
// TODO(veil): Binary search
let section: IndexSourceMapSection | undefined = payload.sections[0]
for (
let i = 0;
i < payload.sections.length &&
payload.sections[i].offset.line <= frameLine &&
payload.sections[i].offset.column <= frameColumn;
i++
) {
section = payload.sections[i]
}

return section === undefined ? undefined : section.map
} else {
return payload
}
}

/**
* @returns 1-based lines and 0-based columns
*/
Expand All @@ -244,7 +192,7 @@ async function nativeTraceSource(
}

if (sourceMapPayload !== undefined) {
let consumer: BasicSourceMapConsumer
let consumer: SourceMapConsumer
try {
consumer = await new SourceMapConsumer(sourceMapPayload)
} catch (cause) {
Expand Down Expand Up @@ -282,7 +230,8 @@ async function nativeTraceSource(
if (traced !== null) {
const { originalPosition, sourceContent } = traced
const applicableSourceMap = findApplicableSourceMapPayload(
frame,
frame.line ?? 0,
frame.column ?? 0,
sourceMapPayload
)

Expand Down
30 changes: 14 additions & 16 deletions packages/next/src/server/dev/middleware-webpack.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { findSourceMap, type SourceMap } from 'module'
import path from 'path'
import { fileURLToPath, pathToFileURL } from 'url'
import {
SourceMapConsumer,
type BasicSourceMapConsumer,
} from 'next/dist/compiled/source-map08'
import { SourceMapConsumer } from 'next/dist/compiled/source-map08'
import type { StackFrame } from 'next/dist/compiled/stacktrace-parser'
import { getSourceMapFromFile } from './get-source-map-from-file'
import {
sourceMapIgnoreListsEverything,
type ModernSourceMapPayload,
} from '../lib/source-maps'
import { openFileInEditor } from '../../next-devtools/server/launch-editor'
import {
getOriginalCodeFrame,
Expand All @@ -15,7 +16,6 @@ import {
type OriginalStackFramesResponse,
} from '../../next-devtools/server/shared'
import { middlewareResponse } from '../../next-devtools/server/middleware-response'
export { getSourceMapFromFile }

import type { IncomingMessage, ServerResponse } from 'http'
import type webpack from 'webpack'
Expand Down Expand Up @@ -50,13 +50,13 @@ type SourceAttributes = {
type Source =
| {
type: 'file'
sourceMap: RawSourceMap
sourceMap: ModernSourceMapPayload
ignoredSources: IgnoredSources
moduleURL: string
}
| {
type: 'bundle'
sourceMap: RawSourceMap
sourceMap: ModernSourceMapPayload
ignoredSources: IgnoredSources
compilation: webpack.Compilation
moduleId: string
Expand Down Expand Up @@ -87,10 +87,10 @@ function getSourcePath(source: string) {
* @returns 1-based lines and 0-based columns
*/
async function findOriginalSourcePositionAndContent(
sourceMap: RawSourceMap,
sourceMap: ModernSourceMapPayload,
position: { lineNumber: number | null; column: number | null }
): Promise<SourceAttributes | null> {
let consumer: BasicSourceMapConsumer
let consumer: SourceMapConsumer
try {
consumer = await new SourceMapConsumer(sourceMap)
} catch (cause) {
Expand Down Expand Up @@ -309,7 +309,11 @@ async function getSource(
return {
type: 'file',
sourceMap: sourceMapPayload,
ignoredSources: getIgnoredSources(sourceMapPayload),

ignoredSources: getIgnoredSources(
// @ts-expect-error -- TODO: Support IndexSourceMap
sourceMapPayload
),
moduleURL: sourceURL,
}
}
Expand Down Expand Up @@ -411,12 +415,6 @@ function getOriginalStackFrames({
)
}

function sourceMapIgnoreListsEverything(
sourceMap: RawSourceMap & { ignoreList?: number[] }
): boolean {
return sourceMap.sources.length === sourceMap.ignoreList?.length
}

async function getOriginalStackFrame({
isServer,
isEdgeServer,
Expand Down
82 changes: 82 additions & 0 deletions packages/next/src/server/lib/source-maps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* https://tc39.es/source-map/#index-map
*/
interface IndexSourceMapSection {
offset: {
line: number
column: number
}
map: BasicSourceMapPayload
}

// TODO(veil): Upstream types
/** https://tc39.es/ecma426/#sec-index-source-map */
interface IndexSourceMap {
version: number
file: string
sections: IndexSourceMapSection[]
}

/** https://tc39.es/ecma426/#sec-source-map-format */
interface BasicSourceMapPayload {
version: number
// TODO: Move to https://github.com/jridgewell/sourcemaps which is actively maintained
/** WARNING: `file` is optional. */
file: string
sourceRoot?: string
// TODO: Move to https://github.com/jridgewell/sourcemaps which is actively maintained
/** WARNING: `sources[number]` can be `null`. */
sources: Array<string>
names: Array<string>
mappings: string
ignoreList?: number[]
}

export type ModernSourceMapPayload = BasicSourceMapPayload | IndexSourceMap

// TODO: This should take the BasicSourceMapPayload. Only the relevant section
// needs to ignore list everything making this check effectively O(1) multiplied
// by the complexity of finding the section.
export function sourceMapIgnoreListsEverything(
sourceMap: ModernSourceMapPayload
): boolean {
if ('sections' in sourceMap) {
return sourceMap.sections.every((section) => {
return sourceMapIgnoreListsEverything(section.map)
})
}
return (
sourceMap.ignoreList !== undefined &&
sourceMap.sources.length === sourceMap.ignoreList.length
)
}

/**
* Finds the sourcemap payload applicable to a given frame.
* Equal to the input unless an Index Source Map is used.
*/
export function findApplicableSourceMapPayload(
lineNumber: number,
columnNumber: number,
payload: ModernSourceMapPayload
): BasicSourceMapPayload | undefined {
if ('sections' in payload) {
// Sections must not overlap and must be sorted: https://tc39.es/source-map/#section-object
// Therefore the last section that has an offset less than or equal to the frame is the applicable one.
// TODO(veil): Binary search
let section: IndexSourceMapSection | undefined = payload.sections[0]
for (
let i = 0;
i < payload.sections.length &&
payload.sections[i].offset.line <= lineNumber &&
payload.sections[i].offset.column <= columnNumber;
i++
) {
section = payload.sections[i]
}

return section === undefined ? undefined : section.map
} else {
return payload
}
}
Loading
Loading