|
| 1 | +import type { DecodedSourceMap, RawSourceMap } from '@ampproject/remapping' |
| 2 | +import type { SourceMap } from 'rollup' |
1 | 3 | import type { ResolvedUnpluginOptions } from './types'
|
2 | 4 | import { isAbsolute, normalize } from 'path'
|
| 5 | +import remapping from '@ampproject/remapping' |
3 | 6 |
|
4 | 7 | /**
|
5 | 8 | * 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 }) {
|
72 | 75 | return query.unpluginName
|
73 | 76 | }
|
74 | 77 | }
|
| 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 | +} |
0 commit comments