Skip to content

Commit

Permalink
Merge pull request #114 from Rich-Harris/better-bundle-sourcemaps
Browse files Browse the repository at this point in the history
Better bundle sourcemaps
  • Loading branch information
Rich-Harris committed Dec 2, 2016
2 parents 891dc8a + 96fcd62 commit 3ca6ffb
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 173 deletions.
71 changes: 47 additions & 24 deletions src/Bundle.js
@@ -1,9 +1,10 @@
import MagicString from './MagicString.js';
import SourceMap from './utils/SourceMap.js';
import getSemis from './utils/getSemis.js';
import getRelativePath from './utils/getRelativePath.js';
import hasOwnProp from './utils/hasOwnProp.js';
import isObject from './utils/isObject.js';
import getLocator from './utils/getLocator.js';
import Mappings from './utils/Mappings.js';

export default function Bundle ( options = {} ) {
this.intro = options.intro || '';
Expand Down Expand Up @@ -87,6 +88,50 @@ Bundle.prototype = {
});
});

const mappings = new Mappings( options.hires );

if ( this.intro ) {
mappings.advance( this.intro );
}

this.sources.forEach( ( source, i ) => {
if ( i > 0 ) {
mappings.advance( this.separator );
}

const sourceIndex = source.filename ? this.uniqueSourceIndexByFilename[ source.filename ] : -1;
const magicString = source.content;
const locate = getLocator( magicString.original );

if ( magicString.intro ) {
mappings.advance( magicString.intro );
}

magicString.firstChunk.eachNext( chunk => {
const loc = locate( chunk.start );

if ( chunk.intro.length ) mappings.advance( chunk.intro );

if ( source.filename ) {
if ( chunk.edited ) {
mappings.addEdit( sourceIndex, chunk.content, chunk.original, loc, chunk.storeName ? names.indexOf( chunk.original ) : -1 );
} else {
mappings.addUneditedChunk( sourceIndex, chunk, magicString.original, loc, magicString.sourcemapLocations );
}
}

else {
mappings.advance( chunk.content );
}

if ( chunk.outro.length ) mappings.advance( chunk.outro );
});

if ( magicString.outro ) {
mappings.advance( magicString.outro );
}
});

return new SourceMap({
file: ( options.file ? options.file.split( /[\/\\]/ ).pop() : null ),
sources: this.uniqueSources.map( source => {
Expand All @@ -96,32 +141,10 @@ Bundle.prototype = {
return options.includeContent ? source.content : null;
}),
names,
mappings: this.getMappings( options, names )
mappings: mappings.encode()
});
},

getMappings ( options, names ) {
const offsets = {};

return (
getSemis( this.intro ) +
this.sources.map( ( source, i ) => {
const prefix = ( i > 0 ) ? ( getSemis( source.separator ) || ',' ) : '';
let mappings;

// we don't bother encoding sources without a filename
if ( !source.filename ) {
mappings = getSemis( source.content.toString() );
} else {
const sourceIndex = this.uniqueSourceIndexByFilename[ source.filename ];
mappings = source.content.getMappings( options, sourceIndex, offsets, names );
}

return prefix + mappings;
}).join( '' )
);
},

getIndentString () {
const indentStringCounts = {};

Expand Down
30 changes: 24 additions & 6 deletions src/MagicString.js
@@ -1,10 +1,10 @@
import Chunk from './Chunk.js';
import SourceMap from './utils/SourceMap.js';
import guessIndent from './utils/guessIndent.js';
import encodeMappings from './utils/encodeMappings.js';
import getRelativePath from './utils/getRelativePath.js';
import isObject from './utils/isObject.js';
import getLocator from './utils/getLocator.js';
import Mappings from './utils/Mappings.js';
import Stats from './utils/Stats.js';

const warned = {
Expand Down Expand Up @@ -130,15 +130,37 @@ MagicString.prototype = {
generateMap ( options ) {
options = options || {};

const sourceIndex = 0;
const names = Object.keys( this.storedNames );
const mappings = new Mappings( options.hires );

const locate = getLocator( this.original );

if ( this.intro ) {
mappings.advance( this.intro );
}

this.firstChunk.eachNext( chunk => {
const loc = locate( chunk.start );

if ( chunk.intro.length ) mappings.advance( chunk.intro );

if ( chunk.edited ) {
mappings.addEdit( sourceIndex, chunk.content, chunk.original, loc, chunk.storeName ? names.indexOf( chunk.original ) : -1 );
} else {
mappings.addUneditedChunk( sourceIndex, chunk, this.original, loc, this.sourcemapLocations );
}

if ( chunk.outro.length ) mappings.advance( chunk.outro );
});

if ( DEBUG ) this.stats.time( 'generateMap' );
const map = new SourceMap({
file: ( options.file ? options.file.split( /[\/\\]/ ).pop() : null ),
sources: [ options.source ? getRelativePath( options.file || '', options.source ) : null ],
sourcesContent: options.includeContent ? [ this.original ] : [ null ],
names,
mappings: this.getMappings( options, 0, {}, names )
mappings: mappings.encode()
});
if ( DEBUG ) this.stats.timeEnd( 'generateMap' );

Expand All @@ -149,10 +171,6 @@ MagicString.prototype = {
return this.indentStr === null ? '\t' : this.indentStr;
},

getMappings ( options, sourceIndex, offsets, names ) {
return encodeMappings( this.original, this.intro, this.outro, this.firstChunk, options.hires, this.sourcemapLocations, sourceIndex, offsets, names );
},

indent ( indentStr, options ) {
const pattern = /^[^\r\n]/gm;

Expand Down
117 changes: 117 additions & 0 deletions src/utils/Mappings.js
@@ -0,0 +1,117 @@
import { encode } from 'vlq';

export default function Mappings ( hires ) {
const offsets = {
generatedCodeColumn: 0,
sourceIndex: 0,
sourceCodeLine: 0,
sourceCodeColumn: 0,
sourceCodeName: 0
};

let generatedCodeLine = 0;
let generatedCodeColumn = 0;

this.raw = [];
let rawSegments = this.raw[ generatedCodeLine ] = [];

let pending = null;

this.addEdit = ( sourceIndex, content, original, loc, nameIndex ) => {
if ( content.length ) {
rawSegments.push([
generatedCodeColumn,
sourceIndex,
loc.line,
loc.column,
nameIndex,
]);
} else if ( pending ) {
rawSegments.push( pending );
}

this.advance( content );
pending = null;
};

this.addUneditedChunk = ( sourceIndex, chunk, original, loc, sourcemapLocations ) => {
let originalCharIndex = chunk.start;
let first = true;

while ( originalCharIndex < chunk.end ) {
if ( hires || first || sourcemapLocations[ originalCharIndex ] ) {
rawSegments.push([
generatedCodeColumn,
sourceIndex,
loc.line,
loc.column,
-1
]);
}

if ( original[ originalCharIndex ] === '\n' ) {
loc.line += 1;
loc.column = 0;
generatedCodeLine += 1;
this.raw[ generatedCodeLine ] = rawSegments = [];
generatedCodeColumn = 0;
} else {
loc.column += 1;
generatedCodeColumn += 1;
}

originalCharIndex += 1;
first = false;
}

pending = [
generatedCodeColumn,
sourceIndex,
loc.line,
loc.column,
-1,
];
};

this.advance = str => {
if ( !str ) return;

const lines = str.split( '\n' );
const lastLine = lines.pop();

if ( lines.length ) {
generatedCodeLine += lines.length;
this.raw[ generatedCodeLine ] = rawSegments = [];
generatedCodeColumn = lastLine.length;
} else {
generatedCodeColumn += lastLine.length;
}
};

this.encode = () => {
return this.raw.map( segments => {
let generatedCodeColumn = 0;

return segments.map( segment => {
const arr = [
segment[0] - generatedCodeColumn,
segment[1] - offsets.sourceIndex,
segment[2] - offsets.sourceCodeLine,
segment[3] - offsets.sourceCodeColumn
];

generatedCodeColumn = segment[0];
offsets.sourceIndex = segment[1];
offsets.sourceCodeLine = segment[2];
offsets.sourceCodeColumn = segment[3];

if ( ~segment[4] ) {
arr.push( segment[4] - offsets.sourceCodeName );
offsets.sourceCodeName = segment[4];
}

return encode( arr );
}).join( ',' );
}).join( ';' );
};
}

0 comments on commit 3ca6ffb

Please sign in to comment.