Skip to content

Commit 15686b8

Browse files
committed
feat: expose getCombinedSourcemap in webpack, rspack and farm
1 parent 2c27d2a commit 15686b8

File tree

4 files changed

+169
-10
lines changed

4 files changed

+169
-10
lines changed

src/farm/index.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import type {
1717
} from '../types'
1818
import type { WatchChangeEvents } from './utils'
1919
import path from 'path'
20-
import { toArray } from '../utils'
20+
import { getCombinedSourcemap, toArray } from '../utils'
2121
import { createFarmContext, unpluginContext } from './context'
2222

2323
import {
@@ -187,7 +187,9 @@ export function toFarmPlugin(plugin: UnpluginOptions, options?: Record<string, a
187187
&& plugin.transformInclude(params.resolvedPath)
188188
const farmContext = createFarmContext(context, params.resolvedPath)
189189
const resource: TransformResult = await _transform.call(
190-
Object.assign(unpluginContext(context), farmContext),
190+
Object.assign({
191+
getCombinedSourcemap: () => getCombinedSourcemap(params.sourceMapChain, params.resolvedPath, params.content),
192+
}, unpluginContext(context), farmContext),
191193
params.content,
192194
params.resolvedPath,
193195
)

src/rspack/loaders/transform.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ export default async function transform(
2626
const context = createContext(this)
2727
const res = await plugin.transform.call(
2828
Object.assign(
29-
{},
29+
{
30+
getCombinedSourcemap: () => map,
31+
},
3032
this._compilation && createBuildContext(this._compiler, this._compilation, this),
3133
context,
3234
),

src/utils.ts

+149
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import type { DecodedSourceMap, RawSourceMap } from '@ampproject/remapping'
2+
import type { SourceMap } from 'rollup'
13
import type { ResolvedUnpluginOptions } from './types'
24
import { isAbsolute, normalize } from 'path'
5+
import remapping from '@ampproject/remapping'
36

47
/**
58
* Normalizes a given path when it's absolute. Normalizing means returning a new path by converting
@@ -72,3 +75,149 @@ export function resolveQuery(query: string | { unpluginName: string }) {
7275
return query.unpluginName
7376
}
7477
}
78+
79+
const postfixRE = /[?#].*$/
80+
export function cleanUrl(url: string): string {
81+
return url.replace(postfixRE, '')
82+
}
83+
84+
/*
85+
The following functions are copied from vite
86+
https://github.com/vitejs/vite/blob/0fe95d4a71930cf55acd628efef59e6eae0f77f7/packages/vite/src/node/utils.ts#L781-L868
87+
88+
MIT License
89+
Copyright (c) 2019-present, VoidZero Inc. and Vite contributors
90+
https://github.com/vitejs/vite/blob/main/LICENSE
91+
*/
92+
const windowsDriveRE = /^[A-Z]:/
93+
const replaceWindowsDriveRE = /^([A-Z]):\//
94+
const linuxAbsolutePathRE = /^\/[^/]/
95+
function escapeToLinuxLikePath(path: string) {
96+
if (windowsDriveRE.test(path)) {
97+
return path.replace(replaceWindowsDriveRE, '/windows/$1/')
98+
}
99+
if (linuxAbsolutePathRE.test(path)) {
100+
return `/linux${path}`
101+
}
102+
return path
103+
}
104+
105+
const revertWindowsDriveRE = /^\/windows\/([A-Z])\//
106+
function unescapeToLinuxLikePath(path: string) {
107+
if (path.startsWith('/linux/')) {
108+
return path.slice('/linux'.length)
109+
}
110+
if (path.startsWith('/windows/')) {
111+
return path.replace(revertWindowsDriveRE, '$1:/')
112+
}
113+
return path
114+
}
115+
116+
const nullSourceMap: RawSourceMap = {
117+
names: [],
118+
sources: [],
119+
mappings: '',
120+
version: 3,
121+
}
122+
function combineSourcemaps(
123+
filename: string,
124+
sourcemapList: Array<DecodedSourceMap | RawSourceMap>,
125+
): RawSourceMap {
126+
if (
127+
sourcemapList.length === 0
128+
|| sourcemapList.every(m => m.sources.length === 0)
129+
) {
130+
return { ...nullSourceMap }
131+
}
132+
133+
// hack for parse broken with normalized absolute paths on windows (C:/path/to/something).
134+
// escape them to linux like paths
135+
// also avoid mutation here to prevent breaking plugin's using cache to generate sourcemaps like vue (see #7442)
136+
sourcemapList = sourcemapList.map((sourcemap) => {
137+
const newSourcemaps = { ...sourcemap }
138+
newSourcemaps.sources = sourcemap.sources.map(source =>
139+
source ? escapeToLinuxLikePath(source) : null,
140+
)
141+
if (sourcemap.sourceRoot) {
142+
newSourcemaps.sourceRoot = escapeToLinuxLikePath(sourcemap.sourceRoot)
143+
}
144+
return newSourcemaps
145+
})
146+
147+
// We don't declare type here so we can convert/fake/map as RawSourceMap
148+
let map // : SourceMap
149+
let mapIndex = 1
150+
const useArrayInterface
151+
= sourcemapList.slice(0, -1).find(m => m.sources.length !== 1) === undefined
152+
if (useArrayInterface) {
153+
map = remapping(sourcemapList, () => null)
154+
}
155+
else {
156+
map = remapping(sourcemapList[0], (sourcefile) => {
157+
const mapForSources = sourcemapList
158+
.slice(mapIndex)
159+
.find(s => s.sources.includes(sourcefile))
160+
161+
if (mapForSources) {
162+
mapIndex++
163+
return mapForSources
164+
}
165+
return null
166+
})
167+
}
168+
if (!map.file) {
169+
delete map.file
170+
}
171+
172+
// unescape the previous hack
173+
map.sources = map.sources.map(source =>
174+
source ? unescapeToLinuxLikePath(source) : source,
175+
)
176+
map.file = filename
177+
178+
return map as RawSourceMap
179+
}
180+
181+
export function getCombinedSourcemap(sourcemapChain: Nullable<Arrayable<SourceMap | string>>, filename: string, originalCode: string): SourceMap | null {
182+
sourcemapChain = toArray(sourcemapChain)
183+
let combinedMap = null
184+
185+
for (let m of sourcemapChain) {
186+
if (typeof m === 'string')
187+
m = JSON.parse(m)
188+
if (!('version' in (m as SourceMap))) {
189+
// { mappings: '' }
190+
if ((m as SourceMap).mappings === '') {
191+
combinedMap = { mappings: '' }
192+
break
193+
}
194+
// empty, nullified source map
195+
combinedMap = null
196+
break
197+
}
198+
if (!combinedMap) {
199+
const sm = m as SourceMap
200+
// sourcemap should not include `sources: [null]` (because `sources` should be string) nor
201+
// `sources: ['']` (because `''` means the path of sourcemap)
202+
// but MagicString generates this when `filename` option is not set.
203+
// Rollup supports these and therefore we support this as well
204+
if (sm.sources.length === 1 && !sm.sources[0]) {
205+
combinedMap = {
206+
...sm,
207+
sources: [filename],
208+
sourcesContent: [originalCode],
209+
}
210+
}
211+
else {
212+
combinedMap = sm
213+
}
214+
}
215+
else {
216+
combinedMap = combineSourcemaps(cleanUrl(filename), [
217+
m as RawSourceMap,
218+
combinedMap as RawSourceMap,
219+
]) as SourceMap
220+
}
221+
}
222+
return combinedMap as SourceMap
223+
}

src/webpack/loaders/transform.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,20 @@ export default async function transform(this: LoaderContext<{ unpluginName: stri
1313

1414
const context = createContext(this)
1515
const res = await plugin.transform.call(
16-
Object.assign({}, createBuildContext({
17-
addWatchFile: (file) => {
18-
this.addDependency(file)
16+
Object.assign(
17+
{
18+
getCombinedSourcemap: () => map,
1919
},
20-
getWatchFiles: () => {
21-
return this.getDependencies()
22-
},
23-
}, this._compiler!, this._compilation, this), context),
20+
createBuildContext({
21+
addWatchFile: (file) => {
22+
this.addDependency(file)
23+
},
24+
getWatchFiles: () => {
25+
return this.getDependencies()
26+
},
27+
}, this._compiler!, this._compilation, this),
28+
context,
29+
),
2430
source,
2531
this.resource,
2632
)

0 commit comments

Comments
 (0)