Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better bundle sourcemaps #114

Merged
merged 3 commits into from Dec 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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( ';' );
};
}