You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

155 lines
4.3 KiB

import { encode, decode } from 'sourcemap-codec';
import { dirname, relative, resolve } from './path.js';
class Source {
constructor ( filename, content ) {
this.isOriginal = true;
this.filename = filename;
this.content = content;
}
traceSegment ( line, column, name ) {
return { line, column, name, source: this };
}
}
class Link {
constructor ( map, sources ) {
if ( !map ) throw new Error( 'Cannot generate a sourcemap if non-sourcemap-generating transformers are used' );
this.sources = sources;
this.names = map.names;
this.mappings = decode( map.mappings );
}
traceMappings () {
let sources = [], sourcesContent = [], names = [];
const mappings = this.mappings.map( line => {
let tracedLine = [];
line.forEach( segment => {
const source = this.sources[ segment[1] ];
const traced = source.traceSegment( segment[2], segment[3], this.names[ segment[4] ] );
if ( traced ) {
let sourceIndex = null, nameIndex = null;
segment = [
segment[0],
null,
traced.line,
traced.column
];
// newer sources are more likely to be used, so search backwards.
sourceIndex = sources.lastIndexOf( traced.source.filename );
if ( sourceIndex === -1 ) {
sourceIndex = sources.length;
sources.push( traced.source.filename );
sourcesContent[ sourceIndex ] = traced.source.content;
} else if ( sourcesContent[ sourceIndex ] == null ) {
sourcesContent[ sourceIndex ] = traced.source.content;
} else if ( traced.source.content != null && sourcesContent[ sourceIndex ] !== traced.source.content ) {
throw new Error( `Multiple conflicting contents for sourcemap source ${source.filename}` );
}
segment[1] = sourceIndex;
if ( traced.name ) {
nameIndex = names.indexOf( traced.name );
if ( nameIndex === -1 ) {
nameIndex = names.length;
names.push( traced.name );
}
segment[4] = nameIndex;
}
tracedLine.push( segment );
}
});
return tracedLine;
});
return { sources, sourcesContent, names, mappings };
}
traceSegment ( line, column, name ) {
const segments = this.mappings[ line ];
if ( !segments ) return null;
for ( let i = 0; i < segments.length; i += 1 ) {
const segment = segments[i];
if ( segment[0] > column ) return null;
if ( segment[0] === column ) {
const source = this.sources[ segment[1] ];
if ( !source ) return null;
return source.traceSegment( segment[2], segment[3], this.names[ segment[4] ] || name );
}
}
return null;
}
}
export default function collapseSourcemaps ( file, map, modules, bundleSourcemapChain ) {
const moduleSources = modules.filter( module => !module.excludeFromSourcemap ).map( module => {
let sourceMapChain = module.sourceMapChain;
let source;
if ( module.originalSourceMap == null ) {
source = new Source( module.id, module.originalCode );
} else {
const sources = module.originalSourceMap.sources;
const sourcesContent = module.originalSourceMap.sourcesContent || [];
if ( sources == null || ( sources.length <= 1 && sources[0] == null ) ) {
source = new Source( module.id, sourcesContent[0] );
sourceMapChain = [ module.originalSourceMap ].concat( sourceMapChain );
} else {
// TODO indiscriminately treating IDs and sources as normal paths is probably bad.
const directory = dirname( module.id ) || '.';
const sourceRoot = module.originalSourceMap.sourceRoot || '.';
const baseSources = sources.map( (source, i) => {
return new Source( resolve( directory, sourceRoot, source ), sourcesContent[i] );
});
source = new Link( module.originalSourceMap, baseSources );
}
}
sourceMapChain.forEach( map => {
source = new Link( map, [ source ]);
});
return source;
});
let source = new Link( map, moduleSources );
bundleSourcemapChain.forEach( map => {
source = new Link( map, [ source ] );
});
let { sources, sourcesContent, names, mappings } = source.traceMappings();
if ( file ) {
const directory = dirname( file );
sources = sources.map( source => relative( directory, source ) );
}
// we re-use the `map` object because it has convenient toString/toURL methods
map.sources = sources;
map.sourcesContent = sourcesContent;
map.names = names;
map.mappings = encode( mappings );
return map;
}