Skip to content

Commit 3a41715

Browse files
authored
[refactor] Move sourcemapping logic to dedicated file (#81310)
Resolves #81231 (comment)
1 parent 09c8af1 commit 3a41715

File tree

9 files changed

+290
-288
lines changed

9 files changed

+290
-288
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,8 @@
305305
"patchedDependencies": {
306306
307307
308-
"@ampproject/[email protected]": "patches/@[email protected]"
308+
"@ampproject/[email protected]": "patches/@[email protected]",
309+
309310
}
310311
}
311312
}

packages/next/src/server/dev/hot-reloader-turbopack.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,10 @@ import { FAST_REFRESH_RUNTIME_RELOAD } from './messages'
7777
import { generateEncryptionKeyBase64 } from '../app-render/encryption-utils-server'
7878
import { isAppPageRouteDefinition } from '../route-definitions/app-page-route-definition'
7979
import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths'
80+
import type { ModernSourceMapPayload } from '../lib/source-maps'
8081
import { getNodeDebugType } from '../lib/utils'
8182
import { isMetadataRouteFile } from '../../lib/metadata/is-metadata-route'
82-
import {
83-
setBundlerFindSourceMapImplementation,
84-
type ModernSourceMapPayload,
85-
} from '../patch-error-inspect'
83+
import { setBundlerFindSourceMapImplementation } from '../patch-error-inspect'
8684
import { getNextErrorFeedbackMiddleware } from '../../next-devtools/server/get-next-error-feedback-middleware'
8785
import {
8886
formatIssue,

packages/next/src/server/dev/middleware-turbopack.ts

Lines changed: 8 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import { openFileInEditor } from '../../next-devtools/server/launch-editor'
1212
import type { StackFrame } from 'next/dist/compiled/stacktrace-parser'
1313
import {
1414
SourceMapConsumer,
15-
type BasicSourceMapConsumer,
1615
type NullableMappedPosition,
1716
} from 'next/dist/compiled/source-map08'
1817
import type { Project, TurbopackStackFrame } from '../../build/swc/types'
18+
import {
19+
type ModernSourceMapPayload,
20+
findApplicableSourceMapPayload,
21+
} from '../lib/source-maps'
1922
import { getSourceMapFromFile } from './get-source-map-from-file'
20-
import { findSourceMap, type SourceMapPayload } from 'node:module'
23+
import { findSourceMap } from 'node:module'
2124
import { pathToFileURL } from 'node:url'
2225
import { inspect } from 'node:util'
2326

@@ -170,61 +173,6 @@ function createStackFrame(
170173
} satisfies TurbopackStackFrame
171174
}
172175

173-
/**
174-
* https://tc39.es/source-map/#index-map
175-
*/
176-
interface IndexSourceMapSection {
177-
offset: {
178-
line: number
179-
column: number
180-
}
181-
map: ModernRawSourceMap
182-
}
183-
184-
// TODO(veil): Upstream types
185-
interface IndexSourceMap {
186-
version: number
187-
file: string
188-
sections: IndexSourceMapSection[]
189-
}
190-
191-
interface ModernRawSourceMap extends SourceMapPayload {
192-
ignoreList?: number[]
193-
}
194-
195-
type ModernSourceMapPayload = ModernRawSourceMap | IndexSourceMap
196-
197-
/**
198-
* Finds the sourcemap payload applicable to a given frame.
199-
* Equal to the input unless an Index Source Map is used.
200-
*/
201-
function findApplicableSourceMapPayload(
202-
frame: TurbopackStackFrame,
203-
payload: ModernSourceMapPayload
204-
): ModernRawSourceMap | undefined {
205-
if ('sections' in payload) {
206-
const frameLine = frame.line ?? 0
207-
const frameColumn = frame.column ?? 0
208-
// Sections must not overlap and must be sorted: https://tc39.es/source-map/#section-object
209-
// Therefore the last section that has an offset less than or equal to the frame is the applicable one.
210-
// TODO(veil): Binary search
211-
let section: IndexSourceMapSection | undefined = payload.sections[0]
212-
for (
213-
let i = 0;
214-
i < payload.sections.length &&
215-
payload.sections[i].offset.line <= frameLine &&
216-
payload.sections[i].offset.column <= frameColumn;
217-
i++
218-
) {
219-
section = payload.sections[i]
220-
}
221-
222-
return section === undefined ? undefined : section.map
223-
} else {
224-
return payload
225-
}
226-
}
227-
228176
/**
229177
* @returns 1-based lines and 0-based columns
230178
*/
@@ -244,7 +192,7 @@ async function nativeTraceSource(
244192
}
245193

246194
if (sourceMapPayload !== undefined) {
247-
let consumer: BasicSourceMapConsumer
195+
let consumer: SourceMapConsumer
248196
try {
249197
consumer = await new SourceMapConsumer(sourceMapPayload)
250198
} catch (cause) {
@@ -282,7 +230,8 @@ async function nativeTraceSource(
282230
if (traced !== null) {
283231
const { originalPosition, sourceContent } = traced
284232
const applicableSourceMap = findApplicableSourceMapPayload(
285-
frame,
233+
frame.line ?? 0,
234+
frame.column ?? 0,
286235
sourceMapPayload
287236
)
288237

packages/next/src/server/dev/middleware-webpack.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { findSourceMap, type SourceMap } from 'module'
22
import path from 'path'
33
import { fileURLToPath, pathToFileURL } from 'url'
4-
import {
5-
SourceMapConsumer,
6-
type BasicSourceMapConsumer,
7-
} from 'next/dist/compiled/source-map08'
4+
import { SourceMapConsumer } from 'next/dist/compiled/source-map08'
85
import type { StackFrame } from 'next/dist/compiled/stacktrace-parser'
96
import { getSourceMapFromFile } from './get-source-map-from-file'
7+
import {
8+
sourceMapIgnoreListsEverything,
9+
type ModernSourceMapPayload,
10+
} from '../lib/source-maps'
1011
import { openFileInEditor } from '../../next-devtools/server/launch-editor'
1112
import {
1213
getOriginalCodeFrame,
@@ -15,7 +16,6 @@ import {
1516
type OriginalStackFramesResponse,
1617
} from '../../next-devtools/server/shared'
1718
import { middlewareResponse } from '../../next-devtools/server/middleware-response'
18-
export { getSourceMapFromFile }
1919

2020
import type { IncomingMessage, ServerResponse } from 'http'
2121
import type webpack from 'webpack'
@@ -50,13 +50,13 @@ type SourceAttributes = {
5050
type Source =
5151
| {
5252
type: 'file'
53-
sourceMap: RawSourceMap
53+
sourceMap: ModernSourceMapPayload
5454
ignoredSources: IgnoredSources
5555
moduleURL: string
5656
}
5757
| {
5858
type: 'bundle'
59-
sourceMap: RawSourceMap
59+
sourceMap: ModernSourceMapPayload
6060
ignoredSources: IgnoredSources
6161
compilation: webpack.Compilation
6262
moduleId: string
@@ -87,10 +87,10 @@ function getSourcePath(source: string) {
8787
* @returns 1-based lines and 0-based columns
8888
*/
8989
async function findOriginalSourcePositionAndContent(
90-
sourceMap: RawSourceMap,
90+
sourceMap: ModernSourceMapPayload,
9191
position: { lineNumber: number | null; column: number | null }
9292
): Promise<SourceAttributes | null> {
93-
let consumer: BasicSourceMapConsumer
93+
let consumer: SourceMapConsumer
9494
try {
9595
consumer = await new SourceMapConsumer(sourceMap)
9696
} catch (cause) {
@@ -309,7 +309,11 @@ async function getSource(
309309
return {
310310
type: 'file',
311311
sourceMap: sourceMapPayload,
312-
ignoredSources: getIgnoredSources(sourceMapPayload),
312+
313+
ignoredSources: getIgnoredSources(
314+
// @ts-expect-error -- TODO: Support IndexSourceMap
315+
sourceMapPayload
316+
),
313317
moduleURL: sourceURL,
314318
}
315319
}
@@ -411,12 +415,6 @@ function getOriginalStackFrames({
411415
)
412416
}
413417

414-
function sourceMapIgnoreListsEverything(
415-
sourceMap: RawSourceMap & { ignoreList?: number[] }
416-
): boolean {
417-
return sourceMap.sources.length === sourceMap.ignoreList?.length
418-
}
419-
420418
async function getOriginalStackFrame({
421419
isServer,
422420
isEdgeServer,
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* https://tc39.es/source-map/#index-map
3+
*/
4+
interface IndexSourceMapSection {
5+
offset: {
6+
line: number
7+
column: number
8+
}
9+
map: BasicSourceMapPayload
10+
}
11+
12+
// TODO(veil): Upstream types
13+
/** https://tc39.es/ecma426/#sec-index-source-map */
14+
interface IndexSourceMap {
15+
version: number
16+
file: string
17+
sections: IndexSourceMapSection[]
18+
}
19+
20+
/** https://tc39.es/ecma426/#sec-source-map-format */
21+
interface BasicSourceMapPayload {
22+
version: number
23+
// TODO: Move to https://github.com/jridgewell/sourcemaps which is actively maintained
24+
/** WARNING: `file` is optional. */
25+
file: string
26+
sourceRoot?: string
27+
// TODO: Move to https://github.com/jridgewell/sourcemaps which is actively maintained
28+
/** WARNING: `sources[number]` can be `null`. */
29+
sources: Array<string>
30+
names: Array<string>
31+
mappings: string
32+
ignoreList?: number[]
33+
}
34+
35+
export type ModernSourceMapPayload = BasicSourceMapPayload | IndexSourceMap
36+
37+
// TODO: This should take the BasicSourceMapPayload. Only the relevant section
38+
// needs to ignore list everything making this check effectively O(1) multiplied
39+
// by the complexity of finding the section.
40+
export function sourceMapIgnoreListsEverything(
41+
sourceMap: ModernSourceMapPayload
42+
): boolean {
43+
if ('sections' in sourceMap) {
44+
return sourceMap.sections.every((section) => {
45+
return sourceMapIgnoreListsEverything(section.map)
46+
})
47+
}
48+
return (
49+
sourceMap.ignoreList !== undefined &&
50+
sourceMap.sources.length === sourceMap.ignoreList.length
51+
)
52+
}
53+
54+
/**
55+
* Finds the sourcemap payload applicable to a given frame.
56+
* Equal to the input unless an Index Source Map is used.
57+
*/
58+
export function findApplicableSourceMapPayload(
59+
lineNumber: number,
60+
columnNumber: number,
61+
payload: ModernSourceMapPayload
62+
): BasicSourceMapPayload | undefined {
63+
if ('sections' in payload) {
64+
// Sections must not overlap and must be sorted: https://tc39.es/source-map/#section-object
65+
// Therefore the last section that has an offset less than or equal to the frame is the applicable one.
66+
// TODO(veil): Binary search
67+
let section: IndexSourceMapSection | undefined = payload.sections[0]
68+
for (
69+
let i = 0;
70+
i < payload.sections.length &&
71+
payload.sections[i].offset.line <= lineNumber &&
72+
payload.sections[i].offset.column <= columnNumber;
73+
i++
74+
) {
75+
section = payload.sections[i]
76+
}
77+
78+
return section === undefined ? undefined : section.map
79+
} else {
80+
return payload
81+
}
82+
}

0 commit comments

Comments
 (0)